summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ANNOUNCE.1.5.015
-rw-r--r--CREDITS130
-rw-r--r--FS/Changes5
-rw-r--r--FS/FS.pm243
-rw-r--r--FS/FS/CGI.pm393
-rw-r--r--FS/FS/ClientAPI.pm44
-rw-r--r--FS/FS/ClientAPI/Agent.pm189
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm788
-rw-r--r--FS/FS/ClientAPI/Signup.pm288
-rw-r--r--FS/FS/ClientAPI/passwd.pm53
-rw-r--r--FS/FS/Conf.pm1312
-rw-r--r--FS/FS/ConfItem.pm63
-rw-r--r--FS/FS/InitHandler.pm91
-rw-r--r--FS/FS/Misc.pm102
-rw-r--r--FS/FS/Msgcat.pm98
-rw-r--r--FS/FS/Record.pm1687
-rw-r--r--FS/FS/Report.pm46
-rw-r--r--FS/FS/Report/Table.pm27
-rw-r--r--FS/FS/Report/Table/Monthly.pm172
-rw-r--r--FS/FS/SearchCache.pm96
-rw-r--r--FS/FS/UI/Base.pm194
-rw-r--r--FS/FS/UI/CGI.pm239
-rw-r--r--FS/FS/UI/Gtk.pm224
-rw-r--r--FS/FS/UI/agent.pm62
-rw-r--r--FS/FS/UID.pm318
-rw-r--r--FS/FS/acct_snarf.pm128
-rwxr-xr-xFS/FS/addr_block.pm331
-rw-r--r--FS/FS/agent.pm279
-rw-r--r--FS/FS/agent_type.pm166
-rw-r--r--FS/FS/cust_bill.pm1487
-rw-r--r--FS/FS/cust_bill_event.pm180
-rw-r--r--FS/FS/cust_bill_pay.pm176
-rw-r--r--FS/FS/cust_bill_pkg.pm215
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm124
-rw-r--r--FS/FS/cust_credit.pm330
-rw-r--r--FS/FS/cust_credit_bill.pm177
-rw-r--r--FS/FS/cust_credit_refund.pm183
-rw-r--r--FS/FS/cust_main.pm3346
-rw-r--r--FS/FS/cust_main_county.pm290
-rw-r--r--FS/FS/cust_main_invoice.pm177
-rw-r--r--FS/FS/cust_pay.pm484
-rw-r--r--FS/FS/cust_pay_batch.pm397
-rw-r--r--FS/FS/cust_pay_refund.pm177
-rw-r--r--FS/FS/cust_pay_void.pm196
-rw-r--r--FS/FS/cust_pkg.pm973
-rw-r--r--FS/FS/cust_refund.pm317
-rw-r--r--FS/FS/cust_svc.pm615
-rw-r--r--FS/FS/cust_tax_exempt.pm132
-rw-r--r--FS/FS/domain_record.pm417
-rw-r--r--FS/FS/export_svc.pm283
-rw-r--r--FS/FS/msgcat.pm132
-rw-r--r--FS/FS/nas.pm154
-rw-r--r--FS/FS/part_bill_event.pm186
-rw-r--r--FS/FS/part_export.pm590
-rw-r--r--FS/FS/part_export/acct_sql.pm177
-rw-r--r--FS/FS/part_export/apache.pm47
-rw-r--r--FS/FS/part_export/artera_turbo.pm155
-rw-r--r--FS/FS/part_export/bind.pm35
-rw-r--r--FS/FS/part_export/bind_slave.pm28
-rw-r--r--FS/FS/part_export/bsdshell.pm25
-rw-r--r--FS/FS/part_export/communigate_pro.pm178
-rw-r--r--FS/FS/part_export/communigate_pro_singledomain.pm37
-rw-r--r--FS/FS/part_export/cp.pm160
-rw-r--r--FS/FS/part_export/cyrus.pm120
-rw-r--r--FS/FS/part_export/domain_shellcommands.pm161
-rw-r--r--FS/FS/part_export/forward_shellcommands.pm159
-rw-r--r--FS/FS/part_export/http.pm134
-rw-r--r--FS/FS/part_export/infostreet.pm277
-rw-r--r--FS/FS/part_export/ldap.pm294
-rw-r--r--FS/FS/part_export/null.pm13
-rw-r--r--FS/FS/part_export/passwdfile.pm18
-rw-r--r--FS/FS/part_export/postfix.pm32
-rw-r--r--FS/FS/part_export/router.pm190
-rw-r--r--FS/FS/part_export/shellcommands.pm338
-rw-r--r--FS/FS/part_export/shellcommands_withdomain.pm105
-rw-r--r--FS/FS/part_export/sqlmail.pm220
-rw-r--r--FS/FS/part_export/sqlradius.pm444
-rw-r--r--FS/FS/part_export/sqlradius_withdomain.pm28
-rw-r--r--FS/FS/part_export/sysvshell.pm25
-rw-r--r--FS/FS/part_export/textradius.pm191
-rw-r--r--FS/FS/part_export/vpopmail.pm254
-rw-r--r--FS/FS/part_export/www_shellcommands.pm166
-rw-r--r--FS/FS/part_export_option.pm134
-rw-r--r--FS/FS/part_pkg.pm335
-rw-r--r--FS/FS/part_pop_local.pm117
-rw-r--r--FS/FS/part_referral.pm126
-rw-r--r--FS/FS/part_svc.pm418
-rw-r--r--FS/FS/part_svc_column.pm118
-rwxr-xr-xFS/FS/part_svc_router.pm32
-rwxr-xr-xFS/FS/part_virtual_field.pm303
-rw-r--r--FS/FS/pkg_svc.pm155
-rw-r--r--FS/FS/port.pm160
-rw-r--r--FS/FS/prepay_credit.pm127
-rw-r--r--FS/FS/queue.pm438
-rw-r--r--FS/FS/queue_arg.pm121
-rw-r--r--FS/FS/queue_depend.pm121
-rw-r--r--FS/FS/raddb.pm1599
-rw-r--r--FS/FS/radius_usergroup.pm131
-rwxr-xr-xFS/FS/router.pm144
-rw-r--r--FS/FS/session.pm269
-rw-r--r--FS/FS/svc_Common.pm553
-rw-r--r--FS/FS/svc_acct.pm1456
-rw-r--r--FS/FS/svc_acct_pop.pm210
-rwxr-xr-xFS/FS/svc_broadband.pm243
-rw-r--r--FS/FS/svc_domain.pm445
-rw-r--r--FS/FS/svc_external.pm180
-rw-r--r--FS/FS/svc_forward.pm306
-rw-r--r--FS/FS/svc_www.pm284
-rw-r--r--FS/FS/type_pkgs.pm126
-rw-r--r--FS/MANIFEST212
-rw-r--r--FS/MANIFEST.SKIP1
-rw-r--r--FS/Makefile.PL10
-rw-r--r--FS/README6
-rw-r--r--FS/bin/freeside-addoutsource24
-rw-r--r--FS/bin/freeside-addoutsourceuser15
-rw-r--r--FS/bin/freeside-adduser63
-rwxr-xr-xFS/bin/freeside-apply-credits21
-rwxr-xr-xFS/bin/freeside-bill128
-rwxr-xr-xFS/bin/freeside-count-active-customers17
-rwxr-xr-xFS/bin/freeside-daily151
-rw-r--r--FS/bin/freeside-deloutsource11
-rw-r--r--FS/bin/freeside-deloutsourceuser6
-rw-r--r--FS/bin/freeside-deluser64
-rwxr-xr-xFS/bin/freeside-email59
-rwxr-xr-xFS/bin/freeside-expiration-alerter226
-rw-r--r--FS/bin/freeside-queued310
-rw-r--r--FS/bin/freeside-radgroup76
-rw-r--r--FS/bin/freeside-reexport71
-rw-r--r--FS/bin/freeside-selfservice-server295
-rw-r--r--FS/bin/freeside-setinvoice42
-rwxr-xr-xFS/bin/freeside-setup1158
-rw-r--r--FS/bin/freeside-sqlradius-radacctd180
-rwxr-xr-xFS/bin/freeside-sqlradius-reset85
-rw-r--r--FS/bin/freeside-sqlradius-seconds58
-rw-r--r--FS/t/CGI.t5
-rw-r--r--FS/t/ClientAPI.t5
-rw-r--r--FS/t/Conf.t5
-rw-r--r--FS/t/ConfItem.t5
-rw-r--r--FS/t/InitHandler.t5
-rw-r--r--FS/t/Misc.t5
-rw-r--r--FS/t/Msgcat.t5
-rw-r--r--FS/t/Record.t5
-rw-r--r--FS/t/Report-Table-Monthly.t5
-rw-r--r--FS/t/Report-Table.t5
-rw-r--r--FS/t/Report.t5
-rw-r--r--FS/t/SearchCache.t5
-rw-r--r--FS/t/UID.t5
-rw-r--r--FS/t/acct_snarf.t5
-rw-r--r--FS/t/agent.t5
-rw-r--r--FS/t/agent_type.t5
-rw-r--r--FS/t/cust_bill.t5
-rw-r--r--FS/t/cust_bill_event.t5
-rw-r--r--FS/t/cust_bill_pay.t5
-rw-r--r--FS/t/cust_bill_pkg.t5
-rw-r--r--FS/t/cust_bill_pkg_detail.t5
-rw-r--r--FS/t/cust_credit.t5
-rw-r--r--FS/t/cust_credit_bill.t5
-rw-r--r--FS/t/cust_credit_refund.t5
-rw-r--r--FS/t/cust_main.t5
-rw-r--r--FS/t/cust_main_county.t5
-rw-r--r--FS/t/cust_main_invoice.t5
-rw-r--r--FS/t/cust_pay.t5
-rw-r--r--FS/t/cust_pay_batch.t5
-rw-r--r--FS/t/cust_pay_refund.t5
-rw-r--r--FS/t/cust_pay_void.t5
-rw-r--r--FS/t/cust_pkg.t5
-rw-r--r--FS/t/cust_refund.t5
-rw-r--r--FS/t/cust_svc.t5
-rw-r--r--FS/t/cust_tax_exempt.pm5
-rw-r--r--FS/t/cust_tax_exempt.t5
-rw-r--r--FS/t/domain_record.t5
-rw-r--r--FS/t/export_svc.t5
-rw-r--r--FS/t/msgcat.t5
-rw-r--r--FS/t/nas.t5
-rw-r--r--FS/t/part_bill_event.t5
-rw-r--r--FS/t/part_export-acct_sql.t5
-rw-r--r--FS/t/part_export-apache.t5
-rw-r--r--FS/t/part_export-bind.t5
-rw-r--r--FS/t/part_export-bind_slave.t5
-rw-r--r--FS/t/part_export-bsdshell.t5
-rw-r--r--FS/t/part_export-communigate_pro.t5
-rw-r--r--FS/t/part_export-communigate_pro_singledomain.t5
-rw-r--r--FS/t/part_export-cp.t5
-rw-r--r--FS/t/part_export-cyrus.t5
-rw-r--r--FS/t/part_export-domain_shellcommands.t5
-rw-r--r--FS/t/part_export-forward_shellcommands.t5
-rw-r--r--FS/t/part_export-http.t5
-rw-r--r--FS/t/part_export-infostreet.t5
-rw-r--r--FS/t/part_export-ldap.t5
-rw-r--r--FS/t/part_export-null.t5
-rw-r--r--FS/t/part_export-passwdfile.t5
-rw-r--r--FS/t/part_export-postfix.t5
-rw-r--r--FS/t/part_export-router.t5
-rw-r--r--FS/t/part_export-shellcommands.t5
-rw-r--r--FS/t/part_export-shellcommands_withdomain.t5
-rw-r--r--FS/t/part_export-sqlmail.t5
-rw-r--r--FS/t/part_export-sqlradius.t5
-rw-r--r--FS/t/part_export-sqlradius_withdomain.t5
-rw-r--r--FS/t/part_export-sysvshell.t5
-rw-r--r--FS/t/part_export-textradius.t5
-rw-r--r--FS/t/part_export-vpopmail.t5
-rw-r--r--FS/t/part_export-www_shellcommands.t5
-rw-r--r--FS/t/part_export.t5
-rw-r--r--FS/t/part_export_option.t5
-rw-r--r--FS/t/part_pkg.t5
-rw-r--r--FS/t/part_pop_local.t5
-rw-r--r--FS/t/part_referral.t5
-rw-r--r--FS/t/part_svc.t5
-rw-r--r--FS/t/part_svc_column.t5
-rw-r--r--FS/t/pkg_svc.t5
-rw-r--r--FS/t/port.t5
-rw-r--r--FS/t/prepay_credit.t5
-rw-r--r--FS/t/queue.t5
-rw-r--r--FS/t/queue_arg.t5
-rw-r--r--FS/t/queue_depend.t5
-rw-r--r--FS/t/raddb.t5
-rw-r--r--FS/t/radius_usergroup.t5
-rw-r--r--FS/t/session.t5
-rw-r--r--FS/t/svc_Common.t5
-rw-r--r--FS/t/svc_acct.t5
-rw-r--r--FS/t/svc_acct_pop.t5
-rw-r--r--FS/t/svc_broadband.t5
-rw-r--r--FS/t/svc_domain.t5
-rw-r--r--FS/t/svc_external.t5
-rw-r--r--FS/t/svc_forward.t5
-rw-r--r--FS/t/svc_www.t5
-rw-r--r--FS/t/type_pkgs.t5
-rw-r--r--GPL339
-rw-r--r--INSTALL1
-rw-r--r--Makefile297
-rw-r--r--README43
-rw-r--r--README.1.5.0pre637
-rw-r--r--TODO9
-rwxr-xr-xbin/apache.export67
-rw-r--r--bin/artera.import75
-rwxr-xr-xbin/bind.export191
-rwxr-xr-xbin/bind.import214
-rwxr-xr-xbin/bsdshell.export114
-rw-r--r--bin/create-fetchmailrc47
-rwxr-xr-xbin/create-history-tables93
-rwxr-xr-xbin/dbdef-create24
-rwxr-xr-xbin/fix-sequences69
-rwxr-xr-xbin/freeside-init60
-rwxr-xr-xbin/freeside-session-kill103
-rw-r--r--bin/freeside.import146
-rwxr-xr-xbin/fs-migrate-part_svc41
-rwxr-xr-xbin/fs-migrate-payref31
-rwxr-xr-xbin/fs-migrate-svc_acct_sm229
-rwxr-xr-xbin/fs-radius-add-check68
-rwxr-xr-xbin/fs-radius-add-reply69
-rwxr-xr-xbin/generate-prepay35
-rwxr-xr-xbin/generate-raddb47
-rwxr-xr-xbin/generate-tests21
-rwxr-xr-xbin/ispman.ldap.import114
-rwxr-xr-xbin/masonize80
-rwxr-xr-xbin/passwd.import120
-rwxr-xr-xbin/pod2x56
-rwxr-xr-xbin/populate-msgcat135
-rwxr-xr-xbin/postfix.export122
-rwxr-xr-xbin/postfix_courierimap.import137
-rw-r--r--bin/sendmail.import178
-rw-r--r--bin/sequences.reset32
-rwxr-xr-xbin/shadow.reimport125
-rwxr-xr-xbin/sqlradius-norealm.reimport113
-rw-r--r--bin/sqlradius.import152
-rwxr-xr-xbin/sqlradius.reimport160
-rwxr-xr-xbin/svc_acct.export641
-rwxr-xr-xbin/svc_acct.import238
-rwxr-xr-xbin/svc_domain.erase17
-rwxr-xr-xbin/sysvshell.export112
-rw-r--r--conf/agent_defaultpkg0
-rw-r--r--conf/alerter_template20
-rw-r--r--conf/cust_pkg-change_svcpart0
-rw-r--r--conf/declinetemplate10
-rw-r--r--conf/home1
-rw-r--r--conf/invoice_from1
-rw-r--r--conf/invoice_latex155
-rw-r--r--conf/invoice_latexfooter5
-rw-r--r--conf/invoice_latexnotes8
-rw-r--r--conf/invoice_latexsmallfooter1
-rw-r--r--conf/invoice_template27
-rw-r--r--conf/locale1
-rw-r--r--conf/logo.eps13503
-rw-r--r--conf/lpr1
-rw-r--r--conf/maxsearchrecordsperpage1
-rw-r--r--conf/payment_receipt_email26
-rw-r--r--conf/report_template14
-rw-r--r--conf/shells5
-rw-r--r--conf/show-msgcat-codes0
-rw-r--r--conf/smtpmachine1
-rw-r--r--conf/soadefaultttl1
-rw-r--r--conf/soaexpire1
-rw-r--r--conf/soarefresh1
-rw-r--r--conf/soaretry1
-rw-r--r--debian/README.Debian6
-rw-r--r--debian/changelog9
-rw-r--r--debian/conffiles.ex7
-rw-r--r--debian/control59
-rw-r--r--debian/copyright10
-rw-r--r--debian/cron.d.ex4
-rw-r--r--debian/dirs2
-rw-r--r--debian/docs3
-rw-r--r--debian/ex.doc-base.package22
-rw-r--r--debian/freeside-doc.docs2
-rw-r--r--debian/freeside-doc.files2
-rw-r--r--debian/init.d.ex70
-rw-r--r--debian/manpage.1.ex60
-rw-r--r--debian/manpage.sgml.ex143
-rw-r--r--debian/menu.ex2
-rw-r--r--debian/postinst.ex47
-rw-r--r--debian/postrm.ex36
-rw-r--r--debian/preinst.ex42
-rw-r--r--debian/prerm.ex37
-rwxr-xr-xdebian/rules113
-rw-r--r--debian/watch.ex5
-rwxr-xr-xeg/TEMPLATE_cust_main.import198
-rw-r--r--eg/export_template.pm81
-rw-r--r--eg/table_template-svc.pm161
-rw-r--r--eg/table_template.pm112
-rw-r--r--etc/abbr_state.txt72
-rw-r--r--etc/countries.txt239
-rw-r--r--etc/domain-template.txt231
-rwxr-xr-xetc/megapop.pl116
-rw-r--r--etc/sql-reserved-words.txt103
-rwxr-xr-xfs_passwd/fs_passwd131
-rwxr-xr-xfs_radlog/fs_radlogd51
-rwxr-xr-xfs_selfadmin/FS-MailAdminServer/MailAdminClient.pm541
-rwxr-xr-xfs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi698
-rwxr-xr-xfs_selfadmin/FS-MailAdminServer/fs_mailadmind366
-rw-r--r--fs_selfadmin/README27
-rwxr-xr-xfs_selfadmin/fs_mailadmin_server642
-rwxr-xr-xfs_selfservice/DEPLOY20
-rw-r--r--fs_selfservice/FS-SelfService/Changes6
-rw-r--r--fs_selfservice/FS-SelfService/MANIFEST6
-rw-r--r--fs_selfservice/FS-SelfService/Makefile.PL19
-rw-r--r--fs_selfservice/FS-SelfService/SelfService.pm1083
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent.cgi448
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html7
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html20
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_login.html22
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_logout.html5
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_main.html37
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_menu.html15
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html19
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_provision.html25
-rw-r--r--fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html19
-rw-r--r--fs_selfservice/FS-SelfService/cgi/cvv2.html25
-rw-r--r--fs_selfservice/FS-SelfService/cgi/cvv2.pngbin0 -> 3854 bytes
-rw-r--r--fs_selfservice/FS-SelfService/cgi/cvv2_amex.pngbin0 -> 4573 bytes
-rw-r--r--fs_selfservice/FS-SelfService/cgi/delete_svc.html18
-rw-r--r--fs_selfservice/FS-SelfService/cgi/list_customers.html39
-rw-r--r--fs_selfservice/FS-SelfService/cgi/login.html29
-rw-r--r--fs_selfservice/FS-SelfService/cgi/logout.html5
-rw-r--r--fs_selfservice/FS-SelfService/cgi/make_payment.html119
-rw-r--r--fs_selfservice/FS-SelfService/cgi/myaccount.html46
-rw-r--r--fs_selfservice/FS-SelfService/cgi/myaccount_menu.html13
-rw-r--r--fs_selfservice/FS-SelfService/cgi/order_pkg.html75
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/passwd.cgi60
-rw-r--r--fs_selfservice/FS-SelfService/cgi/passwd.html28
-rw-r--r--fs_selfservice/FS-SelfService/cgi/payment_results.html17
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_svc_acct.html14
-rw-r--r--fs_selfservice/FS-SelfService/cgi/process_svc_external.html16
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision.html11
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision_list.html87
-rw-r--r--fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html12
-rw-r--r--fs_selfservice/FS-SelfService/cgi/selfservice.cgi287
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/signup.html233
-rw-r--r--fs_selfservice/FS-SelfService/cgi/svc_acct.html55
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_customer.html29
-rw-r--r--fs_selfservice/FS-SelfService/cgi/view_invoice.html18
-rw-r--r--fs_selfservice/FS-SelfService/freeside-selfservice-clientd271
-rw-r--r--fs_selfservice/FS-SelfService/test.pl17
-rwxr-xr-xfs_selfservice/fs_passwd_test19
-rw-r--r--fs_sesmon/FS-SessionClient/Changes5
-rw-r--r--fs_sesmon/FS-SessionClient/MANIFEST11
-rw-r--r--fs_sesmon/FS-SessionClient/MANIFEST.SKIP1
-rw-r--r--fs_sesmon/FS-SessionClient/Makefile.PL10
-rw-r--r--fs_sesmon/FS-SessionClient/SessionClient.pm122
-rw-r--r--fs_sesmon/FS-SessionClient/bin/freeside-login36
-rw-r--r--fs_sesmon/FS-SessionClient/bin/freeside-logout36
-rw-r--r--fs_sesmon/FS-SessionClient/cgi/login.cgi108
-rw-r--r--fs_sesmon/FS-SessionClient/cgi/logout.cgi83
-rw-r--r--fs_sesmon/FS-SessionClient/fs_sessiond65
-rw-r--r--fs_sesmon/FS-SessionClient/test.pl21
-rw-r--r--fs_sesmon/fs_session_server140
-rw-r--r--fs_signup/FS-SignupClient/Changes5
-rw-r--r--fs_signup/FS-SignupClient/MANIFEST7
-rw-r--r--fs_signup/FS-SignupClient/MANIFEST.SKIP1
-rw-r--r--fs_signup/FS-SignupClient/Makefile.PL17
-rw-r--r--fs_signup/FS-SignupClient/SignupClient.pm208
-rw-r--r--fs_signup/FS-SignupClient/cgi/cvv2.html25
-rw-r--r--fs_signup/FS-SignupClient/cgi/cvv2.pngbin0 -> 3854 bytes
-rw-r--r--fs_signup/FS-SignupClient/cgi/cvv2_amex.pngbin0 -> 4573 bytes
-rw-r--r--fs_signup/FS-SignupClient/cgi/decline.html5
-rw-r--r--fs_signup/FS-SignupClient/cgi/map.gifbin0 -> 8181 bytes
-rwxr-xr-xfs_signup/FS-SignupClient/cgi/signup-agentselect.html195
-rwxr-xr-xfs_signup/FS-SignupClient/cgi/signup-alternate.html218
-rwxr-xr-xfs_signup/FS-SignupClient/cgi/signup-snarf.html228
-rwxr-xr-xfs_signup/FS-SignupClient/cgi/signup.cgi389
-rwxr-xr-xfs_signup/FS-SignupClient/cgi/signup.html219
-rw-r--r--fs_signup/FS-SignupClient/cgi/stateselect.html134
-rw-r--r--fs_signup/FS-SignupClient/cgi/success.html11
-rw-r--r--fs_signup/FS-SignupClient/test.pl20
-rw-r--r--fs_signup/cck.template14
-rwxr-xr-xfs_signup/fs_signup_server289
-rwxr-xr-xfs_signup/ieak.template40
-rwxr-xr-xfs_webdemo/register.cgi136
-rw-r--r--fs_webdemo/register.html33
-rwxr-xr-xfs_webdemo/registerd192
-rwxr-xr-xfs_webdemo/registerd.Pg221
-rw-r--r--htetc/global.asa242
-rw-r--r--htetc/handler.pl305
-rwxr-xr-xhttemplate/.htaccess3
-rw-r--r--httemplate/autohandler21
-rw-r--r--httemplate/browse/addr_block.cgi76
-rwxr-xr-xhttemplate/browse/agent.cgi100
-rwxr-xr-xhttemplate/browse/agent_type.cgi60
-rwxr-xr-xhttemplate/browse/cust_main_county.cgi142
-rwxr-xr-xhttemplate/browse/cust_pay_batch.cgi76
-rw-r--r--httemplate/browse/generic.cgi46
-rwxr-xr-xhttemplate/browse/msgcat.cgi50
-rwxr-xr-xhttemplate/browse/nas.cgi80
-rwxr-xr-xhttemplate/browse/part_bill_event.cgi71
-rwxr-xr-xhttemplate/browse/part_export.cgi39
-rwxr-xr-xhttemplate/browse/part_pkg.cgi172
-rwxr-xr-xhttemplate/browse/part_referral.cgi97
-rwxr-xr-xhttemplate/browse/part_svc.cgi133
-rw-r--r--httemplate/browse/part_virtual_field.cgi39
-rwxr-xr-xhttemplate/browse/queue.cgi7
-rw-r--r--httemplate/browse/router.cgi57
-rwxr-xr-xhttemplate/browse/svc_acct_pop.cgi63
-rw-r--r--httemplate/config/config-process.cgi51
-rw-r--r--httemplate/config/config-view.cgi64
-rw-r--r--httemplate/config/config.cgi176
-rw-r--r--httemplate/docs/ach.html12
-rwxr-xr-xhttemplate/docs/admin.html81
-rw-r--r--httemplate/docs/billing.html54
-rw-r--r--httemplate/docs/config.html36
-rw-r--r--httemplate/docs/cvv2.html25
-rwxr-xr-xhttemplate/docs/export.html55
-rw-r--r--httemplate/docs/ieak.html75
-rw-r--r--httemplate/docs/index.html30
-rw-r--r--httemplate/docs/install.html212
-rwxr-xr-xhttemplate/docs/legacy.html39
-rw-r--r--httemplate/docs/man/FS/part_export/.cvs_is_on_crack0
-rw-r--r--httemplate/docs/overview.diabin0 -> 2800 bytes
-rw-r--r--httemplate/docs/overview.pngbin0 -> 13064 bytes
-rwxr-xr-xhttemplate/docs/passwd.html23
-rw-r--r--httemplate/docs/schema.diabin0 -> 14438 bytes
-rw-r--r--httemplate/docs/schema.html457
-rw-r--r--httemplate/docs/schema.pngbin0 -> 681043 bytes
-rw-r--r--httemplate/docs/session.html59
-rw-r--r--httemplate/docs/signup.html54
-rwxr-xr-xhttemplate/docs/ssh.html16
-rwxr-xr-xhttemplate/docs/trouble.html26
-rw-r--r--httemplate/docs/upgrade-1.4.2.html27
-rw-r--r--httemplate/docs/upgrade10.html255
-rw-r--r--httemplate/docs/upgrade7.html24
-rw-r--r--httemplate/docs/upgrade8.html392
-rw-r--r--httemplate/docs/upgrade9.html28
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi131
-rwxr-xr-xhttemplate/edit/agent.cgi79
-rwxr-xr-xhttemplate/edit/agent_type.cgi63
-rwxr-xr-xhttemplate/edit/cust_bill_pay.cgi95
-rwxr-xr-xhttemplate/edit/cust_credit.cgi63
-rwxr-xr-xhttemplate/edit/cust_credit_bill.cgi101
-rwxr-xr-xhttemplate/edit/cust_main.cgi572
-rwxr-xr-xhttemplate/edit/cust_main_county-expand.cgi54
-rwxr-xr-xhttemplate/edit/cust_main_county.cgi98
-rwxr-xr-xhttemplate/edit/cust_pay.cgi129
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi117
-rwxr-xr-xhttemplate/edit/cust_refund.cgi94
-rwxr-xr-xhttemplate/edit/msgcat.cgi58
-rwxr-xr-xhttemplate/edit/part_bill_event.cgi288
-rw-r--r--httemplate/edit/part_export.cgi128
-rwxr-xr-xhttemplate/edit/part_pkg.cgi627
-rwxr-xr-xhttemplate/edit/part_referral.cgi48
-rwxr-xr-xhttemplate/edit/part_svc.cgi332
-rw-r--r--httemplate/edit/part_virtual_field.cgi92
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi24
-rwxr-xr-xhttemplate/edit/process/addr_block/add.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/allocate.cgi25
-rwxr-xr-xhttemplate/edit/process/addr_block/deallocate.cgi24
-rwxr-xr-xhttemplate/edit/process/addr_block/split.cgi19
-rwxr-xr-xhttemplate/edit/process/agent.cgi28
-rwxr-xr-xhttemplate/edit/process/agent_type.cgi55
-rwxr-xr-xhttemplate/edit/process/cust_bill_pay.cgi43
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi26
-rwxr-xr-xhttemplate/edit/process/cust_credit_bill.cgi43
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi131
-rwxr-xr-xhttemplate/edit/process/cust_main_county-collapse.cgi35
-rwxr-xr-xhttemplate/edit/process/cust_main_county-expand.cgi58
-rwxr-xr-xhttemplate/edit/process/cust_main_county.cgi30
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi39
-rwxr-xr-xhttemplate/edit/process/cust_pkg.cgi43
-rwxr-xr-xhttemplate/edit/process/cust_refund.cgi42
-rw-r--r--httemplate/edit/process/cust_svc.cgi30
-rwxr-xr-xhttemplate/edit/process/domain_record.cgi34
-rw-r--r--httemplate/edit/process/generic.cgi70
-rw-r--r--httemplate/edit/process/msgcat.cgi20
-rwxr-xr-xhttemplate/edit/process/part_bill_event.cgi54
-rw-r--r--httemplate/edit/process/part_export.cgi39
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi117
-rwxr-xr-xhttemplate/edit/process/part_referral.cgi28
-rw-r--r--httemplate/edit/process/quick-charge.cgi32
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi25
-rw-r--r--httemplate/edit/process/router.cgi67
-rwxr-xr-xhttemplate/edit/process/svc_acct.cgi49
-rwxr-xr-xhttemplate/edit/process/svc_acct_pop.cgi28
-rw-r--r--httemplate/edit/process/svc_broadband.cgi45
-rwxr-xr-xhttemplate/edit/process/svc_domain.cgi31
-rwxr-xr-xhttemplate/edit/process/svc_external.cgi29
-rwxr-xr-xhttemplate/edit/process/svc_forward.cgi29
-rw-r--r--httemplate/edit/process/svc_www.cgi36
-rwxr-xr-xhttemplate/edit/router.cgi77
-rwxr-xr-xhttemplate/edit/svc_acct.cgi301
-rwxr-xr-xhttemplate/edit/svc_acct_pop.cgi56
-rw-r--r--httemplate/edit/svc_broadband.cgi175
-rwxr-xr-xhttemplate/edit/svc_domain.cgi98
-rw-r--r--httemplate/edit/svc_external.cgi105
-rwxr-xr-xhttemplate/edit/svc_forward.cgi177
-rw-r--r--httemplate/edit/svc_www.cgi221
-rw-r--r--httemplate/elements/calendar-en.js123
-rw-r--r--httemplate/elements/calendar-setup.js181
-rw-r--r--httemplate/elements/calendar-win2k-2.css270
-rw-r--r--httemplate/elements/calendar.js1715
-rw-r--r--httemplate/elements/calendar_stripped.js12
-rw-r--r--httemplate/elements/header.html21
-rw-r--r--httemplate/elements/menubar.html8
-rw-r--r--httemplate/elements/pager.html42
-rw-r--r--httemplate/elements/small_custview.html2
-rw-r--r--httemplate/elements/table.html8
-rwxr-xr-xhttemplate/graph/money_time-graph.cgi68
-rw-r--r--httemplate/graph/money_time.cgi125
-rw-r--r--httemplate/images/ach.pngbin0 -> 29759 bytes
-rw-r--r--httemplate/images/calendar.pngbin0 -> 426 bytes
-rw-r--r--httemplate/images/cvv2.pngbin0 -> 3854 bytes
-rw-r--r--httemplate/images/cvv2_amex.pngbin0 -> 4573 bytes
-rw-r--r--httemplate/images/small-logo.pngbin0 -> 5261 bytes
-rw-r--r--httemplate/index.html212
-rwxr-xr-xhttemplate/misc/bill.cgi38
-rwxr-xr-xhttemplate/misc/cancel-unaudited.cgi34
-rwxr-xr-xhttemplate/misc/cancel_pkg.cgi15
-rwxr-xr-xhttemplate/misc/catchall.cgi133
-rwxr-xr-xhttemplate/misc/change_pkg.cgi66
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi16
-rw-r--r--httemplate/misc/cust_main-import.cgi51
-rw-r--r--httemplate/misc/cust_main-import_charges.cgi14
-rwxr-xr-xhttemplate/misc/delete-cust_credit.cgi16
-rwxr-xr-xhttemplate/misc/delete-cust_pay.cgi16
-rwxr-xr-xhttemplate/misc/delete-customer.cgi60
-rwxr-xr-xhttemplate/misc/delete-domain_record.cgi15
-rwxr-xr-xhttemplate/misc/delete-part_export.cgi15
-rw-r--r--httemplate/misc/download-batch.cgi16
-rw-r--r--httemplate/misc/dump.cgi19
-rwxr-xr-xhttemplate/misc/email-invoice.cgi23
-rwxr-xr-xhttemplate/misc/expire_pkg.cgi55
-rwxr-xr-xhttemplate/misc/link.cgi74
-rw-r--r--httemplate/misc/meta-import.cgi64
-rw-r--r--httemplate/misc/payment.cgi209
-rwxr-xr-xhttemplate/misc/print-invoice.cgi29
-rwxr-xr-xhttemplate/misc/process/catchall.cgi33
-rw-r--r--httemplate/misc/process/cust_main-import.cgi30
-rw-r--r--httemplate/misc/process/cust_main-import_charges.cgi26
-rwxr-xr-xhttemplate/misc/process/delete-customer.cgi29
-rwxr-xr-xhttemplate/misc/process/expire_pkg.cgi25
-rwxr-xr-xhttemplate/misc/process/link.cgi57
-rw-r--r--httemplate/misc/process/meta-import.cgi178
-rw-r--r--httemplate/misc/process/payment.cgi148
-rw-r--r--httemplate/misc/queue.cgi47
-rwxr-xr-xhttemplate/misc/susp_pkg.cgi15
-rwxr-xr-xhttemplate/misc/unapply-cust_credit.cgi18
-rwxr-xr-xhttemplate/misc/unapply-cust_pay.cgi18
-rwxr-xr-xhttemplate/misc/unprovision.cgi29
-rwxr-xr-xhttemplate/misc/unsusp_pkg.cgi15
-rw-r--r--httemplate/misc/upload-batch.cgi30
-rwxr-xr-xhttemplate/misc/void-cust_pay.cgi16
-rw-r--r--httemplate/misc/whois.cgi25
-rwxr-xr-xhttemplate/search/cust_bill.cgi165
-rwxr-xr-xhttemplate/search/cust_bill.html101
-rw-r--r--httemplate/search/cust_bill_event.cgi62
-rwxr-xr-xhttemplate/search/cust_bill_event.html54
-rwxr-xr-xhttemplate/search/cust_credit.html80
-rwxr-xr-xhttemplate/search/cust_main-otaker.cgi28
-rwxr-xr-xhttemplate/search/cust_main-payinfo.html20
-rwxr-xr-xhttemplate/search/cust_main-quickpay.html44
-rwxr-xr-xhttemplate/search/cust_main.cgi608
-rwxr-xr-xhttemplate/search/cust_main.html42
-rwxr-xr-xhttemplate/search/cust_pay.cgi137
-rwxr-xr-xhttemplate/search/cust_pay.html18
-rwxr-xr-xhttemplate/search/cust_pkg.cgi363
-rwxr-xr-xhttemplate/search/cust_pkg_report.cgi63
-rw-r--r--httemplate/search/elements/search.html139
-rw-r--r--httemplate/search/report_cust_credit.html58
-rw-r--r--httemplate/search/report_cust_pay.html55
-rw-r--r--httemplate/search/report_prepaid_income.cgi86
-rw-r--r--httemplate/search/report_prepaid_income.html39
-rwxr-xr-xhttemplate/search/report_receivables.cgi158
-rwxr-xr-xhttemplate/search/report_tax.cgi184
-rwxr-xr-xhttemplate/search/report_tax.html44
-rw-r--r--httemplate/search/sql.html7
-rw-r--r--httemplate/search/sqlradius.cgi260
-rw-r--r--httemplate/search/sqlradius.html70
-rwxr-xr-xhttemplate/search/svc_acct.cgi294
-rwxr-xr-xhttemplate/search/svc_acct.html19
-rwxr-xr-xhttemplate/search/svc_domain.cgi161
-rwxr-xr-xhttemplate/search/svc_domain.html19
-rwxr-xr-xhttemplate/search/svc_external.cgi101
-rwxr-xr-xhttemplate/search/svc_forward.cgi79
-rwxr-xr-xhttemplate/search/svc_www.cgi42
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi18
-rwxr-xr-xhttemplate/view/cust_bill-ps.cgi14
-rwxr-xr-xhttemplate/view/cust_bill.cgi82
-rwxr-xr-xhttemplate/view/cust_main.cgi1064
-rwxr-xr-xhttemplate/view/cust_pkg.cgi164
-rwxr-xr-xhttemplate/view/svc_acct.cgi272
-rw-r--r--httemplate/view/svc_broadband.cgi142
-rwxr-xr-xhttemplate/view/svc_domain.cgi108
-rw-r--r--httemplate/view/svc_external.cgi54
-rwxr-xr-xhttemplate/view/svc_forward.cgi84
-rw-r--r--httemplate/view/svc_www.cgi61
-rw-r--r--init.d/freeside-init62
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/Changes352
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/MANIFEST38
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/Makefile.PL83
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/Pg.h46
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/Pg.pm1913
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/Pg.xs644
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/README166
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/README.win3263
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/dbd-pg.pod411
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.c2024
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.h81
-rwxr-xr-xinstall/5.005/DBD-Pg-1.22-fixvercmp/eg/ApacheDBI.pl70
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/eg/lotest.pl74
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/eg/notify_test.patch82
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/00basic.t10
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/01connect.t26
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/01constants.t25
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/01setup.t38
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/02prepare.t84
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/03bind.t85
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/04execute.t113
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/05fetch.t131
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/06disconnect.t31
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/07reuse.t28
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/08txn.t102
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/09autocommit.t68
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/11quoting.t50
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/12placeholders.t125
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/13pgtype.t43
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/15funct.t353
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/99cleanup.t24
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info.pm1167
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler.pm305
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler/Prompt.pm170
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS.pm55
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS/PostgreSQL.pm730
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Request.pm287
-rw-r--r--install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Util.pm456
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/Changes62
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema.pm367
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup.pm141
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Index.pm37
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Unique.pm38
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Column.pm300
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD.pm113
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Pg.pm175
-rwxr-xr-xinstall/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Sybase.pm141
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/mysql.pm126
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Table.pm471
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST19
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST.SKIP1
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/Makefile.PL11
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/README42
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/TODO6
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-mysql.t5
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-pg.t12
-rw-r--r--install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load.t5
-rw-r--r--install/debian/3.0/INSTALL31
-rwxr-xr-xinstall/fedora/fc1/INSTALL66
-rw-r--r--install/fedora/fc1/sources.list12
-rwxr-xr-xinstall/freebsd/INSTALL40
-rw-r--r--install/freebsd/ports44
-rw-r--r--install/openbsd/INSTALL54
-rw-r--r--install/openbsd/cpan15
-rw-r--r--install/openbsd/ports24
-rw-r--r--install/redhat/7.3/INSTALL40
-rw-r--r--install/redhat/7.3/sources.list2
-rwxr-xr-xinstall/redhat/8/INSTALL47
-rw-r--r--install/redhat/8/README.insecure6
-rw-r--r--install/redhat/8/sources.list1
-rw-r--r--install/redhat/9/INSTALL48
-rw-r--r--install/redhat/9/sources.list2
-rw-r--r--install/suse/9.0/INSTALL52
-rwxr-xr-xrt/COPYING339
-rw-r--r--rt/Changelog2913
-rw-r--r--rt/FREESIDE_MODIFIED6
-rw-r--r--rt/HOWTO/README14
-rw-r--r--rt/HOWTO/change.txt67
-rw-r--r--rt/HOWTO/release.txt124
-rw-r--r--rt/HOWTO/version-control.txt41
-rw-r--r--rt/Makefile490
-rw-r--r--rt/Makefile.in490
-rwxr-xr-xrt/README302
-rw-r--r--rt/README.Oracle37
-rw-r--r--rt/UPGRADING64
-rw-r--r--rt/aclocal.m4158
-rw-r--r--rt/autom4te.cache/output.02771
-rw-r--r--rt/autom4te.cache/requests94
-rw-r--r--rt/autom4te.cache/traces.0158
-rwxr-xr-xrt/bin/mason_handler.fcgi68
-rw-r--r--rt/bin/mason_handler.fcgi.in68
-rwxr-xr-xrt/bin/mason_handler.scgi43
-rw-r--r--rt/bin/mason_handler.scgi.in43
-rw-r--r--rt/bin/mason_handler.svc234
-rw-r--r--rt/bin/mason_handler.svc.in234
-rw-r--r--rt/bin/rt-commit-handler846
-rw-r--r--rt/bin/rt-commit-handler.in846
-rw-r--r--rt/bin/rt-crontool220
-rw-r--r--rt/bin/rt-crontool.in220
-rwxr-xr-xrt/bin/rt-mailgate648
-rw-r--r--rt/bin/rt-mailgate.in648
-rw-r--r--rt/bin/rt.in1816
-rwxr-xr-xrt/bin/webmux.pl148
-rw-r--r--rt/bin/webmux.pl.in148
-rw-r--r--rt/config256
-rw-r--r--rt/config.layout.in127
-rw-r--r--rt/config.log118
-rw-r--r--rt/config.pld19
-rwxr-xr-xrt/config.status713
-rwxr-xr-xrt/configure2771
-rw-r--r--rt/configure.ac229
-rwxr-xr-xrt/docs/README.docs2
-rw-r--r--rt/docs/Security25
-rwxr-xr-xrt/docs/design_docs/CARS66
-rwxr-xr-xrt/docs/design_docs/TransactionTypes.txt48
-rw-r--r--rt/docs/design_docs/acls50
-rw-r--r--rt/docs/design_docs/approval_notices8
-rw-r--r--rt/docs/design_docs/approval_template25
-rw-r--r--rt/docs/design_docs/cf_search72
-rw-r--r--rt/docs/design_docs/cli_spec31
-rw-r--r--rt/docs/design_docs/cvs_integration164
-rw-r--r--rt/docs/design_docs/delegation115
-rw-r--r--rt/docs/design_docs/evil_plans162
-rw-r--r--rt/docs/design_docs/groups_notes88
-rw-r--r--rt/docs/design_docs/link-definitions.txt143
-rw-r--r--rt/docs/design_docs/recursive_group_membership_algorithm109
-rw-r--r--rt/docs/design_docs/rql_parser_machine.graphviz32
-rw-r--r--rt/docs/design_docs/string-extraction-guide.txt100
-rwxr-xr-xrt/docs/design_docs/subscription-definitions.txt113
-rw-r--r--rt/docs/design_docs/ticket_templates16
-rw-r--r--rt/docs/design_docs/users14
-rw-r--r--rt/docs/rt3-schema-relationships.dot81
-rw-r--r--rt/etc/RT_Config.pm374
-rw-r--r--rt/etc/RT_Config.pm.in374
-rw-r--r--rt/etc/RT_SiteConfig.pm13
-rw-r--r--rt/etc/acl.Informix5
-rw-r--r--rt/etc/acl.Oracle10
-rwxr-xr-xrt/etc/acl.Pg63
-rwxr-xr-xrt/etc/acl.mysql8
-rw-r--r--rt/etc/constraints.mysql42
-rw-r--r--rt/etc/drop.Informix19
-rw-r--r--rt/etc/drop.Oracle37
-rw-r--r--rt/etc/initialdata569
-rw-r--r--rt/etc/rt.spec137
-rw-r--r--rt/etc/schema.Informix342
-rwxr-xr-xrt/etc/schema.Pg578
-rw-r--r--rt/etc/schema.SQLite390
-rwxr-xr-xrt/etc/schema.mysql422
-rw-r--r--rt/etc/upgrade/2.1.71211
-rw-r--r--rt/html/Admin/Elements/AddCustomFieldValue44
-rw-r--r--rt/html/Admin/Elements/CreateUserCalled26
-rw-r--r--rt/html/Admin/Elements/EditCustomField127
-rw-r--r--rt/html/Admin/Elements/EditCustomFieldValues42
-rw-r--r--rt/html/Admin/Elements/EditCustomFields213
-rw-r--r--rt/html/Admin/Elements/EditQueueWatchers55
-rw-r--r--rt/html/Admin/Elements/EditScrip158
-rw-r--r--rt/html/Admin/Elements/EditScrips99
-rw-r--r--rt/html/Admin/Elements/EditTemplates104
-rw-r--r--rt/html/Admin/Elements/EditUserComments32
-rw-r--r--rt/html/Admin/Elements/GroupTabs76
-rw-r--r--rt/html/Admin/Elements/Header28
-rw-r--r--rt/html/Admin/Elements/ListGlobalCustomFields37
-rw-r--r--rt/html/Admin/Elements/ListGlobalScrips37
-rw-r--r--rt/html/Admin/Elements/ModifyQueue78
-rw-r--r--rt/html/Admin/Elements/ModifyTemplate60
-rw-r--r--rt/html/Admin/Elements/ModifyUser99
-rw-r--r--rt/html/Admin/Elements/QueueRightsForUser40
-rw-r--r--rt/html/Admin/Elements/QueueTabs93
-rw-r--r--rt/html/Admin/Elements/SelectCustomFieldType36
-rw-r--r--rt/html/Admin/Elements/SelectGroups38
-rw-r--r--rt/html/Admin/Elements/SelectModifyGroup33
-rw-r--r--rt/html/Admin/Elements/SelectModifyQueue33
-rw-r--r--rt/html/Admin/Elements/SelectModifyUser49
-rw-r--r--rt/html/Admin/Elements/SelectNewGroupMembers61
-rw-r--r--rt/html/Admin/Elements/SelectRights91
-rw-r--r--rt/html/Admin/Elements/SelectScrip48
-rw-r--r--rt/html/Admin/Elements/SelectScripAction48
-rw-r--r--rt/html/Admin/Elements/SelectScripCondition48
-rw-r--r--rt/html/Admin/Elements/SelectSingleOrMultiple43
-rw-r--r--rt/html/Admin/Elements/SelectStage39
-rw-r--r--rt/html/Admin/Elements/SelectTemplate61
-rw-r--r--rt/html/Admin/Elements/SelectUsers40
-rw-r--r--rt/html/Admin/Elements/SystemTabs70
-rw-r--r--rt/html/Admin/Elements/Tabs63
-rw-r--r--rt/html/Admin/Elements/UserTabs74
-rw-r--r--rt/html/Admin/Global/CustomField.html61
-rw-r--r--rt/html/Admin/Global/CustomFields.html47
-rw-r--r--rt/html/Admin/Global/GroupRights.html99
-rw-r--r--rt/html/Admin/Global/Scrip.html56
-rw-r--r--rt/html/Admin/Global/Scrips.html53
-rw-r--r--rt/html/Admin/Global/Template.html101
-rw-r--r--rt/html/Admin/Global/Templates.html53
-rw-r--r--rt/html/Admin/Global/UserRights.html77
-rw-r--r--rt/html/Admin/Global/index.html64
-rw-r--r--rt/html/Admin/Groups/GroupRights.html95
-rw-r--r--rt/html/Admin/Groups/Members.html134
-rw-r--r--rt/html/Admin/Groups/Modify.html134
-rw-r--r--rt/html/Admin/Groups/UserRights.html92
-rw-r--r--rt/html/Admin/Groups/index.html43
-rw-r--r--rt/html/Admin/Queues/CustomField.html60
-rw-r--r--rt/html/Admin/Queues/CustomFields.html49
-rw-r--r--rt/html/Admin/Queues/GroupRights.html110
-rw-r--r--rt/html/Admin/Queues/Modify.html163
-rw-r--r--rt/html/Admin/Queues/People.html186
-rw-r--r--rt/html/Admin/Queues/Scrip.html67
-rw-r--r--rt/html/Admin/Queues/Scrips.html63
-rw-r--r--rt/html/Admin/Queues/Template.html101
-rw-r--r--rt/html/Admin/Queues/Templates.html57
-rw-r--r--rt/html/Admin/Queues/UserRights.html90
-rw-r--r--rt/html/Admin/Queues/index.html61
-rw-r--r--rt/html/Admin/Users/Modify.html349
-rw-r--r--rt/html/Admin/Users/Prefs.html122
-rw-r--r--rt/html/Admin/Users/index.html81
-rw-r--r--rt/html/Admin/index.html40
-rw-r--r--rt/html/Approvals/Display.html50
-rw-r--r--rt/html/Approvals/Elements/Approve56
-rw-r--r--rt/html/Approvals/Elements/PendingMyApproval87
-rw-r--r--rt/html/Approvals/Elements/ShowDependency85
-rw-r--r--rt/html/Approvals/Elements/Tabs34
-rw-r--r--rt/html/Approvals/index.html66
-rw-r--r--rt/html/Elements/BevelBoxRaisedEnd26
-rw-r--r--rt/html/Elements/BevelBoxRaisedStart26
-rw-r--r--rt/html/Elements/Callback66
-rw-r--r--rt/html/Elements/Checkbox39
-rw-r--r--rt/html/Elements/CreateTicket26
-rw-r--r--rt/html/Elements/Error62
-rw-r--r--rt/html/Elements/Footer60
-rw-r--r--rt/html/Elements/GotoTicket24
-rw-r--r--rt/html/Elements/Header82
-rw-r--r--rt/html/Elements/ListActions43
-rw-r--r--rt/html/Elements/Login101
-rw-r--r--rt/html/Elements/Menu84
-rw-r--r--rt/html/Elements/MessageBox49
-rw-r--r--rt/html/Elements/MyRequests78
-rw-r--r--rt/html/Elements/MyTickets81
-rw-r--r--rt/html/Elements/PageLayout99
-rw-r--r--rt/html/Elements/Quicksearch61
-rw-r--r--rt/html/Elements/Refresh45
-rw-r--r--rt/html/Elements/Section34
-rw-r--r--rt/html/Elements/SelectAttachmentField31
-rw-r--r--rt/html/Elements/SelectBoolean46
-rw-r--r--rt/html/Elements/SelectCustomFieldOperator40
-rw-r--r--rt/html/Elements/SelectCustomFieldValue41
-rw-r--r--rt/html/Elements/SelectDate48
-rw-r--r--rt/html/Elements/SelectDateRelation36
-rw-r--r--rt/html/Elements/SelectDateType36
-rw-r--r--rt/html/Elements/SelectEqualityOperator40
-rw-r--r--rt/html/Elements/SelectGroups29
-rw-r--r--rt/html/Elements/SelectLang56
-rw-r--r--rt/html/Elements/SelectLinkType37
-rw-r--r--rt/html/Elements/SelectMatch53
-rw-r--r--rt/html/Elements/SelectNewTicketQueue56
-rw-r--r--rt/html/Elements/SelectOwner59
-rw-r--r--rt/html/Elements/SelectQueue59
-rw-r--r--rt/html/Elements/SelectResultsPerPage43
-rw-r--r--rt/html/Elements/SelectSortOrder41
-rw-r--r--rt/html/Elements/SelectStatus39
-rw-r--r--rt/html/Elements/SelectTicketSortBy38
-rw-r--r--rt/html/Elements/SelectTicketTypes34
-rw-r--r--rt/html/Elements/SelectUsers31
-rw-r--r--rt/html/Elements/SelectWatcherType47
-rw-r--r--rt/html/Elements/SetupSessionCookie85
-rw-r--r--rt/html/Elements/ShadedBox33
-rw-r--r--rt/html/Elements/ShadedInputRow35
-rw-r--r--rt/html/Elements/ShadedRow31
-rw-r--r--rt/html/Elements/SimpleSearch27
-rw-r--r--rt/html/Elements/Submit62
-rw-r--r--rt/html/Elements/Tabs82
-rw-r--r--rt/html/Elements/TitleBoxEnd31
-rw-r--r--rt/html/Elements/TitleBoxStart60
-rw-r--r--rt/html/Elements/ViewUser51
-rw-r--r--rt/html/NoAuth/Logout.html46
-rw-r--r--rt/html/NoAuth/Reminder.html26
-rw-r--r--rt/html/NoAuth/images/back_home.gifbin0 -> 330 bytes
-rw-r--r--rt/html/NoAuth/images/bplogo.gifbin0 -> 825 bytes
-rw-r--r--rt/html/NoAuth/images/favicon.pngbin0 -> 335 bytes
-rw-r--r--rt/html/NoAuth/images/head_requestracker.gifbin0 -> 1233 bytes
-rw-r--r--rt/html/NoAuth/images/rt.jpgbin0 -> 917 bytes
-rw-r--r--rt/html/NoAuth/images/space.gifbin0 -> 43 bytes
-rw-r--r--rt/html/NoAuth/images/spacer.gifbin0 -> 43 bytes
-rw-r--r--rt/html/NoAuth/images/squares_blue.gifbin0 -> 219 bytes
-rw-r--r--rt/html/NoAuth/webrt.css340
-rw-r--r--rt/html/REST/1.0/Forms/queue/default123
-rw-r--r--rt/html/REST/1.0/Forms/queue/ns38
-rw-r--r--rt/html/REST/1.0/Forms/ticket/attachments107
-rw-r--r--rt/html/REST/1.0/Forms/ticket/default253
-rw-r--r--rt/html/REST/1.0/Forms/ticket/history144
-rw-r--r--rt/html/REST/1.0/Forms/ticket/links148
-rw-r--r--rt/html/REST/1.0/Forms/user/default141
-rw-r--r--rt/html/REST/1.0/Forms/user/ns41
-rw-r--r--rt/html/REST/1.0/NoAuth/mail-gateway52
-rw-r--r--rt/html/REST/1.0/autohandler32
-rw-r--r--rt/html/REST/1.0/dhandler287
-rw-r--r--rt/html/REST/1.0/logout27
-rw-r--r--rt/html/REST/1.0/search/dhandler32
-rw-r--r--rt/html/REST/1.0/search/ticket119
-rw-r--r--rt/html/REST/1.0/ticket/comment149
-rw-r--r--rt/html/REST/1.0/ticket/link96
-rw-r--r--rt/html/REST/1.0/ticket/merge78
-rw-r--r--rt/html/Search/Bulk.html218
-rw-r--r--rt/html/Search/Elements/PickRestriction142
-rw-r--r--rt/html/Search/Elements/TicketHeader40
-rw-r--r--rt/html/Search/Elements/TicketHeaderCell55
-rw-r--r--rt/html/Search/Elements/TicketRow55
-rw-r--r--rt/html/Search/Listing.html112
-rw-r--r--rt/html/SelfService/Attachment/dhandler27
-rw-r--r--rt/html/SelfService/Closed.html27
-rw-r--r--rt/html/SelfService/Create.html80
-rw-r--r--rt/html/SelfService/Display.html180
-rw-r--r--rt/html/SelfService/Elements/GotoTicket24
-rw-r--r--rt/html/SelfService/Elements/Header25
-rw-r--r--rt/html/SelfService/Elements/MyRequests63
-rw-r--r--rt/html/SelfService/Elements/Tabs64
-rw-r--r--rt/html/SelfService/Error.html46
-rw-r--r--rt/html/SelfService/Prefs.html68
-rw-r--r--rt/html/SelfService/Update.html78
-rw-r--r--rt/html/SelfService/index.html26
-rw-r--r--rt/html/Ticket/Attachment/dhandler70
-rw-r--r--rt/html/Ticket/Create.html266
-rw-r--r--rt/html/Ticket/Display.html118
-rw-r--r--rt/html/Ticket/Elements/AddWatchers99
-rw-r--r--rt/html/Ticket/Elements/BulkLinks53
-rw-r--r--rt/html/Ticket/Elements/EditBasics79
-rw-r--r--rt/html/Ticket/Elements/EditCustomField74
-rw-r--r--rt/html/Ticket/Elements/EditCustomFields74
-rw-r--r--rt/html/Ticket/Elements/EditDates53
-rw-r--r--rt/html/Ticket/Elements/EditLinks133
-rw-r--r--rt/html/Ticket/Elements/EditPeople69
-rw-r--r--rt/html/Ticket/Elements/EditWatchers52
-rw-r--r--rt/html/Ticket/Elements/ShowAttachments79
-rw-r--r--rt/html/Ticket/Elements/ShowBasics54
-rw-r--r--rt/html/Ticket/Elements/ShowCustomFields46
-rw-r--r--rt/html/Ticket/Elements/ShowDates63
-rw-r--r--rt/html/Ticket/Elements/ShowDependencies41
-rw-r--r--rt/html/Ticket/Elements/ShowHistory86
-rw-r--r--rt/html/Ticket/Elements/ShowLink40
-rw-r--r--rt/html/Ticket/Elements/ShowLinks87
-rw-r--r--rt/html/Ticket/Elements/ShowMemberOf35
-rw-r--r--rt/html/Ticket/Elements/ShowMembers45
-rw-r--r--rt/html/Ticket/Elements/ShowMessageHeaders32
-rw-r--r--rt/html/Ticket/Elements/ShowMessageStanza61
-rw-r--r--rt/html/Ticket/Elements/ShowPeople45
-rw-r--r--rt/html/Ticket/Elements/ShowReferences50
-rw-r--r--rt/html/Ticket/Elements/ShowRequestor59
-rw-r--r--rt/html/Ticket/Elements/ShowSummary82
-rw-r--r--rt/html/Ticket/Elements/ShowTransaction181
-rw-r--r--rt/html/Ticket/Elements/Tabs176
-rw-r--r--rt/html/Ticket/History.html52
-rw-r--r--rt/html/Ticket/Modify.html63
-rw-r--r--rt/html/Ticket/ModifyAll.html158
-rw-r--r--rt/html/Ticket/ModifyDates.html52
-rw-r--r--rt/html/Ticket/ModifyLinks.html54
-rw-r--r--rt/html/Ticket/ModifyPeople.html68
-rw-r--r--rt/html/Ticket/Update.html205
-rw-r--r--rt/html/User/Delegation.html83
-rw-r--r--rt/html/User/Elements/DelegateRights85
-rw-r--r--rt/html/User/Elements/GroupTabs60
-rw-r--r--rt/html/User/Elements/Tabs56
-rw-r--r--rt/html/User/Groups/Members.html136
-rw-r--r--rt/html/User/Groups/Modify.html133
-rw-r--r--rt/html/User/Groups/index.html43
-rw-r--r--rt/html/User/Prefs.html256
-rw-r--r--rt/html/autohandler210
-rw-r--r--rt/html/index.html85
-rw-r--r--rt/html/l26
-rw-r--r--rt/install-sh251
-rw-r--r--rt/lib/RT.pm323
-rw-r--r--rt/lib/RT.pm.in323
-rwxr-xr-xrt/lib/RT/ACE.pm304
-rw-r--r--rt/lib/RT/ACE_Overlay.pm907
-rwxr-xr-xrt/lib/RT/ACL.pm115
-rw-r--r--rt/lib/RT/ACL_Overlay.pm295
-rw-r--r--rt/lib/RT/Action/AutoOpen.pm86
-rwxr-xr-xrt/lib/RT/Action/Autoreply.pm104
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm566
-rw-r--r--rt/lib/RT/Action/EscalatePriority.pm142
-rwxr-xr-xrt/lib/RT/Action/Generic.pm195
-rwxr-xr-xrt/lib/RT/Action/Notify.pm132
-rwxr-xr-xrt/lib/RT/Action/NotifyAsComment.pm55
-rw-r--r--rt/lib/RT/Action/ResolveMembers.pm88
-rwxr-xr-xrt/lib/RT/Action/SendEmail.pm685
-rw-r--r--rt/lib/RT/Action/SetPriority.pm61
-rw-r--r--rt/lib/RT/Action/UserDefined.pm71
-rwxr-xr-xrt/lib/RT/Attachment.pm372
-rw-r--r--rt/lib/RT/Attachment_Overlay.pm592
-rwxr-xr-xrt/lib/RT/Attachments.pm115
-rw-r--r--rt/lib/RT/Attachments_Overlay.pm116
-rw-r--r--rt/lib/RT/Base.pm112
-rw-r--r--rt/lib/RT/CachedGroupMember.pm258
-rw-r--r--rt/lib/RT/CachedGroupMember_Overlay.pm323
-rw-r--r--rt/lib/RT/CachedGroupMembers.pm115
-rw-r--r--rt/lib/RT/CachedGroupMembers_Overlay.pm150
-rw-r--r--rt/lib/RT/Condition/AnyTransaction.pm51
-rw-r--r--rt/lib/RT/Condition/BeforeDue.pm64
-rwxr-xr-xrt/lib/RT/Condition/Generic.pm211
-rw-r--r--rt/lib/RT/Condition/Overdue.pm68
-rw-r--r--rt/lib/RT/Condition/OwnerChange.pm102
-rw-r--r--rt/lib/RT/Condition/PriorityExceeds.pm57
-rw-r--r--rt/lib/RT/Condition/QueueChange.pm57
-rw-r--r--rt/lib/RT/Condition/StatusChange.pm59
-rw-r--r--rt/lib/RT/Condition/UserDefined.pm57
-rwxr-xr-xrt/lib/RT/CurrentUser.pm394
-rw-r--r--rt/lib/RT/CustomField.pm340
-rw-r--r--rt/lib/RT/CustomFieldValue.pm294
-rw-r--r--rt/lib/RT/CustomFieldValues.pm121
-rw-r--r--rt/lib/RT/CustomFieldValues_Overlay.pm47
-rw-r--r--rt/lib/RT/CustomField_Overlay.pm566
-rw-r--r--rt/lib/RT/CustomFields.pm121
-rw-r--r--rt/lib/RT/CustomFields_Overlay.pm135
-rw-r--r--rt/lib/RT/Date.pm557
-rw-r--r--rt/lib/RT/EmailParser.pm820
-rwxr-xr-xrt/lib/RT/Group.pm258
-rwxr-xr-xrt/lib/RT/GroupMember.pm189
-rw-r--r--rt/lib/RT/GroupMember_Overlay.pm351
-rwxr-xr-xrt/lib/RT/GroupMembers.pm115
-rw-r--r--rt/lib/RT/GroupMembers_Overlay.pm126
-rw-r--r--rt/lib/RT/Group_Overlay.pm1255
-rwxr-xr-xrt/lib/RT/Groups.pm115
-rw-r--r--rt/lib/RT/Groups_Overlay.pm360
-rw-r--r--rt/lib/RT/Handle.pm106
-rw-r--r--rt/lib/RT/I18N.pm436
-rw-r--r--rt/lib/RT/I18N/cs.pm91
-rw-r--r--rt/lib/RT/I18N/cs.po4496
-rw-r--r--rt/lib/RT/I18N/de.po4792
-rw-r--r--rt/lib/RT/I18N/en.po88
-rw-r--r--rt/lib/RT/I18N/es.po4749
-rw-r--r--rt/lib/RT/I18N/fi.po4750
-rw-r--r--rt/lib/RT/I18N/fr.po4959
-rw-r--r--rt/lib/RT/I18N/he.po4871
-rw-r--r--rt/lib/RT/I18N/i_default.pm86
-rw-r--r--rt/lib/RT/I18N/it.po6175
-rw-r--r--rt/lib/RT/I18N/ja.po4822
-rw-r--r--rt/lib/RT/I18N/nl.po4819
-rw-r--r--rt/lib/RT/I18N/no.po4879
-rw-r--r--rt/lib/RT/I18N/pt_br.po4829
-rw-r--r--rt/lib/RT/I18N/ru.po4540
-rw-r--r--rt/lib/RT/I18N/zh_cn.po6677
-rw-r--r--rt/lib/RT/I18N/zh_tw.po6677
-rw-r--r--rt/lib/RT/Interface/CLI.pm246
-rwxr-xr-xrt/lib/RT/Interface/Email.pm760
-rw-r--r--rt/lib/RT/Interface/Email/Auth/MailFrom.pm131
-rw-r--r--rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm63
-rw-r--r--rt/lib/RT/Interface/REST.pm252
-rw-r--r--rt/lib/RT/Interface/Web.pm1453
-rw-r--r--rt/lib/RT/Link.pm302
-rw-r--r--rt/lib/RT/Link_Overlay.pm360
-rw-r--r--rt/lib/RT/Links.pm115
-rw-r--r--rt/lib/RT/Links_Overlay.pm125
-rw-r--r--rt/lib/RT/Principal.pm212
-rw-r--r--rt/lib/RT/Principal_Overlay.pm576
-rw-r--r--rt/lib/RT/Principals.pm115
-rw-r--r--rt/lib/RT/Principals_Overlay.pm52
-rwxr-xr-xrt/lib/RT/Queue.pm371
-rw-r--r--rt/lib/RT/Queue_Overlay.pm1015
-rwxr-xr-xrt/lib/RT/Queues.pm115
-rw-r--r--rt/lib/RT/Queues_Overlay.pm130
-rwxr-xr-xrt/lib/RT/Record.pm458
-rwxr-xr-xrt/lib/RT/Scrip.pm500
-rwxr-xr-xrt/lib/RT/ScripAction.pm279
-rw-r--r--rt/lib/RT/ScripAction_Overlay.pm233
-rwxr-xr-xrt/lib/RT/ScripActions.pm115
-rw-r--r--rt/lib/RT/ScripActions_Overlay.pm87
-rwxr-xr-xrt/lib/RT/ScripCondition.pm302
-rw-r--r--rt/lib/RT/ScripCondition_Overlay.pm210
-rwxr-xr-xrt/lib/RT/ScripConditions.pm115
-rw-r--r--rt/lib/RT/ScripConditions_Overlay.pm87
-rw-r--r--rt/lib/RT/Scrip_Overlay.pm515
-rwxr-xr-xrt/lib/RT/Scrips.pm115
-rw-r--r--rt/lib/RT/Scrips_Overlay.pm208
-rw-r--r--rt/lib/RT/Search/ActiveTicketsInQueue.pm78
-rw-r--r--rt/lib/RT/Search/Generic.pm128
-rw-r--r--rt/lib/RT/SearchBuilder.pm200
-rw-r--r--rt/lib/RT/StyleGuide.pod891
-rw-r--r--rt/lib/RT/System.pm165
-rwxr-xr-xrt/lib/RT/Template.pm363
-rw-r--r--rt/lib/RT/Template_Overlay.pm418
-rwxr-xr-xrt/lib/RT/Templates.pm115
-rw-r--r--rt/lib/RT/Templates_Overlay.pm141
-rwxr-xr-xrt/lib/RT/Ticket.pm662
-rw-r--r--rt/lib/RT/TicketCustomFieldValue.pm286
-rw-r--r--rt/lib/RT/TicketCustomFieldValue_Overlay.pm52
-rw-r--r--rt/lib/RT/TicketCustomFieldValues.pm115
-rw-r--r--rt/lib/RT/TicketCustomFieldValues_Overlay.pm86
-rw-r--r--rt/lib/RT/Ticket_Overlay.pm4095
-rwxr-xr-xrt/lib/RT/Tickets.pm115
-rw-r--r--rt/lib/RT/Tickets_Overlay.pm2140
-rw-r--r--rt/lib/RT/Tickets_Overlay_SQL.pm447
-rwxr-xr-xrt/lib/RT/Transaction.pm364
-rw-r--r--rt/lib/RT/Transaction_Overlay.pm796
-rwxr-xr-xrt/lib/RT/Transactions.pm115
-rw-r--r--rt/lib/RT/Transactions_Overlay.pm86
-rw-r--r--rt/lib/RT/URI.pm249
-rw-r--r--rt/lib/RT/URI/base.pm129
-rw-r--r--rt/lib/RT/URI/freeside.pm188
-rw-r--r--rt/lib/RT/URI/fsck_com_rt.pm246
-rwxr-xr-xrt/lib/RT/User.pm854
-rw-r--r--rt/lib/RT/User_Overlay.pm1594
-rwxr-xr-xrt/lib/RT/Users.pm115
-rw-r--r--rt/lib/RT/Users_Overlay.pm310
-rw-r--r--rt/lib/t/00smoke.t14
-rw-r--r--rt/lib/t/00smoke.t.in14
-rw-r--r--rt/lib/t/01harness.t12
-rw-r--r--rt/lib/t/01harness.t.in12
-rw-r--r--rt/lib/t/02regression.t44
-rw-r--r--rt/lib/t/02regression.t.in44
-rw-r--r--rt/lib/t/03web.pl94
-rw-r--r--rt/lib/t/03web.pl.in94
-rw-r--r--rt/lib/t/04_send_email.pl481
-rw-r--r--rt/lib/t/04_send_email.pl.in481
-rw-r--r--rt/lib/t/data/8859-15-message-series/dir356
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg136
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg236
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg335
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg435
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg535
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg635
-rw-r--r--rt/lib/t/data/8859-15-message-series/msg736
-rw-r--r--rt/lib/t/data/crashes-file-based-parser193
-rw-r--r--rt/lib/t/data/multipart-alternative-with-umlaut62
-rw-r--r--rt/lib/t/data/multipart-report66
-rw-r--r--rt/lib/t/data/nested-mime-sample396
-rw-r--r--rt/lib/t/data/nested-rfc-822253
-rw-r--r--rt/lib/t/data/new-ticket-from-iso-8859-131
-rw-r--r--rt/lib/t/data/new-ticket-from-iso-8859-1-full38
-rw-r--r--rt/lib/t/data/notes-uuencoded2368
-rw-r--r--rt/lib/t/data/russian-subject-no-content-type42
-rw-r--r--rt/lib/t/data/text-html-in-russian87
-rw-r--r--rt/lib/t/data/text-html-with-umlaut35
-rw-r--r--rt/lib/t/regression/00placeholder1
-rw-r--r--rt/lib/t/regression/mime_tests19
-rw-r--r--rt/m4/rt_enable_layout.m436
-rw-r--r--rt/m4/rt_expand_var.m418
-rw-r--r--rt/m4/rt_layout.m474
-rw-r--r--rt/m4/rt_subst_expanded_arg.m414
-rw-r--r--rt/sbin/extract-message-catalog246
-rw-r--r--rt/sbin/extract_pod_tests129
-rw-r--r--rt/sbin/factory428
-rw-r--r--rt/sbin/license_tag196
-rw-r--r--rt/sbin/regression_harness33
-rw-r--r--rt/sbin/rt-setup-database624
-rw-r--r--rt/sbin/rt-setup-database.in624
-rw-r--r--rt/sbin/rt-test-dependencies278
-rw-r--r--rt/sbin/rt-test-dependencies.in278
-rwxr-xr-xtest/cgi-test560
-rwxr-xr-xtest/dup-test32
1167 files changed, 259187 insertions, 0 deletions
diff --git a/ANNOUNCE.1.5.0 b/ANNOUNCE.1.5.0
new file mode 100644
index 0000000..8748ffd
--- /dev/null
+++ b/ANNOUNCE.1.5.0
@@ -0,0 +1,15 @@
+- broadband (dsl/wireless) tracking, etc etc
+- Extended description on invoice for time/data charges
+- Multiple, named taxes
+- */*FIX
+- extended reported and graphing
+- integrated RT ticketing system
+- one-time payments (in signup server too). DCRD and DCHK on-demand payment types
+- credit report
+- reseller interface
+
+1.5.0pre6:
+- cust_pay_refund and credit card/ACH refunds w/supported processor
+- proper payment receipts (not invoice copies)
+- expanded reseller interface
+- RADIUS session viewing
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..39b53a7
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,130 @@
+Thanks to Matt Simerson <matt@michweb.net> of MichWeb Inc. for documentation
+and pre-release testing. Without his help the documentation in 1.0.0
+release would have consisted of a single screenfull of text.
+(To clear up some misunderstanding, Matt did not write the current
+documentation.)
+
+Steve Cleff <cleff@yahoo.com> did the default background image in 1.0.x and
+is also the creator of Freeside's elusive mascot, Snakeman, who we hope will
+make an appearance in an upcoming version.
+
+Jerry St. Pierre <jstpi@city.timmins.on.ca> did the "SISD" graphic used in
+1.0.x and most of 1.1.x.
+
+Mark Norris of Urban Design, Inc. <http://www.urban.com/> did the red "S"
+logo for later 1.1.x versions and 1.2.x
+
+Brian McCane? <bmccane@maxbaud.net> contributed PostgreSQL support, HTML
+style enhancements and many, many bugfixes.
+
+Cerkit <cerkit@alfheim.net> contributed rsync support and desynced hosts.
+His changes will hopefully be included in an upcoming version.
+
+CompleteHOST, Inc. (http://www.completehost.com) funded the development of the
+following features:
+ - Multiple, separate databases and configurations on one box.
+ - Per-customer pricing (custom packages)
+ - Internationalization wrt addresses (cust_main, cust_main_county)
+Thanks!
+
+Mark Williamson <mark.williamson@ebbs.com.au> and Roger Mangraviti
+<rem@atu.com.au> contributed state/provence listings for Australia.
+
+Peter Wemm <peter@netplex.com.au> sent in a bunch of bugfixes for the 1.2
+release.
+
+Greg Kuhnert <gregk@no1.com.au> sent some documentation updates.
+
+Joel Griffiths <griff@aver-computer.com> contribued many bugfixes as well as
+the print-batch script.
+
+NetLoud <http://www.netloud.com/> funded the development of the following
+features:
+ - IEAK support for the signup server
+ - Pre-payment support
+
+NetAcces.Net (not netaccess.net) funded the development of the following
+features:
+ - DNS tracking and export to BIND configuration files
+ - Web site virtual host tracking and export to Apache configuration files
+
+Kristian Hoffmann <khoff@pc-intouch.com> contributed Netscape CCK
+autoconfiguration support for the signup server, lots of great mailing
+lists posts which I shamelessly made into documentation, fixes to get rid of
+the embarassing and non-database-normal "owed" field, and many other things
+I'm forgetting. Most recently Kristian and Mark (last name?) contributed
+the IP address tracking and svc_broadband in 1.5.
+
+Jeff Finucane <jeff@cmh.net> send in a bunch of bugfixes (for the sendmail
+export, cancel-unaudited.cgi), patches to support billing date modification,
+and probably other things too (sorry if I forgot them). And yet even more
+bug squashing, thanks! *and* he single-handedly implemented all the necessary
+work to get rid of svc_acct_sm and the "default domain" thanks!! and rewrote
+the financials! wow, thanks jeff! and contributed financial reports!
+
+Kenny Elliott <kenny@neoserve.com> contributed ICRADIUS radreply table support,
+allowing attributes with ICRADIUS, helped fix many bugs, and some
+other stuff I can't recall (sorry).
+
+Stephen Amadei <amadei@dandy.net> contribued portability cleanups for the
+low-level DBI stuff.
+
+Jason Spence <thalakan@frys.com> contributed admin.html and other
+documentation, autocapnames javascript, bugfixes & other neat stuff I can't
+remember.
+
+Brad Dameron <bdameron@tscnet.com> contributed code to do configurable state
+and referral defaults.
+
+Surf and Sip, Inc., <http://www.surfandsip.com> sponsored a long-requested
+feature - the session monitor and time-based prepaid cards.
+Matt Peterson <matt@peterson.org> and Mack ? <mackn@mackn.net> tested
+the new features and contributed many bugfixes.
+
+Landel Telecom <http://www.landel.com/> sponsored shipping addresses and
+customer notes, as well as an update of the CP provisioning.
+
+nikotel, Inc. <http://www.nikojet.com> sponsored the inclusion of
+customer-to-customer referrals in the web interface and signup server.
+
+Three Bubba's Innanet <http://www.inna.net> sponsored expedited check entry,
+the "similar names warning" feature, and a number of other enhancements.
+
+Dave Burgess <burgess@neonramp.com> sent in a bunch of fixes and small changes
+and will doubtless send more once he's got his tree under control.
+
+Luke Pfeifer <freeside@globalli.com> contributed the "subscription" price plan.
+
+Noment Networks, LLC <http://www.noment.com/> sponsored ICRADIUS/FreeRADIUS
+groups, message catalogs, and signup server enhancements.
+
+Donald Greer <dgreer@austintx.com> provided the SQL to work around MySQL's lack
+of subqueries, and Dale Hege <fhege@lumenexus.net> provided the patches.
+Thanks!
+
+<baloo@gimpgirl.com> sent in several documentation patches.
+
+"Stephen Bechard" <steve@destek.net> sent in patches for svc_www services and
+other fixes.
+
+Charles A Beasley <cbeasley@noment.net> contributed quota editing for the
+Infostreet export.
+
+Richard Siddall <richard.siddall@elirion.net> sent in Mason fixes and other
+things I'm probably forgetting.
+
+Contains "JS Calendar" v0.9.3 <http://dynarch.com/mishoo/calendar.epl>
+by Mihai Bazon <mishoo@infoiasi.ro> licensed under the terms of the GNU LGPL.
+
+Latex invoice template based on a template from eBills
+<http://ebills.sourceforge.net/> by Mark Asplen-Taylor <mark@asplen.co.uk>,
+licensed under the terms fo the GNU GPL.
+
+Contains "Request Tracker" <http://www.bestpractical.com/rt/> by Jesse
+Vincent <jesse@bestpractical.com> licensed under the terms of the GNU GPL.
+
+Contains "SQL Ledger" <http://www.sql-ledger.com/> by DWS Systems Inc. and
+contributors licensed under the terms of the GNU GPL.
+
+Everything else is my (Ivan Kohler <ivan@420.am>) fault.
+
diff --git a/FS/Changes b/FS/Changes
new file mode 100644
index 0000000..c94ef10
--- /dev/null
+++ b/FS/Changes
@@ -0,0 +1,5 @@
+Revision history for Perl extension FS.
+
+0.01 Wed Aug 4 00:13:45 1999
+ - original version; created by h2xs 1.19
+
diff --git a/FS/FS.pm b/FS/FS.pm
new file mode 100644
index 0000000..c6ce761
--- /dev/null
+++ b/FS/FS.pm
@@ -0,0 +1,243 @@
+package FS;
+
+use strict;
+use vars qw($VERSION);
+
+$VERSION = '0.01';
+
+#find missing entries in this file with:
+# for a in `ls *pm | cut -d. -f1`; do grep 'L<FS::'$a'>' ../FS.pm >/dev/null || echo "missing $a" ; done
+
+1;
+__END__
+
+=head1 NAME
+
+FS - Freeside Perl modules
+
+=head1 SYNOPSIS
+
+Freeside perl modules and CLI utilities.
+
+=head2 Utility classes
+
+L<FS::Conf> - Freeside configuration values
+
+L<FS::ConfItem> - Freeside configuration option meta-data.
+
+L<FS::UID> - User class (not yet OO)
+
+L<FS::CGI> - Non OO-subroutines for the web interface.
+
+L<FS::Msgcat> - Message catalog
+
+L<FS::SearchCache> - Search cache
+
+L<FS::raddb> - RADIUS dictionary
+
+=head2 Database record classes
+
+L<FS::Record> - Database record base class
+
+L<FS::svc_acct_pop> - POP (Point of Presence, not Post
+Office Protocol) class
+
+L<FS::part_pop_local> - Local calling area class
+
+L<FS::part_referral> - Referral class
+
+L<FS::cust_main_county> - Locale (tax rate) class
+
+L<FS::cust_tax_exempt> - Tax exemption record class
+
+L<FS::svc_Common> - Service base class
+
+L<FS::svc_acct> - Account (shell, RADIUS, POP3) class
+
+L<FS::acct_snarf> - External mail account class
+
+L<FS::radius_usergroup> - RADIUS groups
+
+L<FS::svc_domain> - Domain class
+
+L<FS::domain_record> - DNS zone entries
+
+L<FS::svc_forward> - Mail forwarding class
+
+L<FS::svc_www> - Web virtual host class.
+
+L<FS::svc_broadband> - DSL, wireless and other broadband class.
+
+L<FS::svc_external> - Externally tracked service class.
+
+L<FS::part_svc> - Service definition class
+
+L<FS::part_svc_column> - Column constraint class
+
+L<FS::export_svc> - Class linking service definitions (see L<FS::part_svc>)
+with exports (see L<FS::part_export>)
+
+L<FS::part_export> - External provisioning export class
+
+L<FS::part_export_option> - Export option class
+
+L<FS::part_pkg> - Package (billing item) definition class
+
+L<FS::pkg_svc> - Class linking package (billing item)
+definitions (see L<FS::part_pkg>) with service definitions
+(see L<FS::part_svc>)
+
+L<FS::agent> - Agent (reseller) class
+
+L<FS::agent_type> - Agent type class
+
+L<FS::type_pkgs> - Class linking agent types (see
+L<FS::agent_type>) with package (billing item) definitions
+(see L<FS::part_pkg>)
+
+L<FS::cust_svc> - Service class
+
+L<FS::cust_pkg> - Package (billing item) class
+
+L<FS::cust_main> - Customer class
+
+L<FS::cust_main_invoice> - Invoice destination
+class
+
+L<FS::cust_bill> - Invoice class
+
+L<FS::cust_bill_pkg> - Invoice line item class
+
+L<FS::cust_bill_pkg_detail> - Invoice line item detail class
+
+L<FS::part_bill_event> - Invoice event definition class
+
+L<FS::cust_bill_event> - Completed invoice event class
+
+L<FS::cust_pay> - Payment class
+
+L<FS::cust_pay_void> - Voided payment class
+
+L<FS::cust_bill_pay> - Payment application class
+
+L<FS::cust_credit> - Credit class
+
+L<FS::cust_refund> - Refund class
+
+L<FS::cust_credit_refund> - Refund application to credit class
+
+L<FS::cust_credit_bill> - Credit application to invoice class
+
+L<FS::cust_pay_refund> - Refund application to payment class
+
+L<FS::cust_pay_batch> - Credit card transaction queue class
+
+L<FS::prepay_credit> - Prepaid "calling card" credit class.
+
+L<FS::nas> - Network Access Server class
+
+L<FS::port> - NAS port class
+
+L<FS::session> - User login session class
+
+L<FS::queue> - Job queue
+
+L<FS::queue_arg> - Job arguments
+
+L<FS::queue_depend> - Job dependencies
+
+L<FS::msgcat> - Message catalogs
+
+=head1 Remote API modules
+
+L<FS::SelfService>
+
+L<FS::SignupClient>
+
+L<FS::SessionClient>
+
+L<FS::MailAdminServer> (deprecated in favor of the self-service server)
+
+=head2 Command-line utilities
+
+L<freeside-adduser>
+
+L<freeside-queued>
+
+L<freeside-daily>
+
+L<freeside-expiration-alerter>
+
+L<freeside-email>
+
+L<freeside-cc-receipts-report>
+
+L<freeside-credit-report>
+
+L<freeside-receivables-report>
+
+L<freeside-tax-report>
+
+L<freeside-bill>
+
+L<freeside-overdue>
+
+=head2 User Interface classes (under (stalled) development; not yet usable)
+
+L<FS::UI::Base> - User-interface base class
+
+L<FS::UI::Gtk> - Gtk user-interface class
+
+L<FS::UI::CGI> - CGI (HTML) user-interface class
+
+L<FS::UI::agent> - agent table user-interface class
+
+=head2 Notes
+
+To quote perl(1), "If you're intending to read these straight through for the
+first time, the suggested order will tend to reduce the number of forward
+references."
+
+If you've never used OO modules before,
+http://www.perl.com/doc/FMTEYEWTK/easy_objects.html might help you out.
+
+=head1 DESCRIPTION
+
+Freeside is a billing and administration package for Internet Service
+Providers.
+
+The Freeside home page is at <http://www.sisd.com/freeside>.
+
+The main documentation is in httemplate/docs.
+
+=head1 SUPPORT
+
+A mailing list for users is available. Send a blank message to
+<ivan-freeside-subscribe@sisd.com> to subscribe.
+
+A mailing list for developers is available. It is intended to be lower volume
+and higher SNR than the users list. Send a blank message to
+<ivan-freeside-devel-subscribe@sisd.com> to subscribe.
+
+Commercial support is available; see
+<http://www.sisd.com/freeside/commercial.html>.
+
+=head1 AUTHOR
+
+Primarily Ivan Kohler <ivan@sisd.com>, with help from many kind folks.
+
+See the CREDITS file in the Freeside distribution for a (hopefully) complete
+list and the individal files for details.
+
+=head1 SEE ALSO
+
+perl(1), main Freeside documentation in htdocs/docs/
+
+=head1 BUGS
+
+Those modules which would be useful separately should be pulled out,
+renamed appropriately and uploaded to CPAN. So far: DBIx::DBSchema, Net::SSH
+and Net::SCP...
+
+=cut
+
diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm
new file mode 100644
index 0000000..1ddc62c
--- /dev/null
+++ b/FS/FS/CGI.pm
@@ -0,0 +1,393 @@
+package FS::CGI;
+
+use strict;
+use vars qw(@EXPORT_OK @ISA);
+use Exporter;
+use CGI;
+use URI::URL;
+#use CGI::Carp qw(fatalsToBrowser);
+use FS::UID;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(header menubar idiot eidiot popurl table itable ntable
+ small_custview myexit http_header);
+
+=head1 NAME
+
+FS::CGI - Subroutines for the web interface
+
+=head1 SYNOPSIS
+
+ use FS::CGI qw(header menubar idiot eidiot popurl);
+
+ print header( 'Title', '' );
+ print header( 'Title', menubar('item', 'URL', ... ) );
+
+ idiot "error message";
+ eidiot "error message";
+
+ $url = popurl; #returns current url
+ $url = popurl(3); #three levels up
+
+=head1 DESCRIPTION
+
+Provides a few common subroutines for the web interface.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item header TITLE, MENUBAR
+
+Returns an HTML header.
+
+=cut
+
+sub header {
+ use Carp;
+ carp 'FS::CGI::header deprecated; include /elements/header.html instead';
+
+ my($title,$menubar,$etc)=@_; #$etc is for things like onLoad= etc.
+ $etc = '' unless defined $etc;
+
+ my $x = <<END;
+ <HTML>
+ <HEAD>
+ <TITLE>
+ $title
+ </TITLE>
+ <META HTTP-Equiv="Cache-Control" Content="no-cache">
+ <META HTTP-Equiv="Pragma" Content="no-cache">
+ <META HTTP-Equiv="Expires" Content="0">
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8"$etc>
+ <FONT SIZE=6>
+ $title
+ </FONT>
+ <BR><BR>
+END
+ $x .= $menubar. "<BR><BR>" if $menubar;
+ $x;
+}
+
+=item http_header
+
+Sets an http header.
+
+=cut
+
+sub http_header {
+ my ( $header, $value ) = @_;
+ if (exists $ENV{MOD_PERL}) {
+ if ( defined $main::Response
+ && $main::Response->isa('Apache::ASP::Response') ) { #Apache::ASP
+ if ( $header =~ /^Content-Type$/ ) {
+ $main::Response->{ContentType} = $value;
+ } else {
+ $main::Response->AddHeader( $header => $value );
+ }
+ } elsif ( defined $HTML::Mason::Commands::r ) { #Mason
+ ## is this the correct pacakge for $r ??? for 1.0x and 1.1x ?
+ if ( $header =~ /^Content-Type$/ ) {
+ $HTML::Mason::Commands::r->content_type($value);
+ } else {
+ $HTML::Mason::Commands::r->header_out( $header => $value );
+ }
+ } else {
+ die "http_header called in unknown environment";
+ }
+ } else {
+ die "http_header called not running under mod_perl";
+ }
+
+}
+
+=item menubar ITEM, URL, ...
+
+Returns an HTML menubar.
+
+=cut
+
+sub menubar { #$menubar=menubar('Main Menu', '../', 'Item', 'url', ... );
+ use Carp;
+ carp 'FS::CGI::menubar deprecated; include /elements/menubar.html instead';
+
+ my($item,$url,@html);
+ while (@_) {
+ ($item,$url)=splice(@_,0,2);
+ push @html, qq!<A HREF="$url">$item</A>!;
+ }
+ join(' | ',@html);
+}
+
+=item idiot ERROR
+
+This is depriciated. Don't use it.
+
+Sends an HTML error message.
+
+=cut
+
+sub idiot {
+ #warn "idiot depriciated";
+ my($error)=@_;
+# my $cgi = &FS::UID::cgi();
+# if ( $cgi->isa('CGI::Base') ) {
+# no strict 'subs';
+# &CGI::Base::SendHeaders;
+# } else {
+# print $cgi->header( @FS::CGI::header );
+# }
+ print <<END;
+<HTML>
+ <HEAD>
+ <TITLE>Error processing your request</TITLE>
+ <META HTTP-Equiv="Cache-Control" Content="no-cache">
+ <META HTTP-Equiv="Pragma" Content="no-cache">
+ <META HTTP-Equiv="Expires" Content="0">
+ </HEAD>
+ <BODY>
+ <CENTER>
+ <H4>Error processing your request</H4>
+ </CENTER>
+ Your request could not be processed because of the following error:
+ <P><B>$error</B>
+ </BODY>
+</HTML>
+END
+
+}
+
+=item eidiot ERROR
+
+This is depriciated. Don't use it.
+
+Sends an HTML error message, then exits.
+
+=cut
+
+sub eidiot {
+ warn "eidiot depriciated";
+ $HTML::Mason::Commands::r->send_http_header
+ if defined $HTML::Mason::Commands::r;
+ idiot(@_);
+ &myexit();
+}
+
+=item myexit
+
+You probably shouldn't use this; but if you must:
+
+If running under mod_perl, calles Apache::exit, otherwise, calls exit.
+
+=cut
+
+sub myexit {
+ if (exists $ENV{MOD_PERL}) {
+
+ if ( defined $main::Response
+ && $main::Response->isa('Apache::ASP::Response') ) { #Apache::ASP
+ $main::Response->End();
+ require Apache;
+ Apache::exit();
+ } elsif ( defined $HTML::Mason::Commands::m ) { #Mason
+ #$HTML::Mason::Commands::m->flush_buffer();
+ $HTML::Mason::Commands::m->abort();
+ die "shouldn't fall through to here (mason \$m->abort didn't)";
+ } else {
+ #??? well, it is $ENV{MOD_PERL}
+ warn "running under unknown mod_perl environment; trying Apache::exit()";
+ require Apache;
+ Apache::exit();
+ }
+ } else {
+ exit;
+ }
+}
+
+=item popurl LEVEL
+
+Returns current URL with LEVEL levels of path removed from the end (default 0).
+
+=cut
+
+sub popurl {
+ my($up)=@_;
+ my $cgi = &FS::UID::cgi;
+ my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
+ $url_string =~ s/\?.*//;
+ my $url = new URI::URL ( $url_string );
+ my(@path)=$url->path_components;
+ splice @path, 0-$up;
+ $url->path_components(@path);
+ my $x = $url->as_string;
+ $x .= '/' unless $x =~ /\/$/;
+ $x;
+}
+
+=item table
+
+Returns HTML tag for beginning a table.
+
+=cut
+
+sub table {
+ use Carp;
+ carp 'FS::CGI::table deprecated; include /elements/table.html instead';
+
+ my $col = shift;
+ if ( $col ) {
+ qq!<TABLE BGCOLOR="$col" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">!;
+ } else {
+ '<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">';
+ }
+}
+
+=item itable
+
+Returns HTML tag for beginning an (invisible) table.
+
+=cut
+
+sub itable {
+ my $col = shift;
+ my $cellspacing = shift || 0;
+ if ( $col ) {
+ qq!<TABLE BGCOLOR="$col" BORDER=0 CELLSPACING=$cellspacing WIDTH="100%">!;
+ } else {
+ qq!<TABLE BORDER=0 CELLSPACING=$cellspacing WIDTH="100%">!;
+ }
+}
+
+=item ntable
+
+This is getting silly.
+
+=cut
+
+sub ntable {
+ my $col = shift;
+ my $cellspacing = shift || 0;
+ if ( $col ) {
+ qq!<TABLE BGCOLOR="$col" BORDER=0 CELLSPACING=$cellspacing>!;
+ } else {
+ '<TABLE BORDER CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">';
+ }
+
+}
+
+=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT
+
+Sheesh. I should just switch to Mason.
+
+=cut
+
+sub small_custview {
+ use FS::Record qw(qsearchs);
+ use FS::cust_main;
+
+ my $arg = shift;
+ my $countrydefault = shift || 'US';
+
+ my $cust_main = ref($arg) ? $arg
+ : qsearchs('cust_main', { 'custnum' => $arg } )
+ or die "unknown custnum $arg";
+
+ my $html = 'Customer #<B>'. $cust_main->custnum. '</B>'.
+ ' - <B><FONT COLOR="'. $cust_main->statuscolor. '">'.
+ ucfirst($cust_main->status). '</FONT></B>'.
+ ntable('#e8e8e8'). '<TR><TD>'. ntable("#cccccc",2).
+ '<TR><TD ALIGN="right" VALIGN="top">Billing<BR>Address</TD><TD BGCOLOR="#ffffff">'.
+ $cust_main->getfield('last'). ', '. $cust_main->first. '<BR>';
+
+ $html .= $cust_main->company. '<BR>' if $cust_main->company;
+ $html .= $cust_main->address1. '<BR>';
+ $html .= $cust_main->address2. '<BR>' if $cust_main->address2;
+ $html .= $cust_main->city. ', '. $cust_main->state. ' '. $cust_main->zip. '<BR>';
+ $html .= $cust_main->country. '<BR>'
+ if $cust_main->country && $cust_main->country ne $countrydefault;
+
+ $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">';
+ if ( $cust_main->daytime && $cust_main->night ) {
+ use FS::Msgcat;
+ $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
+ ' '. $cust_main->daytime.
+ '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
+ ' '. $cust_main->night;
+ } elsif ( $cust_main->daytime || $cust_main->night ) {
+ $html .= $cust_main->daytime || $cust_main->night;
+ }
+ if ( $cust_main->fax ) {
+ $html .= '<BR>Fax '. $cust_main->fax;
+ }
+
+ $html .= '</TD></TR></TABLE></TD>';
+
+ if ( defined $cust_main->dbdef_table->column('ship_last') ) {
+
+ my $pre = $cust_main->ship_last ? 'ship_' : '';
+
+ $html .= '<TD>'. ntable("#cccccc",2).
+ '<TR><TD ALIGN="right" VALIGN="top">Service<BR>Address</TD><TD BGCOLOR="#ffffff">'.
+ $cust_main->get("${pre}last"). ', '.
+ $cust_main->get("${pre}first"). '<BR>';
+ $html .= $cust_main->get("${pre}company"). '<BR>'
+ if $cust_main->get("${pre}company");
+ $html .= $cust_main->get("${pre}address1"). '<BR>';
+ $html .= $cust_main->get("${pre}address2"). '<BR>'
+ if $cust_main->get("${pre}address2");
+ $html .= $cust_main->get("${pre}city"). ', '.
+ $cust_main->get("${pre}state"). ' '.
+ $cust_main->get("${pre}ship_zip"). '<BR>';
+ $html .= $cust_main->get("${pre}country"). '<BR>'
+ if $cust_main->get("${pre}country")
+ && $cust_main->get("${pre}country") ne $countrydefault;
+
+ $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">';
+
+ if ( $cust_main->get("${pre}daytime") && $cust_main->get("${pre}night") ) {
+ use FS::Msgcat;
+ $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
+ ' '. $cust_main->get("${pre}daytime").
+ '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
+ ' '. $cust_main->get("${pre}night");
+ } elsif ( $cust_main->get("${pre}daytime")
+ || $cust_main->get("${pre}night") ) {
+ $html .= $cust_main->get("${pre}daytime")
+ || $cust_main->get("${pre}night");
+ }
+ if ( $cust_main->get("${pre}fax") ) {
+ $html .= '<BR>Fax '. $cust_main->get("${pre}fax");
+ }
+
+ $html .= '</TD></TR></TABLE></TD>';
+ }
+
+ $html .= '</TR></TABLE>';
+
+ $html .= '<BR>Balance: <B>$'. $cust_main->balance. '</B><BR>';
+
+ # last payment might be good here too?
+
+ $html;
+}
+
+=back
+
+=head1 BUGS
+
+Not OO.
+
+Not complete.
+
+small_custview sooooo doesn't belong here. i should just switch to Mason.
+
+=head1 SEE ALSO
+
+L<CGI>, L<CGI::Base>
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm
new file mode 100644
index 0000000..7cbbdbf
--- /dev/null
+++ b/FS/FS/ClientAPI.pm
@@ -0,0 +1,44 @@
+package FS::ClientAPI;
+
+use strict;
+use vars qw(%handler $domain);
+
+%handler = ();
+
+#find modules
+foreach my $INC ( @INC ) {
+ foreach my $file ( glob("$INC/FS/ClientAPI/*.pm") ) {
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized ClientAPI file: $file";
+ next
+ };
+ my $mod = $1;
+ #warn "using FS::ClientAPI::$mod";
+ eval "use FS::ClientAPI::$mod;";
+ die "error using FS::ClientAPI::$mod: $@" if $@;
+ }
+}
+
+#(sub for modules)
+sub register_handlers {
+ my $self = shift;
+ my %new_handlers = @_;
+ foreach my $key ( keys %new_handlers ) {
+ warn "WARNING: redefining sub $key" if exists $handler{$key};
+ #warn "registering $key";
+ $handler{$key} = $new_handlers{$key};
+ }
+}
+
+#---
+
+sub dispatch {
+ my ( $self, $name ) = ( shift, shift );
+ my $sub = $handler{$name}
+ or die "unknown FS::ClientAPI sub $name (known: ". join(" ", keys %handler );
+ #or die "unknown FS::ClientAPI sub $name";
+ &{$sub}(@_);
+}
+
+1;
+
diff --git a/FS/FS/ClientAPI/Agent.pm b/FS/FS/ClientAPI/Agent.pm
new file mode 100644
index 0000000..1cc11d5
--- /dev/null
+++ b/FS/FS/ClientAPI/Agent.pm
@@ -0,0 +1,189 @@
+package FS::ClientAPI::Agent;
+
+#some false laziness w/MyAccount
+
+use strict;
+use vars qw($cache);
+use Digest::MD5 qw(md5_hex);
+use Cache::SharedMemoryCache; #store in db?
+use FS::Record qw(qsearchs qsearch dbdef dbh);
+use FS::agent;
+use FS::cust_main;
+
+use FS::ClientAPI;
+FS::ClientAPI->register_handlers(
+ 'Agent/agent_login' => \&agent_login,
+ 'Agent/agent_logout' => \&agent_logout,
+ 'Agent/agent_info' => \&agent_info,
+ 'Agent/agent_list_customers' => \&agent_list_customers,
+);
+
+#store in db?
+my $cache = new Cache::SharedMemoryCache( {
+ 'namespace' => 'FS::ClientAPI::Agent',
+} );
+
+sub agent_login {
+ my $p = shift;
+
+ #don't allow a blank login to first unconfigured agent with no user/pass
+ return { error => 'Must specify your reseller username and password.' }
+ unless length($p->{'username'}) && length($p->{'password'});
+
+ my $agent = qsearchs( 'agent', {
+ 'username' => $p->{'username'},
+ '_password' => $p->{'password'},
+ } );
+
+ unless ( $agent ) { return { error => 'Incorrect password.' } }
+
+ my $session = {
+ 'agentnum' => $agent->agentnum,
+ 'agent' => $agent->agent,
+ };
+
+ my $session_id;
+ do {
+ $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+ } until ( ! defined $cache->get($session_id) ); #just in case
+
+ $cache->set( $session_id, $session, '1 hour' );
+
+ { 'error' => '',
+ 'session_id' => $session_id,
+ };
+}
+
+sub agent_logout {
+ my $p = shift;
+ if ( $p->{'session_id'} ) {
+ $cache->remove($p->{'session_id'});
+ return { 'error' => '' };
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+}
+
+sub agent_info {
+ my $p = shift;
+
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ #my %return;
+
+ my $agentnum = $session->{'agentnum'};
+
+ my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } )
+ or return { 'error' => "unknown agentnum $agentnum" };
+
+ { 'error' => '',
+ 'agentnum' => $agentnum,
+ 'agent' => $agent->agent,
+ 'num_prospect' => $agent->num_prospect_cust_main,
+ 'num_active' => $agent->num_active_cust_main,
+ 'num_susp' => $agent->num_susp_cust_main,
+ 'num_cancel' => $agent->num_cancel_cust_main,
+ #%return,
+ };
+
+}
+
+sub agent_list_customers {
+ my $p = shift;
+
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ #my %return;
+
+ my $agentnum = $session->{'agentnum'};
+
+ my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } )
+ or return { 'error' => "unknown agentnum $agentnum" };
+
+ my @cust_main = ();
+
+ #warn $p->{'search'};
+ if ( $p->{'search'} =~ /^\s*(\d+)\s*$/ ) { # customer # search
+ push @cust_main, qsearch('cust_main', { 'agentnum' => $agentnum,
+ 'custnum' => $1 } );
+ } elsif ( $p->{'search'} =~ /^\s*(\S.*\S)\s*$/ ) { #value search
+ my $value = lc($1);
+ my $q_value = dbh->quote($value);
+
+ #exact
+ my $sql = " AND ( LOWER(last) = $q_value OR LOWER(company) = $q_value";
+ $sql .= " OR LOWER(ship_last) = $q_value OR LOWER(ship_company) = $q_value"
+ if defined dbdef->table('cust_main')->column('ship_last');
+ $sql .= ' )';
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'agentnum' => $agentnum },
+ '',
+ $sql
+ );
+
+ unless ( @cust_main ) {
+ warn "no exact match, trying substring/fuzzy\n";
+
+ #still some false laziness w/ search/cust_main.cgi
+
+ #substring
+ push @cust_main, qsearch( 'cust_main',
+ { 'agentnum' => $agentnum,
+ 'last' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'agentnum' => $agentnum,
+ 'ship_last' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'agentnum' => $agentnum,
+ 'company' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'agentnum' => $agentnum,
+ 'ship_company' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ #fuzzy
+ push @cust_main, FS::cust_main->fuzzy_search(
+ { 'last' => $value },
+ { 'agentnum' => $agentnum }
+ );
+ push @cust_main, FS::cust_main->fuzzy_search(
+ { 'company' => $value },
+ { 'agentnum' => $agentnum }
+ );
+
+ }
+ }
+
+ #aggregate searches
+ push @cust_main,
+ map $agent->$_(), map $_.'_cust_main',
+ grep $p->{$_}, qw( prospect active susp cancel );
+
+ #eliminate dups?
+ my %saw = ();
+ @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+
+ { customers => [ map {
+ my $cust_main = $_;
+ my $hashref = $cust_main->hashref;
+ $hashref->{$_} = $cust_main->$_()
+ foreach qw(name status statuscolor);
+ delete $hashref->{$_} foreach qw( payinfo paycvv );
+ $hashref;
+ } @cust_main
+ ],
+ }
+
+}
+
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
new file mode 100644
index 0000000..fe2e1c2
--- /dev/null
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -0,0 +1,788 @@
+package FS::ClientAPI::MyAccount;
+
+use strict;
+use vars qw($cache);
+use Digest::MD5 qw(md5_hex);
+use Date::Format;
+use Business::CreditCard;
+use Cache::SharedMemoryCache; #store in db?
+use FS::CGI qw(small_custview); #doh
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs);
+use FS::Msgcat qw(gettext);
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_external;
+use FS::part_svc;
+use FS::cust_main;
+use FS::cust_bill;
+use FS::cust_main_county;
+use FS::cust_pkg;
+
+use FS::ClientAPI; #hmm
+FS::ClientAPI->register_handlers(
+ 'MyAccount/login' => \&login,
+ 'MyAccount/logout' => \&logout,
+ 'MyAccount/customer_info' => \&customer_info,
+ 'MyAccount/edit_info' => \&edit_info,
+ 'MyAccount/invoice' => \&invoice,
+ 'MyAccount/list_invoices' => \&list_invoices,
+ 'MyAccount/cancel' => \&cancel,
+ 'MyAccount/payment_info' => \&payment_info,
+ 'MyAccount/process_payment' => \&process_payment,
+ 'MyAccount/list_pkgs' => \&list_pkgs,
+ 'MyAccount/order_pkg' => \&order_pkg,
+ 'MyAccount/cancel_pkg' => \&cancel_pkg,
+ 'MyAccount/charge' => \&charge,
+ 'MyAccount/part_svc_info' => \&part_svc_info,
+ 'MyAccount/provision_acct' => \&provision_acct,
+ 'MyAccount/provision_external' => \&provision_external,
+ 'MyAccount/unprovision_svc' => \&unprovision_svc,
+);
+
+use vars qw( @cust_main_editable_fields );
+@cust_main_editable_fields = qw(
+ first last company address1 address2 city
+ county state zip country daytime night fax
+ ship_first ship_last ship_company ship_address1 ship_address2 ship_city
+ ship_state ship_zip ship_country ship_daytime ship_night ship_fax
+ payby payinfo payname
+);
+
+use subs qw(_provision);
+
+#store in db?
+my $cache = new Cache::SharedMemoryCache( {
+ 'namespace' => 'FS::ClientAPI::MyAccount',
+} );
+
+#false laziness w/FS::ClientAPI::passwd::passwd
+sub login {
+ my $p = shift;
+
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
+ or return { error => 'Domain '. $p->{'domain'}. ' not found' };
+
+ my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{'username'},
+ 'domsvc' => $svc_domain->svcnum, }
+ );
+ return { error => 'User not found.' } unless $svc_acct;
+
+ my $conf = new FS::Conf;
+ my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
+ return { error => 'Only primary user may log in.' }
+ if $conf->exists('selfservice_server-primary_only')
+ && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
+
+ return { error => 'Incorrect password.' }
+ unless $svc_acct->check_password($p->{'password'});
+
+ my $session = {
+ 'svcnum' => $svc_acct->svcnum,
+ };
+
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+ $session->{'custnum'} = $cust_main->custnum;
+ }
+
+ my $session_id;
+ do {
+ $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+ } until ( ! defined $cache->get($session_id) ); #just in case
+
+ $cache->set( $session_id, $session, '1 hour' );
+
+ return { 'error' => '',
+ 'session_id' => $session_id,
+ };
+}
+
+sub logout {
+ my $p = shift;
+ if ( $p->{'session_id'} ) {
+ $cache->remove($p->{'session_id'});
+ return { 'error' => '' };
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+}
+
+sub customer_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my %return;
+ if ( $custnum ) { #customer record
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ $return{balance} = $cust_main->balance;
+
+ my @open = map {
+ {
+ invnum => $_->invnum,
+ date => time2str("%b %o, %Y", $_->_date),
+ owed => $_->owed,
+ };
+ } $cust_main->open_cust_bill;
+ $return{open_invoices} = \@open;
+
+ my $conf = new FS::Conf;
+ $return{small_custview} =
+ small_custview( $cust_main, $conf->config('defaultcountry') );
+
+ $return{name} = $cust_main->first. ' '. $cust_main->get('last');
+
+ for (@cust_main_editable_fields) {
+ $return{$_} = $cust_main->get($_);
+ }
+
+ if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ $return{payinfo} = $cust_main->payinfo_masked;
+ @return{'month', 'year'} = $cust_main->paydate_monthyear;
+ }
+
+ $return{'invoicing_list'} =
+ join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list );
+ $return{'postal_invoicing'} =
+ 0 < ( grep { $_ eq 'POST' } $cust_main->invoicing_list );
+
+ } else { #no customer record
+
+ my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
+ or die "unknown svcnum";
+ $return{name} = $svc_acct->email;
+
+ }
+
+ return { 'error' => '',
+ 'custnum' => $custnum,
+ %return,
+ };
+
+}
+
+sub edit_info {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'}
+ or return { 'error' => "no customer record" };
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $new = new FS::cust_main { $cust_main->hash };
+ $new->set( $_ => $p->{$_} )
+ foreach grep { exists $p->{$_} } @cust_main_editable_fields;
+
+ if ( $p->{'payby'} =~ /^(CARD|DCRD)$/ ) {
+ $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
+ if ( $new->payinfo eq $cust_main->payinfo_masked ) {
+ $new->payinfo($cust_main->payinfo);
+ } else {
+ $new->paycvv($p->{'paycvv'});
+ }
+ }
+
+ my @invoicing_list;
+ if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
+ #false laziness with httemplate/edit/process/cust_main.cgi
+ @invoicing_list = split( /\s*\,\s*/, $p->{'invoicing_list'} );
+ push @invoicing_list, 'POST' if $p->{'postal_invoicing'};
+ } else {
+ @invoicing_list = $cust_main->invoicing_list;
+ }
+
+ my $error = $new->replace($cust_main, \@invoicing_list);
+ return { 'error' => $error } if $error;
+ #$cust_main = $new;
+
+ return { 'error' => '' };
+}
+
+sub payment_info {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ ##
+ #generic
+ ##
+
+ my $conf = new FS::Conf;
+ my %states = map { $_->state => 1 }
+ qsearch('cust_main_county', {
+ 'country' => $conf->config('defaultcountry') || 'US'
+ } );
+
+ use vars qw($payment_info); #cache for performance
+ $payment_info ||= {
+
+ #list all counties/states/countries
+ 'cust_main_county' =>
+ [ map { $_->hashref } qsearch('cust_main_county', {}) ],
+
+ #shortcut for one-country folks
+ 'states' =>
+ [ sort { $a cmp $b } keys %states ],
+
+ 'card_types' => {
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ },
+
+ };
+
+ ##
+ #customer-specific
+ ##
+
+ my %return = %$payment_info;
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ $return{balance} = $cust_main->balance;
+
+ $return{payname} = $cust_main->payname
+ || ( $cust_main->first. ' '. $cust_main->get('last') );
+
+ $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
+
+ $return{payby} = $cust_main->payby;
+
+ if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ #warn $return{card_type} = cardtype($cust_main->payinfo);
+ $return{payinfo} = $cust_main->payinfo;
+
+ @return{'month', 'year'} = $cust_main->paydate_monthyear;
+
+ }
+
+ #doubleclick protection
+ my $_date = time;
+ $return{paybatch} = "webui-MyAccount-$_date-$$-". rand() * 2**32;
+
+ return { 'error' => '',
+ %return,
+ };
+
+};
+
+#some false laziness with httemplate/process/payment.cgi - look there for
+#ACH and CVV support stuff
+sub process_payment {
+
+ my $p = shift;
+
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my %return;
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ $p->{'payname'} =~ /^([\w \,\.\-\']+)$/
+ or return { 'error' => gettext('illegal_name'). " payname: ". $p->{'payname'} };
+ my $payname = $1;
+
+ $p->{'paybatch'} =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or return { 'error' => gettext('illegal_text'). " paybatch: ". $p->{'paybatch'} };
+ my $paybatch = $1;
+
+ my $payinfo;
+ my $paycvv = '';
+ #if ( $payby eq 'CHEK' ) {
+ #
+ # $p->{'payinfo1'} =~ /^(\d+)$/
+ # or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
+ # my $payinfo1 = $1;
+ # $p->{'payinfo2'} =~ /^(\d+)$/
+ # or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
+ # my $payinfo2 = $1;
+ # $payinfo = $payinfo1. '@'. $payinfo2;
+ #
+ #} elsif ( $payby eq 'CARD' ) {
+
+ $payinfo = $p->{'payinfo'};
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^(\d{13,16})$/
+ or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
+ $payinfo = $1;
+ validate($payinfo)
+ or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
+ return { 'error' => gettext('unknown_card_type') }
+ if cardtype($payinfo) eq "Unknown";
+
+ if ( defined $cust_main->dbdef_table->column('paycvv') ) {
+ if ( length($p->{'paycvv'} ) ) {
+ if ( cardtype($payinfo) eq 'American Express card' ) {
+ $p->{'paycvv'} =~ /^(\d{4})$/
+ or return { 'error' => "CVV2 (CID) for American Express cards is four digits." };
+ $paycvv = $1;
+ } else {
+ $p->{'paycvv'} =~ /^(\d{3})$/
+ or return { 'error' => "CVV2 (CVC2/CID) is three digits." };
+ $paycvv = $1;
+ }
+ }
+ }
+
+ #} else {
+ # die "unknown payby $payby";
+ #}
+
+ my $error = $cust_main->realtime_bop( 'CC', $p->{'amount'},
+ 'quiet' => 1,
+ 'payinfo' => $payinfo,
+ 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01',
+ 'payname' => $payname,
+ 'paybatch' => $paybatch,
+ 'paycvv' => $paycvv,
+ map { $_ => $p->{$_} } qw( address1 address2 city state zip )
+ );
+ return { 'error' => $error } if $error;
+
+ $cust_main->apply_payments;
+
+ if ( $p->{'save'} ) {
+ my $new = new FS::cust_main { $cust_main->hash };
+ $new->set( $_ => $p->{$_} )
+ foreach qw( payname address1 address2 city state zip payinfo );
+ $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
+ $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
+ my $error = $new->replace($cust_main);
+ return { 'error' => $error } if $error;
+ $cust_main = $new;
+ }
+
+ return { 'error' => '' };
+
+}
+
+sub invoice {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $invnum = $p->{'invnum'};
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum,
+ 'custnum' => $custnum } )
+ or return { 'error' => "Can't find invnum" };
+
+ #my %return;
+
+ return { 'error' => '',
+ 'invnum' => $invnum,
+ 'invoice_text' => join('', $cust_bill->print_text ),
+ };
+
+}
+
+sub list_invoices {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my @cust_bill = $cust_main->cust_bill;
+
+ return { 'error' => '',
+ 'invoices' => [ map { { 'invnum' => $_->invnum,
+ '_date' => $_->_date,
+ }
+ } @cust_bill
+ ]
+ };
+}
+
+sub cancel {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my @errors = $cust_main->cancel( 'quiet'=>1 );
+
+ my $error = scalar(@errors) ? join(' / ', @errors) : '';
+
+ return { 'error' => $error };
+
+}
+
+sub list_pkgs {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ #return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
+
+ my $conf = new FS::Conf;
+
+ { 'svcnum' => $session->{'svcnum'},
+ 'custnum' => $custnum,
+ 'cust_pkg' => [ map {
+ { $_->hash,
+ $_->part_pkg->hash,
+ part_svc =>
+ [ map $_->hashref, $_->available_part_svc ],
+ cust_svc =>
+ [ map { my $ref = { $_->hash,
+ label => [ $_->label ],
+ };
+ $ref->{_password} = $_->svc_x->_password
+ if $context eq 'agent'
+ && $conf->exists('agent-showpasswords')
+ && $_->part_svc->svcdb eq 'svc_acct';
+ $ref;
+ } $_->cust_svc
+ ],
+ };
+ } $cust_main->ncancelled_pkgs
+ ],
+ 'small_custview' =>
+ small_custview( $cust_main, $conf->config('defaultcountry') ),
+ };
+
+}
+
+sub order_pkg {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ #false laziness w/ClientAPI/Signup.pm
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'custnum' => $custnum,
+ 'pkgpart' => $p->{'pkgpart'},
+ } );
+ my $error = $cust_pkg->check;
+ return { 'error' => $error } if $error;
+
+ my @svc = ();
+ unless ( $p->{'svcpart'} eq 'none' ) {
+
+ my $svcdb;
+ my $svcpart = '';
+ if ( $p->{'svcpart'} =~ /^(\d+)$/ ) {
+ $svcpart = $1;
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ return { 'error' => "Unknown svcpart $svcpart" } unless $part_svc;
+ $svcdb = $part_svc->svcdb;
+ } else {
+ $svcdb = 'svc_acct';
+ }
+ $svcpart ||= $cust_pkg->part_pkg->svcpart($svcdb);
+
+ my %fields = (
+ 'svc_acct' => [ qw( username _password sec_phrase popnum ) ],
+ 'svc_domain' => [ qw( domain ) ],
+ 'svc_external' => [ qw( id title ) ],
+ );
+
+ my $svc_x = "FS::$svcdb"->new( {
+ 'svcpart' => $svcpart,
+ map { $_ => $p->{$_} } @{$fields{$svcdb}}
+ } );
+
+ if ( $svcdb eq 'svc_acct' ) {
+ my @acct_snarf;
+ my $snarfnum = 1;
+ while ( length($p->{"snarf_machine$snarfnum"}) ) {
+ my $acct_snarf = new FS::acct_snarf ( {
+ 'machine' => $p->{"snarf_machine$snarfnum"},
+ 'protocol' => $p->{"snarf_protocol$snarfnum"},
+ 'username' => $p->{"snarf_username$snarfnum"},
+ '_password' => $p->{"snarf_password$snarfnum"},
+ } );
+ $snarfnum++;
+ push @acct_snarf, $acct_snarf;
+ }
+ $svc_x->child_objects( \@acct_snarf );
+ }
+
+ my $y = $svc_x->setdefault; # arguably should be in new method
+ return { 'error' => $y } if $y && !ref($y);
+
+ $error = $svc_x->check;
+ return { 'error' => $error } if $error;
+
+ push @svc, $svc_x;
+
+ }
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => \@svc );
+ #msgcat
+ $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
+ return { 'error' => $error } if $error;
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('signup_server-realtime') ) {
+
+ my $old_balance = $cust_main->balance;
+
+ my $bill_error = $cust_main->bill;
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+ $bill_error = $cust_main->collect;
+
+ if ( $cust_main->balance > $old_balance
+ && $cust_main->balance > 0
+ && $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/ ) {
+ #this makes sense. credit is "un-doing" the invoice
+ $cust_main->credit( sprintf("%.2f", $cust_main->balance - $old_balance ),
+ 'self-service decline' );
+ $cust_main->apply_credits( 'order' => 'newest' );
+
+ $cust_pkg->cancel('quiet'=>1);
+ return { 'error' => '_decline', 'bill_error' => $bill_error };
+ } else {
+ $cust_pkg->reexport;
+ }
+
+ } else {
+ $cust_pkg->reexport;
+ }
+
+ return { error => '', pkgnum => $cust_pkg->pkgnum };
+
+}
+
+sub cancel_pkg {
+ my $p = shift;
+ my $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+
+ my $custnum = $session->{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $pkgnum = $p->{'pkgnum'};
+
+ my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+ 'pkgnum' => $pkgnum, } )
+ or return { 'error' => "unknown pkgnum $pkgnum" };
+
+ my $error = $cust_pkg->cancel( 'quiet'=>1 );
+ return { 'error' => $error };
+
+}
+
+sub provision_acct {
+ my $p = shift;
+
+ return { 'error' => gettext('passwords_dont_match') }
+ if $p->{'_password'} ne $p->{'_password2'};
+ return { 'error' => gettext('empty_password') }
+ unless length($p->{'_password'});
+
+ _provision( 'FS::svc_acct',
+ [qw(username _password)],
+ [qw(username _password)],
+ $p,
+ @_
+ );
+}
+
+sub provision_external {
+ my $p = shift;
+ #_provision( 'FS::svc_external', [qw(id title)], [qw(id title)], $p, @_ );
+ _provision( 'FS::svc_external',
+ [],
+ [qw(id title)],
+ $p,
+ @_
+ );
+}
+
+sub _provision {
+ my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4);
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $pkgnum = $p->{'pkgnum'};
+
+ my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+ 'pkgnum' => $pkgnum,
+ } )
+ or return { 'error' => "unknown pkgnum $pkgnum" };
+
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
+ or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
+
+ my $svc_x = $class->new( {
+ 'pkgnum' => $p->{'pkgnum'},
+ 'svcpart' => $p->{'svcpart'},
+ map { $_ => $p->{$_} } @$fields
+ } );
+ my $error = $svc_x->insert;
+ $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum })
+ unless $error;
+
+ return { 'svc' => $part_svc->svc,
+ 'error' => $error,
+ map { $_ => $svc_x->get($_) } @$return_fields
+ };
+
+}
+
+sub part_svc_info {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $pkgnum = $p->{'pkgnum'};
+
+ my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+ 'pkgnum' => $pkgnum,
+ } )
+ or return { 'error' => "unknown pkgnum $pkgnum" };
+
+ my $svcpart = $p->{'svcpart'};
+
+ my $pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $cust_pkg->pkgpart,
+ 'svcpart' => $svcpart, } )
+ or return { 'error' => "unknown svcpart $svcpart for pkgnum $pkgnum" };
+ my $part_svc = $pkg_svc->part_svc;
+
+ my $conf = new FS::Conf;
+
+ return {
+ 'svc' => $part_svc->svc,
+ 'svcdb' => $part_svc->svcdb,
+ 'pkgnum' => $pkgnum,
+ 'svcpart' => $svcpart,
+ 'custnum' => $custnum,
+
+ 'security_phrase' => 0, #XXX !
+ 'svc_acct_pop' => [], #XXX !
+ 'popnum' => '',
+ 'init_popstate' => '',
+ 'popac' => '',
+ 'acstate' => '',
+
+ 'small_custview' =>
+ small_custview( $cust_main, $conf->config('defaultcountry') ),
+
+ };
+
+}
+
+sub unprovision_svc {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $search = { 'custnum' => $custnum };
+ $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+ my $cust_main = qsearchs('cust_main', $search )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $svcnum = $p->{'svcnum'};
+
+ my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svcnum, } )
+ or return { 'error' => "unknown svcnum $svcnum" };
+
+ return { 'error' => "Service $svcnum does not belong to customer $custnum" }
+ unless $cust_svc->cust_pkg->custnum == $custnum;
+
+ my $conf = new FS::Conf;
+
+ return { 'svc' => $cust_svc->part_svc->svc,
+ 'error' => $cust_svc->cancel,
+ 'small_custview' =>
+ small_custview( $cust_main, $conf->config('defaultcountry') ),
+ };
+
+}
+
+#--
+
+sub _custoragent_session_custnum {
+ my $p = shift;
+
+ my($context, $session, $custnum);
+ if ( $p->{'session_id'} ) {
+
+ $context = 'customer';
+ $session = $cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+ $custnum = $session->{'custnum'};
+
+ } elsif ( $p->{'agent_session_id'} ) {
+
+ $context = 'agent';
+ my $agent_cache = new Cache::SharedMemoryCache( {
+ 'namespace' => 'FS::ClientAPI::Agent',
+ } );
+ $session = $agent_cache->get($p->{'agent_session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+ $custnum = $p->{'custnum'};
+
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+
+ ($context, $session, $custnum);
+
+}
+
+
+1;
+
diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm
new file mode 100644
index 0000000..bdcd2fb
--- /dev/null
+++ b/FS/FS/ClientAPI/Signup.pm
@@ -0,0 +1,288 @@
+package FS::ClientAPI::Signup;
+
+use strict;
+use Tie::RefHash;
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs dbdef);
+use FS::Msgcat qw(gettext);
+use FS::agent;
+use FS::cust_main_county;
+use FS::part_pkg;
+use FS::svc_acct_pop;
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::svc_acct;
+use FS::acct_snarf;
+use FS::queue;
+
+use FS::ClientAPI; #hmm
+FS::ClientAPI->register_handlers(
+ 'Signup/signup_info' => \&signup_info,
+ 'Signup/new_customer' => \&new_customer,
+);
+
+sub signup_info {
+ my $packet = shift;
+
+ my $conf = new FS::Conf;
+
+ use vars qw($signup_info); #cache for performance;
+ $signup_info ||= {
+
+ 'cust_main_county' =>
+ [ map { $_->hashref } qsearch('cust_main_county', {}) ],
+
+ 'agent' =>
+ [
+ map { $_->hashref }
+ qsearch('agent', dbdef->table('agent')->column('disabled')
+ ? { 'disabled' => '' }
+ : {}
+ )
+ ],
+
+ 'part_referral' =>
+ [
+ map { $_->hashref }
+ qsearch('part_referral',
+ dbdef->table('part_referral')->column('disabled')
+ ? { 'disabled' => '' }
+ : {}
+ )
+ ],
+
+ 'agentnum2part_pkg' =>
+ {
+ map {
+ my $href = $_->pkgpart_hashref;
+ $_->agentnum =>
+ [
+ map { { 'payby' => [ $_->payby ], %{$_->hashref} } }
+ grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } }
+ qsearch( 'part_pkg', { 'disabled' => '' } )
+ ];
+ } qsearch('agent', dbdef->table('agent')->column('disabled')
+ ? { 'disabled' => '' }
+ : {}
+ )
+ },
+
+ 'svc_acct_pop' => [ map { $_->hashref } qsearch('svc_acct_pop',{} ) ],
+
+ 'security_phrase' => $conf->exists('security_phrase'),
+
+ 'payby' => [ $conf->config('signup_server-payby') ],
+
+ 'cvv_enabled' => defined dbdef->table('cust_main')->column('paycvv'),
+
+ 'msgcat' => { map { $_=>gettext($_) } qw(
+ passwords_dont_match invalid_card unknown_card_type not_a empty_password
+ ) },
+
+ 'statedefault' => $conf->config('statedefault') || 'CA',
+
+ 'countrydefault' => $conf->config('countrydefault') || 'US',
+
+ 'refnum' => $conf->config('signup_server-default_refnum'),
+
+ };
+
+ my $agentnum = $conf->config('signup_server-default_agentnum');
+
+ my $session = '';
+ if ( exists $packet->{'session_id'} ) {
+ my $cache = new Cache::SharedMemoryCache( {
+ 'namespace' => 'FS::ClientAPI::Agent',
+ } );
+ $session = $cache->get($packet->{'session_id'});
+ if ( $session ) {
+ $agentnum = $session->{'agentnum'};
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+ }
+
+ if ( $agentnum ) {
+ $signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum};
+ } else {
+ delete $signup_info->{'part_pkg'};
+ }
+
+ if ( $session ) {
+ my $agent_signup_info = { %$signup_info };
+ delete $agent_signup_info->{agentnum2part_pkg};
+ $agent_signup_info->{'agent'} = $session->{'agent'};
+ $agent_signup_info;
+ } else {
+ $signup_info;
+ }
+
+}
+
+sub new_customer {
+ my $packet = shift;
+
+ my $conf = new FS::Conf;
+
+ #things that aren't necessary in base class, but are for signup server
+ #return "Passwords don't match"
+ # if $hashref->{'_password'} ne $hashref->{'_password2'}
+ return { 'error' => gettext('empty_password') }
+ unless length($packet->{'_password'});
+ # a bit inefficient for large numbers of pops
+ return { 'error' => gettext('no_access_number_selected') }
+ unless $packet->{'popnum'} || !scalar(qsearch('svc_acct_pop',{} ));
+
+ my $agentnum;
+ if ( exists $packet->{'session_id'} ) {
+ my $cache = new Cache::SharedMemoryCache( {
+ 'namespace' => 'FS::ClientAPI::Agent',
+ } );
+ my $session = $cache->get($packet->{'session_id'});
+ if ( $session ) {
+ $agentnum = $session->{'agentnum'};
+ } else {
+ return { 'error' => "Can't resume session" }; #better error message
+ }
+ } else {
+ $agentnum = $packet->{agentnum}
+ || $conf->config('signup_server-default_agentnum');
+ }
+
+ #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
+ # common that are still here and library them.
+ my $cust_main = new FS::cust_main ( {
+ #'custnum' => '',
+ 'agentnum' => $agentnum,
+ 'refnum' => $packet->{refnum}
+ || $conf->config('signup_server-default_refnum'),
+
+ map { $_ => $packet->{$_} } qw(
+ last first ss company address1 address2 city county state zip country
+ daytime night fax payby payinfo paycvv paydate payname referral_custnum
+ comments
+ ),
+
+ } );
+
+ return { 'error' => "Illegal payment type" }
+ unless grep { $_ eq $packet->{'payby'} }
+ $conf->config('signup_server-payby');
+
+ $cust_main->payinfo($cust_main->daytime)
+ if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
+
+ my @invoicing_list = split( /\s*\,\s*/, $packet->{'invoicing_list'} );
+
+ $packet->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
+ my $pkgpart = $1;
+ return { 'error' => 'Please select a package' } unless $pkgpart; #msgcat
+
+ my $part_pkg =
+ qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
+ or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" };
+ my $svcpart = $part_pkg->svcpart('svc_acct');
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ #later#'custnum' => $custnum,
+ 'pkgpart' => $packet->{'pkgpart'},
+ } );
+ my $error = $cust_pkg->check;
+ return { 'error' => $error } if $error;
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ map { $_ => $packet->{$_} }
+ qw( username _password sec_phrase popnum ),
+ } );
+
+ my @acct_snarf;
+ my $snarfnum = 1;
+ while ( exists($packet->{"snarf_machine$snarfnum"})
+ && length($packet->{"snarf_machine$snarfnum"}) ) {
+ my $acct_snarf = new FS::acct_snarf ( {
+ 'machine' => $packet->{"snarf_machine$snarfnum"},
+ 'protocol' => $packet->{"snarf_protocol$snarfnum"},
+ 'username' => $packet->{"snarf_username$snarfnum"},
+ '_password' => $packet->{"snarf_password$snarfnum"},
+ } );
+ $snarfnum++;
+ push @acct_snarf, $acct_snarf;
+ }
+ $svc_acct->child_objects( \@acct_snarf );
+
+ my $y = $svc_acct->setdefault; # arguably should be in new method
+ return { 'error' => $y } if $y && !ref($y);
+
+ $error = $svc_acct->check;
+ return { 'error' => $error } if $error;
+
+ #setup a job dependancy to delay provisioning
+ my $placeholder = new FS::queue ( {
+ 'job' => 'FS::ClientAPI::Signup::__placeholder',
+ 'status' => 'locked',
+ } );
+ $error = $placeholder->insert;
+ return { 'error' => $error } if $error;
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => [ $svc_acct ] );
+ #msgcat
+ $error = $cust_main->insert(
+ \%hash,
+ \@invoicing_list,
+ 'depend_jobnum' => $placeholder->jobnum,
+ );
+ if ( $error ) {
+ my $perror = $placeholder->delete;
+ $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
+ return { 'error' => $error };
+ }
+
+ if ( $conf->exists('signup_server-realtime') ) {
+
+ #warn "[fs_signup_server] Billing customer...\n" if $Debug;
+
+ my $bill_error = $cust_main->bill;
+ #warn "[fs_signup_server] error billing new customer: $bill_error"
+ # if $bill_error;
+
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ $bill_error = $cust_main->collect;
+ #warn "[fs_signup_server] error collecting from new customer: $bill_error"
+ # if $bill_error;
+
+ if ( $cust_main->balance > 0 ) {
+
+ #this makes sense. credit is "un-doing" the invoice
+ $cust_main->credit( $cust_main->balance, 'signup server decline' );
+ $cust_main->apply_credits;
+
+ #should check list for errors...
+ #$cust_main->suspend;
+ local $FS::svc_Common::noexport_hack = 1;
+ $cust_main->cancel('quiet'=>1);
+
+ my $perror = $placeholder->depended_delete;
+ warn "error removing provisioning jobs after decline: $perror" if $perror;
+ unless ( $perror ) {
+ $perror = $placeholder->delete;
+ warn "error removing placeholder after decline: $perror" if $perror;
+ }
+
+ return { 'error' => '_decline' };
+ }
+
+ }
+
+ $error = $placeholder->delete;
+ return { 'error' => $error } if $error;
+
+ return { error => '' };
+
+}
+
+1;
diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm
new file mode 100644
index 0000000..cb839ec
--- /dev/null
+++ b/FS/FS/ClientAPI/passwd.pm
@@ -0,0 +1,53 @@
+package FS::ClientAPI::passwd;
+
+use strict;
+use FS::Record qw(qsearchs);
+use FS::svc_acct;
+use FS::svc_domain;
+
+use FS::ClientAPI; #hmm
+FS::ClientAPI->register_handlers(
+ 'passwd/passwd' => \&passwd,
+ 'passwd/chfn' => \&chfn,
+ 'passwd/chsh' => \&chsh,
+);
+
+sub passwd {
+ my $packet = shift;
+
+ my $domain = $FS::ClientAPI::domain || $packet->{'domain'};
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ or return { error => "Domain $domain not found" };
+
+ my $old_password = $packet->{'old_password'};
+ my $new_password = $packet->{'new_password'};
+ my $new_gecos = $packet->{'new_gecos'};
+ my $new_shell = $packet->{'new_shell'};
+
+ #false laziness w/FS::ClientAPI::MyAccount::login
+
+ my $svc_acct = qsearchs( 'svc_acct', { 'username' => $packet->{'username'},
+ 'domsvc' => $svc_domain->svcnum, }
+ );
+ return { error => 'User not found.' } unless $svc_acct;
+ return { error => 'Incorrect password.' }
+ unless $svc_acct->check_password($old_password);
+
+ my %hash = $svc_acct->hash;
+ my $new_svc_acct = new FS::svc_acct ( \%hash );
+ $new_svc_acct->setfield('_password', $new_password )
+ if $new_password && $new_password ne $old_password;
+ $new_svc_acct->setfield('finger',$new_gecos) if $new_gecos;
+ $new_svc_acct->setfield('shell',$new_shell) if $new_shell;
+ my $error = $new_svc_acct->replace($svc_acct);
+
+ return { error => $error };
+
+}
+
+sub chfn {}
+
+sub chsh {}
+
+1;
+
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
new file mode 100644
index 0000000..eb6c690
--- /dev/null
+++ b/FS/FS/Conf.pm
@@ -0,0 +1,1312 @@
+package FS::Conf;
+
+use vars qw($default_dir @config_items $DEBUG );
+use IO::File;
+use File::Basename;
+use FS::ConfItem;
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::Conf - Freeside configuration values
+
+=head1 SYNOPSIS
+
+ use FS::Conf;
+
+ $conf = new FS::Conf "/config/directory";
+
+ $FS::Conf::default_dir = "/config/directory";
+ $conf = new FS::Conf;
+
+ $dir = $conf->dir;
+
+ $value = $conf->config('key');
+ @list = $conf->config('key');
+ $bool = $conf->exists('key');
+
+ $conf->touch('key');
+ $conf->set('key' => 'value');
+ $conf->delete('key');
+
+ @config_items = $conf->config_items;
+
+=head1 DESCRIPTION
+
+Read and write Freeside configuration values. Keys currently map to filenames,
+but this may change in the future.
+
+=head1 METHODS
+
+=over 4
+
+=item new [ DIRECTORY ]
+
+Create a new configuration object. A directory arguement is required if
+$FS::Conf::default_dir has not been set.
+
+=cut
+
+sub new {
+ my($proto,$dir) = @_;
+ my($class) = ref($proto) || $proto;
+ my($self) = { 'dir' => $dir || $default_dir } ;
+ bless ($self, $class);
+}
+
+=item dir
+
+Returns the directory.
+
+=cut
+
+sub dir {
+ my($self) = @_;
+ my $dir = $self->{dir};
+ -e $dir or die "FATAL: $dir doesn't exist!";
+ -d $dir or die "FATAL: $dir isn't a directory!";
+ -r $dir or die "FATAL: Can't read $dir!";
+ -x $dir or die "FATAL: $dir not searchable (executable)!";
+ $dir =~ /^(.*)$/;
+ $1;
+}
+
+=item config KEY
+
+Returns the configuration value or values (depending on context) for key.
+
+=cut
+
+sub config {
+ my($self,$file)=@_;
+ my($dir)=$self->dir;
+ my $fh = new IO::File "<$dir/$file" or return;
+ if ( wantarray ) {
+ map {
+ /^(.*)$/
+ or die "Illegal line (array context) in $dir/$file:\n$_\n";
+ $1;
+ } <$fh>;
+ } else {
+ <$fh> =~ /^(.*)$/
+ or die "Illegal line (scalar context) in $dir/$file:\n$_\n";
+ $1;
+ }
+}
+
+=item exists KEY
+
+Returns true if the specified key exists, even if the corresponding value
+is undefined.
+
+=cut
+
+sub exists {
+ my($self,$file)=@_;
+ my($dir) = $self->dir;
+ -e "$dir/$file";
+}
+
+=item config_orbase KEY SUFFIX
+
+Returns the configuration value or values (depending on context) for
+KEY_SUFFIX, if it exists, otherwise for KEY
+
+=cut
+
+sub config_orbase {
+ my( $self, $file, $suffix ) = @_;
+ if ( $self->exists("${file}_$suffix") ) {
+ $self->config("${file}_$suffix");
+ } else {
+ $self->config($file);
+ }
+}
+
+=item touch KEY
+
+Creates the specified configuration key if it does not exist.
+
+=cut
+
+sub touch {
+ my($self, $file) = @_;
+ my $dir = $self->dir;
+ unless ( $self->exists($file) ) {
+ warn "[FS::Conf] TOUCH $file\n" if $DEBUG;
+ system('touch', "$dir/$file");
+ }
+}
+
+=item set KEY VALUE
+
+Sets the specified configuration key to the given value.
+
+=cut
+
+sub set {
+ my($self, $file, $value) = @_;
+ my $dir = $self->dir;
+ $value =~ /^(.*)$/s;
+ $value = $1;
+ unless ( join("\n", @{[ $self->config($file) ]}) eq $value ) {
+ warn "[FS::Conf] SET $file\n" if $DEBUG;
+# warn "$dir" if is_tainted($dir);
+# warn "$dir" if is_tainted($file);
+ chmod 0644, "$dir/$file";
+ my $fh = new IO::File ">$dir/$file" or return;
+ chmod 0644, "$dir/$file";
+ print $fh "$value\n";
+ }
+}
+#sub is_tainted {
+# return ! eval { join('',@_), kill 0; 1; };
+# }
+
+=item delete KEY
+
+Deletes the specified configuration key.
+
+=cut
+
+sub delete {
+ my($self, $file) = @_;
+ my $dir = $self->dir;
+ if ( $self->exists($file) ) {
+ warn "[FS::Conf] DELETE $file\n";
+ unlink "$dir/$file";
+ }
+}
+
+=item config_items
+
+Returns all of the possible configuration items as FS::ConfItem objects. See
+L<FS::ConfItem>.
+
+=cut
+
+sub config_items {
+ my $self = shift;
+ #quelle kludge
+ @config_items,
+ ( map {
+ my $basename = basename($_);
+ $basename =~ /^(.*)$/;
+ $basename = $1;
+ new FS::ConfItem {
+ 'key' => $basename,
+ 'section' => 'billing',
+ 'description' => 'Alternate template file for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ }
+ } glob($self->dir. '/invoice_template_*')
+ ),
+ ( map {
+ my $basename = basename($_);
+ $basename =~ /^(.*)$/;
+ $basename = $1;
+ new FS::ConfItem {
+ 'key' => $basename,
+ 'section' => 'billing',
+ 'description' => 'Alternate LaTeX template for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ }
+ } glob($self->dir. '/invoice_latex_*')
+ ),
+ ( map {
+ my $basename = basename($_);
+ $basename =~ /^(.*)$/;
+ $basename = $1;
+ new FS::ConfItem {
+ 'key' => $basename,
+ 'section' => 'billing',
+ 'description' => 'Alternate Notes section for LaTeX typeset PostScript invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ }
+ } glob($self->dir. '/invoice_latexnotes_*')
+ );
+}
+
+=back
+
+=head1 BUGS
+
+If this was more than just crud that will never be useful outside Freeside I'd
+worry that config_items is freeside-specific and icky.
+
+=head1 SEE ALSO
+
+"Configuration" in the web interface (config/config.cgi).
+
+httemplate/docs/config.html
+
+=cut
+
+@config_items = map { new FS::ConfItem $_ } (
+
+ {
+ 'key' => 'address',
+ 'section' => 'deprecated',
+ 'description' => 'This configuration option is no longer used. See <a href="#invoice_template">invoice_template</a> instead.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'alerter_template',
+ 'section' => 'billing',
+ 'description' => 'Template file for billing method expiration alerts. See the <a href="../docs/billing.html#invoice_template">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'apacheroot',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. The directory containing Apache virtual hosts',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'apacheip',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>apache</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the current IP address to assign to new virtual hosts',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'apachemachine',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. A machine with the apacheroot directory and user home directories. The existance of this file enables setup of virtual host directories, and, in conjunction with the `home\' configuration file, symlinks into user home directories.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'apachemachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>apache</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be Apache machines, one per line. This enables export of `/etc/apache/vhosts.conf\', which can be included in your Apache configuration via the <a href="http://www.apache.org/docs/mod/core.html#include">Include</a> directive.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'bindprimary',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>bind</i> <a href="../browse/part_export.cgi">export</a> instead. Your BIND primary nameserver. This enables export of /var/named/named.conf and zone files into /var/named',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'bindsecondaries',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>bind_slave</i> <a href="../browse/part_export.cgi">export</a> instead. Your BIND secondary nameservers, one per line. This enables export of /var/named/named.conf',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'business-onlinepayment',
+ 'section' => 'billing',
+ 'description' => '<a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support, at least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'business-onlinepayment-ach',
+ 'section' => 'billing',
+ 'description' => 'Alternate <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> support for ACH transactions (defaults to regular <b>business-onlinepayment</b>). At least three lines: processor, login, and password. An optional fourth line specifies the action or actions (multiple actions are separated with `,\': for example: `Authorization Only, Post Authorization\'). Optional additional lines are passed to Business::OnlinePayment as %processor_options.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'business-onlinepayment-description',
+ 'section' => 'billing',
+ 'description' => 'String passed as the description field to <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a>. Evaluated as a double-quoted perl string, with the following variables available: <code>$agent</code> (the agent name), and <code>$pkgs</code> (a comma-separated list of packages for which these charges apply)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'bsdshellmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>bsdshell</i> <a href="../browse/part_export.cgi">export</a> instead. Your BSD flavored shell (and mail) machines, one per line. This enables export of `/etc/passwd\' and `/etc/master.passwd\'.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'countrydefault',
+ 'section' => 'UI',
+ 'description' => 'Default two-letter country code (if not supplied, the default is `US\')',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'cyrus',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>cyrus</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to integrate with <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>, three lines: IMAP server, admin username, and admin password. Cyrus::IMAP::Admin should be installed locally and the connection to the server secured.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cp_app',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>cp</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to integrate with <a href="http://www.cp.net/">Critial Path Account Provisioning Protocol</a>, four lines: "host:port", username, password, and workgroup (for new users).',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'deletecustomers',
+ 'section' => 'UI',
+ 'description' => 'Enable customer deletions. Be very careful! Deleting a customer will remove all traces that this customer ever existed! It should probably only be used when auditing a legacy database. Normally, you cancel all of a customers\' packages if they cancel service.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'deletepayments',
+ 'section' => 'UI',
+ 'description' => 'Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.',
+ 'type' => [qw( checkbox text )],
+ },
+
+ {
+ 'key' => 'deletecredits',
+ 'section' => 'UI',
+ 'description' => 'Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.',
+ 'type' => [qw( checkbox text )],
+ },
+
+ {
+ 'key' => 'unapplypayments',
+ 'section' => 'UI',
+ 'description' => 'Enable "unapplication" of unclosed payments.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'unapplycredits',
+ 'section' => 'UI',
+ 'description' => 'Enable "unapplication" of unclosed credits.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'dirhash',
+ 'section' => 'shell',
+ 'description' => 'Optional numeric value to control directory hashing. If positive, hashes directories for the specified number of levels from the front of the username. If negative, hashes directories for the specified number of levels from the end of the username. Some examples: <ul><li>1: user -> <a href="#home">/home</a>/u/user<li>2: user -> <a href="#home">/home</a>/u/s/user<li>-1: user -> <a href="#home">/home</a>/r/user<li>-2: user -> <a href="#home">home</a>/r/e/user</ul>',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'disable_customer_referrals',
+ 'section' => 'UI',
+ 'description' => 'Disable new customer-to-customer referrals in the web interface',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'editreferrals',
+ 'section' => 'UI',
+ 'description' => 'Enable advertising source modification for existing customers',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'emailinvoiceonly',
+ 'section' => 'billing',
+ 'description' => 'Disables postal mail invoices',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'disablepostalinvoicedefault',
+ 'section' => 'billing',
+ 'description' => 'Disables postal mail invoices as the default option in the UI. Be careful not to setup customers which are not sent invoices. See <a href ="#emailinvoiceauto">emailinvoiceauto</a>.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'emailinvoiceauto',
+ 'section' => 'billing',
+ 'description' => 'Automatically adds new accounts to the email invoice list',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'exclude_ip_addr',
+ 'section' => '',
+ 'description' => 'Exclude these from the list of available broadband service IP addresses. (One per line)',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'erpcdmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, ERPCD is no longer supported. Used to be ERPCD authenticaion machines, one per line. This enables export of `/usr/annex/acp_passwd\' and `/usr/annex/acp_dialup\'',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'hidecancelledpackages',
+ 'section' => 'UI',
+ 'description' => 'Prevent cancelled packages from showing up in listings (though they will still be in the database)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'hidecancelledcustomers',
+ 'section' => 'UI',
+ 'description' => 'Prevent customers with only cancelled packages from showing up in listings (though they will still be in the database)',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'home',
+ 'section' => 'required',
+ 'description' => 'For new users, prefixed to username to create a directory name. Should have a leading but not a trailing slash.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'icradiusmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually). You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option. <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line. Machines listed in this file will have the radcheck table exported to them. Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password. For example: <CODE>"radius.isp.tld&nbsp;radius_db&nbsp;radius_user&nbsp;passw0rd"</CODE></blockquote>',
+ 'type' => [qw( checkbox textarea )],
+ },
+
+ {
+ 'key' => 'icradius_mysqldest',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the destination directory for the MySQL databases, on the ICRADIUS/FreeRADIUS machines. Defaults to "/usr/local/var/".',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'icradius_mysqlsource',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be the source directory for for the MySQL radcheck table files, on the Freeside machine. Defaults to "/usr/local/var/freeside".',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'icradius_secrets',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to specify a database for ICRADIUS/FreeRADIUS export. Three lines: DBI data source, username and password.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_from',
+ 'section' => 'required',
+ 'description' => 'Return address on email invoices',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'invoice_template',
+ 'section' => 'required',
+ 'description' => 'Required template file for invoices. See the <a href="../docs/billing.html">billing documentation</a> for details.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latex',
+ 'section' => 'billing',
+ 'description' => 'Optional LaTeX template for typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexnotes',
+ 'section' => 'billing',
+ 'description' => 'Notes section for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexfooter',
+ 'section' => 'billing',
+ 'description' => 'Footer for LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_latexsmallfooter',
+ 'section' => 'billing',
+ 'description' => 'Optional small footer for multi-page LaTeX typeset PostScript invoices.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'invoice_default_terms',
+ 'section' => 'billing',
+ 'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
+ 'type' => 'select',
+ 'select_enum' => [ '', 'Payable upon receipt', 'Net 0', 'Net 10', 'Net 15', 'Net 30', 'Net 45', 'Net 60' ],
+ },
+
+ {
+ 'key' => 'invoice_send_receipts',
+ 'section' => 'deprecated',q
+ 'description' => '<b>DEPRECATED</b>, this used to send an invoice copy on payments and credits. See the payment_receipt_email and XXXX instead.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'payment_receipt_email',
+ 'section' => 'billing',
+ 'description' => 'Template file for payment receipts.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'lpr',
+ 'section' => 'required',
+ 'description' => 'Print command for paper invoices, for example `lpr -h\'',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'maildisablecatchall',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, now the default. Turning this option on used to disable the requirement that each virtual domain have a catch-all mailbox.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'money_char',
+ 'section' => '',
+ 'description' => 'Currency symbol - defaults to `$\'',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'mxmachines',
+ 'section' => 'deprecated',
+ 'description' => 'MX entries for new domains, weight and machine, one per line, with trailing `.\'',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'nsmachines',
+ 'section' => 'deprecated',
+ 'description' => 'NS nameservers for new domains, one per line, with trailing `.\'',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'defaultrecords',
+ 'section' => 'BIND',
+ 'description' => 'DNS entries to add automatically when creating a domain',
+ 'type' => 'editlist',
+ 'editlist_parts' => [ { type=>'text' },
+ { type=>'immutable', value=>'IN' },
+ { type=>'select',
+ select_enum=>{ map { $_=>$_ } qw(A CNAME MX NS)} },
+ { type=> 'text' }, ],
+ },
+
+ {
+ 'key' => 'arecords',
+ 'section' => 'deprecated',
+ 'description' => 'A list of tab seperated CNAME records to add automatically when creating a domain',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cnamerecords',
+ 'section' => 'deprecated',
+ 'description' => 'A list of tab seperated CNAME records to add automatically when creating a domain',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'nismachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>. Your NIS master (not slave master) machines, one per line. This enables export of `/etc/global/passwd\' and `/etc/global/shadow\'.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'passwordmin',
+ 'section' => 'password',
+ 'description' => 'Minimum password length (default 6)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'passwordmax',
+ 'section' => 'password',
+ 'description' => 'Maximum password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'qmailmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add <i>qmail</i> and <i>shellcommands</i> <a href="../browse/part_export.cgi">exports</a> instead. This option used to export `/var/qmail/control/virtualdomains\', `/var/qmail/control/recipientmap\', and `/var/qmail/control/rcpthosts\'. Setting this option (even if empty) also turns on user `.qmail-extension\' file maintenance in conjunction with the <b>shellmachine</b> option.',
+ 'type' => [qw( checkbox textarea )],
+ },
+
+ {
+ 'key' => 'radiusmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to export to be: your RADIUS authentication machines, one per line. This enables export of `/etc/raddb/users\'.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'referraldefault',
+ 'section' => 'UI',
+ 'description' => 'Default referral, specified by refnum',
+ 'type' => 'text',
+ },
+
+# {
+# 'key' => 'registries',
+# 'section' => 'required',
+# 'description' => 'Directory which contains domain registry information. Each registry is a directory.',
+# },
+
+ {
+ 'key' => 'report_template',
+ 'section' => 'deprecated',
+ 'description' => 'Deprecated template file for reports.',
+ 'type' => 'textarea',
+ },
+
+
+ {
+ 'key' => 'maxsearchrecordsperpage',
+ 'section' => 'UI',
+ 'description' => 'If set, number of search records to return per page.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sendmailconfigpath',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be sendmail configuration file path. Defaults to `/etc\'. Many newer distributions use `/etc/mail\'.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sendmailmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to be sendmail machines, one per line. This enables export of `/etc/virtusertable\' and `/etc/sendmail.cw\'.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'sendmailrestart',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>sendmail</i> <a href="../browse/part_export.cgi">export</a> instead. Used to define the command which is run on sendmail machines after files are copied.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'session-start',
+ 'section' => 'session',
+ 'description' => 'If defined, the command which is executed on the Freeside machine when a session begins. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'session-stop',
+ 'section' => 'session',
+ 'description' => 'If defined, the command which is executed on the Freeside machine when a session ends. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'shellmachine',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain a single machine with user home directories mounted. This enables home directory creation, renaming and archiving/deletion. In conjunction with `qmailmachines\', it also enables `.qmail-extension\' file maintenance.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'shellmachine-useradd',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is created. If the <b>shellmachine</b> option is set but this option is not, <code>useradd -d $dir -m -s $shell -u $uid $username</code> is the default. If this option is set but empty, <code>cp -pr /etc/skel $dir; chown -R $uid.$gid $dir</code> is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: <code>$username</code>, <code>$uid</code>, <code>$gid</code>, <code>$dir</code>, and <code>$shell</code>.',
+ 'type' => [qw( checkbox text )],
+ },
+
+ {
+ 'key' => 'shellmachine-userdel',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is deleted. If the <b>shellmachine</b> option is set but this option is not, <code>userdel $username</code> is the default. If this option is set but empty, <code>rm -rf $dir</code> is the default instead. Otherwise the value is evaluated as a double-quoted perl string, with the following variables available: <code>$username</code> and <code>$dir</code>.',
+ 'type' => [qw( checkbox text )],
+ },
+
+ {
+ 'key' => 'shellmachine-usermod',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain command(s) to run on shellmachine when an account is modified. If the <b>shellmachine</b> option is set but this option is empty, <code>[ -d $old_dir ] &amp;&amp; mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $uid.$gid $new_dir; rm -rf $old_dir )</code> is the default. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$old_dir</code>, <code>$new_dir</code>, <code>$uid</code> and <code>$gid</code>.',
+ #'type' => [qw( checkbox text )],
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'shellmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>sysvshell</i> <a href="../browse/part_export.cgi">export</a> instead. Your Linux and System V flavored shell (and mail) machines, one per line. This enables export of `/etc/passwd\' and `/etc/shadow\' files.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'shells',
+ 'section' => 'required',
+ 'description' => 'Legal shells (think /etc/shells). You probably want to `cut -d: -f7 /etc/passwd | sort | uniq\' initially so that importing doesn\'t fail with `Illegal shell\' errors, then remove any special entries afterwords. A blank line specifies that an empty shell is permitted.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'showpasswords',
+ 'section' => 'UI',
+ 'description' => 'Display unencrypted user passwords in the backend (employee) web interface',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signupurl',
+ 'section' => 'UI',
+ 'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your <a href="../docs/signup.html">signup server CGI</a>, the customer view screen will display a customized link to the signup server with the appropriate customer as referral',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'smtpmachine',
+ 'section' => 'required',
+ 'description' => 'SMTP relay for Freeside\'s outgoing mail',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soadefaultttl',
+ 'section' => 'BIND',
+ 'description' => 'SOA default TTL for new domains.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soaemail',
+ 'section' => 'BIND',
+ 'description' => 'SOA email for new domains, in BIND form (`.\' instead of `@\'), with trailing `.\'',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soaexpire',
+ 'section' => 'BIND',
+ 'description' => 'SOA expire for new domains',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soamachine',
+ 'section' => 'BIND',
+ 'description' => 'SOA machine for new domains, with trailing `.\'',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soarefresh',
+ 'section' => 'BIND',
+ 'description' => 'SOA refresh for new domains',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'soaretry',
+ 'section' => 'BIND',
+ 'description' => 'SOA retry for new domains',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'statedefault',
+ 'section' => 'UI',
+ 'description' => 'Default state or province (if not supplied, the default is `CA\')',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'radiusprepend',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, real-time text radius now edits an existing file in place - just (turn off freeside-queued and) edit your RADIUS users file directly. The contents used to be be prepended to the top of the RADIUS users file (text exports only).',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'textradiusprepend',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, use RADIUS check attributes instead. The contents used to be prepended to the first line of a user\'s RADIUS entry in text exports.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'unsuspendauto',
+ 'section' => 'billing',
+ 'description' => 'Enables the automatic unsuspension of suspended packages when a customer\'s balance due changes from positive to zero or negative as the result of a payment or credit',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'usernamemin',
+ 'section' => 'username',
+ 'description' => 'Minimum username length (default 2)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'usernamemax',
+ 'section' => 'username',
+ 'description' => 'Maximum username length',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'username-ampersand',
+ 'section' => 'username',
+ 'description' => 'Allow the ampersand character (&amp;) in usernames. Be careful when using this option in conjunction with <a href="../browse/part_export.cgi">exports</a> which execute shell commands, as the ampersand will be interpreted by the shell if not quoted.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-letter',
+ 'section' => 'username',
+ 'description' => 'Usernames must contain at least one letter',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-letterfirst',
+ 'section' => 'username',
+ 'description' => 'Usernames must start with a letter',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-noperiod',
+ 'section' => 'username',
+ 'description' => 'Disallow periods in usernames',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-nounderscore',
+ 'section' => 'username',
+ 'description' => 'Disallow underscores in usernames',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-nodash',
+ 'section' => 'username',
+ 'description' => 'Disallow dashes in usernames',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username-uppercase',
+ 'section' => 'username',
+ 'description' => 'Allow uppercase characters in usernames',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'username_policy',
+ 'section' => 'deprecated',
+ 'description' => 'This file controls the mechanism for preventing duplicate usernames in passwd/radius files exported from svc_accts. This should be one of \'prepend domsvc\' \'append domsvc\' \'append domain\' or \'append @domain\'',
+ 'type' => 'select',
+ 'select_enum' => [ 'prepend domsvc', 'append domsvc', 'append domain', 'append @domain' ],
+ #'type' => 'text',
+ },
+
+ {
+ 'key' => 'vpopmailmachines',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>vpopmail</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to contain your vpopmail pop toasters, one per line. Each line is of the form "machinename vpopdir vpopuid vpopgid". For example: <code>poptoaster.domain.tld /home/vpopmail 508 508</code> Note: vpopuid and vpopgid are values taken from the vpopmail machine\'s /etc/passwd',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'vpopmailrestart',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, add a <i>vpopmail</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to define the shell commands to run on vpopmail machines after files are copied. An example can be found in eg/vpopmailrestart of the source distribution.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'safe-part_pkg',
+ 'section' => 'UI',
+ 'description' => 'Validates package definition setup and recur expressions against a preset list. Useful for webdemos, annoying to powerusers.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'safe-part_bill_event',
+ 'section' => 'UI',
+ 'description' => 'Validates invoice event expressions against a preset list. Useful for webdemos, annoying to powerusers.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'show_ss',
+ 'section' => 'UI',
+ 'description' => 'Turns on display/collection of SS# in the web interface.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'agent_defaultpkg',
+ 'section' => 'UI',
+ 'description' => 'Setting this option will cause new packages to be available to all agent types by default.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'legacy_link',
+ 'section' => 'UI',
+ 'description' => 'Display options in the web interface to link legacy pre-Freeside services.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'legacy_link-steal',
+ 'section' => 'UI',
+ 'description' => 'Allow "stealing" an already-audited service from one customer (or package) to another using the link function.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'queue_dangerous_controls',
+ 'section' => 'UI',
+ 'description' => 'Enable queue modification controls on account pages and for new jobs. Unless you are a developer working on new export code, you should probably leave this off to avoid causing provisioning problems.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'security_phrase',
+ 'section' => 'password',
+ 'description' => 'Enable the tracking of a "security phrase" with each account. Not recommended, as it is vulnerable to social engineering.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'locale',
+ 'section' => 'UI',
+ 'description' => 'Message locale',
+ 'type' => 'select',
+ 'select_enum' => [ qw(en_US) ],
+ },
+
+ {
+ 'key' => 'selfservice_server-quiet',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, the self-service server no longer sends superfluous decline and cancel emails. Used to disable decline and cancel emails generated by transactions initiated by the selfservice server.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup_server-quiet',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, the signup server is now part of the self-service server and no longer sends superfluous decline and cancel emails. Used to disable decline and cancel emails generated by transactions initiated by the signup server. Does not disable welcome emails.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup_server-payby',
+ 'section' => '',
+ 'description' => 'Acceptable payment types for the signup server',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
+ },
+
+ {
+ 'key' => 'signup_server-email',
+ 'section' => 'deprecated',
+ 'description' => '<b>DEPRECATED</b>, this feature is no longer available. See the ***fill me in*** report instead. Used to contain a comma-separated list of email addresses to receive notification of signups via the signup server.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'signup_server-default_agentnum',
+ 'section' => '',
+ 'description' => 'Default agentnum for the signup server',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'signup_server-default_refnum',
+ 'section' => '',
+ 'description' => 'Default advertising source number for the signup server',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'show-msgcat-codes',
+ 'section' => 'UI',
+ 'description' => 'Show msgcat codes in error messages. Turn this option on before reporting errors to the mailing list.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'signup_server-realtime',
+ 'section' => '',
+ 'description' => 'Run billing for signup server signups immediately, and suspend accounts which subsequently have a balance.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'declinetemplate',
+ 'section' => 'billing',
+ 'description' => 'Template file for credit card decline emails.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'emaildecline',
+ 'section' => 'billing',
+ 'description' => 'Enable emailing of credit card decline notices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'emaildecline-exclude',
+ 'section' => 'billing',
+ 'description' => 'List of error messages that should not trigger email decline notices, one per line.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cancelmessage',
+ 'section' => 'billing',
+ 'description' => 'Template file for cancellation emails.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cancelsubject',
+ 'section' => 'billing',
+ 'description' => 'Subject line for cancellation emails.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'emailcancel',
+ 'section' => 'billing',
+ 'description' => 'Enable emailing of cancellation notices.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'require_cardname',
+ 'section' => 'billing',
+ 'description' => 'Require an "Exact name on card" to be entered explicitly; don\'t default to using the first and last name.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'enable_taxclasses',
+ 'section' => 'billing',
+ 'description' => 'Enable per-package tax classes',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'welcome_email',
+ 'section' => '',
+ 'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created. See the <a href="http://search.cpan.org/doc/MJD/Text-Template-1.42/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available: <code>$username</code>, <code>$password</code>, <code>$first</code>, <code>$last</code> and <code>$pkg</code>.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'welcome_email-from',
+ 'section' => '',
+ 'description' => 'From: address header for welcome email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'welcome_email-subject',
+ 'section' => '',
+ 'description' => 'Subject: header for welcome email',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'welcome_email-mimetype',
+ 'section' => '',
+ 'description' => 'MIME type for welcome email',
+ 'type' => 'select',
+ 'select_enum' => [ 'text/plain', 'text/html' ],
+ },
+
+ {
+ 'key' => 'payby-default',
+ 'section' => 'UI',
+ 'description' => 'Default payment type. HIDE disables display of billing information and sets customers to BILL.',
+ 'type' => 'select',
+ 'select_enum' => [ '', qw(CARD DCRD CHEK DCHK LECB BILL COMP HIDE) ],
+ },
+
+ {
+ 'key' => 'svc_acct-notes',
+ 'section' => 'UI',
+ 'description' => 'Extra HTML to be displayed on the Account View screen.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'radius-password',
+ 'section' => '',
+ 'description' => 'RADIUS attribute for plain-text passwords.',
+ 'type' => 'select',
+ 'select_enum' => [ 'Password', 'User-Password' ],
+ },
+
+ {
+ 'key' => 'radius-ip',
+ 'section' => '',
+ 'description' => 'RADIUS attribute for IP addresses.',
+ 'type' => 'select',
+ 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ],
+ },
+
+ {
+ 'key' => 'svc_acct-alldomains',
+ 'section' => '',
+ 'description' => 'Allow accounts to select any domain in the database. Normally accounts can only select from the domain set in the service definition and those purchased by the customer.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'dump-scpdest',
+ 'section' => '',
+ 'description' => 'destination for scp database dumps: user@host:/path',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'dump-pgpid',
+ 'section' => '',
+ 'description' => "Optional PGP public key user or key id for database dumps. The public key should exist on the freeside user's public keyring, and the gpg binary and GnuPG perl module should be installed.",
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'users-allow_comp',
+ 'section' => '',
+ 'description' => 'Usernames (Freeside users, created with <a href="../docs/man/bin/freeside-adduser.html">freeside-adduser</a>) which can create complimentary customers, one per line. If no usernames are entered, all users can create complimentary accounts.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cvv-save',
+ 'section' => 'billing',
+ 'description' => 'Save CVV2 information after the initial transaction for the selected credit card types. Enabling this option may be in violation of your merchant agreement(s), so please check them carefully before enabling this option for any credit card types.',
+ 'type' => 'selectmultiple',
+ 'select_enum' => [ "VISA card",
+ "MasterCard",
+ "Discover card",
+ "American Express card",
+ "Diner's Club/Carte Blanche",
+ "enRoute",
+ "JCB",
+ "BankCard",
+ ],
+ },
+
+ {
+ 'key' => 'allow_negative_charges',
+ 'section' => 'billing',
+ 'description' => 'Allow negative charges. Normally not used unless importing data from a legacy system that requires this.',
+ 'type' => 'checkbox',
+ },
+ {
+ 'key' => 'auto_unset_catchall',
+ 'section' => '',
+ 'description' => 'When canceling a svc_acct that is the email catchall for one or more svc_domains, automatically set their catchall fields to null. If this option is not set, the attempt will simply fail.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'system_usernames',
+ 'section' => 'username',
+ 'description' => 'A list of system usernames that cannot be edited or removed, one per line. Use a bare username to prohibit modification/deletion of the username in any domain, or username@domain to prohibit modification/deletetion of a specific username and domain.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'cust_pkg-change_svcpart',
+ 'section' => '',
+ 'description' => "When changing packages, move services even if svcparts don't match between old and new pacakge definitions.",
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'disable_autoreverse',
+ 'section' => 'BIND',
+ 'description' => 'Disable automatic synchronization of reverse-ARPA entries.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_www-enable_subdomains',
+ 'section' => '',
+ 'description' => 'Enable selection of specific subdomains for virtual host creation.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_www-usersvc_svcpart',
+ 'section' => '',
+ 'description' => 'Allowable service definition svcparts for virtual hosts, one per line.',
+ 'type' => 'textarea',
+ },
+
+ {
+ 'key' => 'selfservice_server-primary_only',
+ 'section' => '',
+ 'description' => 'Only allow primary accounts to access self-service functionality.',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'card_refund-days',
+ 'section' => 'billing',
+ 'description' => 'After a payment, the number of days a refund link will be available for that payment. Defaults to 120.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'agent-showpasswords',
+ 'section' => '',
+ 'description' => 'Display unencrypted user passwords in the agent (reseller) interface',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'global_unique-username',
+ 'section' => 'username',
+ 'description' => 'Global username uniqueness control: none (usual setting - check uniqueness per exports), username (all usernames are globally unique, regardless of domain or exports), or username@domain (all username@domain pairs are globally unique, regardless of exports)',
+ 'type' => 'select',
+ 'select_enum' => [ 'none', 'username', 'username@domain' ],
+ },
+
+ {
+ 'key' => 'svc_external-skip_manual',
+ 'section' => 'UI',
+ 'description' => 'When provisioning svc_external services, skip manual entry of id and title fields in the UI. Usually used in conjunction with an export that populates these fields (i.e. artera_turbo).',
+ 'type' => 'checkbox',
+ },
+
+ {
+ 'key' => 'svc_external-display_type',
+ 'section' => 'UI',
+ 'description' => 'Select a specific svc_external type to enable some UI changes specific to that type (i.e. artera_turbo).',
+ 'type' => 'select',
+ 'select_enum' => [ 'generic', 'artera_turbo', ],
+ },
+
+);
+
+1;
+
diff --git a/FS/FS/ConfItem.pm b/FS/FS/ConfItem.pm
new file mode 100644
index 0000000..83295b4
--- /dev/null
+++ b/FS/FS/ConfItem.pm
@@ -0,0 +1,63 @@
+package FS::ConfItem;
+
+=head1 NAME
+
+FS::ConfItem - Configutaion option meta-data.
+
+=head1 SYNOPSIS
+
+ use FS::Conf;
+ @config_items = $conf->config_items;
+
+ foreach $item ( @config_items ) {
+ $key = $item->key;
+ $section = $item->section;
+ $description = $item->description;
+ }
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = @_ ? shift : {};
+ bless ($self, $class);
+}
+
+=item key
+
+=item section
+
+=item description
+
+=cut
+
+sub AUTOLOAD {
+ my $self = shift;
+ my $field = $AUTOLOAD;
+ $field =~ s/.*://;
+ $self->{$field};
+}
+
+=back
+
+=head1 BUGS
+
+Terse docs.
+
+=head1 SEE ALSO
+
+L<FS::Conf>
+
+=cut
+
+1;
+
diff --git a/FS/FS/InitHandler.pm b/FS/FS/InitHandler.pm
new file mode 100644
index 0000000..5038cf3
--- /dev/null
+++ b/FS/FS/InitHandler.pm
@@ -0,0 +1,91 @@
+package FS::InitHandler;
+
+# this leaks memory under graceful restarts and i wouldn't use it on any
+# modern server. useful for very slow machines with memory to spare, just
+# always do a full restart
+
+use strict;
+use vars qw($DEBUG);
+use FS::UID qw(adminsuidsetup);
+use FS::Record;
+
+$DEBUG = 1;
+
+sub handler {
+
+ use Date::Format;
+ use Date::Parse;
+ use Tie::IxHash;
+ use HTML::Entities;
+ use IO::Handle;
+ use IO::File;
+ use String::Approx;
+ use HTML::Widgets::SelectLayers 0.02;
+ #use FS::UID;
+ #use FS::Record;
+ use FS::Conf;
+ use FS::CGI;
+ use FS::Msgcat;
+
+ use FS::agent;
+ use FS::agent_type;
+ use FS::domain_record;
+ use FS::cust_bill;
+ use FS::cust_bill_pay;
+ use FS::cust_credit;
+ use FS::cust_credit_bill;
+ use FS::cust_main;
+ use FS::cust_main_county;
+ use FS::cust_pay;
+ use FS::cust_pkg;
+ use FS::cust_refund;
+ use FS::cust_svc;
+ use FS::nas;
+ use FS::part_bill_event;
+ use FS::part_pkg;
+ use FS::part_referral;
+ use FS::part_svc;
+ use FS::pkg_svc;
+ use FS::port;
+ use FS::queue;
+ use FS::raddb;
+ use FS::session;
+ use FS::svc_acct;
+ use FS::svc_acct_pop;
+ use FS::svc_domain;
+ use FS::svc_forward;
+ use FS::svc_www;
+ use FS::type_pkgs;
+ use FS::part_export;
+ use FS::part_export_option;
+ use FS::export_svc;
+ use FS::msgcat;
+
+ warn "[FS::InitHandler] handler called\n" if $DEBUG;
+
+ #this is sure to be broken on freebsd
+ $> = $FS::UID::freeside_uid;
+
+ open(MAPSECRETS,"<$FS::UID::conf_dir/mapsecrets")
+ or die "can't read $FS::UID::conf_dir/mapsecrets: $!";
+
+ my %seen;
+ while (<MAPSECRETS>) {
+ next if /^\s*(#|$)/;
+ /^([\w\-\.]+)\s(.*)$/
+ or do { warn "strange line in mapsecrets: $_"; next; };
+ my($user, $datasrc) = ($1, $2);
+ next if $seen{$datasrc}++;
+ warn "[FS::InitHandler] preloading $datasrc for $user\n" if $DEBUG;
+ adminsuidsetup($user);
+ }
+
+ close MAPSECRETS;
+
+ #lalala probably broken on freebsd
+ ($<, $>) = ($>, $<);
+ $< = 0;
+
+}
+
+1;
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
new file mode 100644
index 0000000..efad2df
--- /dev/null
+++ b/FS/FS/Misc.pm
@@ -0,0 +1,102 @@
+package FS::Misc;
+
+use strict;
+use vars qw ( @ISA @EXPORT_OK );
+use Exporter;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( send_email );
+
+=head1 NAME
+
+FS::Misc - Miscellaneous subroutines
+
+=head1 SYNOPSIS
+
+ use FS::Misc qw(send_email);
+
+ send_email();
+
+=head1 DESCRIPTION
+
+Miscellaneous subroutines. This module contains miscellaneous subroutines
+called from multiple other modules. These are not OO or necessarily related,
+but are collected here to elimiate code duplication.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item send_email OPTION => VALUE ...
+
+Options:
+
+I<from> - (required)
+
+I<to> - (required) comma-separated scalar or arrayref of recipients
+
+I<subject> - (required)
+
+I<content-type> - (optional) MIME type
+
+I<body> - (required) arrayref of body text lines
+
+=cut
+
+use vars qw( $conf );
+use Date::Format;
+use Mail::Header;
+use Mail::Internet 1.44;
+use FS::UID;
+
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
+sub send_email {
+ my(%options) = @_;
+
+ $ENV{MAILADDRESS} = $options{'from'};
+ my $to = ref($options{to}) ? join(', ', @{ $options{to} } ) : $options{to};
+ my @header = (
+ 'From: '. $options{'from'},
+ 'To: '. $to,
+ 'Sender: '. $options{'from'},
+ 'Reply-To: '. $options{'from'},
+ 'Date: '. time2str("%a, %d %b %Y %X %z", time),
+ 'Subject: '. $options{'subject'},
+ );
+ push @header, 'Content-Type: '. $options{'content-type'}
+ if exists($options{'content-type'});
+ my $header = new Mail::Header ( \@header );
+
+ my $message = new Mail::Internet (
+ 'Header' => $header,
+ 'Body' => $options{'body'},
+ );
+
+ my $smtpmachine = $conf->config('smtpmachine');
+ $!=0;
+
+ my $rv = $message->smtpsend( 'Host' => $smtpmachine )
+ or $message->smtpsend( Host => $smtpmachine, Debug => 1 );
+
+ if ($rv) { #smtpsend returns a list of addresses, not true/false
+ return '';
+ } else {
+ return "can't send email to $to via server $smtpmachine with SMTP: $!";
+ }
+
+}
+
+=head1 BUGS
+
+This package exists.
+
+=head1 SEE ALSO
+
+L<FS::UID>, L<FS::CGI>, L<FS::Record>, the base documentation.
+
+=cut
+
+1;
diff --git a/FS/FS/Msgcat.pm b/FS/FS/Msgcat.pm
new file mode 100644
index 0000000..625743d
--- /dev/null
+++ b/FS/FS/Msgcat.pm
@@ -0,0 +1,98 @@
+package FS::Msgcat;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $conf $locale $debug );
+use Exporter;
+use FS::UID;
+#use FS::Record qw( qsearchs ); # wtf? won't import...
+use FS::Record;
+use FS::Conf;
+use FS::msgcat;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( gettext geterror );
+
+$FS::UID::callback{'Msgcat'} = sub {
+ $conf = new FS::Conf;
+ $locale = $conf->config('locale') || 'en_US';
+ $debug = $conf->exists('show-msgcat-codes')
+};
+
+=head1 NAME
+
+FS::Msgcat - Message catalog functions
+
+=head1 SYNOPSIS
+
+ use FS::Msgcat qw(gettext geterror);
+
+ #simple interface for retreiving messages...
+ $message = gettext('msgcode');
+ #or errors (includes the error code)
+ $message = geterror('msgcode');
+
+=head1 DESCRIPTION
+
+FS::Msgcat provides functions to use the message catalog. If you want to
+maintain the message catalog database, see L<FS::msgcat> instead.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item gettext MSGCODE
+
+Returns the full message for the supplied message code.
+
+=cut
+
+sub gettext {
+ $debug ? geterror(@_) : _gettext(@_);
+}
+
+sub _gettext {
+ my $msgcode = shift;
+ my $msgcat = FS::Record::qsearchs('msgcat', {
+ 'msgcode' => $msgcode,
+ 'locale' => $locale
+ } );
+ if ( $msgcat ) {
+ $msgcat->msg;
+ } else {
+ warn "WARNING: message for msgcode $msgcode in locale $locale not found";
+ $msgcode;
+ }
+
+}
+
+=item geterror MSGCODE
+
+Returns the full message for the supplied message code, including the message
+code.
+
+=cut
+
+sub geterror {
+ my $msgcode = shift;
+ my $msg = _gettext($msgcode);
+ if ( $msg eq $msgcode ) {
+ "Error code $msgcode (message for locale $locale not found)";
+ } else {
+ "$msg (error code $msgcode)";
+ }
+}
+
+=back
+
+=head1 BUGS
+
+i18n/l10n, eek
+
+=head1 SEE ALSO
+
+L<FS::msgcat>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
new file mode 100644
index 0000000..4e5e18a
--- /dev/null
+++ b/FS/FS/Record.pm
@@ -0,0 +1,1687 @@
+package FS::Record;
+
+use strict;
+use vars qw( $dbdef_file $dbdef $setup_hack $AUTOLOAD @ISA @EXPORT_OK $DEBUG
+ $me %dbdef_cache %virtual_fields_cache );
+use subs qw(reload_dbdef);
+use Exporter;
+use Carp qw(carp cluck croak confess);
+use File::CounterFile;
+use Locale::Country;
+use DBI qw(:sql_types);
+use DBIx::DBSchema 0.23;
+use FS::UID qw(dbh getotaker datasrc driver_name);
+use FS::SearchCache;
+use FS::Msgcat qw(gettext);
+
+use FS::part_virtual_field;
+
+use Tie::IxHash;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch);
+
+$DEBUG = 0;
+$me = '[FS::Record]';
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::Record'} = sub {
+ $File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc;
+ $dbdef_file = "/usr/local/etc/freeside/dbdef.". datasrc;
+ &reload_dbdef unless $setup_hack; #$setup_hack needed now?
+};
+
+=head1 NAME
+
+FS::Record - Database record objects
+
+=head1 SYNOPSIS
+
+ use FS::Record;
+ use FS::Record qw(dbh fields qsearch qsearchs dbdef);
+
+ $record = new FS::Record 'table', \%hash;
+ $record = new FS::Record 'table', { 'column' => 'value', ... };
+
+ $record = qsearchs FS::Record 'table', \%hash;
+ $record = qsearchs FS::Record 'table', { 'column' => 'value', ... };
+ @records = qsearch FS::Record 'table', \%hash;
+ @records = qsearch FS::Record 'table', { 'column' => 'value', ... };
+
+ $table = $record->table;
+ $dbdef_table = $record->dbdef_table;
+
+ $value = $record->get('column');
+ $value = $record->getfield('column');
+ $value = $record->column;
+
+ $record->set( 'column' => 'value' );
+ $record->setfield( 'column' => 'value' );
+ $record->column('value');
+
+ %hash = $record->hash;
+
+ $hashref = $record->hashref;
+
+ $error = $record->insert;
+
+ $error = $record->delete;
+
+ $error = $new_record->replace($old_record);
+
+ # external use deprecated - handled by the database (at least for Pg, mysql)
+ $value = $record->unique('column');
+
+ $error = $record->ut_float('column');
+ $error = $record->ut_number('column');
+ $error = $record->ut_numbern('column');
+ $error = $record->ut_money('column');
+ $error = $record->ut_text('column');
+ $error = $record->ut_textn('column');
+ $error = $record->ut_alpha('column');
+ $error = $record->ut_alphan('column');
+ $error = $record->ut_phonen('column');
+ $error = $record->ut_anything('column');
+ $error = $record->ut_name('column');
+
+ $dbdef = reload_dbdef;
+ $dbdef = reload_dbdef "/non/standard/filename";
+ $dbdef = dbdef;
+
+ $quoted_value = _quote($value,'table','field');
+
+ #deprecated
+ $fields = hfields('table');
+ if ( $fields->{Field} ) { # etc.
+
+ @fields = fields 'table'; #as a subroutine
+ @fields = $record->fields; #as a method call
+
+
+=head1 DESCRIPTION
+
+(Mostly) object-oriented interface to database records. Records are currently
+implemented on top of DBI. FS::Record is intended as a base class for
+table-specific classes to inherit from, i.e. FS::cust_main.
+
+=head1 CONSTRUCTORS
+
+=over 4
+
+=item new [ TABLE, ] HASHREF
+
+Creates a new record. It doesn't store it in the database, though. See
+L<"insert"> for that.
+
+Note that the object stores this hash reference, not a distinct copy of the
+hash it points to. You can ask the object for a copy with the I<hash>
+method.
+
+TABLE can only be omitted when a dervived class overrides the table method.
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+
+ unless ( defined ( $self->table ) ) {
+ $self->{'Table'} = shift;
+ carp "warning: FS::Record::new called with table name ". $self->{'Table'};
+ }
+
+ my $hashref = $self->{'Hash'} = shift;
+
+ foreach my $field ( grep !defined($hashref->{$_}), $self->fields ) {
+ $hashref->{$field}='';
+ }
+
+ $self->_cache($hashref, shift) if $self->can('_cache') && @_;
+
+ $self;
+}
+
+sub new_or_cached {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+
+ $self->{'Table'} = shift unless defined ( $self->table );
+
+ my $hashref = $self->{'Hash'} = shift;
+ my $cache = shift;
+ if ( defined( $cache->cache->{$hashref->{$cache->key}} ) ) {
+ my $obj = $cache->cache->{$hashref->{$cache->key}};
+ $obj->_cache($hashref, $cache) if $obj->can('_cache');
+ $obj;
+ } else {
+ $cache->cache->{$hashref->{$cache->key}} = $self->new($hashref, $cache);
+ }
+
+}
+
+sub create {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ if ( defined $self->table ) {
+ cluck "create constructor is deprecated, use new!";
+ $self->new(@_);
+ } else {
+ croak "FS::Record::create called (not from a subclass)!";
+ }
+}
+
+=item qsearch TABLE, HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ
+
+Searches the database for all records matching (at least) the key/value pairs
+in HASHREF. Returns all the records found as `FS::TABLE' objects if that
+module is loaded (i.e. via `use FS::cust_main;'), otherwise returns FS::Record
+objects.
+
+###oops, argh, FS::Record::new only lets us create database fields.
+#Normal behaviour if SELECT is not specified is `*', as in
+#C<SELECT * FROM table WHERE ...>. However, there is an experimental new
+#feature where you can specify SELECT - remember, the objects returned,
+#although blessed into the appropriate `FS::TABLE' package, will only have the
+#fields you specify. This might have unwanted results if you then go calling
+#regular FS::TABLE methods
+#on it.
+
+=cut
+
+sub qsearch {
+ my($stable, $record, $select, $extra_sql, $cache ) = @_;
+ #$stable =~ /^([\w\_]+)$/ or die "Illegal table: $table";
+ #for jsearch
+ $stable =~ /^([\w\s\(\)\.\,\=]+)$/ or die "Illegal table: $stable";
+ $stable = $1;
+ $select ||= '*';
+ my $dbh = dbh;
+
+ my $table = $cache ? $cache->table : $stable;
+ my $dbdef_table = $dbdef->table($table)
+ or die "No schema for table $table found - ".
+ "do you need to create it or run dbdef-create?";
+ my $pkey = $dbdef_table->primary_key;
+
+ my @real_fields = grep exists($record->{$_}), real_fields($table);
+ my @virtual_fields;
+ if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
+ @virtual_fields = grep exists($record->{$_}), "FS::$table"->virtual_fields;
+ } else {
+ cluck "warning: FS::$table not loaded; virtual fields not searchable";
+ @virtual_fields = ();
+ }
+
+ my $statement = "SELECT $select FROM $stable";
+ if ( @real_fields or @virtual_fields ) {
+ $statement .= ' WHERE '. join(' AND ',
+ ( map {
+
+ my $op = '=';
+ my $column = $_;
+ if ( ref($record->{$_}) ) {
+ $op = $record->{$_}{'op'} if $record->{$_}{'op'};
+ #$op = 'LIKE' if $op =~ /^ILIKE$/i && driver_name ne 'Pg';
+ if ( uc($op) eq 'ILIKE' ) {
+ $op = 'LIKE';
+ $record->{$_}{'value'} = lc($record->{$_}{'value'});
+ $column = "LOWER($_)";
+ }
+ $record->{$_} = $record->{$_}{'value'}
+ }
+
+ if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) {
+ if ( $op eq '=' ) {
+ if ( driver_name eq 'Pg' ) {
+ my $type = $dbdef->table($table)->column($column)->type;
+ if ( $type =~ /(int|serial)/i ) {
+ qq-( $column IS NULL )-;
+ } else {
+ qq-( $column IS NULL OR $column = '' )-;
+ }
+ } else {
+ qq-( $column IS NULL OR $column = "" )-;
+ }
+ } elsif ( $op eq '!=' ) {
+ if ( driver_name eq 'Pg' ) {
+ my $type = $dbdef->table($table)->column($column)->type;
+ if ( $type =~ /(int|serial)/i ) {
+ qq-( $column IS NOT NULL )-;
+ } else {
+ qq-( $column IS NOT NULL AND $column != '' )-;
+ }
+ } else {
+ qq-( $column IS NOT NULL AND $column != "" )-;
+ }
+ } else {
+ if ( driver_name eq 'Pg' ) {
+ qq-( $column $op '' )-;
+ } else {
+ qq-( $column $op "" )-;
+ }
+ }
+ } else {
+ "$column $op ?";
+ }
+ } @real_fields ),
+ ( map {
+ my $op = '=';
+ my $column = $_;
+ if ( ref($record->{$_}) ) {
+ $op = $record->{$_}{'op'} if $record->{$_}{'op'};
+ if ( uc($op) eq 'ILIKE' ) {
+ $op = 'LIKE';
+ $record->{$_}{'value'} = lc($record->{$_}{'value'});
+ $column = "LOWER($_)";
+ }
+ $record->{$_} = $record->{$_}{'value'};
+ }
+
+ # ... EXISTS ( SELECT name, value FROM part_virtual_field
+ # JOIN virtual_field
+ # ON part_virtual_field.vfieldpart = virtual_field.vfieldpart
+ # WHERE recnum = svc_acct.svcnum
+ # AND (name, value) = ('egad', 'brain') )
+
+ my $value = $record->{$_};
+
+ my $subq;
+
+ $subq = ($value ? 'EXISTS ' : 'NOT EXISTS ') .
+ "( SELECT part_virtual_field.name, virtual_field.value ".
+ "FROM part_virtual_field JOIN virtual_field ".
+ "ON part_virtual_field.vfieldpart = virtual_field.vfieldpart ".
+ "WHERE virtual_field.recnum = ${table}.${pkey} ".
+ "AND part_virtual_field.name = '${column}'".
+ ($value ?
+ " AND virtual_field.value ${op} '${value}'"
+ : "") . ")";
+ $subq;
+
+ } @virtual_fields ) );
+
+ }
+
+ $statement .= " $extra_sql" if defined($extra_sql);
+
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ my $sth = $dbh->prepare($statement)
+ or croak "$dbh->errstr doing $statement";
+
+ my $bind = 1;
+
+ foreach my $field (
+ grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
+ ) {
+ if ( $record->{$field} =~ /^\d+(\.\d+)?$/
+ && $dbdef->table($table)->column($field)->type =~ /(int|serial)/i
+ ) {
+ $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } );
+ } else {
+ $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_VARCHAR } );
+ }
+ }
+
+# $sth->execute( map $record->{$_},
+# grep defined( $record->{$_} ) && $record->{$_} ne '', @fields
+# ) or croak "Error executing \"$statement\": ". $sth->errstr;
+
+ $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
+
+ if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
+ @virtual_fields = "FS::$table"->virtual_fields;
+ } else {
+ cluck "warning: FS::$table not loaded; virtual fields not returned either";
+ @virtual_fields = ();
+ }
+
+ my %result;
+ tie %result, "Tie::IxHash";
+ my @stuff = @{ $sth->fetchall_arrayref( {} ) };
+ if($pkey) {
+ %result = map { $_->{$pkey}, $_ } @stuff;
+ } else {
+ @result{@stuff} = @stuff;
+ }
+
+ $sth->finish;
+
+ if ( keys(%result) and @virtual_fields ) {
+ $statement =
+ "SELECT virtual_field.recnum, part_virtual_field.name, ".
+ "virtual_field.value ".
+ "FROM part_virtual_field JOIN virtual_field USING (vfieldpart) ".
+ "WHERE part_virtual_field.dbtable = '$table' AND ".
+ "virtual_field.recnum IN (".
+ join(',', keys(%result)). ") AND part_virtual_field.name IN ('".
+ join(q!', '!, @virtual_fields) . "')";
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ $sth = $dbh->prepare($statement) or croak "$dbh->errstr doing $statement";
+ $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
+
+ foreach (@{ $sth->fetchall_arrayref({}) }) {
+ my $recnum = $_->{recnum};
+ my $name = $_->{name};
+ my $value = $_->{value};
+ if (exists($result{$recnum})) {
+ $result{$recnum}->{$name} = $value;
+ }
+ }
+ }
+
+ if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
+ if ( eval 'FS::'. $table. '->can(\'new\')' eq \&new ) {
+ #derivied class didn't override new method, so this optimization is safe
+ if ( $cache ) {
+ map {
+ new_or_cached( "FS::$table", { %{$_} }, $cache )
+ } values(%result);
+ } else {
+ map {
+ new( "FS::$table", { %{$_} } )
+ } values(%result);
+ }
+ } else {
+ warn "untested code (class FS::$table uses custom new method)";
+ map {
+ eval 'FS::'. $table. '->new( { %{$_} } )';
+ } values(%result);
+ }
+ } else {
+ cluck "warning: FS::$table not loaded; returning FS::Record objects";
+ map {
+ FS::Record->new( $table, { %{$_} } );
+ } values(%result);
+ }
+
+}
+
+=item jsearch TABLE, HASHREF, SELECT, EXTRA_SQL, PRIMARY_TABLE, PRIMARY_KEY
+
+Experimental JOINed search method. Using this method, you can execute a
+single SELECT spanning multiple tables, and cache the results for subsequent
+method calls. Interface will almost definately change in an incompatible
+fashion.
+
+Arguments:
+
+=cut
+
+sub jsearch {
+ my($table, $record, $select, $extra_sql, $ptable, $pkey ) = @_;
+ my $cache = FS::SearchCache->new( $ptable, $pkey );
+ my %saw;
+ ( $cache,
+ grep { !$saw{$_->getfield($pkey)}++ }
+ qsearch($table, $record, $select, $extra_sql, $cache )
+ );
+}
+
+=item qsearchs TABLE, HASHREF
+
+Same as qsearch, except that if more than one record matches, it B<carp>s but
+returns the first. If this happens, you either made a logic error in asking
+for a single item, or your data is corrupted.
+
+=cut
+
+sub qsearchs { # $result_record = &FS::Record:qsearchs('table',\%hash);
+ my $table = $_[0];
+ my(@result) = qsearch(@_);
+ carp "warning: Multiple records in scalar search ($table)"
+ if scalar(@result) > 1;
+ #should warn more vehemently if the search was on a primary key?
+ scalar(@result) ? ($result[0]) : ();
+}
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item table
+
+Returns the table name.
+
+=cut
+
+sub table {
+# cluck "warning: FS::Record::table deprecated; supply one in subclass!";
+ my $self = shift;
+ $self -> {'Table'};
+}
+
+=item dbdef_table
+
+Returns the DBIx::DBSchema::Table object for the table.
+
+=cut
+
+sub dbdef_table {
+ my($self)=@_;
+ my($table)=$self->table;
+ $dbdef->table($table);
+}
+
+=item get, getfield COLUMN
+
+Returns the value of the column/field/key COLUMN.
+
+=cut
+
+sub get {
+ my($self,$field) = @_;
+ # to avoid "Use of unitialized value" errors
+ if ( defined ( $self->{Hash}->{$field} ) ) {
+ $self->{Hash}->{$field};
+ } else {
+ '';
+ }
+}
+sub getfield {
+ my $self = shift;
+ $self->get(@_);
+}
+
+=item set, setfield COLUMN, VALUE
+
+Sets the value of the column/field/key COLUMN to VALUE. Returns VALUE.
+
+=cut
+
+sub set {
+ my($self,$field,$value) = @_;
+ $self->{'Hash'}->{$field} = $value;
+}
+sub setfield {
+ my $self = shift;
+ $self->set(@_);
+}
+
+=item AUTLOADED METHODS
+
+$record->column is a synonym for $record->get('column');
+
+$record->column('value') is a synonym for $record->set('column','value');
+
+=cut
+
+# readable/safe
+sub AUTOLOAD {
+ my($self,$value)=@_;
+ my($field)=$AUTOLOAD;
+ $field =~ s/.*://;
+ if ( defined($value) ) {
+ confess "errant AUTOLOAD $field for $self (arg $value)"
+ unless ref($self) && $self->can('setfield');
+ $self->setfield($field,$value);
+ } else {
+ confess "errant AUTOLOAD $field for $self (no args)"
+ unless ref($self) && $self->can('getfield');
+ $self->getfield($field);
+ }
+}
+
+# efficient
+#sub AUTOLOAD {
+# my $field = $AUTOLOAD;
+# $field =~ s/.*://;
+# if ( defined($_[1]) ) {
+# $_[0]->setfield($field, $_[1]);
+# } else {
+# $_[0]->getfield($field);
+# }
+#}
+
+=item hash
+
+Returns a list of the column/value pairs, usually for assigning to a new hash.
+
+To make a distinct duplicate of an FS::Record object, you can do:
+
+ $new = new FS::Record ( $old->table, { $old->hash } );
+
+=cut
+
+sub hash {
+ my($self) = @_;
+ confess $self. ' -> hash: Hash attribute is undefined'
+ unless defined($self->{'Hash'});
+ %{ $self->{'Hash'} };
+}
+
+=item hashref
+
+Returns a reference to the column/value hash.
+
+=cut
+
+sub hashref {
+ my($self) = @_;
+ $self->{'Hash'};
+}
+
+=item insert
+
+Inserts this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ my $error = $self->check;
+ return $error if $error;
+
+ #single-field unique keys are given a value if false
+ #(like MySQL's AUTO_INCREMENT or Pg SERIAL)
+ foreach ( $self->dbdef_table->unique->singles ) {
+ $self->unique($_) unless $self->getfield($_);
+ }
+
+ #and also the primary key, if the database isn't going to
+ my $primary_key = $self->dbdef_table->primary_key;
+ my $db_seq = 0;
+ if ( $primary_key ) {
+ my $col = $self->dbdef_table->column($primary_key);
+
+ $db_seq =
+ uc($col->type) eq 'SERIAL'
+ || ( driver_name eq 'Pg'
+ && defined($col->default)
+ && $col->default =~ /^nextval\(/i
+ )
+ || ( driver_name eq 'mysql'
+ && defined($col->local)
+ && $col->local =~ /AUTO_INCREMENT/i
+ );
+ $self->unique($primary_key) unless $self->getfield($primary_key) || $db_seq;
+ }
+
+ my $table = $self->table;
+ #false laziness w/delete
+ my @real_fields =
+ grep defined($self->getfield($_)) && $self->getfield($_) ne "",
+ real_fields($table)
+ ;
+ my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
+ #eslaf
+
+ my $statement = "INSERT INTO $table ( ".
+ join( ', ', @real_fields ).
+ ") VALUES (".
+ join( ', ', @values ).
+ ")"
+ ;
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ my $sth = dbh->prepare($statement) or return dbh->errstr;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ $sth->execute or return $sth->errstr;
+
+ my $insertid = '';
+ if ( $db_seq ) { # get inserted id from the database, if applicable
+ warn "[debug]$me retreiving sequence from database\n" if $DEBUG;
+ if ( driver_name eq 'Pg' ) {
+
+ my $oid = $sth->{'pg_oid_status'};
+ my $i_sql = "SELECT $primary_key FROM $table WHERE oid = ?";
+ my $i_sth = dbh->prepare($i_sql) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+ $i_sth->execute($oid) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return $i_sth->errstr;
+ };
+ $insertid = $i_sth->fetchrow_arrayref->[0];
+
+ } elsif ( driver_name eq 'mysql' ) {
+
+ $insertid = dbh->{'mysql_insertid'};
+ # work around mysql_insertid being null some of the time, ala RT :/
+ unless ( $insertid ) {
+ warn "WARNING: DBD::mysql didn't return mysql_insertid; ".
+ "using SELECT LAST_INSERT_ID();";
+ my $i_sql = "SELECT LAST_INSERT_ID()";
+ my $i_sth = dbh->prepare($i_sql) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+ $i_sth->execute or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return $i_sth->errstr;
+ };
+ $insertid = $i_sth->fetchrow_arrayref->[0];
+ }
+
+ } else {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return "don't know how to retreive inserted ids from ". driver_name.
+ ", try using counterfiles (maybe run dbdef-create?)";
+ }
+ $self->setfield($primary_key, $insertid);
+ }
+
+ my @virtual_fields =
+ grep defined($self->getfield($_)) && $self->getfield($_) ne "",
+ $self->virtual_fields;
+ if (@virtual_fields) {
+ my %v_values = map { $_, $self->getfield($_) } @virtual_fields;
+
+ my $vfieldpart = $self->vfieldpart_hashref;
+
+ my $v_statement = "INSERT INTO virtual_field(recnum, vfieldpart, value) ".
+ "VALUES (?, ?, ?)";
+
+ my $v_sth = dbh->prepare($v_statement) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+
+ foreach (keys(%v_values)) {
+ $v_sth->execute($self->getfield($primary_key),
+ $vfieldpart->{$_},
+ $v_values{$_})
+ or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return $v_sth->errstr;
+ };
+ }
+ }
+
+
+ my $h_sth;
+ if ( defined $dbdef->table('h_'. $table) ) {
+ my $h_statement = $self->_h_statement('insert');
+ warn "[debug]$me $h_statement\n" if $DEBUG > 2;
+ $h_sth = dbh->prepare($h_statement) or do {
+ dbh->rollback if $FS::UID::AutoCommit;
+ return dbh->errstr;
+ };
+ } else {
+ $h_sth = '';
+ }
+ $h_sth->execute or return $h_sth->errstr if $h_sth;
+
+ dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
+
+ '';
+}
+
+=item add
+
+Depriciated (use insert instead).
+
+=cut
+
+sub add {
+ cluck "warning: FS::Record::add deprecated!";
+ insert @_; #call method in this scope
+}
+
+=item delete
+
+Delete this record from the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ my $statement = "DELETE FROM ". $self->table. " WHERE ". join(' AND ',
+ map {
+ $self->getfield($_) eq ''
+ #? "( $_ IS NULL OR $_ = \"\" )"
+ ? ( driver_name eq 'Pg'
+ ? "$_ IS NULL"
+ : "( $_ IS NULL OR $_ = \"\" )"
+ )
+ : "$_ = ". _quote($self->getfield($_),$self->table,$_)
+ } ( $self->dbdef_table->primary_key )
+ ? ( $self->dbdef_table->primary_key)
+ : real_fields($self->table)
+ );
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ my $sth = dbh->prepare($statement) or return dbh->errstr;
+
+ my $h_sth;
+ if ( defined $dbdef->table('h_'. $self->table) ) {
+ my $h_statement = $self->_h_statement('delete');
+ warn "[debug]$me $h_statement\n" if $DEBUG > 2;
+ $h_sth = dbh->prepare($h_statement) or return dbh->errstr;
+ } else {
+ $h_sth = '';
+ }
+
+ my $primary_key = $self->dbdef_table->primary_key;
+ my $v_sth;
+ my @del_vfields;
+ my $vfp = $self->vfieldpart_hashref;
+ foreach($self->virtual_fields) {
+ next if $self->getfield($_) eq '';
+ unless(@del_vfields) {
+ my $st = "DELETE FROM virtual_field WHERE recnum = ? AND vfieldpart = ?";
+ $v_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @del_vfields, $_;
+ }
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $rc = $sth->execute or return $sth->errstr;
+ #not portable #return "Record not found, statement:\n$statement" if $rc eq "0E0";
+ $h_sth->execute or return $h_sth->errstr if $h_sth;
+ $v_sth->execute($self->getfield($primary_key), $vfp->{$_})
+ or return $v_sth->errstr
+ foreach (@del_vfields);
+
+ dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
+
+ #no need to needlessly destoy the data either (causes problems actually)
+ #undef $self; #no need to keep object!
+
+ '';
+}
+
+=item del
+
+Depriciated (use delete instead).
+
+=cut
+
+sub del {
+ cluck "warning: FS::Record::del deprecated!";
+ &delete(@_); #call method in this scope
+}
+
+=item replace OLD_RECORD
+
+Replace the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my $new = shift;
+
+ my $old;
+ if ( @_ ) {
+ $old = shift;
+ } else {
+ warn "[debug]$me replace called with no arguments; autoloading old record\n"
+ if $DEBUG;
+ my $primary_key = $new->dbdef_table->primary_key;
+ if ( $primary_key ) {
+ $old = qsearchs($new->table, { $primary_key => $new->$primary_key() } )
+ or croak "can't find ". $new->table. ".$primary_key ".
+ $new->$primary_key();
+ } else {
+ croak $new->table. " has no primary key; pass old record as argument";
+ }
+ }
+
+ warn "[debug]$me $new ->replace $old\n" if $DEBUG;
+
+ return "Records not in same table!" unless $new->table eq $old->table;
+
+ my $primary_key = $old->dbdef_table->primary_key;
+ return "Can't change $primary_key"
+ if $primary_key
+ && ( $old->getfield($primary_key) ne $new->getfield($primary_key) );
+
+ my $error = $new->check;
+ return $error if $error;
+
+ #my @diff = grep $new->getfield($_) ne $old->getfield($_), $old->fields;
+ my %diff = map { ($new->getfield($_) ne $old->getfield($_))
+ ? ($_, $new->getfield($_)) : () } $old->fields;
+
+ unless ( keys(%diff) ) {
+ carp "[warning]$me $new -> replace $old: records identical";
+ return '';
+ }
+
+ my $statement = "UPDATE ". $old->table. " SET ". join(', ',
+ map {
+ "$_ = ". _quote($new->getfield($_),$old->table,$_)
+ } real_fields($old->table)
+ ). ' WHERE '.
+ join(' AND ',
+ map {
+ $old->getfield($_) eq ''
+ #? "( $_ IS NULL OR $_ = \"\" )"
+ ? ( driver_name eq 'Pg'
+ ? "( $_ IS NULL OR $_ = '' )"
+ : "( $_ IS NULL OR $_ = \"\" )"
+ )
+ : "$_ = ". _quote($old->getfield($_),$old->table,$_)
+ } ( $primary_key ? ( $primary_key ) : real_fields($old->table) )
+ )
+ ;
+ warn "[debug]$me $statement\n" if $DEBUG > 1;
+ my $sth = dbh->prepare($statement) or return dbh->errstr;
+
+ my $h_old_sth;
+ if ( defined $dbdef->table('h_'. $old->table) ) {
+ my $h_old_statement = $old->_h_statement('replace_old');
+ warn "[debug]$me $h_old_statement\n" if $DEBUG > 2;
+ $h_old_sth = dbh->prepare($h_old_statement) or return dbh->errstr;
+ } else {
+ $h_old_sth = '';
+ }
+
+ my $h_new_sth;
+ if ( defined $dbdef->table('h_'. $new->table) ) {
+ my $h_new_statement = $new->_h_statement('replace_new');
+ warn "[debug]$me $h_new_statement\n" if $DEBUG > 2;
+ $h_new_sth = dbh->prepare($h_new_statement) or return dbh->errstr;
+ } else {
+ $h_new_sth = '';
+ }
+
+ # For virtual fields we have three cases with different SQL
+ # statements: add, replace, delete
+ my $v_add_sth;
+ my $v_rep_sth;
+ my $v_del_sth;
+ my (@add_vfields, @rep_vfields, @del_vfields);
+ my $vfp = $old->vfieldpart_hashref;
+ foreach(grep { exists($diff{$_}) } $new->virtual_fields) {
+ if($diff{$_} eq '') {
+ # Delete
+ unless(@del_vfields) {
+ my $st = "DELETE FROM virtual_field WHERE recnum = ? ".
+ "AND vfieldpart = ?";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_del_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @del_vfields, $_;
+ } elsif($old->getfield($_) eq '') {
+ # Add
+ unless(@add_vfields) {
+ my $st = "INSERT INTO virtual_field (value, recnum, vfieldpart) ".
+ "VALUES (?, ?, ?)";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_add_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @add_vfields, $_;
+ } else {
+ # Replace
+ unless(@rep_vfields) {
+ my $st = "UPDATE virtual_field SET value = ? ".
+ "WHERE recnum = ? AND vfieldpart = ?";
+ warn "[debug]$me $st\n" if $DEBUG > 2;
+ $v_rep_sth = dbh->prepare($st) or return dbh->errstr;
+ }
+ push @rep_vfields, $_;
+ }
+ }
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $rc = $sth->execute or return $sth->errstr;
+ #not portable #return "Record not found (or records identical)." if $rc eq "0E0";
+ $h_old_sth->execute or return $h_old_sth->errstr if $h_old_sth;
+ $h_new_sth->execute or return $h_new_sth->errstr if $h_new_sth;
+
+ $v_del_sth->execute($old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_del_sth->errstr
+ foreach(@del_vfields);
+
+ $v_add_sth->execute($new->getfield($_),
+ $old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_add_sth->errstr
+ foreach(@add_vfields);
+
+ $v_rep_sth->execute($new->getfield($_),
+ $old->getfield($primary_key),
+ $vfp->{$_})
+ or return $v_rep_sth->errstr
+ foreach(@rep_vfields);
+
+ dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit;
+
+ '';
+
+}
+
+=item rep
+
+Depriciated (use replace instead).
+
+=cut
+
+sub rep {
+ cluck "warning: FS::Record::rep deprecated!";
+ replace @_; #call method in this scope
+}
+
+=item check
+
+Checks virtual fields (using check_blocks). Subclasses should still provide
+a check method to validate real fields, foreign keys, etc., and call this
+method via $self->SUPER::check.
+
+(FIXME: Should this method try to make sure that it I<is> being called from
+a subclass's check method, to keep the current semantics as far as possible?)
+
+=cut
+
+sub check {
+ #confess "FS::Record::check not implemented; supply one in subclass!";
+ my $self = shift;
+
+ foreach my $field ($self->virtual_fields) {
+ for ($self->getfield($field)) {
+ # See notes on check_block in FS::part_virtual_field.
+ eval $self->pvf($field)->check_block;
+ if ( $@ ) {
+ #this is bad, probably want to follow the stack backtrace up and see
+ #wtf happened
+ my $err = "Fatal error checking $field for $self";
+ cluck "$err: $@";
+ return "$err (see log for backtrace): $@";
+
+ }
+ $self->setfield($field, $_);
+ }
+ }
+ '';
+}
+
+sub _h_statement {
+ my( $self, $action ) = @_;
+
+ my @fields =
+ grep defined($self->getfield($_)) && $self->getfield($_) ne "",
+ real_fields($self->table);
+ ;
+ my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields;
+
+ "INSERT INTO h_". $self->table. " ( ".
+ join(', ', qw(history_date history_user history_action), @fields ).
+ ") VALUES (".
+ join(', ', time, dbh->quote(getotaker()), dbh->quote($action), @values).
+ ")"
+ ;
+}
+
+=item unique COLUMN
+
+B<Warning>: External use is B<deprecated>.
+
+Replaces COLUMN in record with a unique number, using counters in the
+filesystem. Used by the B<insert> method on single-field unique columns
+(see L<DBIx::DBSchema::Table>) and also as a fallback for primary keys
+that aren't SERIAL (Pg) or AUTO_INCREMENT (mysql).
+
+Returns the new value.
+
+=cut
+
+sub unique {
+ my($self,$field) = @_;
+ my($table)=$self->table;
+
+ croak "Unique called on field $field, but it is ",
+ $self->getfield($field),
+ ", not null!"
+ if $self->getfield($field);
+
+ #warn "table $table is tainted" if is_tainted($table);
+ #warn "field $field is tainted" if is_tainted($field);
+
+ my($counter) = new File::CounterFile "$table.$field",0;
+# hack for web demo
+# getotaker() =~ /^([\w\-]{1,16})$/ or die "Illegal CGI REMOTE_USER!";
+# my($user)=$1;
+# my($counter) = new File::CounterFile "$user/$table.$field",0;
+# endhack
+
+ my $index = $counter->inc;
+ $index = $counter->inc while qsearchs($table, { $field=>$index } );
+
+ $index =~ /^(\d*)$/;
+ $index=$1;
+
+ $self->setfield($field,$index);
+
+}
+
+=item ut_float COLUMN
+
+Check/untaint floating point numeric data: 1.1, 1, 1.1e10, 1e10. May not be
+null. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_float {
+ my($self,$field)=@_ ;
+ ($self->getfield($field) =~ /^(\d+\.\d+)$/ ||
+ $self->getfield($field) =~ /^(\d+)$/ ||
+ $self->getfield($field) =~ /^(\d+\.\d+e\d+)$/ ||
+ $self->getfield($field) =~ /^(\d+e\d+)$/)
+ or return "Illegal or empty (float) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_snumber COLUMN
+
+Check/untaint signed numeric data (whole numbers). May not be null. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_snumber {
+ my($self, $field) = @_;
+ $self->getfield($field) =~ /^(-?)\s*(\d+)$/
+ or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
+ $self->setfield($field, "$1$2");
+ '';
+}
+
+=item ut_number COLUMN
+
+Check/untaint simple numeric data (whole numbers). May not be null. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_number {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^(\d+)$/
+ or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_numbern COLUMN
+
+Check/untaint simple numeric data (whole numbers). May be null. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_numbern {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^(\d*)$/
+ or return "Illegal (numeric) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_money COLUMN
+
+Check/untaint monetary numbers. May be negative. Set to 0 if null. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_money {
+ my($self,$field)=@_;
+ $self->setfield($field, 0) if $self->getfield($field) eq '';
+ $self->getfield($field) =~ /^(\-)? ?(\d*)(\.\d{2})?$/
+ or return "Illegal (money) $field: ". $self->getfield($field);
+ #$self->setfield($field, "$1$2$3" || 0);
+ $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
+ '';
+}
+
+=item ut_text COLUMN
+
+Check/untaint text. Alphanumerics, spaces, and the following punctuation
+symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / =
+May not be null. If there is an error, returns the error, otherwise returns
+false.
+
+=cut
+
+sub ut_text {
+ my($self,$field)=@_;
+ #warn "msgcat ". \&msgcat. "\n";
+ #warn "notexist ". \&notexist. "\n";
+ #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
+ $self->getfield($field) =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]+)$/
+ or return gettext('illegal_or_empty_text'). " $field: ".
+ $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_textn COLUMN
+
+Check/untaint text. Alphanumerics, spaces, and the following punctuation
+symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? /
+May be null. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_textn {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or return gettext('illegal_text'). " $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_alpha COLUMN
+
+Check/untaint alphanumeric strings (no spaces). May not be null. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alpha {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^(\w+)$/
+ or return "Illegal or empty (alphanumeric) $field: ".
+ $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_alpha COLUMN
+
+Check/untaint alphanumeric strings (no spaces). May be null. If there is an
+error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alphan {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^(\w*)$/
+ or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_phonen COLUMN [ COUNTRY ]
+
+Check/untaint phone numbers. May be null. If there is an error, returns
+the error, otherwise returns false.
+
+Takes an optional two-letter ISO country code; without it or with unsupported
+countries, ut_phonen simply calls ut_alphan.
+
+=cut
+
+sub ut_phonen {
+ my( $self, $field, $country ) = @_;
+ return $self->ut_alphan($field) unless defined $country;
+ my $phonen = $self->getfield($field);
+ if ( $phonen eq '' ) {
+ $self->setfield($field,'');
+ } elsif ( $country eq 'US' || $country eq 'CA' ) {
+ $phonen =~ s/\D//g;
+ $phonen =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
+ or return gettext('illegal_phone'). " $field: ". $self->getfield($field);
+ $phonen = "$1-$2-$3";
+ $phonen .= " x$4" if $4;
+ $self->setfield($field,$phonen);
+ } else {
+ warn "warning: don't know how to check phone numbers for country $country";
+ return $self->ut_textn($field);
+ }
+ '';
+}
+
+=item ut_ip COLUMN
+
+Check/untaint ip addresses. IPv4 only for now.
+
+=cut
+
+sub ut_ip {
+ my( $self, $field ) = @_;
+ $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
+ or return "Illegal (IP address) $field: ". $self->getfield($field);
+ for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
+ $self->setfield($field, "$1.$2.$3.$4");
+ '';
+}
+
+=item ut_ipn COLUMN
+
+Check/untaint ip addresses. IPv4 only for now. May be null.
+
+=cut
+
+sub ut_ipn {
+ my( $self, $field ) = @_;
+ if ( $self->getfield($field) =~ /^()$/ ) {
+ $self->setfield($field,'');
+ '';
+ } else {
+ $self->ut_ip($field);
+ }
+}
+
+=item ut_domain COLUMN
+
+Check/untaint host and domain names.
+
+=cut
+
+sub ut_domain {
+ my( $self, $field ) = @_;
+ #$self->getfield($field) =~/^(\w+\.)*\w+$/
+ $self->getfield($field) =~/^(([\w\-]+\.)*\w+)$/
+ or return "Illegal (domain) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_name COLUMN
+
+Check/untaint proper names; allows alphanumerics, spaces and the following
+punctuation: , . - '
+
+May not be null.
+
+=cut
+
+sub ut_name {
+ my( $self, $field ) = @_;
+ $self->getfield($field) =~ /^([\w \,\.\-\']+)$/
+ or return gettext('illegal_name'). " $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_zip COLUMN
+
+Check/untaint zip codes.
+
+=cut
+
+sub ut_zip {
+ my( $self, $field, $country ) = @_;
+ if ( $country eq 'US' ) {
+ $self->getfield($field) =~ /\s*(\d{5}(\-\d{4})?)\s*$/
+ or return gettext('illegal_zip'). " $field for country $country: ".
+ $self->getfield($field);
+ $self->setfield($field,$1);
+ } else {
+ if ( $self->getfield($field) =~ /^\s*$/ ) {
+ $self->setfield($field,'');
+ } else {
+ $self->getfield($field) =~ /^\s*(\w[\w\-\s]{2,8}\w)\s*$/
+ or return gettext('illegal_zip'). " $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ }
+ }
+ '';
+}
+
+=item ut_country COLUMN
+
+Check/untaint country codes. Country names are changed to codes, if possible -
+see L<Locale::Country>.
+
+=cut
+
+sub ut_country {
+ my( $self, $field ) = @_;
+ unless ( $self->getfield($field) =~ /^(\w\w)$/ ) {
+ if ( $self->getfield($field) =~ /^([\w \,\.\(\)\']+)$/
+ && country2code($1) ) {
+ $self->setfield($field,uc(country2code($1)));
+ }
+ }
+ $self->getfield($field) =~ /^(\w\w)$/
+ or return "Illegal (country) $field: ". $self->getfield($field);
+ $self->setfield($field,uc($1));
+ '';
+}
+
+=item ut_anything COLUMN
+
+Untaints arbitrary data. Be careful.
+
+=cut
+
+sub ut_anything {
+ my( $self, $field ) = @_;
+ $self->getfield($field) =~ /^(.*)$/s
+ or return "Illegal $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+=item ut_enum COLUMN CHOICES_ARRAYREF
+
+Check/untaint a column, supplying all possible choices, like the "enum" type.
+
+=cut
+
+sub ut_enum {
+ my( $self, $field, $choices ) = @_;
+ foreach my $choice ( @$choices ) {
+ if ( $self->getfield($field) eq $choice ) {
+ $self->setfield($choice);
+ return '';
+ }
+ }
+ return "Illegal (enum) field $field: ". $self->getfield($field);
+}
+
+=item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
+
+Check/untaint a foreign column key. Call a regular ut_ method (like ut_number)
+on the column first.
+
+=cut
+
+sub ut_foreign_key {
+ my( $self, $field, $table, $foreign ) = @_;
+ qsearchs($table, { $foreign => $self->getfield($field) })
+ or return "Can't find $field ". $self->getfield($field).
+ " in $table.$foreign";
+ '';
+}
+
+=item ut_foreign_keyn COLUMN FOREIGN_TABLE FOREIGN_COLUMN
+
+Like ut_foreign_key, except the null value is also allowed.
+
+=cut
+
+sub ut_foreign_keyn {
+ my( $self, $field, $table, $foreign ) = @_;
+ $self->getfield($field)
+ ? $self->ut_foreign_key($field, $table, $foreign)
+ : '';
+}
+
+
+=item virtual_fields [ TABLE ]
+
+Returns a list of virtual fields defined for the table. This should not
+be exported, and should only be called as an instance or class method.
+
+=cut
+
+sub virtual_fields {
+ my $self = shift;
+ my $table;
+ $table = $self->table or confess "virtual_fields called on non-table";
+
+ confess "Unknown table $table" unless $dbdef->table($table);
+
+ return () unless $self->dbdef->table('part_virtual_field');
+
+ unless ( $virtual_fields_cache{$table} ) {
+ my $query = 'SELECT name from part_virtual_field ' .
+ "WHERE dbtable = '$table'";
+ my $dbh = dbh;
+ my $result = $dbh->selectcol_arrayref($query);
+ confess $dbh->errstr if $dbh->err;
+ $virtual_fields_cache{$table} = $result;
+ }
+
+ @{$virtual_fields_cache{$table}};
+
+}
+
+
+=item fields [ TABLE ]
+
+This is a wrapper for real_fields and virtual_fields. Code that called
+fields before should probably continue to call fields.
+
+=cut
+
+sub fields {
+ my $something = shift;
+ my $table;
+ if($something->isa('FS::Record')) {
+ $table = $something->table;
+ } else {
+ $table = $something;
+ $something = "FS::$table";
+ }
+ return (real_fields($table), $something->virtual_fields());
+}
+
+=back
+
+=item pvf FIELD_NAME
+
+Returns the FS::part_virtual_field object corresponding to a field in the
+record (specified by FIELD_NAME).
+
+=cut
+
+sub pvf {
+ my ($self, $name) = (shift, shift);
+
+ if(grep /^$name$/, $self->virtual_fields) {
+ return qsearchs('part_virtual_field', { dbtable => $self->table,
+ name => $name } );
+ }
+ ''
+}
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item real_fields [ TABLE ]
+
+Returns a list of the real columns in the specified table. Called only by
+fields() and other subroutines elsewhere in FS::Record.
+
+=cut
+
+sub real_fields {
+ my $table = shift;
+
+ my($table_obj) = $dbdef->table($table);
+ confess "Unknown table $table" unless $table_obj;
+ $table_obj->columns;
+}
+
+=item reload_dbdef([FILENAME])
+
+Load a database definition (see L<DBIx::DBSchema>), optionally from a
+non-default filename. This command is executed at startup unless
+I<$FS::Record::setup_hack> is true. Returns a DBIx::DBSchema object.
+
+=cut
+
+sub reload_dbdef {
+ my $file = shift || $dbdef_file;
+
+ unless ( exists $dbdef_cache{$file} ) {
+ warn "[debug]$me loading dbdef for $file\n" if $DEBUG;
+ $dbdef_cache{$file} = DBIx::DBSchema->load( $file )
+ or die "can't load database schema from $file";
+ } else {
+ warn "[debug]$me re-using cached dbdef for $file\n" if $DEBUG;
+ }
+ $dbdef = $dbdef_cache{$file};
+}
+
+=item dbdef
+
+Returns the current database definition. See L<DBIx::DBSchema>.
+
+=cut
+
+sub dbdef { $dbdef; }
+
+=item _quote VALUE, TABLE, COLUMN
+
+This is an internal function used to construct SQL statements. It returns
+VALUE DBI-quoted (see L<DBI/"quote">) unless VALUE is a number and the column
+type (see L<DBIx::DBSchema::Column>) does not end in `char' or `binary'.
+
+=cut
+
+sub _quote {
+ my($value, $table, $column) = @_;
+ my $column_obj = $dbdef->table($table)->column($column);
+ my $column_type = $column_obj->type;
+
+ if ( $value eq '' && $column_type =~ /^int/ ) {
+ if ( $column_obj->null ) {
+ 'NULL';
+ } else {
+ cluck "WARNING: Attempting to set non-null integer $table.$column null; ".
+ "using 0 instead";
+ 0;
+ }
+ } elsif ( $value =~ /^\d+(\.\d+)?$/ &&
+ ! $column_type =~ /(char|binary|text)$/i ) {
+ $value;
+ } else {
+ dbh->quote($value);
+ }
+}
+
+=item vfieldpart_hashref TABLE
+
+Returns a hashref of virtual field names and vfieldparts applicable to the given
+TABLE.
+
+=cut
+
+sub vfieldpart_hashref {
+ my $self = shift;
+ my $table = $self->table;
+
+ return {} unless $self->dbdef->table('part_virtual_field');
+
+ my $dbh = dbh;
+ my $statement = "SELECT vfieldpart, name FROM part_virtual_field WHERE ".
+ "dbtable = '$table'";
+ my $sth = $dbh->prepare($statement);
+ $sth->execute or croak "Execution of '$statement' failed: ".$dbh->errstr;
+ return { map { $_->{name}, $_->{vfieldpart} }
+ @{$sth->fetchall_arrayref({})} };
+
+}
+
+
+=item hfields TABLE
+
+This is deprecated. Don't use it.
+
+It returns a hash-type list with the fields of this record's table set true.
+
+=cut
+
+sub hfields {
+ carp "warning: hfields is deprecated";
+ my($table)=@_;
+ my(%hash);
+ foreach (fields($table)) {
+ $hash{$_}=1;
+ }
+ \%hash;
+}
+
+sub _dump {
+ my($self)=@_;
+ join("\n", map {
+ "$_: ". $self->getfield($_). "|"
+ } (fields($self->table)) );
+}
+
+sub DESTROY { return; }
+
+#sub DESTROY {
+# my $self = shift;
+# #use Carp qw(cluck);
+# #cluck "DESTROYING $self";
+# warn "DESTROYING $self";
+#}
+
+#sub is_tainted {
+# return ! eval { join('',@_), kill 0; 1; };
+# }
+
+=back
+
+=head1 BUGS
+
+This module should probably be renamed, since much of the functionality is
+of general use. It is not completely unlike Adapter::DBI (see below).
+
+Exported qsearch and qsearchs should be deprecated in favor of method calls
+(against an FS::Record object like the old search and searchs that qsearch
+and qsearchs were on top of.)
+
+The whole fields / hfields mess should be removed.
+
+The various WHERE clauses should be subroutined.
+
+table string should be deprecated in favor of DBIx::DBSchema::Table.
+
+No doubt we could benefit from a Tied hash. Documenting how exists / defined
+true maps to the database (and WHERE clauses) would also help.
+
+The ut_ methods should ask the dbdef for a default length.
+
+ut_sqltype (like ut_varchar) should all be defined
+
+A fallback check method should be provided which uses the dbdef.
+
+The ut_money method assumes money has two decimal digits.
+
+The Pg money kludge in the new method only strips `$'.
+
+The ut_phonen method only checks US-style phone numbers.
+
+The _quote function should probably use ut_float instead of a regex.
+
+All the subroutines probably should be methods, here or elsewhere.
+
+Probably should borrow/use some dbdef methods where appropriate (like sub
+fields)
+
+As of 1.14, DBI fetchall_hashref( {} ) doesn't set fetchrow_hashref NAME_lc,
+or allow it to be set. Working around it is ugly any way around - DBI should
+be fixed. (only affects RDBMS which return uppercase column names)
+
+ut_zip should take an optional country like ut_phone.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<FS::UID>, L<DBI>
+
+Adapter::DBI from Ch. 11 of Advanced Perl Programming by Sriram Srinivasan.
+
+=cut
+
+1;
+
diff --git a/FS/FS/Report.pm b/FS/FS/Report.pm
new file mode 100644
index 0000000..181fea2
--- /dev/null
+++ b/FS/FS/Report.pm
@@ -0,0 +1,46 @@
+package FS::Report;
+
+use strict;
+
+=head1 NAME
+
+FS::Report - Report data objects
+
+=head1 SYNOPSIS
+
+ #see the more speicific report objects, currently only FS::Report::Table
+
+=head1 DESCRIPTION
+
+See the more specific report objects, currently only FS::Report::Table
+
+=head1 METHODS
+
+=over 4
+
+=item new [ OPTION => VALUE ... ]
+
+Constructor. Takes a list of options and their values.
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = @_ ? ( ref($_[0]) ? shift : { @_ } ) : {};
+ bless( $self, $class );
+}
+
+=back
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+L<FS::Report::Table>, reports in the web interface.
+
+=cut
+
+1;
diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm
new file mode 100644
index 0000000..9f636fa
--- /dev/null
+++ b/FS/FS/Report/Table.pm
@@ -0,0 +1,27 @@
+package FS::Report::Table;
+
+use strict;
+use vars qw( @ISA );
+use FS::Report;
+
+@ISA = qw( FS::Report );
+
+=head1 NAME
+
+FS::Report::Table - Tables of report data
+
+=head1 SYNOPSIS
+
+See the more specific report objects, currently only FS::Report::Table::Monthly
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+L<FS::Report::Table::Monthly>, reports in the web interface.
+
+=cut
+
+1;
diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm
new file mode 100644
index 0000000..d3ff5d1
--- /dev/null
+++ b/FS/FS/Report/Table/Monthly.pm
@@ -0,0 +1,172 @@
+package FS::Report::Table::Monthly;
+
+use strict;
+use vars qw( @ISA $expenses_kludge );
+use Time::Local;
+use FS::UID qw( dbh );
+use FS::Report::Table;
+
+@ISA = qw( FS::Report::Table );
+
+$expenses_kludge = 0;
+
+=head1 NAME
+
+FS::Report::Table::Monthly - Tables of report data, indexed monthly
+
+=head1 SYNOPSIS
+
+ use FS::Report::Table::Monthly;
+
+ my $report = new FS::Report::Table::Monthly (
+ 'items' => [ 'invoiced', 'netsales', 'credits', 'receipts', ],
+ 'start_month' => 4,
+ 'start_year' => 2000,
+ 'end_month' => 4,
+ 'end_year' => 2020,
+ );
+
+ my $data = $report->data;
+
+=head1 METHODS
+
+=over 4
+
+=item data
+
+Returns a hashref of data (!! describe)
+
+=cut
+
+sub data {
+ my $self = shift;
+
+ my $smonth = $self->{'start_month'};
+ my $syear = $self->{'start_year'};
+ my $emonth = $self->{'end_month'};
+ my $eyear = $self->{'end_year'};
+
+ my %data;
+
+ while ( $syear < $eyear || ( $syear == $eyear && $smonth < $emonth+1 ) ) {
+
+ push @{$data{label}}, "$smonth/$syear";
+
+ my $speriod = timelocal(0,0,0,1,$smonth-1,$syear);
+ push @{$data{speriod}}, $speriod;
+ if ( ++$smonth == 13 ) { $syear++; $smonth=1; }
+ my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear);
+ push @{$data{eperiod}}, $eperiod;
+
+ foreach my $item ( @{$self->{'items'}} ) {
+ push @{$data{$item}}, $self->$item($speriod, $eperiod);
+ }
+
+ }
+
+ \%data;
+
+}
+
+sub invoiced { #invoiced
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+ $self->scalar_sql("
+ SELECT SUM(charged) FROM cust_bill
+ WHERE ". $self->in_time_period($speriod, $eperiod)
+ );
+}
+
+sub netsales { #net sales
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+
+ my $credited = $self->scalar_sql("
+ SELECT SUM(cust_credit_bill.amount)
+ FROM cust_credit_bill, cust_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum
+ AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill')
+ );
+
+ #horrible local kludge
+ my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
+ SELECT SUM(cust_bill_pkg.setup)
+ FROM cust_bill_pkg, cust_bill, cust_pkg, part_pkg
+ WHERE cust_bill.invnum = cust_bill_pkg.invnum
+ AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill'). "
+ AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum
+ AND cust_pkg.pkgpart = part_pkg.pkgpart
+ AND LOWER(part_pkg.pkg) LIKE 'expense _%'
+ ");
+
+ $self->invoiced($speriod,$eperiod) - $credited - $expenses;
+}
+
+#deferred revenue
+
+sub receipts { #cashflow
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+
+ my $refunded = $self->scalar_sql("
+ SELECT SUM(refund) FROM cust_refund
+ WHERE ". $self->in_time_period($speriod, $eperiod)
+ );
+
+ #horrible local kludge that doesn't even really work right
+ my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
+ SELECT SUM(cust_bill_pay.amount)
+ FROM cust_bill_pay, cust_bill
+ WHERE cust_bill_pay.invnum = cust_bill.invnum
+ AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill_pay'). "
+ AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg
+ WHERE cust_bill.invnum = cust_bill_pkg.invnum
+ AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum
+ AND cust_pkg.pkgpart = part_pkg.pkgpart
+ AND LOWER(part_pkg.pkg) LIKE 'expense _%'
+ )
+ ");
+ # my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'";
+
+ $self->payments($speriod, $eperiod) - $refunded - $expenses;
+}
+
+sub payments {
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+ $self->scalar_sql("
+ SELECT SUM(paid) FROM cust_pay
+ WHERE ". $self->in_time_period($speriod, $eperiod)
+ );
+}
+
+sub credits {
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+ $self->scalar_sql("
+ SELECT SUM(amount) FROM cust_credit
+ WHERE ". $self->in_time_period($speriod, $eperiod)
+ );
+}
+
+sub in_time_period {
+ my( $self, $speriod, $eperiod ) = ( shift, shift, shift );
+ my $table = @_ ? shift().'.' : '';
+ "${table}_date >= $speriod AND ${table}_date < $eperiod";
+}
+
+sub scalar_sql {
+ my( $self, $sql ) = ( shift, shift );
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ $sth->fetchrow_arrayref->[0] || 0;
+}
+
+=back
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/FS/SearchCache.pm b/FS/FS/SearchCache.pm
new file mode 100644
index 0000000..4218acf
--- /dev/null
+++ b/FS/FS/SearchCache.pm
@@ -0,0 +1,96 @@
+package FS::SearchCache;
+
+use strict;
+use vars qw($DEBUG);
+#use Carp qw(carp cluck croak confess);
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::SearchCache - cache
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my( $table, $key ) = @_;
+ warn "table $table\n" if $DEBUG > 1;
+ warn "key $key\n" if $DEBUG > 1;
+ my $self = { 'table' => $table,
+ 'key' => $key,
+ 'cache' => {},
+ 'subcache' => {},
+ };
+ bless ($self, $class);
+
+ $self;
+}
+
+=item table
+
+=cut
+
+sub table { my $self = shift; $self->{table}; }
+
+=item key
+
+=cut
+
+sub key { my $self = shift; $self->{key}; }
+
+=item cache
+
+=cut
+
+sub cache { my $self = shift; $self->{cache}; }
+
+=item subcache
+
+=cut
+
+sub subcache {
+ my $self = shift;
+ my $col = shift;
+ my $table = shift;
+ my $keyval = shift;
+ if ( exists $self->{subcache}->{$col}->{$keyval} ) {
+ warn "returning existing subcache for $keyval ($col)".
+ "$self->{subcache}->{$col}->{$keyval}\n" if $DEBUG;
+ return $self->{subcache}->{$col}->{$keyval};
+ } else {
+ #my $tablekey = @_ ? shift : $col;
+ my $tablekey = $col;
+ my $subcache = ref($self)->new( $table, $tablekey );
+ $self->{subcache}->{$col}->{$keyval} = $subcache;
+ warn "creating new subcache $table $tablekey: $subcache\n" if $DEBUG;
+ $subcache;
+ }
+}
+
+=back
+
+=head1 BUGS
+
+Dismal documentation.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/UI/Base.pm b/FS/FS/UI/Base.pm
new file mode 100644
index 0000000..bbeb9e1
--- /dev/null
+++ b/FS/FS/UI/Base.pm
@@ -0,0 +1,194 @@
+package FS::UI::Base;
+
+use strict;
+use vars qw ( @ISA );
+use FS::Record qw( fields qsearch );
+
+@ISA = ( $FS::UI::Base::_lock );
+
+=head1 NAME
+
+FS::UI::Base - Base class for all user-interface objects
+
+=head1 SYNOPSIS
+
+ use FS::UI::SomeInterface;
+ use FS::UI::some_table;
+
+ $interface = new FS::UI::some_table;
+
+ $error = $interface->browse;
+ $error = $interface->search;
+ $error = $interface->view;
+ $error = $interface->edit;
+ $error = $interface->process;
+
+=head1 DESCRIPTION
+
+An FS::UI::Base object represents a user interface object. FS::UI::Base
+is intended as a base class for table-specfic classes to inherit from, i.e.
+FS::UI::cust_main. The simplest case, which will provide a default UI for your
+new table, is as follows:
+
+ package FS::UI::table_name;
+ use vars qw ( @ISA );
+ use FS::UI::Base;
+ @ISA = qw( FS::UI::Base );
+ sub db_table { 'table_name'; }
+
+Currently available interfaces are:
+ FS::UI::Gtk, an X-Windows UI implemented using the Gtk+ toolkit
+ FS::UI::CGI, a web interface implemented using CGI.pm, etc.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+=item browse
+
+=cut
+
+sub browse {
+ my $self = shift;
+
+ my @fields = $self->list_fields;
+
+ #begin browse-specific stuff
+
+ $self->title( "Browse ". $self->db_names ) unless $self->title;
+ my @records = qsearch ( $self->db_table, {} );
+
+ #end browse-specific stuff
+
+ $self->addwidget ( new FS::UI::_Text ( $self->db_description ) );
+
+ my @header = $self->list_header;
+ my @headerspan = $self->list_headerspan;
+ my %callback = $self->db_callback;
+
+ my $columns;
+
+ my $table = new FS::UI::_Tableborder (
+ 'rows' => 1 + scalar(@records),
+ 'columns' => $columns || scalar(@fields),
+ );
+
+ my $c = 0;
+ foreach my $header ( @header ) {
+ my $headerspan = shift(@headerspan) || 1;
+ $table->attach(
+ 0, $c, new FS::UI::_Text ( $header ), 1, $headerspan
+ );
+ $c += $headerspan;
+ }
+
+ my $r = 1;
+
+ foreach my $record ( @records ) {
+ $c = 0;
+ foreach my $field ( @fields ) {
+ my $value = $record->getfield($field);
+ my $widget;
+ if ( $callback{$field} ) {
+ $widget = &{ $callback{$field} }( $value, $record );
+ } else {
+ $widget = new FS::UI::_Text ( $value );
+ }
+ $table->attach( $r, $c++, $widget, 1, 1 );
+ }
+ $r++;
+ }
+
+ $self->addwidget( $table );
+
+ $self->activate;
+
+}
+
+=item title
+
+=cut
+
+sub title {
+ my $self = shift;
+ my $value = shift;
+ if ( defined($value) ) {
+ $self->{'title'} = $value;
+ } else {
+ $self->{'title'};
+ }
+}
+
+=item addwidget
+
+=cut
+
+sub addwidget {
+ my $self = shift;
+ my $widget = shift;
+ push @{ $self->{'Widgets'} }, $widget;
+}
+
+#fallback methods
+
+sub db_description {}
+
+sub db_name {}
+
+sub db_names {
+ my $self = shift;
+ $self->db_name. 's';
+}
+
+sub list_fields {
+ my $self = shift;
+ fields( $self->db_table );
+}
+
+sub list_header {
+ my $self = shift;
+ $self->list_fields
+}
+
+sub list_headerspan {
+ my $self = shift;
+ map 1, $self->list_header;
+}
+
+sub db_callback {}
+
+=back
+
+=head1 VERSION
+
+$Id: Base.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
+
+=head1 BUGS
+
+This documentation is incomplete.
+
+There should be some sort of per-(freeside)-user preferences and the ability
+for specific FS::UI:: modules to put their own values there as well.
+
+=head1 SEE ALSO
+
+L<FS::UI::Gtk>, L<FS::UI::CGI>
+
+=head1 HISTORY
+
+$Log: Base.pm,v $
+Revision 1.1 1999-08-04 09:03:53 ivan
+initial checkin of module files for proper perl installation
+
+Revision 1.1 1999/01/20 09:30:36 ivan
+skeletal cross-UI UI code.
+
+
+=cut
+
+1;
+
diff --git a/FS/FS/UI/CGI.pm b/FS/FS/UI/CGI.pm
new file mode 100644
index 0000000..ae87d13
--- /dev/null
+++ b/FS/FS/UI/CGI.pm
@@ -0,0 +1,239 @@
+package FS::UI::CGI;
+
+use strict;
+use CGI;
+#use CGI::Switch; #when FS::UID user and preference callback stuff is fixed
+use CGI::Carp qw(fatalsToBrowser);
+use HTML::Table;
+use FS::UID qw(adminsuidsetup);
+#use FS::Record qw( qsearch fields );
+
+die "Can't initialize CGI interface; $FS::UI::Base::_lock used"
+ if $FS::UI::Base::_lock;
+$FS::UI::Base::_lock = "FS::UI::CGI";
+
+=head1 NAME
+
+FS::UI::CGI - Base class for CGI user-interface objects
+
+=head1 SYNOPSIS
+
+ use FS::UI::CGI;
+ use FS::UI::some_table;
+
+ $interface = new FS::UI::some_table;
+
+ $error = $interface->browse;
+ $error = $interface->search;
+ $error = $interface->view;
+ $error = $interface->edit;
+ $error = $interface->process;
+
+=head1 DESCRIPTION
+
+An FS::UI::CGI object represents a CGI interface object.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+
+ $self->{'_cgi'} = new CGI;
+ $self->{'_user'} = $self->{'_cgi'}->remote_user;
+ $self->{'_dbh'} = FS::UID::adminsuidsetup $self->{'_user'};
+
+ bless ( $self, $class);
+}
+
+sub activate {
+ my $self = shift;
+ print $self->_header,
+ join ( "<BR>", map $_->sprint, @{ $self->{'Widgets'} } ),
+ $self->_footer,
+ ;
+}
+
+=item _header
+
+=cut
+
+sub _header {
+ my $self = shift;
+ my $cgi = $self->{'_cgi'};
+
+ $cgi->header( '-expires' => 'now' ), '<HTML>',
+ '<HEAD><TITLE>', $self->title, '</TITLE></HEAD>',
+ '<BODY BGCOLOR="#ffffff">',
+ '<FONT COLOR="#ff0000" SIZE=7>', $self->title, '</FONT><BR><BR>',
+ ;
+}
+
+=item _footer
+
+=cut
+
+sub _footer {
+ "</BODY></HTML>";
+}
+
+=item interface
+
+Returns the string `CGI'. Useful for the author of a table-specific UI class
+to conditionally specify certain behaviour.
+
+=cut
+
+sub interface { 'CGI'; }
+
+=back
+
+=cut
+
+package FS::UI::_Widget;
+
+use vars qw( $AUTOLOAD );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+ bless ( $self, $class );
+}
+
+sub AUTOLOAD {
+ my $self = shift;
+ my $value = shift;
+ my($field)=$AUTOLOAD;
+ $field =~ s/.*://;
+ if ( defined($value) ) {
+ $self->{$field} = $value;
+ } else {
+ $self->{$field};
+ }
+}
+
+package FS::UI::_Text;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Widget);
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ $self->{'_text'} = shift;
+ bless ( $self, $class );
+}
+
+sub sprint {
+ my $self = shift;
+ $self->{'_text'};
+}
+
+package FS::UI::_Link;
+
+use vars qw ( @ISA $BASE_URL );
+
+@ISA = qw ( FS::UI::_Widget);
+$BASE_URL = "http://rootwood.sisd.com/freeside";
+
+sub sprint {
+ my $self = shift;
+ my $table = $self->{'table'};
+ my $method = $self->{'method'};
+
+ # i will be cleaned up when we're done moving from the old webinterface!
+ my @arg = @{$self->{'arg'}};
+ my $yuck = join( "&", @arg);
+ qq(<A HREF="$BASE_URL/$method/$table.cgi?$yuck">). $self->{'text'}. "<\A>";
+}
+
+package FS::UI::_Table;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Widget);
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = $class eq $proto ? { @_ } : $proto;
+ bless ( $self, $class );
+ $self->{'_table'} = new HTML::Table ( $self->rows, $self->columns );
+ $self;
+}
+
+sub attach {
+ my $self = shift;
+ my ( $row, $column, $widget, $rowspan, $colspan ) = @_;
+ $self->{"_table"}->setCell( $row+1, $column+1, $widget->sprint );
+ $self->{"_table"}->setCellRowSpan( $row+1, $column+1, $rowspan ) if $rowspan;
+ $self->{"_table"}->setCellColSpan( $row+1, $column+1, $colspan ) if $colspan;
+}
+
+sub sprint {
+ my $self = shift;
+ $self->{'_table'}->getTable;
+}
+
+package FS::UI::_Tableborder;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Table );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = $class eq $proto ? { @_ } : $proto;
+ bless ( $self, $class );
+ $self->SUPER::new(@_);
+ $self->{'_table'}->setBorder;
+ $self;
+}
+
+=head1 VERSION
+
+$Id: CGI.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
+
+=head1 BUGS
+
+This documentation is incomplete.
+
+In _Tableborder, headers should be links that sort on their fields.
+
+_Link uses a constant $BASE_URL
+
+_Link passes the arguments as a manually-constructed GET string instead
+of POSTing, for compatability while the web interface is upgraded. Once
+this is done it should pass arguements properly (i.e. as a POST, 8-bit clean)
+
+Still some small bits of widget code same as FS::UI::Gtk.
+
+=head1 SEE ALSO
+
+L<FS::UI::Base>
+
+=head1 HISTORY
+
+$Log: CGI.pm,v $
+Revision 1.1 1999-08-04 09:03:53 ivan
+initial checkin of module files for proper perl installation
+
+Revision 1.1 1999/01/20 09:30:36 ivan
+skeletal cross-UI UI code.
+
+
+=cut
+
+1;
+
diff --git a/FS/FS/UI/Gtk.pm b/FS/FS/UI/Gtk.pm
new file mode 100644
index 0000000..507a293
--- /dev/null
+++ b/FS/FS/UI/Gtk.pm
@@ -0,0 +1,224 @@
+package FS::UI::Gtk;
+
+use strict;
+use Gtk;
+use FS::UID qw(adminsuidsetup);
+
+die "Can't initialize Gtk interface; $FS::UI::Base::_lock used"
+ if $FS::UI::Base::_lock;
+$FS::UI::Base::_lock = "FS::UI::Gtk";
+
+=head1 NAME
+
+FS::UI::Gtk - Base class for Gtk user-interface objects
+
+=head1 SYNOPSIS
+
+ use FS::UI::Gtk;
+ use FS::UI::some_table;
+
+ $interface = new FS::UI::some_table;
+
+ $error = $interface->browse;
+ $error = $interface->search;
+ $error = $interface->view;
+ $error = $interface->edit;
+ $error = $interface->process;
+
+=head1 DESCRIPTION
+
+An FS::UI::Gtk object represents a Gtk user interface object.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+
+ bless ( $self, $class );
+
+ $self->{'_user'} = 'ivan'; #Pop up login window?
+ $self->{'_dbh'} = FS::UID::adminsuidsetup $self->{'_user'};
+
+
+
+ $self;
+}
+
+sub activate {
+ my $self = shift;
+
+ my $vbox = new Gtk::VBox ( 0, 4 );
+
+ foreach my $widget ( @{ $self->{'Widgets'} } ) {
+ $widget->_gtk->show;
+ $vbox->pack_start ( $widget->_gtk, 1, 1, 4 );
+ }
+ $vbox->show;
+
+ my $window = new Gtk::Window "toplevel";
+ $self->{'_gtk'} = $window;
+ $window->set_title( $self->title );
+ $window->add ( $vbox );
+ $window->show;
+ main Gtk;
+}
+
+=item interface
+
+Returns the string `Gtk'. Useful for the author of a table-specific UI class
+to conditionally specify certain behaviour.
+
+=cut
+
+sub interface { 'Gtk'; }
+
+=back
+
+=cut
+
+package FS::UI::_Widget;
+
+use vars qw( $AUTOLOAD );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+ bless ( $self, $class );
+}
+
+sub _gtk {
+ my $self = shift;
+ $self->{'_gtk'};
+}
+
+sub AUTOLOAD {
+ my $self = shift;
+ my $value = shift;
+ my($field)=$AUTOLOAD;
+ $field =~ s/.*://;
+ if ( defined($value) ) {
+ $self->{$field} = $value;
+ } else {
+ $self->{$field};
+ }
+}
+
+package FS::UI::_Text;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Widget );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ $self->{'_gtk'} = new Gtk::Label ( shift );
+ bless ( $self, $class );
+}
+
+package FS::UI::_Link;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Widget );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+ $self->{'_gtk'} = new_with_label Gtk::Button ( $self->{'text'} );
+ $self->{'_gtk'}->signal_connect( 'clicked', sub {
+ print "STUB: (Gtk) FS::UI::_Link";
+ }, "hi", "there" );
+ bless ( $self, $class );
+}
+
+
+package FS::UI::_Table;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Widget );
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = { @_ };
+ bless ( $self, $class );
+
+ $self->{'_gtk'} = new Gtk::Table (
+ $self->rows,
+ $self->columns,
+ 0, #homogeneous
+ );
+
+ $self;
+}
+
+sub attach {
+ my $self = shift;
+ my ( $row, $column, $widget, $rowspan, $colspan ) = @_;
+ $rowspan ||= 1;
+ $colspan ||= 1;
+ $self->_gtk->attach_defaults(
+ $widget->_gtk,
+ $column,
+ $column + $colspan,
+ $row,
+ $row + $rowspan,
+ );
+ $widget->_gtk->show;
+}
+
+package FS::UI::_Tableborder;
+
+use vars qw ( @ISA );
+
+@ISA = qw ( FS::UI::_Table );
+
+=head1 VERSION
+
+$Id: Gtk.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
+
+=head1 BUGS
+
+This documentation is incomplete.
+
+_Tableborder is just a _Table now. _Tableborders should scroll (but not the
+headers) and need and need more decoration. (data in white section ala gtksql
+and sliding field widths) headers should be buttons that callback to sort on
+their fields.
+
+There should be a persistant, per-(freeside)-user store for window positions
+and sizes and sort fields etc (see L<FS::UI::CGI/BUGS>.
+
+Still some small bits of widget code same as FS::UI::CGI.
+
+=head1 SEE ALSO
+
+L<FS::UI::Base>
+
+=head1 HISTORY
+
+$Log: Gtk.pm,v $
+Revision 1.1 1999-08-04 09:03:53 ivan
+initial checkin of module files for proper perl installation
+
+Revision 1.1 1999/01/20 09:30:36 ivan
+skeletal cross-UI UI code.
+
+
+=cut
+
+1;
+
diff --git a/FS/FS/UI/agent.pm b/FS/FS/UI/agent.pm
new file mode 100644
index 0000000..ce9744a
--- /dev/null
+++ b/FS/FS/UI/agent.pm
@@ -0,0 +1,62 @@
+package FS::UI::agent;
+
+use strict;
+use vars qw ( @ISA );
+use FS::UI::Base;
+use FS::Record qw( qsearchs );
+use FS::agent;
+use FS::agent_type;
+
+@ISA = qw ( FS::UI::Base );
+
+sub db_table { 'agent' };
+
+sub db_name { 'Agent' };
+
+sub db_description { <<END;
+Agents are resellers of your service. Agents may be limited to a subset of your
+full offerings (via their type).
+END
+}
+
+sub list_fields {
+ 'agentnum',
+ 'typenum',
+# 'freq',
+# 'prog',
+; }
+
+sub list_header {
+ 'Agent',
+ 'Type',
+# 'Freq (n/a)',
+# 'Prog (n/a)',
+; }
+
+sub db_callback {
+ 'agentnum' =>
+ sub {
+ my ( $agentnum, $record ) = @_;
+ my $agent = $record->agent;
+ new FS::UI::_Link (
+ 'table' => 'agent',
+ 'method' => 'edit',
+ 'arg' => [ $agentnum ],
+ 'text' => "$agentnum: $agent",
+ );
+ },
+ 'typenum' =>
+ sub {
+ my $typenum = shift;
+ my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } );
+ my $atype = $agent_type->atype;
+ new FS::UI::_Link (
+ 'table' => 'agent_type',
+ 'method' => 'edit',
+ 'arg' => [ $typenum ],
+ 'text' => "$typenum: $atype"
+ );
+ },
+}
+
+1;
diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm
new file mode 100644
index 0000000..3d893ee
--- /dev/null
+++ b/FS/FS/UID.pm
@@ -0,0 +1,318 @@
+package FS::UID;
+
+use strict;
+use vars qw(
+ @ISA @EXPORT_OK $cgi $dbh $freeside_uid $user
+ $conf_dir $secrets $datasrc $db_user $db_pass %callback @callback
+ $driver_name $AutoCommit
+);
+use subs qw(
+ getsecrets cgisetotaker
+);
+use Exporter;
+use Carp qw(carp croak cluck);
+use DBI;
+use FS::Conf;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup
+ getotaker dbh datasrc getsecrets driver_name myconnect );
+
+$freeside_uid = scalar(getpwnam('freeside'));
+
+$conf_dir = "/usr/local/etc/freeside/";
+
+$AutoCommit = 1; #ours, not DBI
+
+=head1 NAME
+
+FS::UID - Subroutines for database login and assorted other stuff
+
+=head1 SYNOPSIS
+
+ use FS::UID qw(adminsuidsetup cgisuidsetup dbh datasrc getotaker
+ checkeuid checkruid);
+
+ adminsuidsetup $user;
+
+ $cgi = new CGI;
+ $dbh = cgisuidsetup($cgi);
+
+ $dbh = dbh;
+
+ $datasrc = datasrc;
+
+ $driver_name = driver_name;
+
+=head1 DESCRIPTION
+
+Provides a hodgepodge of subroutines.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item adminsuidsetup USER
+
+Sets the user to USER (see config.html from the base documentation).
+Cleans the environment.
+Make sure the script is running as freeside, or setuid freeside.
+Opens a connection to the database.
+Swaps real and effective UIDs.
+Runs any defined callbacks (see below).
+Returns the DBI database handle (usually you don't need this).
+
+=cut
+
+sub adminsuidsetup {
+ $dbh->disconnect if $dbh;
+ &forksuidsetup(@_);
+}
+
+sub forksuidsetup {
+ $user = shift;
+ croak "fatal: adminsuidsetup called without arguements" unless $user;
+
+ $user =~ /^([\w\-\.]+)$/ or croak "fatal: illegal user $user";
+ $user = $1;
+
+ $ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+ $ENV{'SHELL'} = '/bin/sh';
+ $ENV{'IFS'} = " \t\n";
+ $ENV{'CDPATH'} = '';
+ $ENV{'ENV'} = '';
+ $ENV{'BASH_ENV'} = '';
+
+ croak "Not running uid freeside!" unless checkeuid();
+
+ $dbh = &myconnect;
+
+ foreach ( keys %callback ) {
+ &{$callback{$_}};
+ # breaks multi-database installs # delete $callback{$_}; #run once
+ }
+
+ &{$_} foreach @callback;
+
+ $dbh;
+}
+
+sub myconnect {
+ $dbh = DBI->connect( getsecrets, {'AutoCommit' => 0, 'ChopBlanks' => 1, } )
+ or die "DBI->connect error: $DBI::errstr\n";
+}
+
+=item install_callback
+
+A package can install a callback to be run in adminsuidsetup by passing
+a coderef to the FS::UID->install_callback class method. If adminsuidsetup has
+run already, the callback will also be run immediately.
+
+ $coderef = sub { warn "Hi, I'm returning your call!" };
+ FS::UID->install_callback($coderef);
+
+ install_callback FS::UID sub {
+ warn "Hi, I'm returning your call!"
+ };
+
+=cut
+
+sub install_callback {
+ my $class = shift;
+ my $callback = shift;
+ push @callback, $callback;
+ &{$callback} if $dbh;
+}
+
+=item cgisuidsetup CGI_object
+
+Takes a single argument, which is a CGI (see L<CGI>) or Apache (see L<Apache>)
+object (CGI::Base is depriciated). Runs cgisetotaker and then adminsuidsetup.
+
+=cut
+
+sub cgisuidsetup {
+ $cgi=shift;
+ if ( $cgi->isa('CGI::Base') ) {
+ carp "Use of CGI::Base is depriciated";
+ } elsif ( $cgi->isa('Apache') ) {
+
+ } elsif ( ! $cgi->isa('CGI') ) {
+ croak "fatal: unrecognized object $cgi";
+ }
+ cgisetotaker;
+ adminsuidsetup($user);
+}
+
+=item cgi
+
+Returns the CGI (see L<CGI>) object.
+
+=cut
+
+sub cgi {
+ carp "warning: \$FS::UID::cgi isa Apache" if $cgi->isa('Apache');
+ $cgi;
+}
+
+=item dbh
+
+Returns the DBI database handle.
+
+=cut
+
+sub dbh {
+ $dbh;
+}
+
+=item datasrc
+
+Returns the DBI data source.
+
+=cut
+
+sub datasrc {
+ $datasrc;
+}
+
+=item driver_name
+
+Returns just the driver name portion of the DBI data source.
+
+=cut
+
+sub driver_name {
+ return $driver_name if defined $driver_name;
+ $driver_name = ( split(':', $datasrc) )[1];
+}
+
+sub suidsetup {
+ croak "suidsetup depriciated";
+}
+
+=item getotaker
+
+Returns the current Freeside user.
+
+=cut
+
+sub getotaker {
+ $user;
+}
+
+=item cgisetotaker
+
+Sets and returns the CGI REMOTE_USER. $cgi should be defined as a CGI.pm
+object (see L<CGI>) or an Apache object (see L<Apache>). Support for CGI::Base
+and derived classes is depriciated.
+
+=cut
+
+sub cgisetotaker {
+ if ( $cgi && $cgi->isa('CGI::Base') && defined $cgi->var('REMOTE_USER')) {
+ carp "Use of CGI::Base is depriciated";
+ $user = lc ( $cgi->var('REMOTE_USER') );
+ } elsif ( $cgi && $cgi->isa('CGI') && defined $cgi->remote_user ) {
+ $user = lc ( $cgi->remote_user );
+ } elsif ( $cgi && $cgi->isa('Apache') ) {
+ $user = lc ( $cgi->connection->user );
+ } else {
+ die "fatal: Can't get REMOTE_USER! for cgi $cgi - you need to setup ".
+ "Apache user authentication as documented in httemplate/docs/install.html";
+ }
+ $user;
+}
+
+=item checkeuid
+
+Returns true if effective UID is that of the freeside user.
+
+=cut
+
+sub checkeuid {
+ ( $> == $freeside_uid );
+}
+
+=item checkruid
+
+Returns true if the real UID is that of the freeside user.
+
+=cut
+
+sub checkruid {
+ ( $< == $freeside_uid );
+}
+
+=item getsecrets [ USER ]
+
+Sets the user to USER, if supplied.
+Sets and returns the DBI datasource, username and password for this user from
+the `/usr/local/etc/freeside/mapsecrets' file.
+
+=cut
+
+sub getsecrets {
+ my($setuser) = shift;
+ $user = $setuser if $setuser;
+ die "No user!" unless $user;
+ my($conf) = new FS::Conf $conf_dir;
+ my($line) = grep /^\s*$user\s/, $conf->config('mapsecrets');
+ die "User $user not found in mapsecrets!" unless $line;
+ $line =~ /^\s*$user\s+(.*)$/;
+ $secrets = $1;
+ die "Illegal mapsecrets line for user?!" unless $secrets;
+ ($datasrc, $db_user, $db_pass) = $conf->config($secrets)
+ or die "Can't get secrets: $!";
+ $FS::Conf::default_dir = $conf_dir. "/conf.$datasrc";
+ undef $driver_name;
+ ($datasrc, $db_user, $db_pass);
+}
+
+=back
+
+=head1 CALLBACKS
+
+Warning: this interface is (still) likely to change in future releases.
+
+New (experimental) callback interface:
+
+A package can install a callback to be run in adminsuidsetup by passing
+a coderef to the FS::UID->install_callback class method. If adminsuidsetup has
+run already, the callback will also be run immediately.
+
+ $coderef = sub { warn "Hi, I'm returning your call!" };
+ FS::UID->install_callback($coderef);
+
+ install_callback FS::UID sub {
+ warn "Hi, I'm returning your call!"
+ };
+
+Old (deprecated) callback interface:
+
+A package can install a callback to be run in adminsuidsetup by putting a
+coderef into the hash %FS::UID::callback :
+
+ $coderef = sub { warn "Hi, I'm returning your call!" };
+ $FS::UID::callback{'Package::Name'} = $coderef;
+
+=head1 BUGS
+
+Too many package-global variables.
+
+Not OO.
+
+No capabilities yet. When mod_perl and Authen::DBI are implemented,
+cgisuidsetup will go away as well.
+
+Goes through contortions to support non-OO syntax with multiple datasrc's.
+
+Callbacks are (still) inelegant.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<CGI>, L<DBI>, config.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm
new file mode 100644
index 0000000..b4e88bf
--- /dev/null
+++ b/FS/FS/acct_snarf.pm
@@ -0,0 +1,128 @@
+package FS::acct_snarf;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::acct_snarf - Object methods for acct_snarf records
+
+=head1 SYNOPSIS
+
+ use FS::acct_snarf;
+
+ $record = new FS::acct_snarf \%hash;
+ $record = new FS::acct_snarf { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::svc_acct object represents an external mail account, typically for
+download of mail. FS::acct_snarf inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item snarfnum - primary key
+
+=item svcnum - Account (see L<FS::svc_acct>)
+
+=item machine - external machine to download mail from
+
+=item protocol - protocol (pop3, imap, etc.)
+
+=item username - external login username
+
+=item _password - external login password
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'acct_snarf'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid external mail account. If
+there is an error, returns the error, otherwise returns false. Called by the
+insert and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('snarfnum')
+ || $self->ut_number('svcnum')
+ || $self->ut_foreign_key('svcnum', 'svc_acct', 'svcnum')
+ || $self->ut_domain('machine')
+ || $self->ut_alphan('protocol')
+ || $self->ut_textn('username')
+ ;
+ return $error if $error;
+
+ $self->_password =~ /^[^\t\n]*$/ or return "illegal password";
+ $self->_password($1);
+
+ ''; #no error
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
new file mode 100755
index 0000000..1fb6060
--- /dev/null
+++ b/FS/FS/addr_block.pm
@@ -0,0 +1,331 @@
+package FS::addr_block;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch dbh );
+use FS::router;
+use FS::svc_broadband;
+use FS::Conf;
+use NetAddr::IP;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::addr_block - Object methods for addr_block records
+
+=head1 SYNOPSIS
+
+ use FS::addr_block;
+
+ $record = new FS::addr_block \%hash;
+ $record = new FS::addr_block { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::addr_block record describes an address block assigned for broadband
+access. FS::addr_block inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item blocknum - primary key, used in FS::svc_broadband to associate
+services to the block.
+
+=item routernum - the router (see FS::router) to which this
+block is assigned.
+
+=item ip_gateway - the gateway address used by customers within this block.
+
+=item ip_netmask - the netmask of the block, expressed as an integer.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'addr_block'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+sub delete {
+ my $self = shift;
+ return 'Block must be deallocated before deletion'
+ if $self->router;
+
+ $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_number('routernum')
+ || $self->ut_ip('ip_gateway')
+ || $self->ut_number('ip_netmask')
+ ;
+ return $error if $error;
+
+
+ # A routernum of 0 indicates an unassigned block and is allowed
+ return "Unknown routernum"
+ if ($self->routernum and not $self->router);
+
+ my $self_addr = $self->NetAddr;
+ return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask
+ unless $self_addr;
+
+ if (not $self->blocknum) {
+ my @block = grep {
+ my $block_addr = $_->NetAddr;
+ if($block_addr->contains($self_addr)
+ or $self_addr->contains($block_addr)) { $_; };
+ } qsearch( 'addr_block', {});
+ foreach(@block) {
+ return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask;
+ }
+ }
+
+ $self->SUPER::check;
+}
+
+
+=item router
+
+Returns the FS::router object corresponding to this object. If the
+block is unassigned, returns undef.
+
+=cut
+
+sub router {
+ my $self = shift;
+ return qsearchs('router', { routernum => $self->routernum });
+}
+
+=item svc_broadband
+
+Returns a list of FS::svc_broadband objects associated
+with this object.
+
+=cut
+
+sub svc_broadband {
+ my $self = shift;
+ return qsearch('svc_broadband', { blocknum => $self->blocknum });
+}
+
+=item NetAddr
+
+Returns a NetAddr::IP object for this block's address and netmask.
+
+=cut
+
+sub NetAddr {
+ my $self = shift;
+
+ return new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
+}
+
+=item next_free_addr
+
+Returns a NetAddr::IP object corresponding to the first unassigned address
+in the block (other than the network, broadcast, or gateway address). If
+there are no free addresses, returns false.
+
+=cut
+
+sub next_free_addr {
+ my $self = shift;
+
+ my $conf = new FS::Conf;
+ my @excludeaddr = $conf->config('exclude_ip_addr');
+
+my @used =
+( (map { $_->NetAddr->addr }
+ ($self,
+ qsearch('svc_broadband', { blocknum => $self->blocknum }))
+ ), @excludeaddr
+);
+
+ my @free = $self->NetAddr->hostenum;
+ while (my $ip = shift @free) {
+ if (not grep {$_ eq $ip->addr;} @used) { return $ip; };
+ }
+
+ '';
+
+}
+
+=item allocate
+
+Allocates this address block to a router. Takes an FS::router object
+as an argument.
+
+At present it's not possible to reallocate a block to a different router
+except by deallocating it first, which requires that none of its addresses
+be assigned. This is probably as it should be.
+
+=cut
+
+sub allocate {
+ my ($self, $router) = @_;
+
+ return 'Block is already allocated'
+ if($self->router);
+
+ return 'Block must be allocated to a router'
+ unless(ref $router eq 'FS::router');
+
+ my @svc = $self->svc_broadband;
+ if (@svc) {
+ return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
+ }
+
+ my $new = new FS::addr_block {$self->hash};
+ $new->routernum($router->routernum);
+ return $new->replace($self);
+
+}
+
+=item deallocate
+
+Deallocates the block (i.e. sets the routernum to 0). If any addresses in the
+block are assigned to services, it fails.
+
+=cut
+
+sub deallocate {
+ my $self = shift;
+
+ my @svc = $self->svc_broadband;
+ if (@svc) {
+ return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
+ }
+
+ my $new = new FS::addr_block {$self->hash};
+ $new->routernum(0);
+ return $new->replace($self);
+}
+
+=item split_block
+
+Splits this address block into two equal blocks, occupying the same space as
+the original block. The first of the two will also have the same blocknum.
+The gateway address of each block will be set to the first usable address, i.e.
+(network address)+1. Since this method is designed for use on unallocated
+blocks, this is probably the correct behavior.
+
+(At present, splitting allocated blocks is disallowed. Anyone who wants to
+implement this is reminded that each split costs three addresses, and any
+customers who were using these addresses will have to be moved; depending on
+how full the block was before being split, they might have to be moved to a
+different block. Anyone who I<still> wants to implement it is asked to tie it
+to a configuration switch so that site admins can disallow it.)
+
+=cut
+
+sub split_block {
+
+ # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/
+ # something to atomicize functions, so that we can say
+ #
+ # sub split_block : atomic {
+ #
+ # instead of repeating all this AutoCommit verbage in every
+ # sub that does more than one database operation.
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $self = shift;
+ my $error;
+
+ if ($self->router) {
+ return 'Block is already allocated';
+ }
+
+ #TODO: Smallest allowed block should be a config option.
+ if ($self->NetAddr->masklen() ge 30) {
+ return 'Cannot split blocks with a mask length >= 30';
+ }
+
+ my (@new, @ip);
+ $ip[0] = $self->NetAddr;
+ @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1);
+
+ foreach (0,1) {
+ $new[$_] = new FS::addr_block {$self->hash};
+ $new[$_]->ip_gateway($ip[$_]->addr);
+ $new[$_]->ip_netmask($ip[$_]->masklen);
+ }
+
+ $new[1]->blocknum('');
+
+ $error = $new[0]->replace($self);
+ if ($error) {
+ $dbh->rollback;
+ return $error;
+ }
+
+ $error = $new[1]->insert;
+ if ($error) {
+ $dbh->rollback;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+}
+
+=item merge
+
+To be implemented.
+
+=back
+
+=head1 BUGS
+
+Minimum block size should be a config option. It's hardcoded at /30 right
+now because that's the smallest block that makes any sense at all.
+
+=cut
+
+1;
+
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
new file mode 100644
index 0000000..a9e41a6
--- /dev/null
+++ b/FS/FS/agent.pm
@@ -0,0 +1,279 @@
+package FS::agent;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::cust_main;
+use FS::agent_type;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::agent - Object methods for agent records
+
+=head1 SYNOPSIS
+
+ use FS::agent;
+
+ $record = new FS::agent \%hash;
+ $record = new FS::agent { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $agent_type = $record->agent_type;
+
+ $hashref = $record->pkgpart_hashref;
+ #may purchase $pkgpart if $hashref->{$pkgpart};
+
+=head1 DESCRIPTION
+
+An FS::agent object represents an agent. Every customer has an agent. Agents
+can be used to track things like resellers or salespeople. FS::agent inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item agentnum - primary key (assigned automatically for new agents)
+
+=item agent - Text name of this agent
+
+=item typenum - Agent type. See L<FS::agent_type>
+
+=item prog - For future use.
+
+=item freq - For future use.
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=item username - Username for the Agent interface
+
+=item _password - Password for the Agent interface
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new agent. To add the agent to the database, see L<"insert">.
+
+=cut
+
+sub table { 'agent'; }
+
+=item insert
+
+Adds this agent to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this agent from the database. Only agents with no customers can be
+deleted. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete an agent with customers!"
+ if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
+
+ $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid agent. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('agentnum')
+ || $self->ut_text('agent')
+ || $self->ut_number('typenum')
+ || $self->ut_numbern('freq')
+ || $self->ut_textn('prog')
+ ;
+ return $error if $error;
+
+ if ( $self->dbdef_table->column('disabled') ) {
+ $error = $self->ut_enum('disabled', [ '', 'Y' ] );
+ return $error if $error;
+ }
+
+ if ( $self->dbdef_table->column('username') ) {
+ $error = $self->ut_alphan('username');
+ return $error if $error;
+ if ( length($self->username) ) {
+ my $conflict = qsearchs('agent', { 'username' => $self->username } );
+ return 'duplicate agent username (with '. $conflict->agent. ')'
+ if $conflict && $conflict->agentnum != $self->agentnum;
+ $error = $self->ut_text('password'); # ut_text... arbitrary choice
+ } else {
+ $self->_password('');
+ }
+ }
+
+ return "Unknown typenum!"
+ unless $self->agent_type;
+
+ $self->SUPER::check;
+}
+
+=item agent_type
+
+Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
+
+=cut
+
+sub agent_type {
+ my $self = shift;
+ qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+}
+
+=item pkgpart_hashref
+
+Returns a hash reference. The keys of the hash are pkgparts. The value is
+true if this agent may purchase the specified package definition. See
+L<FS::part_pkg>.
+
+=cut
+
+sub pkgpart_hashref {
+ my $self = shift;
+ $self->agent_type->pkgpart_hashref;
+}
+
+=item num_prospect_cust_main
+
+Returns the number of prospects (customers with no packages ever ordered) for
+this agent.
+
+=cut
+
+sub num_prospect_cust_main {
+ shift->num_sql(FS::cust_main->prospect_sql);
+}
+
+sub num_sql {
+ my( $self, $sql ) = @_;
+ my $sth = dbh->prepare(
+ "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql"
+ ) or die dbh->errstr;
+ $sth->execute($self->agentnum) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item prospect_cust_main
+
+Returns the prospects (customers with no packages ever ordered) for this agent,
+as cust_main objects.
+
+=cut
+
+sub prospect_cust_main {
+ shift->cust_main_sql(FS::cust_main->prospect_sql);
+}
+
+sub cust_main_sql {
+ my( $self, $sql ) = @_;
+ qsearch( 'cust_main',
+ { 'agentnum' => $self->agentnum },
+ '',
+ " AND $sql"
+ );
+}
+
+=item num_active_cust_main
+
+Returns the number of active customers for this agent.
+
+=cut
+
+sub num_active_cust_main {
+ shift->num_sql(FS::cust_main->active_sql);
+}
+
+=item active_cust_main
+
+Returns the active customers for this agent, as cust_main objects.
+
+=cut
+
+sub active_cust_main {
+ shift->cust_main_sql(FS::cust_main->active_sql);
+}
+
+=item num_susp_cust_main
+
+Returns the number of suspended customers for this agent.
+
+=cut
+
+sub num_susp_cust_main {
+ shift->num_sql(FS::cust_main->susp_sql);
+}
+
+=item susp_cust_main
+
+Returns the suspended customers for this agent, as cust_main objects.
+
+=cut
+
+sub susp_cust_main {
+ shift->cust_main_sql(FS::cust_main->susp_sql);
+}
+
+=item num_cancel_cust_main
+
+Returns the number of cancelled customer for this agent.
+
+=cut
+
+sub num_cancel_cust_main {
+ shift->num_sql(FS::cust_main->cancel_sql);
+}
+
+=item cancel_cust_main
+
+Returns the cancelled customers for this agent, as cust_main objects.
+
+=cut
+
+sub cancel_cust_main {
+ shift->cust_main_sql(FS::cust_main->cancel_sql);
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::agent_type>, L<FS::cust_main>, L<FS::part_pkg>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm
new file mode 100644
index 0000000..5ba5ef2
--- /dev/null
+++ b/FS/FS/agent_type.pm
@@ -0,0 +1,166 @@
+package FS::agent_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch );
+use FS::agent;
+use FS::type_pkgs;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::agent_type - Object methods for agent_type records
+
+=head1 SYNOPSIS
+
+ use FS::agent_type;
+
+ $record = new FS::agent_type \%hash;
+ $record = new FS::agent_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $hashref = $record->pkgpart_hashref;
+ #may purchase $pkgpart if $hashref->{$pkgpart};
+
+ @type_pkgs = $record->type_pkgs;
+
+ @pkgparts = $record->pkgpart;
+
+=head1 DESCRIPTION
+
+An FS::agent_type object represents an agent type. Every agent (see
+L<FS::agent>) has an agent type. Agent types define which packages (see
+L<FS::part_pkg>) may be purchased by customers (see L<FS::cust_main>), via
+FS::type_pkgs records (see L<FS::type_pkgs>). FS::agent_type inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item typenum - primary key (assigned automatically for new agent types)
+
+=item atype - Text name of this agent type
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new agent type. To add the agent type to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'agent_type'; }
+
+=item insert
+
+Adds this agent type to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this agent type from the database. Only agent types with no agents
+can be deleted. If there is an error, returns the error, otherwise returns
+false.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete an agent_type with agents!"
+ if qsearch( 'agent', { 'typenum' => $self->typenum } );
+
+ $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid agent type. If there is an
+error, returns the error, otherwise returns false. Called by the insert and
+replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('typenum')
+ or $self->ut_text('atype')
+ or $self->SUPER::check;
+
+}
+
+=item pkgpart_hashref
+
+Returns a hash reference. The keys of the hash are pkgparts. The value is
+true iff this agent may purchase the specified package definition. See
+L<FS::part_pkg>.
+
+=cut
+
+sub pkgpart_hashref {
+ my $self = shift;
+ my %pkgpart;
+ #$pkgpart{$_}++ foreach $self->pkgpart;
+ # not compatible w/5.004_04 (fixed in 5.004_05)
+ foreach ( $self->pkgpart ) { $pkgpart{$_}++; }
+ \%pkgpart;
+}
+
+=item type_pkgs
+
+Returns all FS::type_pkgs objects (see L<FS::type_pkgs>) for this agent type.
+
+=cut
+
+sub type_pkgs {
+ my $self = shift;
+ qsearch('type_pkgs', { 'typenum' => $self->typenum } );
+}
+
+=item pkgpart
+
+Returns the pkgpart of all package definitions (see L<FS::part_pkg>) for this
+agent type.
+
+=cut
+
+sub pkgpart {
+ my $self = shift;
+ map $_->pkgpart, $self->type_pkgs;
+}
+
+=back
+
+=head1 VERSION
+
+$Id: agent_type.pm,v 1.2 2003-08-05 00:20:40 khoff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::agent>, L<FS::type_pkgs>, L<FS::cust_main>,
+L<FS::part_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
new file mode 100644
index 0000000..2755be0
--- /dev/null
+++ b/FS/FS/cust_bill.pm
@@ -0,0 +1,1487 @@
+package FS::cust_bill;
+
+use strict;
+use vars qw( @ISA $conf $money_char );
+use vars qw( $invoice_lines @buf ); #yuck
+use Date::Format;
+use Text::Template;
+use File::Temp 0.14;
+use String::ShellQuote;
+use FS::UID qw( datasrc );
+use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw( send_email );
+use FS::cust_main;
+use FS::cust_bill_pkg;
+use FS::cust_credit;
+use FS::cust_pay;
+use FS::cust_pkg;
+use FS::cust_credit_bill;
+use FS::cust_pay_batch;
+use FS::cust_bill_event;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $money_char = $conf->config('money_char') || '$';
+} );
+
+=head1 NAME
+
+FS::cust_bill - Object methods for cust_bill records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill;
+
+ $record = new FS::cust_bill \%hash;
+ $record = new FS::cust_bill { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
+
+ @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
+
+ ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
+
+ @cust_pay_objects = $cust_bill->cust_pay;
+
+ $tax_amount = $record->tax;
+
+ @lines = $cust_bill->print_text;
+ @lines = $cust_bill->print_text $time;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill object represents an invoice; a declaration that a customer
+owes you money. The specific charges are itemized as B<cust_bill_pkg> records
+(see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item invnum - primary key (assigned automatically for new invoices)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item charged - amount of this invoice
+
+=item printed - deprecated
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice. To add the invoice to the database, see L<"insert">.
+Invoices are normally created by calling the bill method of a customer object
+(see L<FS::cust_main>).
+
+=cut
+
+sub table { 'cust_bill'; }
+
+=item insert
+
+Adds this invoice to the database ("Posts" the invoice). If there is an error,
+returns the error, otherwise returns false.
+
+=item delete
+
+Currently unimplemented. I don't remove invoices because there would then be
+no record you ever posted this invoice (which is bad, no?)
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+Only printed may be changed. printed is normally updated by calling the
+collect method of a customer object (see L<FS::cust_main>).
+
+=cut
+
+sub replace {
+ my( $new, $old ) = ( shift, shift );
+ return "Can't change custnum!" unless $old->custnum == $new->custnum;
+ #return "Can't change _date!" unless $old->_date eq $new->_date;
+ return "Can't change _date!" unless $old->_date == $new->_date;
+ return "Can't change charged!" unless $old->charged == $new->charged;
+
+ $new->SUPER::replace($old);
+}
+
+=item check
+
+Checks all fields to make sure this is a valid invoice. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('invnum')
+ || $self->ut_number('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('charged')
+ || $self->ut_numbern('printed')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "Unknown customer"
+ unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ $self->printed(0) if $self->printed eq '';
+
+ $self->SUPER::check;
+}
+
+=item previous
+
+Returns a list consisting of the total previous balance for this customer,
+followed by the previous outstanding invoices (as FS::cust_bill objects also).
+
+=cut
+
+sub previous {
+ my $self = shift;
+ my $total = 0;
+ my @cust_bill = sort { $a->_date <=> $b->_date }
+ grep { $_->owed != 0 && $_->_date < $self->_date }
+ qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
+ ;
+ foreach ( @cust_bill ) { $total += $_->owed; }
+ $total, @cust_bill;
+}
+
+=item cust_bill_pkg
+
+Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
+}
+
+=item cust_bill_event
+
+Returns the completed invoice events (see L<FS::cust_bill_event>) for this
+invoice.
+
+=cut
+
+sub cust_bill_event {
+ my $self = shift;
+ qsearch( 'cust_bill_event', { 'invnum' => $self->invnum } );
+}
+
+
+=item cust_main
+
+Returns the customer (see L<FS::cust_main>) for this invoice.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=item cust_credit
+
+Depreciated. See the cust_credited method.
+
+ #Returns a list consisting of the total previous credited (see
+ #L<FS::cust_credit>) and unapplied for this customer, followed by the previous
+ #outstanding credits (FS::cust_credit objects).
+
+=cut
+
+sub cust_credit {
+ use Carp;
+ croak "FS::cust_bill->cust_credit depreciated; see ".
+ "FS::cust_bill->cust_credit_bill";
+ #my $self = shift;
+ #my $total = 0;
+ #my @cust_credit = sort { $a->_date <=> $b->_date }
+ # grep { $_->credited != 0 && $_->_date < $self->_date }
+ # qsearch('cust_credit', { 'custnum' => $self->custnum } )
+ #;
+ #foreach (@cust_credit) { $total += $_->credited; }
+ #$total, @cust_credit;
+}
+
+=item cust_pay
+
+Depreciated. See the cust_bill_pay method.
+
+#Returns all payments (see L<FS::cust_pay>) for this invoice.
+
+=cut
+
+sub cust_pay {
+ use Carp;
+ croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay";
+ #my $self = shift;
+ #sort { $a->_date <=> $b->_date }
+ # qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
+ #;
+}
+
+=item cust_bill_pay
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
+
+=cut
+
+sub cust_bill_pay {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
+}
+
+=item cust_credited
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
+
+=cut
+
+sub cust_credited {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
+ ;
+}
+
+=item tax
+
+Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
+
+=cut
+
+sub tax {
+ my $self = shift;
+ my $total = 0;
+ my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
+ 'pkgnum' => 0 } );
+ foreach (@taxlines) { $total += $_->setup; }
+ $total;
+}
+
+=item owed
+
+Returns the amount owed (still outstanding) on this invoice, which is charged
+minus all payment applications (see L<FS::cust_bill_pay>) and credit
+applications (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub owed {
+ my $self = shift;
+ my $balance = $self->charged;
+ $balance -= $_->amount foreach ( $self->cust_bill_pay );
+ $balance -= $_->amount foreach ( $self->cust_credited );
+ $balance = sprintf( "%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+=item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
+
+Sends this invoice to the destinations configured for this customer: send
+emails or print. See L<FS::cust_main_invoice>.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+AGENTNUM, if specified, means that this invoice will only be sent for customers
+of the specified agent.
+
+INVOICE_FROM, if specified, overrides the default email invoice From: address.
+
+=cut
+
+sub send {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+ return 'N/A' if scalar(@_) && $_[0] && $self->cust_main->agentnum != shift;
+ my $invoice_from =
+ scalar(@_)
+ ? shift
+ : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
+
+ my @print_text = $self->print_text('', $template);
+ my @invoicing_list = $self->cust_main->invoicing_list;
+
+ if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list ) { #email
+
+ #better to notify this person than silence
+ @invoicing_list = ($invoice_from) unless @invoicing_list;
+
+ my $error = send_email(
+ 'from' => $invoice_from,
+ 'to' => [ grep { $_ ne 'POST' } @invoicing_list ],
+ 'subject' => 'Invoice',
+ 'body' => \@print_text,
+ );
+ die "can't email invoice: $error\n" if $error;
+
+ }
+
+ if ( $conf->config('invoice_latex') ) {
+ @print_text = $self->print_ps('', $template);
+ }
+
+ if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
+ my $lpr = $conf->config('lpr');
+ open(LPR, "|$lpr")
+ or die "Can't open pipe to $lpr: $!\n";
+ print LPR @print_text;
+ close LPR
+ or die $! ? "Error closing $lpr: $!\n"
+ : "Exit status $? from $lpr\n";
+ }
+
+ '';
+
+}
+
+=item send_csv OPTIONS
+
+Sends invoice as a CSV data-file to a remote host with the specified protocol.
+
+Options are:
+
+protocol - currently only "ftp"
+server
+username
+password
+dir
+
+The file will be named "N-YYYYMMDDHHMMSS.csv" where N is the invoice number
+and YYMMDDHHMMSS is a timestamp.
+
+The fields of the CSV file is as follows:
+
+record_type, invnum, custnum, _date, charged, first, last, company, address1, address2, city, state, zip, country, pkg, setup, recur, sdate, edate
+
+=over 4
+
+=item record type - B<record_type> is either C<cust_bill> or C<cust_bill_pkg>
+
+If B<record_type> is C<cust_bill>, this is a primary invoice record. The
+last five fields (B<pkg> through B<edate>) are irrelevant, and all other
+fields are filled in.
+
+If B<record_type> is C<cust_bill_pkg>, this is a line item record. Only the
+first two fields (B<record_type> and B<invnum>) and the last five fields
+(B<pkg> through B<edate>) are filled in.
+
+=item invnum - invoice number
+
+=item custnum - customer number
+
+=item _date - invoice date
+
+=item charged - total invoice amount
+
+=item first - customer first name
+
+=item last - customer first name
+
+=item company - company name
+
+=item address1 - address line 1
+
+=item address2 - address line 1
+
+=item city
+
+=item state
+
+=item zip
+
+=item country
+
+=item pkg - line item description
+
+=item setup - line item setup fee (one or both of B<setup> and B<recur> will be defined)
+
+=item recur - line item recurring fee (one or both of B<setup> and B<recur> will be defined)
+
+=item sdate - start date for recurring fee
+
+=item edate - end date for recurring fee
+
+=back
+
+=cut
+
+sub send_csv {
+ my($self, %opt) = @_;
+
+ #part one: create file
+
+ my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
+ mkdir $spooldir, 0700 unless -d $spooldir;
+
+ my $file = $spooldir. '/'. $self->invnum. time2str('-%Y%m%d%H%M%S.csv', time);
+
+ open(CSV, ">$file") or die "can't open $file: $!";
+
+ eval "use Text::CSV_XS";
+ die $@ if $@;
+
+ my $csv = Text::CSV_XS->new({'always_quote'=>1});
+
+ my $cust_main = $self->cust_main;
+
+ $csv->combine(
+ 'cust_bill',
+ $self->invnum,
+ $self->custnum,
+ time2str("%x", $self->_date),
+ sprintf("%.2f", $self->charged),
+ ( map { $cust_main->getfield($_) }
+ qw( first last company address1 address2 city state zip country ) ),
+ map { '' } (1..5),
+ ) or die "can't create csv";
+ print CSV $csv->string. "\n";
+
+ #new charges (false laziness w/print_text)
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+
+ my($pkg, $setup, $recur, $sdate, $edate);
+ if ( $cust_bill_pkg->pkgnum ) {
+
+ ($pkg, $setup, $recur, $sdate, $edate) = (
+ $cust_bill_pkg->cust_pkg->part_pkg->pkg,
+ ( $cust_bill_pkg->setup != 0
+ ? sprintf("%.2f", $cust_bill_pkg->setup )
+ : '' ),
+ ( $cust_bill_pkg->recur != 0
+ ? sprintf("%.2f", $cust_bill_pkg->recur )
+ : '' ),
+ time2str("%x", $cust_bill_pkg->sdate),
+ time2str("%x", $cust_bill_pkg->edate),
+ );
+
+ } else { #pkgnum tax
+ next unless $cust_bill_pkg->setup != 0;
+ my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+ ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+ : 'Tax';
+ ($pkg, $setup, $recur, $sdate, $edate) =
+ ( $itemdesc, sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' );
+ }
+
+ $csv->combine(
+ 'cust_bill_pkg',
+ $self->invnum,
+ ( map { '' } (1..11) ),
+ ($pkg, $setup, $recur, $sdate, $edate)
+ ) or die "can't create csv";
+ print CSV $csv->string. "\n";
+
+ }
+
+ close CSV or die "can't close CSV: $!";
+
+ #part two: upload it
+
+ my $net;
+ if ( $opt{protocol} eq 'ftp' ) {
+ eval "use Net::FTP;";
+ die $@ if $@;
+ $net = Net::FTP->new($opt{server}) or die @$;
+ } else {
+ die "unknown protocol: $opt{protocol}";
+ }
+
+ $net->login( $opt{username}, $opt{password} )
+ or die "can't FTP to $opt{username}\@$opt{server}: login error: $@";
+
+ $net->binary or die "can't set binary mode";
+
+ $net->cwd($opt{dir}) or die "can't cwd to $opt{dir}";
+
+ $net->put($file) or die "can't put $file: $!";
+
+ $net->quit;
+
+ unlink $file;
+
+}
+
+=item comp
+
+Pays this invoice with a compliemntary payment. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub comp {
+ my $self = shift;
+ my $cust_pay = new FS::cust_pay ( {
+ 'invnum' => $self->invnum,
+ 'paid' => $self->owed,
+ '_date' => '',
+ 'payby' => 'COMP',
+ 'payinfo' => $self->cust_main->payinfo,
+ 'paybatch' => '',
+ } );
+ $cust_pay->insert;
+}
+
+=item realtime_card
+
+Attempts to pay this invoice with a credit card payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
+
+=cut
+
+sub realtime_card {
+ my $self = shift;
+ $self->realtime_bop( 'CC', @_ );
+}
+
+=item realtime_ach
+
+Attempts to pay this invoice with an electronic check (ACH) payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
+
+=cut
+
+sub realtime_ach {
+ my $self = shift;
+ $self->realtime_bop( 'ECHECK', @_ );
+}
+
+=item realtime_lec
+
+Attempts to pay this invoice with phone bill (LEC) payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
+
+=cut
+
+sub realtime_lec {
+ my $self = shift;
+ $self->realtime_bop( 'LEC', @_ );
+}
+
+sub realtime_bop {
+ my( $self, $method ) = @_;
+
+ my $cust_main = $self->cust_main;
+ my $balance = $cust_main->balance;
+ my $amount = ( $balance < $self->owed ) ? $balance : $self->owed;
+ $amount = sprintf("%.2f", $amount);
+ return "not run (balance $balance)" unless $amount > 0;
+
+ my $description = 'Internet Services';
+ if ( $conf->exists('business-onlinepayment-description') ) {
+ my $dtempl = $conf->config('business-onlinepayment-description');
+
+ my $agent_obj = $cust_main->agent
+ or die "can't retreive agent for $cust_main (agentnum ".
+ $cust_main->agentnum. ")";
+ my $agent = $agent_obj->agent;
+ my $pkgs = join(', ',
+ map { $_->cust_pkg->part_pkg->pkg }
+ grep { $_->pkgnum } $self->cust_bill_pkg
+ );
+ $description = eval qq("$dtempl");
+ }
+
+ $cust_main->realtime_bop($method, $amount,
+ 'description' => $description,
+ 'invnum' => $self->invnum,
+ );
+
+}
+
+=item batch_card
+
+Adds a payment for this invoice to the pending credit card batch (see
+L<FS::cust_pay_batch>).
+
+=cut
+
+sub batch_card {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+
+ my $cust_pay_batch = new FS::cust_pay_batch ( {
+ 'invnum' => $self->getfield('invnum'),
+ 'custnum' => $cust_main->getfield('custnum'),
+ 'last' => $cust_main->getfield('last'),
+ 'first' => $cust_main->getfield('first'),
+ 'address1' => $cust_main->getfield('address1'),
+ 'address2' => $cust_main->getfield('address2'),
+ 'city' => $cust_main->getfield('city'),
+ 'state' => $cust_main->getfield('state'),
+ 'zip' => $cust_main->getfield('zip'),
+ 'country' => $cust_main->getfield('country'),
+ 'cardnum' => $cust_main->getfield('payinfo'),
+ 'exp' => $cust_main->getfield('paydate'),
+ 'payname' => $cust_main->getfield('payname'),
+ 'amount' => $self->owed,
+ } );
+ my $error = $cust_pay_batch->insert;
+ die $error if $error;
+
+ '';
+}
+
+sub _agent_template {
+ my $self = shift;
+ $self->_agent_plandata('agent_templatename');
+}
+
+sub _agent_invoice_from {
+ my $self = shift;
+ $self->_agent_plandata('agent_invoice_from');
+}
+
+sub _agent_plandata {
+ my( $self, $option ) = @_;
+
+ my $part_bill_event = qsearchs( 'part_bill_event',
+ {
+ 'payby' => $self->cust_main->payby,
+ 'plan' => 'send_agent',
+ 'plandata' => { 'op' => '~',
+ 'value' => "(^|\n)agentnum ".
+ $self->cust_main->agentnum.
+ "(\n|\$)",
+ },
+ },
+ '',
+ 'ORDER BY seconds LIMIT 1'
+ );
+
+ return '' unless $part_bill_event;
+
+ if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) {
+ return $1;
+ } else {
+ warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart.
+ " plandata for $option";
+ return '';
+ }
+
+}
+
+=item print_text [ TIME [ , TEMPLATE ] ]
+
+Returns an text invoice, as a list of lines.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+#still some false laziness w/print_text
+sub print_text {
+
+ my( $self, $today, $template ) = @_;
+ $today ||= time;
+
+# my $invnum = $self->invnum;
+ my $cust_main = $self->cust_main;
+ $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
+ unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
+
+ my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
+ #my $balance_due = $self->owed + $pr_total - $cr_total;
+ my $balance_due = $self->owed + $pr_total;
+
+ #my @collect = ();
+ #my($description,$amount);
+ @buf = ();
+
+ #previous balance
+ foreach ( @pr_cust_bill ) {
+ push @buf, [
+ "Previous Balance, Invoice #". $_->invnum.
+ " (". time2str("%x",$_->_date). ")",
+ $money_char. sprintf("%10.2f",$_->owed)
+ ];
+ }
+ if (@pr_cust_bill) {
+ push @buf,['','-----------'];
+ push @buf,[ 'Total Previous Balance',
+ $money_char. sprintf("%10.2f",$pr_total ) ];
+ push @buf,['',''];
+ }
+
+ #new charges
+ foreach my $cust_bill_pkg (
+ ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first
+ ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes
+ ) {
+
+ if ( $cust_bill_pkg->pkgnum ) {
+
+ my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } );
+ my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
+ my $pkg = $part_pkg->pkg;
+
+ if ( $cust_bill_pkg->setup != 0 ) {
+ my $description = $pkg;
+ $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+ push @buf, [ $description,
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
+ push @buf,
+ map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
+ }
+
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @buf, [
+ "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " .
+ time2str("%x", $cust_bill_pkg->edate) . ")",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
+ ];
+ push @buf,
+ map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
+ }
+
+ push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details;
+
+ } else { #pkgnum tax or one-shot line item
+ my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+ ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+ : 'Tax';
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @buf, [ $itemdesc,
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
+ }
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @buf, [ "$itemdesc (". time2str("%x", $cust_bill_pkg->sdate). " - "
+ . time2str("%x", $cust_bill_pkg->edate). ")",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
+ ];
+ }
+ }
+ }
+
+ push @buf,['','-----------'];
+ push @buf,['Total New Charges',
+ $money_char. sprintf("%10.2f",$self->charged) ];
+ push @buf,['',''];
+
+ push @buf,['','-----------'];
+ push @buf,['Total Charges',
+ $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
+ push @buf,['',''];
+
+ #credits
+ foreach ( $self->cust_credited ) {
+
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+ my $reason = substr($_->cust_credit->reason,0,32);
+ $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ $reason = " ($reason) " if $reason;
+ push @buf,[
+ "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
+ $reason,
+ $money_char. sprintf("%10.2f",$_->amount)
+ ];
+ }
+ #foreach ( @cr_cust_credit ) {
+ # push @buf,[
+ # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+ # $money_char. sprintf("%10.2f",$_->credited)
+ # ];
+ #}
+
+ #get & print payments
+ foreach ( $self->cust_bill_pay ) {
+
+ #something more elaborate if $_->amount ne ->cust_pay->paid ?
+
+ push @buf,[
+ "Payment received ". time2str("%x",$_->cust_pay->_date ),
+ $money_char. sprintf("%10.2f",$_->amount )
+ ];
+ }
+
+ #balance due
+ my $balance_due_msg = $self->balance_due_msg;
+
+ push @buf,['','-----------'];
+ push @buf,[$balance_due_msg, $money_char.
+ sprintf("%10.2f", $balance_due ) ];
+
+ #create the template
+ $template ||= $self->_agent_template;
+ my $templatefile = 'invoice_template';
+ $templatefile .= "_$template" if length($template);
+ my @invoice_template = $conf->config($templatefile)
+ or die "cannot load config file $templatefile";
+ $invoice_lines = 0;
+ my $wasfunc = 0;
+ foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
+ /invoice_lines\((\d*)\)/;
+ $invoice_lines += $1 || scalar(@buf);
+ $wasfunc=1;
+ }
+ die "no invoice_lines() functions in template?" unless $wasfunc;
+ my $invoice_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @invoice_template ],
+ ) or die "can't create new Text::Template object: $Text::Template::ERROR";
+ $invoice_template->compile()
+ or die "can't compile template: $Text::Template::ERROR";
+
+ #setup template variables
+ package FS::cust_bill::_template; #!
+ use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent );
+
+ $invnum = $self->invnum;
+ $date = $self->_date;
+ $page = 1;
+ $agent = $self->cust_main->agent->agent;
+
+ if ( $FS::cust_bill::invoice_lines ) {
+ $total_pages =
+ int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
+ $total_pages++
+ if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
+ } else {
+ $total_pages = 1;
+ }
+
+ #format address (variable for the template)
+ my $l = 0;
+ @address = ( '', '', '', '', '', '' );
+ package FS::cust_bill; #!
+ $FS::cust_bill::_template::address[$l++] =
+ $cust_main->payname.
+ ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
+ ? " (P.O. #". $cust_main->payinfo. ")"
+ : ''
+ )
+ ;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->company
+ if $cust_main->company;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->address2
+ if $cust_main->address2;
+ $FS::cust_bill::_template::address[$l++] =
+ $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
+ $FS::cust_bill::_template::address[$l++] = $cust_main->country
+ unless $cust_main->country eq 'US';
+
+ # #overdue? (variable for the template)
+ # $FS::cust_bill::_template::overdue = (
+ # $balance_due > 0
+ # && $today > $self->_date
+ ## && $self->printed > 1
+ # && $self->printed > 0
+ # );
+
+ #and subroutine for the template
+ sub FS::cust_bill::_template::invoice_lines {
+ my $lines = shift || scalar(@buf);
+ map {
+ scalar(@buf) ? shift @buf : [ '', '' ];
+ }
+ ( 1 .. $lines );
+ }
+
+ #and fill it in
+ $FS::cust_bill::_template::page = 1;
+ my $lines;
+ my @collect;
+ while (@buf) {
+ push @collect, split("\n",
+ $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
+ );
+ $FS::cust_bill::_template::page++;
+ }
+
+ map "$_\n", @collect;
+
+}
+
+=item print_latex [ TIME [ , TEMPLATE ] ]
+
+Internal method - returns a filename of a filled-in LaTeX template for this
+invoice (Note: add ".tex" to get the actual filename).
+
+See print_ps and print_pdf for methods that return PostScript and PDF output.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+#still some false laziness w/print_text
+sub print_latex {
+
+ my( $self, $today, $template ) = @_;
+ $today ||= time;
+
+# my $invnum = $self->invnum;
+ my $cust_main = $self->cust_main;
+ $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
+ unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/;
+
+ my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
+ #my $balance_due = $self->owed + $pr_total - $cr_total;
+ my $balance_due = $self->owed + $pr_total;
+
+ #my @collect = ();
+ #my($description,$amount);
+ @buf = ();
+
+ #create the template
+ $template ||= $self->_agent_template;
+ my $templatefile = 'invoice_latex';
+ my $suffix = length($template) ? "_$template" : '';
+ $templatefile .= $suffix;
+ my @invoice_template = $conf->config($templatefile)
+ or die "cannot load config file $templatefile";
+
+ my %invoice_data = (
+ 'invnum' => $self->invnum,
+ 'date' => time2str('%b %o, %Y', $self->_date),
+ 'agent' => _latex_escape($cust_main->agent->agent),
+ 'payname' => _latex_escape($cust_main->payname),
+ 'company' => _latex_escape($cust_main->company),
+ 'address1' => _latex_escape($cust_main->address1),
+ 'address2' => _latex_escape($cust_main->address2),
+ 'city' => _latex_escape($cust_main->city),
+ 'state' => _latex_escape($cust_main->state),
+ 'zip' => _latex_escape($cust_main->zip),
+ 'country' => _latex_escape($cust_main->country),
+ 'footer' => join("\n", $conf->config('invoice_latexfooter') ),
+ 'smallfooter' => join("\n", $conf->config('invoice_latexsmallfooter') ),
+ 'quantity' => 1,
+ 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt',
+ #'notes' => join("\n", $conf->config('invoice_latexnotes') ),
+ );
+
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ $invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault;
+
+ #do variable substitutions in notes
+ $invoice_data{'notes'} =
+ join("\n",
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ $conf->config_orbase('invoice_latexnotes', $suffix)
+ );
+
+ $invoice_data{'footer'} =~ s/\n+$//;
+ $invoice_data{'smallfooter'} =~ s/\n+$//;
+ $invoice_data{'notes'} =~ s/\n+$//;
+
+ $invoice_data{'po_line'} =
+ ( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
+ ? _latex_escape("Purchase Order #". $cust_main->payinfo)
+ : '~';
+
+ my @line_item = ();
+ my @total_item = ();
+ my @filled_in = ();
+ while ( @invoice_template ) {
+ my $line = shift @invoice_template;
+
+ if ( $line =~ /^%%Detail\s*$/ ) {
+
+ while ( ( my $line_item_line = shift @invoice_template )
+ !~ /^%%EndDetail\s*$/ ) {
+ push @line_item, $line_item_line;
+ }
+ foreach my $line_item ( $self->_items ) {
+ #foreach my $line_item ( $self->_items_pkg ) {
+ $invoice_data{'ref'} = $line_item->{'pkgnum'};
+ $invoice_data{'description'} = _latex_escape($line_item->{'description'});
+ if ( exists $line_item->{'ext_description'} ) {
+ $invoice_data{'description'} .=
+ "\\tabularnewline\n~~".
+ join("\\tabularnewline\n~~", map { _latex_escape($_) } @{$line_item->{'ext_description'}} );
+ }
+ $invoice_data{'amount'} = $line_item->{'amount'};
+ $invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
+ push @filled_in,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } @line_item;
+ }
+
+ } elsif ( $line =~ /^%%TotalDetails\s*$/ ) {
+
+ while ( ( my $total_item_line = shift @invoice_template )
+ !~ /^%%EndTotalDetails\s*$/ ) {
+ push @total_item, $total_item_line;
+ }
+
+ my @total_fill = ();
+
+ my $taxtotal = 0;
+ foreach my $tax ( $self->_items_tax ) {
+ $invoice_data{'total_item'} = _latex_escape($tax->{'description'});
+ $taxtotal += ( $invoice_data{'total_amount'} = $tax->{'amount'} );
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
+ if ( $taxtotal ) {
+ $invoice_data{'total_item'} = 'Sub-total';
+ $invoice_data{'total_amount'} =
+ '\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
+ unshift @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
+ $invoice_data{'total_item'} = '\textbf{Total}';
+ $invoice_data{'total_amount'} =
+ '\textbf{\dollar '. sprintf('%.2f', $self->charged + $pr_total ). '}';
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+
+ #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+
+ # credits
+ foreach my $credit ( $self->_items_credits ) {
+ $invoice_data{'total_item'} = _latex_escape($credit->{'description'});
+ #$credittotal
+ $invoice_data{'total_amount'} = '-\dollar '. $credit->{'amount'};
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
+ # payments
+ foreach my $payment ( $self->_items_payments ) {
+ $invoice_data{'total_item'} = _latex_escape($payment->{'description'});
+ #$paymenttotal
+ $invoice_data{'total_amount'} = '-\dollar '. $payment->{'amount'};
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
+ $invoice_data{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
+ $invoice_data{'total_amount'} =
+ '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+
+ push @filled_in, @total_fill;
+
+ } else {
+ #$line =~ s/\$(\w+)/$invoice_data{$1}/eg;
+ $line =~ s/\$(\w+)/exists($invoice_data{$1}) ? $invoice_data{$1} : nounder($1)/eg;
+ push @filled_in, $line;
+ }
+
+ }
+
+ sub nounder {
+ my $var = $1;
+ $var =~ s/_/\-/g;
+ $var;
+ }
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX',
+ DIR => $dir,
+ SUFFIX => '.tex',
+ UNLINK => 0,
+ ) or die "can't open temp file: $!\n";
+ print $fh join("\n", @filled_in ), "\n";
+ close $fh;
+
+ $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename;
+ return $1;
+
+}
+
+=item print_ps [ TIME [ , TEMPLATE ] ]
+
+Returns an postscript invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_ps {
+ my $self = shift;
+
+ my $file = $self->print_latex(@_);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ chdir($dir);
+
+ my $sfile = shell_quote $file;
+
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed: $!";
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed: $!";
+
+ system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
+ or die "dvips failed: $!";
+
+ open(POSTSCRIPT, "<$file.ps")
+ or die "can't open $file.ps: $! (error in LaTeX template?)\n";
+
+ unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex");
+
+ my $ps = '';
+ while (<POSTSCRIPT>) {
+ $ps .= $_;
+ }
+
+ close POSTSCRIPT;
+
+ return $ps;
+
+}
+
+=item print_pdf [ TIME [ , TEMPLATE ] ]
+
+Returns an PDF invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_pdf {
+ my $self = shift;
+
+ my $file = $self->print_latex(@_);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ chdir($dir);
+
+ #system('pdflatex', "$file.tex");
+ #system('pdflatex', "$file.tex");
+ #! LaTeX Error: Unknown graphics extension: .eps.
+
+ my $sfile = shell_quote $file;
+
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed: $!";
+ system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+ or die "pslatex $file.tex failed: $!";
+
+ #system('dvipdf', "$file.dvi", "$file.pdf" );
+ system(
+ "dvips -q -t letter -f $sfile.dvi ".
+ "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ".
+ " -c save pop -"
+ ) == 0
+ or die "dvips | gs failed: $!";
+
+ open(PDF, "<$file.pdf")
+ or die "can't open $file.pdf: $! (error in LaTeX template?)\n";
+
+ unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex");
+
+ my $pdf = '';
+ while (<PDF>) {
+ $pdf .= $_;
+ }
+
+ close PDF;
+
+ return $pdf;
+
+}
+
+# quick subroutine for print_latex
+#
+# There are ten characters that LaTeX treats as special characters, which
+# means that they do not simply typeset themselves:
+# # $ % & ~ _ ^ \ { }
+#
+# TeX ignores blanks following an escaped character; if you want a blank (as
+# in "10% of ..."), you have to "escape" the blank as well ("10\%\ of ...").
+
+sub _latex_escape {
+ my $value = shift;
+ $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( length($2) ? "\\$2" : '' )/ge;
+ $value;
+}
+
+#utility methods for print_*
+
+sub balance_due_msg {
+ my $self = shift;
+ my $msg = 'Balance Due';
+ return $msg unless $conf->exists('invoice_default_terms');
+ if ( $conf->config('invoice_default_terms') =~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $msg .= ' - Please pay by '. time2str("%x", $self->_date + ($1*86400) );
+ } elsif ( $conf->config('invoice_default_terms') ) {
+ $msg .= ' - '. $conf->config('invoice_default_terms');
+ }
+ $msg;
+}
+
+sub _items {
+ my $self = shift;
+ my @display = scalar(@_)
+ ? @_
+ : qw( _items_previous _items_pkg );
+ #: qw( _items_pkg );
+ #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
+ my @b = ();
+ foreach my $display ( @display ) {
+ push @b, $self->$display(@_);
+ }
+ @b;
+}
+
+sub _items_previous {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+ my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+ my @b = ();
+ foreach ( @pr_cust_bill ) {
+ push @b, {
+ 'description' => 'Previous Balance, Invoice #'. $_->invnum.
+ ' ('. time2str('%x',$_->_date). ')',
+ #'pkgpart' => 'N/A',
+ 'pkgnum' => 'N/A',
+ 'amount' => sprintf("%10.2f", $_->owed),
+ };
+ }
+ @b;
+
+ #{
+ # 'description' => 'Previous Balance',
+ # #'pkgpart' => 'N/A',
+ # 'pkgnum' => 'N/A',
+ # 'amount' => sprintf("%10.2f", $pr_total ),
+ # 'ext_description' => [ map {
+ # "Invoice ". $_->invnum.
+ # " (". time2str("%x",$_->_date). ") ".
+ # sprintf("%10.2f", $_->owed)
+ # } @pr_cust_bill ],
+
+ #};
+}
+
+sub _items_pkg {
+ my $self = shift;
+ my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg;
+ $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+}
+
+sub _items_tax {
+ my $self = shift;
+ my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg;
+ $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+}
+
+sub _items_cust_bill_pkg {
+ my $self = shift;
+ my $cust_bill_pkg = shift;
+
+ my @b = ();
+ foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+
+ if ( $cust_bill_pkg->pkgnum ) {
+
+ my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } );
+ my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
+ my $pkg = $part_pkg->pkg;
+
+ my %labels;
+ #tie %labels, 'Tie::IxHash';
+ push @{ $labels{$_->[0]} }, $_->[1] foreach $cust_pkg->labels;
+ my @ext_description;
+ foreach my $label ( keys %labels ) {
+ my @values = @{ $labels{$label} };
+ my $num = scalar(@values);
+ if ( $num > 5 ) {
+ push @ext_description, "$label ($num)";
+ } else {
+ push @ext_description, map { "$label: $_" } @values;
+ }
+ }
+
+ if ( $cust_bill_pkg->setup != 0 ) {
+ my $description = $pkg;
+ $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+ my @d = @ext_description;
+ push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
+ push @b, {
+ 'description' => $description,
+ #'pkgpart' => $part_pkg->pkgpart,
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup),
+ 'ext_description' => \@d,
+ };
+ }
+
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @b, {
+ 'description' => "$pkg (" .
+ time2str('%x', $cust_bill_pkg->sdate). ' - '.
+ time2str('%x', $cust_bill_pkg->edate). ')',
+ #'pkgpart' => $part_pkg->pkgpart,
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur),
+ 'ext_description' => [ @ext_description,
+ $cust_bill_pkg->details,
+ ],
+ };
+ }
+
+ } else { #pkgnum tax or one-shot line item (??)
+
+ my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+ ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+ : 'Tax';
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @b, {
+ 'description' => $itemdesc,
+ 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup),
+ };
+ }
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @b, {
+ 'description' => "$itemdesc (".
+ time2str("%x", $cust_bill_pkg->sdate). ' - '.
+ time2str("%x", $cust_bill_pkg->edate). ')',
+ 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur),
+ };
+ }
+
+ }
+
+ }
+
+ @b;
+
+}
+
+sub _items_credits {
+ my $self = shift;
+
+ my @b;
+ #credits
+ foreach ( $self->cust_credited ) {
+
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+ my $reason = $_->cust_credit->reason;
+ #my $reason = substr($_->cust_credit->reason,0,32);
+ #$reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ $reason = " ($reason) " if $reason;
+ push @b, {
+ #'description' => 'Credit ref\#'. $_->crednum.
+ # " (". time2str("%x",$_->cust_credit->_date) .")".
+ # $reason,
+ 'description' => 'Credit applied '.
+ time2str("%x",$_->cust_credit->_date). $reason,
+ 'amount' => sprintf("%10.2f",$_->amount),
+ };
+ }
+ #foreach ( @cr_cust_credit ) {
+ # push @buf,[
+ # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+ # $money_char. sprintf("%10.2f",$_->credited)
+ # ];
+ #}
+
+ @b;
+
+}
+
+sub _items_payments {
+ my $self = shift;
+
+ my @b;
+ #get & print payments
+ foreach ( $self->cust_bill_pay ) {
+
+ #something more elaborate if $_->amount ne ->cust_pay->paid ?
+
+ push @b, {
+ 'description' => "Payment received ".
+ time2str("%x",$_->cust_pay->_date ),
+ 'amount' => sprintf("%10.2f", $_->amount )
+ };
+ }
+
+ @b;
+
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+print_text formatting (and some logic :/) is in source, but needs to be
+slurped in from a file. Also number of lines ($=).
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,
+L<FS::cust_bill_pkg>, L<FS::cust_bill_credit>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_event.pm b/FS/FS/cust_bill_event.pm
new file mode 100644
index 0000000..ddd6762
--- /dev/null
+++ b/FS/FS/cust_bill_event.pm
@@ -0,0 +1,180 @@
+package FS::cust_bill_event;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_bill;
+use FS::part_bill_event;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_event - Object methods for cust_bill_event records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_event;
+
+ $record = new FS::cust_bill_event \%hash;
+ $record = new FS::cust_bill_event { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_event object represents an complete invoice event.
+FS::cust_bill_event inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item eventnum - primary key
+
+=item invnum - invoice (see L<FS::cust_bill>)
+
+=item eventpart - event definition (see L<FS::part_bill_event>)
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item status - event status: B<done> or B<failed>
+
+=item statustext - additional status detail (i.e. error message)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new completed invoice event. To add the compelted invoice event to
+the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_bill_event'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid completed invoice event. If
+there is an error, returns the error, otherwise returns false. Called by the
+insert and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error = $self->ut_numbern('eventnum')
+ || $self->ut_number('invnum')
+ || $self->ut_number('eventpart')
+ || $self->ut_number('_date')
+ || $self->ut_enum('status', [qw( done failed )])
+ || $self->ut_textn('statustext')
+ ;
+
+ return "Unknown invnum ". $self->invnum
+ unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
+
+ return "Unknown eventpart ". $self->eventpart
+ unless qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
+
+ $self->SUPER::check;
+}
+
+=item part_bill_event
+
+Returns the invoice event definition (see L<FS::part_bill_event>) for this
+completed invoice event.
+
+=cut
+
+sub part_bill_event {
+ my $self = shift;
+ qsearchs( 'part_bill_event', { 'eventpart' => $self->eventpart } );
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>) for this completed invoice event.
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=item retry
+
+Changes the status of this event from B<done> to B<failed>, allowing it to be
+retried.
+
+=cut
+
+sub retry {
+ my $self = shift;
+ return '' unless $self->status eq 'done';
+ my $old = ref($self)->new( { $self->hash } );
+ $self->status('failed');
+ $self->replace($old);
+}
+
+=back
+
+=head1 BUGS
+
+Far too early in the morning.
+
+=head1 SEE ALSO
+
+L<FS::part_bill_event>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm
new file mode 100644
index 0000000..7abbe9a
--- /dev/null
+++ b/FS/FS/cust_bill_pay.pm
@@ -0,0 +1,176 @@
+package FS::cust_bill_pay;
+
+use strict;
+use vars qw( @ISA $conf );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_bill;
+use FS::cust_pay;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
+=head1 NAME
+
+FS::cust_bill_pay - Object methods for cust_bill_pay records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pay;
+
+ $record = new FS::cust_bill_pay \%hash;
+ $record = new FS::cust_bill_pay { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pay object represents the application of a payment to a
+specific invoice. FS::cust_bill_pay inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item billpaynum - primary key (assigned automatically)
+
+=item invnum - Invoice (see L<FS::cust_bill>)
+
+=item paynum - Payment (see L<FS::cust_pay>)
+
+=item amount - Amount of the payment to apply to the specific invoice.
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_bill_pay'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this payment application, unless the closed flag for the parent payment
+(see L<FS::cust_pay>) is set.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete application for closed payment"
+ if $self->cust_pay->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub replace {
+ return "Can't (yet?) modify cust_bill_pay records!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid payment. If there is an error,
+returns the error, otherwise returns false. Called by the insert method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('billpaynum')
+ || $self->ut_number('invnum')
+ || $self->ut_number('paynum')
+ || $self->ut_money('amount')
+ || $self->ut_numbern('_date')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ return "Unknown invoice"
+ unless my $cust_bill =
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+
+ return "Unknown payment"
+ unless my $cust_pay =
+ qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
+
+ $self->_date(time) unless $self->_date;
+
+ return "Cannot apply more than remaining value of invoice"
+ unless $self->amount <= $cust_bill->owed;
+
+ return "Cannot apply more than remaining value of payment"
+ unless $self->amount <= $cust_pay->unapplied;
+
+ $self->SUPER::check;
+}
+
+=item cust_pay
+
+Returns the payment (see L<FS::cust_pay>)
+
+=cut
+
+sub cust_pay {
+ my $self = shift;
+ qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>)
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods.
+
+the checks for over-applied payments could be better done like the ones in
+cust_bill_credit
+
+=head1 SEE ALSO
+
+L<FS::cust_pay>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
new file mode 100644
index 0000000..6800707
--- /dev/null
+++ b/FS/FS/cust_bill_pkg.pm
@@ -0,0 +1,215 @@
+package FS::cust_bill_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbdef dbh );
+use FS::cust_pkg;
+use FS::cust_bill;
+use FS::cust_bill_pkg_detail;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_bill_pkg - Object methods for cust_bill_pkg records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg;
+
+ $record = new FS::cust_bill_pkg \%hash;
+ $record = new FS::cust_bill_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg object represents an invoice line item.
+FS::cust_bill_pkg inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item invnum - invoice (see L<FS::cust_bill>)
+
+=item pkgnum - package (see L<FS::cust_pkg>) or 0 for the special virtual sales tax package
+
+=item setup - setup fee
+
+=item recur - recurring fee
+
+=item sdate - starting date of recurring fee
+
+=item edate - ending date of recurring fee
+
+=item itemdesc - Line item description (currentlty used only when pkgnum is 0)
+
+=back
+
+sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
+see L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new line item. To add the line item to the database, see
+L<"insert">. Line items are normally created by calling the bill method of a
+customer object (see L<FS::cust_main>).
+
+=cut
+
+sub table { 'cust_bill_pkg'; }
+
+=item insert
+
+Adds this line item to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ unless ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+ }
+
+ foreach my $detail ( @{$self->get('details')} ) {
+ my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
+ 'pkgnum' => $self->pkgnum,
+ 'invnum' => $self->invnum,
+ 'detail' => $detail,
+ };
+ $error = $cust_bill_pkg_detail->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item delete
+
+Currently unimplemented. I don't remove line items because there would then be
+no record the items ever existed (which is bad, no?)
+
+=cut
+
+sub delete {
+ return "Can't delete cust_bill_pkg records!";
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented. This would be even more of an accounting nightmare
+than deleteing the items. Just don't do it.
+
+=cut
+
+sub replace {
+ return "Can't modify cust_bill_pkg records!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid line item. If there is an
+error, returns the error, otherwise returns false. Called by the insert
+method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_number('pkgnum')
+ || $self->ut_number('invnum')
+ || $self->ut_money('setup')
+ || $self->ut_money('recur')
+ || $self->ut_numbern('sdate')
+ || $self->ut_numbern('edate')
+ || $self->ut_textn('itemdesc')
+ ;
+ return $error if $error;
+
+ if ( $self->pkgnum != 0 ) { #allow unchecked pkgnum 0 for tax! (add to part_pkg?)
+ return "Unknown pkgnum ". $self->pkgnum
+ unless qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+ }
+
+ return "Unknown invnum"
+ unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
+
+ $self->SUPER::check;
+}
+
+=item cust_pkg
+
+Returns the package (see L<FS::cust_pkg>) for this invoice line item.
+
+=cut
+
+sub cust_pkg {
+ my $self = shift;
+ qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item details
+
+Returns an array of detail information for the invoice line item.
+
+=cut
+
+sub details {
+ my $self = shift;
+ return () unless defined dbdef->table('cust_bill_pkg_detail');
+ map { $_->detail }
+ qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum,
+ 'invnum' => $self->invnum, } );
+ #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_bill>, L<FS::cust_pkg>, L<FS::cust_main>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
new file mode 100644
index 0000000..261aa80
--- /dev/null
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -0,0 +1,124 @@
+package FS::cust_bill_pkg_detail;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pkg_detail - Object methods for cust_bill_pkg_detail records
+
+=head1 SYNOPSIS
+
+ use FS::cust_bill_pkg_detail;
+
+ $record = new FS::cust_bill_pkg_detail \%hash;
+ $record = new FS::cust_bill_pkg_detail { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_detail object represents additional detail information for
+an invoice line item (see L<FS::cust_bill_pkg>). FS::cust_bill_pkg_detail
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item detailnum - primary key
+
+=item pkgnum -
+
+=item invnum -
+
+=item detail - detail description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new line item detail. To add the line item detail to the database,
+see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_bill_pkg_detail'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid line item detail. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('detailnum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_key('invnum', 'cust_pkg', 'invnum')
+ || $self->ut_text('detail')
+ || $self->SUPER::check
+ ;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
new file mode 100644
index 0000000..8ec255b
--- /dev/null
+++ b/FS/FS/cust_credit.pm
@@ -0,0 +1,330 @@
+package FS::cust_credit;
+
+use strict;
+use vars qw( @ISA $conf $unsuspendauto );
+use Date::Format;
+use FS::UID qw( dbh getotaker );
+use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw(send_email);
+use FS::cust_main;
+use FS::cust_refund;
+use FS::cust_credit_bill;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::cust_credit'} = sub {
+
+ $conf = new FS::Conf;
+ $unsuspendauto = $conf->exists('unsuspendauto');
+
+};
+
+=head1 NAME
+
+FS::cust_credit - Object methods for cust_credit records
+
+=head1 SYNOPSIS
+
+ use FS::cust_credit;
+
+ $record = new FS::cust_credit \%hash;
+ $record = new FS::cust_credit { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit object represents a credit; the equivalent of a negative
+B<cust_bill> record (see L<FS::cust_bill>). FS::cust_credit inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item crednum - primary key (assigned automatically for new credits)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item amount - amount of the credit
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=item reason - text
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new credit. To add the credit to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_credit'; }
+
+=item insert
+
+Adds this credit to the database ("Posts" the credit). If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $cust_main = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+ my $old_balance = $cust_main->balance;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting $self: $error";
+ }
+
+ #false laziness w/ cust_credit::insert
+ if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
+ my @errors = $cust_main->unsuspend;
+ #return
+ # side-fx with nested transactions? upstack rolls back?
+ warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
+ join(' / ', @errors)
+ if @errors;
+ }
+ #eslaf
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Currently unimplemented.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed credit" if $self->closed =~ /^Y/i;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_credit_bill ( $self->cust_credit_bill ) {
+ my $error = $cust_credit_bill->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $conf->config('deletecredits') ne '' ) {
+
+ my $cust_main = qsearchs('cust_main',{ 'custnum' => $self->custnum });
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'), #??? well as good as any
+ 'to' => $conf->config('deletecredits'),
+ 'subject' => 'FREESIDE NOTIFICATION: Credit deleted',
+ 'body' => [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you that the following credit has been deleted:\n",
+ "\n",
+ 'crednum: '. $self->crednum. "\n",
+ 'custnum: '. $self->custnum.
+ " (". $cust_main->last. ", ". $cust_main->first. ")\n",
+ 'amount: $'. sprintf("%.2f", $self->amount). "\n",
+ 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
+ 'reason: '. $self->reason. "\n",
+ ],
+ );
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't send credit deletion notification: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace OLD_RECORD
+
+Credits may not be modified; there would then be no record the credit was ever
+posted.
+
+=cut
+
+sub replace {
+ #return "Can't modify credit!"
+ my $self = shift;
+ return "Can't modify closed credit" if $self->closed =~ /^Y/i;
+ $self->SUPER::replace(@_);
+}
+
+=item check
+
+Checks all fields to make sure this is a valid credit. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('crednum')
+ || $self->ut_number('custnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ || $self->ut_textn('reason')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "amount must be > 0 " if $self->amount <= 0;
+
+ return "Unknown customer"
+ unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ $self->otaker(getotaker);
+
+ $self->SUPER::check;
+}
+
+=item cust_refund
+
+Depreciated. See the cust_credit_refund method.
+
+#Returns all refunds (see L<FS::cust_refund>) for this credit.
+
+=cut
+
+sub cust_refund {
+ use Carp;
+ croak "FS::cust_credit->cust_pay depreciated; see ".
+ "FS::cust_credit->cust_credit_refund";
+ #my $self = shift;
+ #sort { $a->_date <=> $b->_date }
+ # qsearch( 'cust_refund', { 'crednum' => $self->crednum } )
+ #;
+}
+
+=item cust_credit_refund
+
+Returns all refund applications (see L<FS::cust_credit_refund>) for this credit.
+
+=cut
+
+sub cust_credit_refund {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_refund', { 'crednum' => $self->crednum } )
+ ;
+}
+
+=item cust_credit_bill
+
+Returns all application to invoices (see L<FS::cust_credit_bill>) for this
+credit.
+
+=cut
+
+sub cust_credit_bill {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'crednum' => $self->crednum } )
+ ;
+}
+
+=item credited
+
+Returns the amount of this credit that is still outstanding; which is
+amount minus all refund applications (see L<FS::cust_credit_refund>) and
+applications to invoices (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub credited {
+ my $self = shift;
+ my $amount = $self->amount;
+ $amount -= $_->amount foreach ( $self->cust_credit_refund );
+ $amount -= $_->amount foreach ( $self->cust_credit_bill );
+ sprintf( "%.2f", $amount );
+}
+
+=item cust_main
+
+Returns the customer (see L<FS::cust_main>) for this credit.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+
+=back
+
+=head1 BUGS
+
+The delete method. The replace method.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_credit_refund>, L<FS::cust_refund>,
+L<FS::cust_credit_bill> L<FS::cust_bill>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm
new file mode 100644
index 0000000..695df6e
--- /dev/null
+++ b/FS/FS/cust_credit_bill.pm
@@ -0,0 +1,177 @@
+package FS::cust_credit_bill;
+
+use strict;
+use vars qw( @ISA $conf );
+use FS::UID qw( getotaker );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+#use FS::cust_refund;
+use FS::cust_credit;
+use FS::cust_bill;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
+=head1 NAME
+
+FS::cust_credit_bill - Object methods for cust_credit_bill records
+
+=head1 SYNOPSIS
+
+ use FS::cust_credit_bill;
+
+ $record = new FS::cust_credit_bill \%hash;
+ $record = new FS::cust_credit_bill { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit_bill object represents application of a credit (see
+L<FS::cust_credit>) to an invoice (see L<FS::cust_bill>). FS::cust_credit_bill
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item creditbillnum - primary key
+
+=item crednum - credit being applied
+
+=item invnum - invoice to which credit is applied (see L<FS::cust_bill>)
+
+=item amount - amount of the credit applied
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cust_credit_bill. To add the cust_credit_bill to the database,
+see L<"insert">.
+
+=cut
+
+sub table { 'cust_credit_bill'; }
+
+=item insert
+
+Adds this cust_credit_bill to the database ("Posts" all or part of a credit).
+If there is an error, returns the error, otherwise returns false.
+
+=item delete
+
+Currently unimplemented.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete application for closed credit"
+ if $self->cust_credit->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Application of credits may not be modified.
+
+=cut
+
+sub replace {
+ return "Can't modify application of credit!"
+}
+
+=item check
+
+Checks all fields to make sure this is a valid credit application. If there
+is an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('creditbillnum')
+ || $self->ut_number('crednum')
+ || $self->ut_number('invnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ return "Unknown credit"
+ unless my $cust_credit =
+ qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
+
+ return "Unknown invoice"
+ unless my $cust_bill =
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ return "Cannot apply more than remaining value of credit"
+ unless $self->amount <= $cust_credit->credited;
+
+ return "Cannot apply more than remaining value of invoice"
+ unless $self->amount <= $cust_bill->owed;
+
+ $self->SUPER::check;
+}
+
+=item sub cust_credit
+
+Returns the credit (see L<FS::cust_credit>)
+
+=cut
+
+sub cust_credit {
+ my $self = shift;
+ qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>)
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, L<FS::cust_credit>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm
new file mode 100644
index 0000000..ff2454d
--- /dev/null
+++ b/FS/FS/cust_credit_refund.pm
@@ -0,0 +1,183 @@
+package FS::cust_credit_refund;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+#use FS::UID qw(getotaker);
+use FS::cust_credit;
+use FS::cust_refund;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_credit_refund - Object methods for cust_bill_pay records
+
+=head1 SYNOPSIS
+
+ use FS::cust_credit_refund;
+
+ $record = new FS::cust_credit_refund \%hash;
+ $record = new FS::cust_credit_refund { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit_refund represents the application of a refund to a specific
+credit. FS::cust_credit_refund inherits from FS::Record. The following fields
+are currently supported:
+
+=over 4
+
+=item creditrefundnum - primary key (assigned automatically)
+
+=item crednum - Credit (see L<FS::cust_credit>)
+
+=item refundnum - Refund (see L<FS::cust_refund>)
+
+=item amount - Amount of the refund to apply to the specific credit.
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_credit_refund'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error = $self->SUPER::insert;
+ return $error if $error;
+
+ '';
+}
+
+=item delete
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub delete {
+ return "Can't (yet?) delete cust_credit_refund records!";
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub replace {
+ return "Can't (yet?) modify cust_credit_refund records!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid refund application. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('creditrefundnum')
+ || $self->ut_number('crednum')
+ || $self->ut_number('refundnum')
+ || $self->ut_money('amount')
+ || $self->ut_numbern('_date')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ return "unknown cust_credit.crednum: ". $self->crednum
+ unless my $cust_credit =
+ qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
+
+ return "Unknown refund"
+ unless my $cust_refund =
+ qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ return "Cannot apply more than remaining value of credit"
+ unless $self->amount <= $cust_credit->credited;
+
+ return "Cannot apply more than remaining value of refund"
+ unless $self->amount <= $cust_refund->unapplied;
+
+ $self->SUPER::check;
+}
+
+=item cust_refund
+
+Returns the refund (see L<FS::cust_refund>)
+
+=cut
+
+sub cust_refund {
+ my $self = shift;
+ qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
+}
+
+=item cust_credit
+
+Returns the credit (see L<FS::cust_credit>)
+
+=cut
+
+sub cust_credit {
+ my $self = shift;
+ qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
+}
+
+=back
+
+=head1 VERSION
+
+$Id: cust_credit_refund.pm,v 1.11 2004-06-29 04:02:44 ivan Exp $
+
+=head1 BUGS
+
+Delete and replace methods.
+
+the checks for over-applied refunds could be better done like the ones in
+cust_bill_credit
+
+=head1 SEE ALSO
+
+L<FS::cust_credit>, L<FS::cust_refund>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
new file mode 100644
index 0000000..075cce3
--- /dev/null
+++ b/FS/FS/cust_main.pm
@@ -0,0 +1,3346 @@
+package FS::cust_main;
+
+use strict;
+use vars qw( @ISA $conf $DEBUG $import );
+use vars qw( $realtime_bop_decline_quiet ); #ugh
+use Safe;
+use Carp;
+BEGIN {
+ eval "use Time::Local;";
+ die "Time::Local minimum version 1.05 required with Perl versions before 5.6"
+ if $] < 5.006 && !defined($Time::Local::VERSION);
+ eval "use Time::Local qw(timelocal timelocal_nocheck);";
+}
+use Date::Format;
+#use Date::Manip;
+use String::Approx qw(amatch);
+use Business::CreditCard;
+use FS::UID qw( getotaker dbh );
+use FS::Record qw( qsearchs qsearch dbdef );
+use FS::Misc qw( send_email );
+use FS::cust_pkg;
+use FS::cust_bill;
+use FS::cust_bill_pkg;
+use FS::cust_pay;
+use FS::cust_pay_void;
+use FS::cust_credit;
+use FS::cust_refund;
+use FS::part_referral;
+use FS::cust_main_county;
+use FS::agent;
+use FS::cust_main_invoice;
+use FS::cust_credit_bill;
+use FS::cust_bill_pay;
+use FS::prepay_credit;
+use FS::queue;
+use FS::part_pkg;
+use FS::part_bill_event;
+use FS::cust_bill_event;
+use FS::cust_tax_exempt;
+use FS::type_pkgs;
+use FS::Msgcat qw(gettext);
+
+@ISA = qw( FS::Record );
+
+$realtime_bop_decline_quiet = 0;
+
+$DEBUG = 0;
+#$DEBUG = 1;
+
+$import = 0;
+
+#ask FS::UID to run this stuff for us later
+#$FS::UID::callback{'FS::cust_main'} = sub {
+install_callback FS::UID sub {
+ $conf = new FS::Conf;
+ #yes, need it for stuff below (prolly should be cached)
+};
+
+sub _cache {
+ my $self = shift;
+ my ( $hashref, $cache ) = @_;
+ if ( exists $hashref->{'pkgnum'} ) {
+# #@{ $self->{'_pkgnum'} } = ();
+ my $subcache = $cache->subcache( 'pkgnum', 'cust_pkg', $hashref->{custnum});
+ $self->{'_pkgnum'} = $subcache;
+ #push @{ $self->{'_pkgnum'} },
+ FS::cust_pkg->new_or_cached($hashref, $subcache) if $hashref->{pkgnum};
+ }
+}
+
+=head1 NAME
+
+FS::cust_main - Object methods for cust_main records
+
+=head1 SYNOPSIS
+
+ use FS::cust_main;
+
+ $record = new FS::cust_main \%hash;
+ $record = new FS::cust_main { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ @cust_pkg = $record->all_pkgs;
+
+ @cust_pkg = $record->ncancelled_pkgs;
+
+ @cust_pkg = $record->suspended_pkgs;
+
+ $error = $record->bill;
+ $error = $record->bill %options;
+ $error = $record->bill 'time' => $time;
+
+ $error = $record->collect;
+ $error = $record->collect %options;
+ $error = $record->collect 'invoice_time' => $time,
+ 'batch_card' => 'yes',
+ 'report_badcard' => 'yes',
+ ;
+
+=head1 DESCRIPTION
+
+An FS::cust_main object represents a customer. FS::cust_main inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item custnum - primary key (assigned automatically for new customers)
+
+=item agentnum - agent (see L<FS::agent>)
+
+=item refnum - Advertising source (see L<FS::part_referral>)
+
+=item first - name
+
+=item last - name
+
+=item ss - social security number (optional)
+
+=item company - (optional)
+
+=item address1
+
+=item address2 - (optional)
+
+=item city
+
+=item county - (optional, see L<FS::cust_main_county>)
+
+=item state - (see L<FS::cust_main_county>)
+
+=item zip
+
+=item country - (see L<FS::cust_main_county>)
+
+=item daytime - phone (optional)
+
+=item night - phone (optional)
+
+=item fax - phone (optional)
+
+=item ship_first - name
+
+=item ship_last - name
+
+=item ship_company - (optional)
+
+=item ship_address1
+
+=item ship_address2 - (optional)
+
+=item ship_city
+
+=item ship_county - (optional, see L<FS::cust_main_county>)
+
+=item ship_state - (see L<FS::cust_main_county>)
+
+=item ship_zip
+
+=item ship_country - (see L<FS::cust_main_county>)
+
+=item ship_daytime - phone (optional)
+
+=item ship_night - phone (optional)
+
+=item ship_fax - phone (optional)
+
+=item payby - I<CARD> (credit card - automatic), I<DCRD> (credit card - on-demand), I<CHEK> (electronic check - automatic), I<DCHK> (electronic check - on-demand), I<LECB> (Phone bill billing), I<BILL> (billing), I<COMP> (free), or I<PREPAY> (special billing type: applies a credit - see L<FS::prepay_credit> and sets billing type to I<BILL>)
+
+=item payinfo - card number, P.O., comp issuer (4-8 lowercase alphanumerics; think username) or prepayment identifier (see L<FS::prepay_credit>)
+
+=item paycvv - Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
+
+=item paydate - expiration date, mm/yyyy, m/yyyy, mm/yy or m/yy
+
+=item payname - name on card or billing name
+
+=item tax - tax exempt, empty or `Y'
+
+=item otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=item comments - comments (optional)
+
+=item referral_custnum - referring customer number
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new customer. To add the customer to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_main'; }
+
+=item insert [ CUST_PKG_HASHREF [ , INVOICING_LIST_ARYREF ] [ , OPTION => VALUE ... ] ]
+
+Adds this customer to the database. If there is an error, returns the error,
+otherwise returns false.
+
+CUST_PKG_HASHREF: If you pass a Tie::RefHash data structure to the insert
+method containing FS::cust_pkg and FS::svc_I<tablename> objects, all records
+are inserted atomicly, or the transaction is rolled back. Passing an empty
+hash reference is equivalent to not supplying this parameter. There should be
+a better explanation of this, but until then, here's an example:
+
+ use Tie::RefHash;
+ tie %hash, 'Tie::RefHash'; #this part is important
+ %hash = (
+ $cust_pkg => [ $svc_acct ],
+ ...
+ );
+ $cust_main->insert( \%hash );
+
+INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
+be set as the invoicing list (see L<"invoicing_list">). Errors return as
+expected and rollback the entire transaction; it is not necessary to call
+check_invoicing_list first. The invoicing_list is set after the records in the
+CUST_PKG_HASHREF above are inserted, so it is now possible to set an
+invoicing_list destination to the newly-created svc_acct. Here's an example:
+
+ $cust_main->insert( {}, [ $email, 'POST' ] );
+
+Currently available options are: I<depend_jobnum> and I<noexport>.
+
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
+
+The I<noexport> option is deprecated. If I<noexport> is set true, no
+provisioning jobs (exports) are scheduled. (You can schedule them later with
+the B<reexport> method.)
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $cust_pkgs = @_ ? shift : {};
+ my $invoicing_list = @_ ? shift : '';
+ my %options = @_;
+ warn "FS::cust_main::insert called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $amount = 0;
+ my $seconds = 0;
+ if ( $self->payby eq 'PREPAY' ) {
+ $self->payby('BILL');
+ my $prepay_credit = qsearchs(
+ 'prepay_credit',
+ { 'identifier' => $self->payinfo },
+ '',
+ 'FOR UPDATE'
+ );
+ warn "WARNING: can't find pre-found prepay_credit: ". $self->payinfo
+ unless $prepay_credit;
+ $amount = $prepay_credit->amount;
+ $seconds = $prepay_credit->seconds;
+ my $error = $prepay_credit->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "removing prepay_credit (transaction rolled back): $error";
+ }
+ }
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "inserting cust_main record (transaction rolled back): $error";
+ return $error;
+ }
+
+ # invoicing list
+ if ( $invoicing_list ) {
+ $error = $self->check_invoicing_list( $invoicing_list );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "checking invoicing_list (transaction rolled back): $error";
+ }
+ $self->invoicing_list( $invoicing_list );
+ }
+
+ # packages
+ $error = $self->order_pkgs($cust_pkgs, \$seconds, %options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $seconds ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "No svc_acct record to apply pre-paid time";
+ }
+
+ if ( $amount ) {
+ my $cust_credit = new FS::cust_credit {
+ 'custnum' => $self->custnum,
+ 'amount' => $amount,
+ };
+ $error = $cust_credit->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting credit (transaction rolled back): $error";
+ }
+ }
+
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
+
+Like the insert method on an existing record, this method orders a package
+and included services atomicaly. Pass a Tie::RefHash data structure to this
+method containing FS::cust_pkg and FS::svc_I<tablename> objects. There should
+be a better explanation of this, but until then, here's an example:
+
+ use Tie::RefHash;
+ tie %hash, 'Tie::RefHash'; #this part is important
+ %hash = (
+ $cust_pkg => [ $svc_acct ],
+ ...
+ );
+ $cust_main->order_pkgs( \%hash, \'0', 'noexport'=>1 );
+
+Currently available options are: I<depend_jobnum> and I<noexport>.
+
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card sucessfully).
+
+The I<noexport> option is deprecated. If I<noexport> is set true, no
+provisioning jobs (exports) are scheduled. (You can schedule them later with
+the B<reexport> method for each cust_pkg object. Using the B<reexport> method
+on the cust_main object is not recommended, as existing services will also be
+reexported.)
+
+=cut
+
+sub order_pkgs {
+ my $self = shift;
+ my $cust_pkgs = shift;
+ my $seconds = shift;
+ my %options = @_;
+ my %svc_options = ();
+ $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
+ if exists $options{'depend_jobnum'};
+ warn "FS::cust_main::order_pkgs called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'};
+
+ foreach my $cust_pkg ( keys %$cust_pkgs ) {
+ $cust_pkg->custnum( $self->custnum );
+ my $error = $cust_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "inserting cust_pkg (transaction rolled back): $error";
+ }
+ foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) {
+ $svc_something->pkgnum( $cust_pkg->pkgnum );
+ if ( $seconds && $$seconds && $svc_something->isa('FS::svc_acct') ) {
+ $svc_something->seconds( $svc_something->seconds + $$seconds );
+ $$seconds = 0;
+ }
+ $error = $svc_something->insert(%svc_options);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ #return "inserting svc_ (transaction rolled back): $error";
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item reexport
+
+This method is deprecated. See the I<depend_jobnum> option to the insert and
+order_pkgs methods for a better way to defer provisioning.
+
+Re-schedules all exports by calling the B<reexport> method of all associated
+packages (see L<FS::cust_pkg>). If there is an error, returns the error;
+otherwise returns false.
+
+=cut
+
+sub reexport {
+ my $self = shift;
+
+ carp "warning: FS::cust_main::reexport is deprectated; ".
+ "use the depend_jobnum option to insert or order_pkgs to delay export";
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_pkg ( $self->ncancelled_pkgs ) {
+ my $error = $cust_pkg->reexport;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item delete NEW_CUSTNUM
+
+This deletes the customer. If there is an error, returns the error, otherwise
+returns false.
+
+This will completely remove all traces of the customer record. This is not
+what you want when a customer cancels service; for that, cancel all of the
+customer's packages (see L</cancel>).
+
+If the customer has any uncancelled packages, you need to pass a new (valid)
+customer number for those packages to be transferred to. Cancelled packages
+will be deleted. Did I mention that this is NOT what you want when a customer
+cancels service and that you really should be looking see L<FS::cust_pkg/cancel>?
+
+You can't delete a customer with invoices (see L<FS::cust_bill>),
+or credits (see L<FS::cust_credit>), payments (see L<FS::cust_pay>) or
+refunds (see L<FS::cust_refund>).
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $self->cust_bill ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't delete a customer with invoices";
+ }
+ if ( $self->cust_credit ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't delete a customer with credits";
+ }
+ if ( $self->cust_pay ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't delete a customer with payments";
+ }
+ if ( $self->cust_refund ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Can't delete a customer with refunds";
+ }
+
+ my @cust_pkg = $self->ncancelled_pkgs;
+ if ( @cust_pkg ) {
+ my $new_custnum = shift;
+ unless ( qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Invalid new customer number: $new_custnum";
+ }
+ foreach my $cust_pkg ( @cust_pkg ) {
+ my %hash = $cust_pkg->hash;
+ $hash{'custnum'} = $new_custnum;
+ my $new_cust_pkg = new FS::cust_pkg ( \%hash );
+ my $error = $new_cust_pkg->replace($cust_pkg);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+ my @cancelled_cust_pkg = $self->all_pkgs;
+ foreach my $cust_pkg ( @cancelled_cust_pkg ) {
+ my $error = $cust_pkg->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $cust_main_invoice ( #(email invoice destinations, not invoices)
+ qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } )
+ ) {
+ my $error = $cust_main_invoice->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item replace OLD_RECORD [ INVOICING_LIST_ARYREF ]
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
+be set as the invoicing list (see L<"invoicing_list">). Errors return as
+expected and rollback the entire transaction; it is not necessary to call
+check_invoicing_list first. Here's an example:
+
+ $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
+
+=cut
+
+sub replace {
+ my $self = shift;
+ my $old = shift;
+ my @param = @_;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ if ( $self->payby eq 'COMP' && $self->payby ne $old->payby
+ && $conf->config('users-allow_comp') ) {
+ return "You are not permitted to create complimentary accounts."
+ unless grep { $_ eq getotaker } $conf->config('users-allow_comp');
+ }
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::replace($old);
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( @param ) { # INVOICING_LIST_ARYREF
+ my $invoicing_list = shift @param;
+ $error = $self->check_invoicing_list( $invoicing_list );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->invoicing_list( $invoicing_list );
+ }
+
+ if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ &&
+ grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) {
+ # card/check/lec info has changed, want to retry realtime_ invoice events
+ my $error = $self->retry_realtime;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item queue_fuzzyfiles_update
+
+Used by insert & replace to update the fuzzy search cache
+
+=cut
+
+sub queue_fuzzyfiles_update {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
+ my $error = $queue->insert($self->getfield('last'), $self->company);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+
+ if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) {
+ $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
+ $error = $queue->insert($self->getfield('ship_last'), $self->ship_company);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid customer record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ #warn "BEFORE: \n". $self->_dump;
+
+ my $error =
+ $self->ut_numbern('custnum')
+ || $self->ut_number('agentnum')
+ || $self->ut_number('refnum')
+ || $self->ut_name('last')
+ || $self->ut_name('first')
+ || $self->ut_textn('company')
+ || $self->ut_text('address1')
+ || $self->ut_textn('address2')
+ || $self->ut_text('city')
+ || $self->ut_textn('county')
+ || $self->ut_textn('state')
+ || $self->ut_country('country')
+ || $self->ut_anything('comments')
+ || $self->ut_numbern('referral_custnum')
+ ;
+ #barf. need message catalogs. i18n. etc.
+ $error .= "Please select an advertising source."
+ if $error =~ /^Illegal or empty \(numeric\) refnum: /;
+ return $error if $error;
+
+ return "Unknown agent"
+ unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
+
+ return "Unknown refnum"
+ unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
+
+ return "Unknown referring custnum ". $self->referral_custnum
+ unless ! $self->referral_custnum
+ || qsearchs( 'cust_main', { 'custnum' => $self->referral_custnum } );
+
+ if ( $self->ss eq '' ) {
+ $self->ss('');
+ } else {
+ my $ss = $self->ss;
+ $ss =~ s/\D//g;
+ $ss =~ /^(\d{3})(\d{2})(\d{4})$/
+ or return "Illegal social security number: ". $self->ss;
+ $self->ss("$1-$2-$3");
+ }
+
+
+# bad idea to disable, causes billing to fail because of no tax rates later
+# unless ( $import ) {
+ unless ( qsearch('cust_main_county', {
+ 'country' => $self->country,
+ 'state' => '',
+ } ) ) {
+ return "Unknown state/county/country: ".
+ $self->state. "/". $self->county. "/". $self->country
+ unless qsearch('cust_main_county',{
+ 'state' => $self->state,
+ 'county' => $self->county,
+ 'country' => $self->country,
+ } );
+ }
+# }
+
+ $error =
+ $self->ut_phonen('daytime', $self->country)
+ || $self->ut_phonen('night', $self->country)
+ || $self->ut_phonen('fax', $self->country)
+ || $self->ut_zip('zip', $self->country)
+ ;
+ return $error if $error;
+
+ my @addfields = qw(
+ last first company address1 address2 city county state zip
+ country daytime night fax
+ );
+
+ if ( defined $self->dbdef_table->column('ship_last') ) {
+ if ( scalar ( grep { $self->getfield($_) ne $self->getfield("ship_$_") }
+ @addfields )
+ && scalar ( grep { $self->getfield("ship_$_") ne '' } @addfields )
+ )
+ {
+ my $error =
+ $self->ut_name('ship_last')
+ || $self->ut_name('ship_first')
+ || $self->ut_textn('ship_company')
+ || $self->ut_text('ship_address1')
+ || $self->ut_textn('ship_address2')
+ || $self->ut_text('ship_city')
+ || $self->ut_textn('ship_county')
+ || $self->ut_textn('ship_state')
+ || $self->ut_country('ship_country')
+ ;
+ return $error if $error;
+
+ #false laziness with above
+ unless ( qsearchs('cust_main_county', {
+ 'country' => $self->ship_country,
+ 'state' => '',
+ } ) ) {
+ return "Unknown ship_state/ship_county/ship_country: ".
+ $self->ship_state. "/". $self->ship_county. "/". $self->ship_country
+ unless qsearchs('cust_main_county',{
+ 'state' => $self->ship_state,
+ 'county' => $self->ship_county,
+ 'country' => $self->ship_country,
+ } );
+ }
+ #eofalse
+
+ $error =
+ $self->ut_phonen('ship_daytime', $self->ship_country)
+ || $self->ut_phonen('ship_night', $self->ship_country)
+ || $self->ut_phonen('ship_fax', $self->ship_country)
+ || $self->ut_zip('ship_zip', $self->ship_country)
+ ;
+ return $error if $error;
+
+ } else { # ship_ info eq billing info, so don't store dup info in database
+ $self->setfield("ship_$_", '')
+ foreach qw( last first company address1 address2 city county state zip
+ country daytime night fax );
+ }
+ }
+
+ $self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY)$/
+ or return "Illegal payby: ". $self->payby;
+ $self->payby($1);
+
+ if ( $self->payby eq 'CARD' || $self->payby eq 'DCRD' ) {
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^(\d{13,16})$/
+ or return gettext('invalid_card'); # . ": ". $self->payinfo;
+ $payinfo = $1;
+ $self->payinfo($payinfo);
+ validate($payinfo)
+ or return gettext('invalid_card'); # . ": ". $self->payinfo;
+ return gettext('unknown_card_type')
+ if cardtype($self->payinfo) eq "Unknown";
+ if ( defined $self->dbdef_table->column('paycvv') ) {
+ if ( length($self->paycvv) ) {
+ if ( cardtype($self->payinfo) eq 'American Express card' ) {
+ $self->paycvv =~ /^(\d{4})$/
+ or return "CVV2 (CID) for American Express cards is four digits.";
+ $self->paycvv($1);
+ } else {
+ $self->paycvv =~ /^(\d{3})$/
+ or return "CVV2 (CVC2/CID) is three digits.";
+ $self->paycvv($1);
+ }
+ } else {
+ $self->paycvv('');
+ }
+ }
+
+ } elsif ( $self->payby eq 'CHEK' || $self->payby eq 'DCHK' ) {
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/[^\d\@]//g;
+ $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba';
+ $payinfo = "$1\@$2";
+ $self->payinfo($payinfo);
+ $self->paycvv('') if $self->dbdef_table->column('paycvv');
+
+ } elsif ( $self->payby eq 'LECB' ) {
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number';
+ $payinfo = $1;
+ $self->payinfo($payinfo);
+ $self->paycvv('') if $self->dbdef_table->column('paycvv');
+
+ } elsif ( $self->payby eq 'BILL' ) {
+
+ $error = $self->ut_textn('payinfo');
+ return "Illegal P.O. number: ". $self->payinfo if $error;
+ $self->paycvv('') if $self->dbdef_table->column('paycvv');
+
+ } elsif ( $self->payby eq 'COMP' ) {
+
+ if ( !$self->custnum && $conf->config('users-allow_comp') ) {
+ return "You are not permitted to create complimentary accounts."
+ unless grep { $_ eq getotaker } $conf->config('users-allow_comp');
+ }
+
+ $error = $self->ut_textn('payinfo');
+ return "Illegal comp account issuer: ". $self->payinfo if $error;
+ $self->paycvv('') if $self->dbdef_table->column('paycvv');
+
+ } elsif ( $self->payby eq 'PREPAY' ) {
+
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\W//g; #anything else would just confuse things
+ $self->payinfo($payinfo);
+ $error = $self->ut_alpha('payinfo');
+ return "Illegal prepayment identifier: ". $self->payinfo if $error;
+ return "Unknown prepayment identifier"
+ unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
+ $self->paycvv('') if $self->dbdef_table->column('paycvv');
+
+ }
+
+ if ( $self->paydate eq '' || $self->paydate eq '-' ) {
+ return "Expriation date required"
+ unless $self->payby =~ /^(BILL|PREPAY|CHEK|LECB)$/;
+ $self->paydate('');
+ } else {
+ my( $m, $y );
+ if ( $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) {
+ ( $m, $y ) = ( $1, length($2) == 4 ? $2 : "20$2" );
+ } elsif ( $self->paydate =~ /^(20)?(\d{2})[\/\-](\d{1,2})[\/\-]\d+$/ ) {
+ ( $m, $y ) = ( $3, "20$2" );
+ } else {
+ return "Illegal expiration date: ". $self->paydate;
+ }
+ $self->paydate("$y-$m-01");
+ my($nowm,$nowy)=(localtime(time))[4,5]; $nowm++; $nowy+=1900;
+ return gettext('expired_card')
+ if !$import && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) );
+ }
+
+ if ( $self->payname eq '' && $self->payby !~ /^(CHEK|DCHK)$/ &&
+ ( ! $conf->exists('require_cardname')
+ || $self->payby !~ /^(CARD|DCRD)$/ )
+ ) {
+ $self->payname( $self->first. " ". $self->getfield('last') );
+ } else {
+ $self->payname =~ /^([\w \,\.\-\']+)$/
+ or return gettext('illegal_name'). " payname: ". $self->payname;
+ $self->payname($1);
+ }
+
+ $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax;
+ $self->tax($1);
+
+ $self->otaker(getotaker) unless $self->otaker;
+
+ #warn "AFTER: \n". $self->_dump;
+
+ $self->SUPER::check;
+}
+
+=item all_pkgs
+
+Returns all packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub all_pkgs {
+ my $self = shift;
+ if ( $self->{'_pkgnum'} ) {
+ values %{ $self->{'_pkgnum'}->cache };
+ } else {
+ qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
+ }
+}
+
+=item ncancelled_pkgs
+
+Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub ncancelled_pkgs {
+ my $self = shift;
+ if ( $self->{'_pkgnum'} ) {
+ grep { ! $_->getfield('cancel') } values %{ $self->{'_pkgnum'}->cache };
+ } else {
+ @{ [ # force list context
+ qsearch( 'cust_pkg', {
+ 'custnum' => $self->custnum,
+ 'cancel' => '',
+ }),
+ qsearch( 'cust_pkg', {
+ 'custnum' => $self->custnum,
+ 'cancel' => 0,
+ }),
+ ] };
+ }
+}
+
+=item suspended_pkgs
+
+Returns all suspended packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub suspended_pkgs {
+ my $self = shift;
+ grep { $_->susp } $self->ncancelled_pkgs;
+}
+
+=item unflagged_suspended_pkgs
+
+Returns all unflagged suspended packages (see L<FS::cust_pkg>) for this
+customer (thouse packages without the `manual_flag' set).
+
+=cut
+
+sub unflagged_suspended_pkgs {
+ my $self = shift;
+ return $self->suspended_pkgs
+ unless dbdef->table('cust_pkg')->column('manual_flag');
+ grep { ! $_->manual_flag } $self->suspended_pkgs;
+}
+
+=item unsuspended_pkgs
+
+Returns all unsuspended (and uncancelled) packages (see L<FS::cust_pkg>) for
+this customer.
+
+=cut
+
+sub unsuspended_pkgs {
+ my $self = shift;
+ grep { ! $_->susp } $self->ncancelled_pkgs;
+}
+
+=item unsuspend
+
+Unsuspends all unflagged suspended packages (see L</unflagged_suspended_pkgs>
+and L<FS::cust_pkg>) for this customer. Always returns a list: an empty list
+on success or a list of errors.
+
+=cut
+
+sub unsuspend {
+ my $self = shift;
+ grep { $_->unsuspend } $self->suspended_pkgs;
+}
+
+=item suspend
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) for this customer.
+Always returns a list: an empty list on success or a list of errors.
+
+=cut
+
+sub suspend {
+ my $self = shift;
+ grep { $_->suspend } $self->unsuspended_pkgs;
+}
+
+=item suspend_if_pkgpart PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) matching the listed
+PKGPARTs (see L<FS::part_pkg>). Always returns a list: an empty list on
+success or a list of errors.
+
+=cut
+
+sub suspend_if_pkgpart {
+ my $self = shift;
+ my @pkgparts = @_;
+ grep { $_->suspend }
+ grep { my $pkgpart = $_->pkgpart; grep { $pkgpart eq $_ } @pkgparts }
+ $self->unsuspended_pkgs;
+}
+
+=item suspend_unless_pkgpart PKGPART [ , PKGPART ... ]
+
+Suspends all unsuspended packages (see L<FS::cust_pkg>) unless they match the
+listed PKGPARTs (see L<FS::part_pkg>). Always returns a list: an empty list
+on success or a list of errors.
+
+=cut
+
+sub suspend_unless_pkgpart {
+ my $self = shift;
+ my @pkgparts = @_;
+ grep { $_->suspend }
+ grep { my $pkgpart = $_->pkgpart; ! grep { $pkgpart eq $_ } @pkgparts }
+ $self->unsuspended_pkgs;
+}
+
+=item cancel [ OPTION => VALUE ... ]
+
+Cancels all uncancelled packages (see L<FS::cust_pkg>) for this customer.
+
+Available options are: I<quiet>
+
+I<quiet> can be set true to supress email cancellation notices.
+
+Always returns a list: an empty list on success or a list of errors.
+
+=cut
+
+sub cancel {
+ my $self = shift;
+ grep { $_ } map { $_->cancel(@_) } $self->ncancelled_pkgs;
+}
+
+=item agent
+
+Returns the agent (see L<FS::agent>) for this customer.
+
+=cut
+
+sub agent {
+ my $self = shift;
+ qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
+}
+
+=item bill OPTIONS
+
+Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
+conjunction with the collect method.
+
+Options are passed as name-value pairs.
+
+Currently available options are:
+
+resetup - if set true, re-charges setup fees.
+
+time - bills the customer as if it were that time. Specified as a UNIX
+timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and
+L<Date::Parse> for conversion functions. For example:
+
+ use Date::Parse;
+ ...
+ $cust_main->bill( 'time' => str2time('April 20th, 2001') );
+
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub bill {
+ my( $self, %options ) = @_;
+ my $time = $options{'time'} || time;
+
+ my $error;
+
+ #put below somehow?
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $self->select_for_update; #mutex
+
+ # find the packages which are due for billing, find out how much they are
+ # & generate invoice database.
+
+ my( $total_setup, $total_recur ) = ( 0, 0 );
+ #my( $taxable_setup, $taxable_recur ) = ( 0, 0 );
+ my @cust_bill_pkg = ();
+ #my $tax = 0;##
+ #my $taxable_charged = 0;##
+ #my $charged = 0;##
+
+ my %tax;
+
+ foreach my $cust_pkg (
+ qsearch('cust_pkg', { 'custnum' => $self->custnum } )
+ ) {
+
+ #NO!! next if $cust_pkg->cancel;
+ next if $cust_pkg->getfield('cancel');
+
+ #? to avoid use of uninitialized value errors... ?
+ $cust_pkg->setfield('bill', '')
+ unless defined($cust_pkg->bill);
+
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ #so we don't modify cust_pkg record unnecessarily
+ my $cust_pkg_mod_flag = 0;
+ my %hash = $cust_pkg->hash;
+ my $old_cust_pkg = new FS::cust_pkg \%hash;
+
+ my @details = ();
+
+ # bill setup
+ my $setup = 0;
+ if ( !$cust_pkg->setup || $options{'resetup'} ) {
+ my $setup_prog = $part_pkg->getfield('setup');
+ $setup_prog =~ /^(.*)$/ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Illegal setup for pkgpart ". $part_pkg->pkgpart.
+ ": $setup_prog";
+ };
+ $setup_prog = $1;
+ $setup_prog = '0' if $setup_prog =~ /^\s*$/;
+
+ #my $cpt = new Safe;
+ ##$cpt->permit(); #what is necessary?
+ #$cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
+ #$setup = $cpt->reval($setup_prog);
+ $setup = eval $setup_prog;
+ unless ( defined($setup) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error eval-ing part_pkg->setup pkgpart ". $part_pkg->pkgpart.
+ "(expression $setup_prog): $@";
+ }
+ $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup;
+ $cust_pkg_mod_flag=1;
+ }
+
+ #bill recurring fee
+ my $recur = 0;
+ my $sdate;
+ if ( $part_pkg->getfield('freq') ne '0' &&
+ ! $cust_pkg->getfield('susp') &&
+ ( $cust_pkg->getfield('bill') || 0 ) <= $time
+ ) {
+ my $recur_prog = $part_pkg->getfield('recur');
+ $recur_prog =~ /^(.*)$/ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Illegal recur for pkgpart ". $part_pkg->pkgpart.
+ ": $recur_prog";
+ };
+ $recur_prog = $1;
+ $recur_prog = '0' if $recur_prog =~ /^\s*$/;
+
+ # shared with $recur_prog
+ $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+
+ #my $cpt = new Safe;
+ ##$cpt->permit(); #what is necessary?
+ #$cpt->share(qw( $cust_pkg )); #can $cpt now use $cust_pkg methods?
+ #$recur = $cpt->reval($recur_prog);
+ $recur = eval $recur_prog;
+ unless ( defined($recur) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error eval-ing part_pkg->recur pkgpart ". $part_pkg->pkgpart.
+ "(expression $recur_prog): $@";
+ }
+ #change this bit to use Date::Manip? CAREFUL with timezones (see
+ # mailing list archive)
+ my ($sec,$min,$hour,$mday,$mon,$year) =
+ (localtime($sdate) )[0,1,2,3,4,5];
+
+ #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
+ # only for figuring next bill date, nothing else, so, reset $sdate again
+ # here
+ $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+ $cust_pkg->last_bill($sdate)
+ if $cust_pkg->dbdef_table->column('last_bill');
+
+ if ( $part_pkg->freq =~ /^\d+$/ ) {
+ $mon += $part_pkg->freq;
+ until ( $mon < 12 ) { $mon -= 12; $year++; }
+ } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
+ my $weeks = $1;
+ $mday += $weeks * 7;
+ } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
+ my $days = $1;
+ $mday += $days;
+ } else {
+ $dbh->rollback if $oldAutoCommit;
+ return "unparsable frequency: ". $part_pkg->freq;
+ }
+ $cust_pkg->setfield('bill',
+ timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
+ $cust_pkg_mod_flag = 1;
+ }
+
+ warn "\$setup is undefined" unless defined($setup);
+ warn "\$recur is undefined" unless defined($recur);
+ warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
+
+ if ( $cust_pkg_mod_flag ) {
+ $error=$cust_pkg->replace($old_cust_pkg);
+ if ( $error ) { #just in case
+ $dbh->rollback if $oldAutoCommit;
+ return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error";
+ }
+ $setup = sprintf( "%.2f", $setup );
+ $recur = sprintf( "%.2f", $recur );
+ if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
+ }
+ if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
+ }
+ if ( $setup != 0 || $recur != 0 ) {
+ my $cust_bill_pkg = new FS::cust_bill_pkg ({
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'setup' => $setup,
+ 'recur' => $recur,
+ 'sdate' => $sdate,
+ 'edate' => $cust_pkg->bill,
+ 'details' => \@details,
+ });
+ push @cust_bill_pkg, $cust_bill_pkg;
+ $total_setup += $setup;
+ $total_recur += $recur;
+
+ unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
+
+ my @taxes = qsearch( 'cust_main_county', {
+ 'state' => $self->state,
+ 'county' => $self->county,
+ 'country' => $self->country,
+ 'taxclass' => $part_pkg->taxclass,
+ } );
+ unless ( @taxes ) {
+ @taxes = qsearch( 'cust_main_county', {
+ 'state' => $self->state,
+ 'county' => $self->county,
+ 'country' => $self->country,
+ 'taxclass' => '',
+ } );
+ }
+
+ #one more try at a whole-country tax rate
+ unless ( @taxes ) {
+ @taxes = qsearch( 'cust_main_county', {
+ 'state' => '',
+ 'county' => '',
+ 'country' => $self->country,
+ 'taxclass' => '',
+ } );
+ }
+
+ # maybe eliminate this entirely, along with all the 0% records
+ unless ( @taxes ) {
+ $dbh->rollback if $oldAutoCommit;
+ return
+ "fatal: can't find tax rate for state/county/country/taxclass ".
+ join('/', ( map $self->$_(), qw(state county country) ),
+ $part_pkg->taxclass ). "\n";
+ }
+
+ foreach my $tax ( @taxes ) {
+
+ my $taxable_charged = 0;
+ $taxable_charged += $setup
+ unless $part_pkg->setuptax =~ /^Y$/i
+ || $tax->setuptax =~ /^Y$/i;
+ $taxable_charged += $recur
+ unless $part_pkg->recurtax =~ /^Y$/i
+ || $tax->recurtax =~ /^Y$/i;
+ next unless $taxable_charged;
+
+ if ( $tax->exempt_amount > 0 ) {
+ my ($mon,$year) = (localtime($sdate) )[4,5];
+ $mon++;
+ my $freq = $part_pkg->freq || 1;
+ if ( $freq !~ /(\d+)$/ ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "daily/weekly package definitions not (yet?)".
+ " compatible with monthly tax exemptions";
+ }
+ my $taxable_per_month = sprintf("%.2f", $taxable_charged / $freq );
+ foreach my $which_month ( 1 .. $freq ) {
+ my %hash = (
+ 'custnum' => $self->custnum,
+ 'taxnum' => $tax->taxnum,
+ 'year' => 1900+$year,
+ 'month' => $mon++,
+ );
+ #until ( $mon < 12 ) { $mon -= 12; $year++; }
+ until ( $mon < 13 ) { $mon -= 12; $year++; }
+ my $cust_tax_exempt =
+ qsearchs('cust_tax_exempt', \%hash)
+ || new FS::cust_tax_exempt( { %hash, 'amount' => 0 } );
+ my $remaining_exemption = sprintf("%.2f",
+ $tax->exempt_amount - $cust_tax_exempt->amount );
+ if ( $remaining_exemption > 0 ) {
+ my $addl = $remaining_exemption > $taxable_per_month
+ ? $taxable_per_month
+ : $remaining_exemption;
+ $taxable_charged -= $addl;
+ my $new_cust_tax_exempt = new FS::cust_tax_exempt ( {
+ $cust_tax_exempt->hash,
+ 'amount' =>
+ sprintf("%.2f", $cust_tax_exempt->amount + $addl),
+ } );
+ $error = $new_cust_tax_exempt->exemptnum
+ ? $new_cust_tax_exempt->replace($cust_tax_exempt)
+ : $new_cust_tax_exempt->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "fatal: can't update cust_tax_exempt: $error";
+ }
+
+ } # if $remaining_exemption > 0
+
+ } #foreach $which_month
+
+ } #if $tax->exempt_amount
+
+ $taxable_charged = sprintf( "%.2f", $taxable_charged);
+
+ #$tax += $taxable_charged * $cust_main_county->tax / 100
+ $tax{ $tax->taxname || 'Tax' } +=
+ $taxable_charged * $tax->tax / 100
+
+ } #foreach my $tax ( @taxes )
+
+ } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+
+ } #if $setup != 0 || $recur != 0
+
+ } #if $cust_pkg_mod_flag
+
+ } #foreach my $cust_pkg
+
+ my $charged = sprintf( "%.2f", $total_setup + $total_recur );
+# my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur );
+
+ unless ( @cust_bill_pkg ) { #don't create invoices with no line items
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return '';
+ }
+
+# unless ( $self->tax =~ /Y/i
+# || $self->payby eq 'COMP'
+# || $taxable_charged == 0 ) {
+# my $cust_main_county = qsearchs('cust_main_county',{
+# 'state' => $self->state,
+# 'county' => $self->county,
+# 'country' => $self->country,
+# } ) or die "fatal: can't find tax rate for state/county/country ".
+# $self->state. "/". $self->county. "/". $self->country. "\n";
+# my $tax = sprintf( "%.2f",
+# $taxable_charged * ( $cust_main_county->getfield('tax') / 100 )
+# );
+
+ if ( dbdef->table('cust_bill_pkg')->column('itemdesc') ) { #1.5 schema
+
+ foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) {
+ my $tax = sprintf("%.2f", $tax{$taxname} );
+ $charged = sprintf( "%.2f", $charged+$tax );
+
+ my $cust_bill_pkg = new FS::cust_bill_pkg ({
+ 'pkgnum' => 0,
+ 'setup' => $tax,
+ 'recur' => 0,
+ 'sdate' => '',
+ 'edate' => '',
+ 'itemdesc' => $taxname,
+ });
+ push @cust_bill_pkg, $cust_bill_pkg;
+ }
+
+ } else { #1.4 schema
+
+ my $tax = 0;
+ foreach ( values %tax ) { $tax += $_ };
+ $tax = sprintf("%.2f", $tax);
+ if ( $tax > 0 ) {
+ $charged = sprintf( "%.2f", $charged+$tax );
+
+ my $cust_bill_pkg = new FS::cust_bill_pkg ({
+ 'pkgnum' => 0,
+ 'setup' => $tax,
+ 'recur' => 0,
+ 'sdate' => '',
+ 'edate' => '',
+ });
+ push @cust_bill_pkg, $cust_bill_pkg;
+ }
+
+ }
+
+ my $cust_bill = new FS::cust_bill ( {
+ 'custnum' => $self->custnum,
+ '_date' => $time,
+ 'charged' => $charged,
+ } );
+ $error = $cust_bill->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice for customer #". $self->custnum. ": $error";
+ }
+
+ my $invnum = $cust_bill->invnum;
+ my $cust_bill_pkg;
+ foreach $cust_bill_pkg ( @cust_bill_pkg ) {
+ #warn $invnum;
+ $cust_bill_pkg->invnum($invnum);
+ $error = $cust_bill_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't create invoice line item for customer #". $self->custnum.
+ ": $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item collect OPTIONS
+
+(Attempt to) collect money for this customer's outstanding invoices (see
+L<FS::cust_bill>). Usually used after the bill method.
+
+Depending on the value of `payby', this may print or email an invoice (I<BILL>,
+I<DCRD>, or I<DCHK>), charge a credit card (I<CARD>), charge via electronic
+check/ACH (I<CHEK>), or just add any necessary (pseudo-)payment (I<COMP>).
+
+Most actions are now triggered by invoice events; see L<FS::part_bill_event>
+and the invoice events web interface.
+
+If there is an error, returns the error, otherwise returns false.
+
+Options are passed as name-value pairs.
+
+Currently available options are:
+
+invoice_time - Use this time when deciding when to print invoices and
+late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse>
+for conversion functions.
+
+retry - Retry card/echeck/LEC transactions even when not scheduled by invoice
+events.
+
+retry_card - Deprecated alias for 'retry'
+
+batch_card - This option is deprecated. See the invoice events web interface
+to control whether cards are batched or run against a realtime gateway.
+
+report_badcard - This option is deprecated.
+
+force_print - This option is deprecated; see the invoice events web interface.
+
+quiet - set true to surpress email card/ACH decline notices.
+
+=cut
+
+sub collect {
+ my( $self, %options ) = @_;
+ my $invoice_time = $options{'invoice_time'} || time;
+
+ #put below somehow?
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $self->select_for_update; #mutex
+
+ my $balance = $self->balance;
+ warn "collect customer". $self->custnum. ": balance $balance" if $DEBUG;
+ unless ( $balance > 0 ) { #redundant?????
+ $dbh->rollback if $oldAutoCommit; #hmm
+ return '';
+ }
+
+ if ( exists($options{'retry_card'}) ) {
+ carp 'retry_card option passed to collect is deprecated; use retry';
+ $options{'retry'} ||= $options{'retry_card'};
+ }
+ if ( exists($options{'retry'}) && $options{'retry'} ) {
+ my $error = $self->retry_realtime;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $cust_bill ( $self->open_cust_bill ) {
+
+ # don't try to charge for the same invoice if it's already in a batch
+ #next if qsearchs( 'cust_pay_batch', { 'invnum' => $cust_bill->invnum } );
+
+ last if $self->balance <= 0;
+
+ warn "invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")"
+ if $DEBUG;
+
+ foreach my $part_bill_event (
+ sort { $a->seconds <=> $b->seconds
+ || $a->weight <=> $b->weight
+ || $a->eventpart <=> $b->eventpart }
+ grep { $_->seconds <= ( $invoice_time - $cust_bill->_date )
+ && ! qsearch( 'cust_bill_event', {
+ 'invnum' => $cust_bill->invnum,
+ 'eventpart' => $_->eventpart,
+ 'status' => 'done',
+ } )
+ }
+ qsearch('part_bill_event', { 'payby' => $self->payby,
+ 'disabled' => '', } )
+ ) {
+
+ last if $cust_bill->owed <= 0 # don't run subsequent events if owed<=0
+ || $self->balance <= 0; # or if balance<=0
+
+ warn "calling invoice event (". $part_bill_event->eventcode. ")\n"
+ if $DEBUG;
+ my $cust_main = $self; #for callback
+
+ my $error;
+ {
+ local $realtime_bop_decline_quiet = 1 if $options{'quiet'};
+ $error = eval $part_bill_event->eventcode;
+ }
+
+ my $status = '';
+ my $statustext = '';
+ if ( $@ ) {
+ $status = 'failed';
+ $statustext = $@;
+ } elsif ( $error ) {
+ $status = 'done';
+ $statustext = $error;
+ } else {
+ $status = 'done'
+ }
+
+ #add cust_bill_event
+ my $cust_bill_event = new FS::cust_bill_event {
+ 'invnum' => $cust_bill->invnum,
+ 'eventpart' => $part_bill_event->eventpart,
+ #'_date' => $invoice_time,
+ '_date' => time,
+ 'status' => $status,
+ 'statustext' => $statustext,
+ };
+ $error = $cust_bill_event->insert;
+ if ( $error ) {
+ #$dbh->rollback if $oldAutoCommit;
+ #return "error: $error";
+
+ # gah, even with transactions.
+ $dbh->commit if $oldAutoCommit; #well.
+ my $e = 'WARNING: Event run but database not updated - '.
+ 'error inserting cust_bill_event, invnum #'. $cust_bill->invnum.
+ ', eventpart '. $part_bill_event->eventpart.
+ ": $error";
+ warn $e;
+ return $e;
+ }
+
+
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item retry_realtime
+
+Schedules realtime credit card / electronic check / LEC billing events for
+for retry. Useful if card information has changed or manual retry is desired.
+The 'collect' method must be called to actually retry the transaction.
+
+Implementation details: For each of this customer's open invoices, changes
+the status of the first "done" (with statustext error) realtime processing
+event to "failed".
+
+=cut
+
+sub retry_realtime {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_bill (
+ grep { $_->cust_bill_event }
+ $self->open_cust_bill
+ ) {
+ my @cust_bill_event =
+ sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds }
+ grep {
+ #$_->part_bill_event->plan eq 'realtime-card'
+ $_->part_bill_event->eventcode =~
+ /\$cust_bill\->realtime_(card|ach|lec)/
+ && $_->status eq 'done'
+ && $_->statustext
+ }
+ $cust_bill->cust_bill_event;
+ next unless @cust_bill_event;
+ my $error = $cust_bill_event[0]->retry;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error scheduling invoice event for retry: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item realtime_bop METHOD AMOUNT [ OPTION => VALUE ... ]
+
+Runs a realtime credit card, ACH (electronic check) or phone bill transaction
+via a Business::OnlinePayment realtime gateway. See
+L<http://420.am/business-onlinepayment> for supported gateways.
+
+Available methods are: I<CC>, I<ECHECK> and I<LEC>
+
+Available options are: I<description>, I<invnum>, I<quiet>
+
+The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
+I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
+if set, will override the value from the customer record.
+
+I<description> is a free-text field passed to the gateway. It defaults to
+"Internet services".
+
+If an I<invnum> is specified, this payment (if sucessful) is applied to the
+specified invoice. If you don't specify an I<invnum> you might want to
+call the B<apply_payments> method.
+
+I<quiet> can be set true to surpress email decline notices.
+
+(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
+
+=cut
+
+sub realtime_bop {
+ my( $self, $method, $amount, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$self $method $amount\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ $options{'description'} ||= 'Internet services';
+
+ #pre-requisites
+ die "Real-time processing not enabled\n"
+ unless $conf->exists('business-onlinepayment');
+ eval "use Business::OnlinePayment";
+ die $@ if $@;
+
+ #load up config
+ my $bop_config = 'business-onlinepayment';
+ $bop_config .= '-ach'
+ if $method eq 'ECHECK' && $conf->exists($bop_config. '-ach');
+ my ( $processor, $login, $password, $action, @bop_options ) =
+ $conf->config($bop_config);
+ $action ||= 'normal authorization';
+ pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
+
+ #massage data
+
+ my $address = exists($options{'address1'})
+ ? $options{'address1'}
+ : $self->address1;
+ my $address2 = exists($options{'address2'})
+ ? $options{'address2'}
+ : $self->address2;
+ $address .= ", ". $address2 if length($address2);
+
+ my $o_payname = exists($options{'payname'})
+ ? $options{'payname'}
+ : $self->payname;
+ my($payname, $payfirst, $paylast);
+ if ( $o_payname && $method ne 'ECHECK' ) {
+ ($payname = $o_payname) =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+ or return "Illegal payname $payname";
+ ($payfirst, $paylast) = ($1, $2);
+ } else {
+ $payfirst = $self->getfield('first');
+ $paylast = $self->getfield('last');
+ $payname = "$payfirst $paylast";
+ }
+
+ my @invoicing_list = grep { $_ ne 'POST' } $self->invoicing_list;
+ if ( $conf->exists('emailinvoiceauto')
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $self->all_emails;
+ }
+ my $email = $invoicing_list[0];
+
+ my $payinfo = exists($options{'payinfo'})
+ ? $options{'payinfo'}
+ : $self->payinfo;
+
+ my %content = ();
+ if ( $method eq 'CC' ) {
+
+ $content{card_number} = $payinfo;
+ my $paydate = exists($options{'paydate'})
+ ? $options{'paydate'}
+ : $self->paydate;
+ $paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ $content{expiration} = "$2/$1";
+
+ if ( defined $self->dbdef_table->column('paycvv') ) {
+ my $paycvv = exists($options{'paycvv'})
+ ? $options{'paycvv'}
+ : $self->paycvv;
+ $content{cvv2} = $self->paycvv
+ if length($paycvv);
+ }
+
+ $content{recurring_billing} = 'YES'
+ if qsearch('cust_pay', { 'custnum' => $self->custnum,
+ 'payby' => 'CARD',
+ 'payinfo' => $payinfo,
+ } );
+
+ } elsif ( $method eq 'ECHECK' ) {
+ ( $content{account_number}, $content{routing_code} ) =
+ split('@', $payinfo);
+ $content{bank_name} = $o_payname;
+ $content{account_type} = 'CHECKING';
+ $content{account_name} = $payname;
+ $content{customer_org} = $self->company ? 'B' : 'I';
+ $content{customer_ssn} = exists($options{'ss'})
+ ? $options{'ss'}
+ : $self->ss;
+ } elsif ( $method eq 'LEC' ) {
+ $content{phone} = $payinfo;
+ }
+
+ #transaction(s)
+
+ my( $action1, $action2 ) = split(/\s*\,\s*/, $action );
+
+ my $transaction = new Business::OnlinePayment( $processor, @bop_options );
+ $transaction->content(
+ 'type' => $method,
+ 'login' => $login,
+ 'password' => $password,
+ 'action' => $action1,
+ 'description' => $options{'description'},
+ 'amount' => $amount,
+ 'invoice_number' => $options{'invnum'},
+ 'customer_id' => $self->custnum,
+ 'last_name' => $paylast,
+ 'first_name' => $payfirst,
+ 'name' => $payname,
+ 'address' => $address,
+ 'city' => ( exists($options{'city'})
+ ? $options{'city'}
+ : $self->city ),
+ 'state' => ( exists($options{'state'})
+ ? $options{'state'}
+ : $self->state ),
+ 'zip' => ( exists($options{'zip'})
+ ? $options{'zip'}
+ : $self->zip ),
+ 'country' => ( exists($options{'country'})
+ ? $options{'country'}
+ : $self->country ),
+ 'referer' => 'http://cleanwhisker.420.am/',
+ 'email' => $email,
+ 'phone' => $self->daytime || $self->night,
+ %content, #after
+ );
+ $transaction->submit();
+
+ if ( $transaction->is_success() && $action2 ) {
+ my $auth = $transaction->authorization;
+ my $ordernum = $transaction->can('order_number')
+ ? $transaction->order_number
+ : '';
+
+ my $capture =
+ new Business::OnlinePayment( $processor, @bop_options );
+
+ my %capture = (
+ %content,
+ type => $method,
+ action => $action2,
+ login => $login,
+ password => $password,
+ order_number => $ordernum,
+ amount => $amount,
+ authorization => $auth,
+ description => $options{'description'},
+ );
+
+ foreach my $field (qw( authorization_source_code returned_ACI transaction_identifier validation_code
+ transaction_sequence_num local_transaction_date
+ local_transaction_time AVS_result_code )) {
+ $capture{$field} = $transaction->$field() if $transaction->can($field);
+ }
+
+ $capture->content( %capture );
+
+ $capture->submit();
+
+ unless ( $capture->is_success ) {
+ my $e = "Authorization sucessful but capture failed, custnum #".
+ $self->custnum. ': '. $capture->result_code.
+ ": ". $capture->error_message;
+ warn $e;
+ return $e;
+ }
+
+ }
+
+ #remove paycvv after initial transaction
+ #false laziness w/misc/process/payment.cgi - check both to make sure working
+ # correctly
+ if ( defined $self->dbdef_table->column('paycvv')
+ && length($self->paycvv)
+ && ! grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save')
+ ) {
+ my $error = $self->remove_cvv;
+ if ( $error ) {
+ warn "error removing cvv: $error\n";
+ }
+ }
+
+ #result handling
+ if ( $transaction->is_success() ) {
+
+ my %method2payby = (
+ 'CC' => 'CARD',
+ 'ECHECK' => 'CHEK',
+ 'LEC' => 'LECB',
+ );
+
+ my $paybatch = "$processor:". $transaction->authorization;
+ $paybatch .= ':'. $transaction->order_number
+ if $transaction->can('order_number')
+ && length($transaction->order_number);
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'custnum' => $self->custnum,
+ 'invnum' => $options{'invnum'},
+ 'paid' => $amount,
+ '_date' => '',
+ 'payby' => $method2payby{$method},
+ 'payinfo' => $payinfo,
+ 'paybatch' => $paybatch,
+ } );
+ my $error = $cust_pay->insert;
+ if ( $error ) {
+ $cust_pay->invnum(''); #try again with no specific invnum
+ my $error2 = $cust_pay->insert;
+ if ( $error2 ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH debited but database not updated - '.
+ "error inserting payment ($processor): $error2".
+ " (previously tried insert with invnum #$options{'invnum'}" .
+ ": $error )";
+ warn $e;
+ return $e;
+ }
+ }
+ return ''; #no error
+
+ } else {
+
+ my $perror = "$processor error: ". $transaction->error_message;
+
+ if ( !$options{'quiet'} && !$realtime_bop_decline_quiet
+ && $conf->exists('emaildecline')
+ && grep { $_ ne 'POST' } $self->invoicing_list
+ && ! grep { $transaction->error_message =~ /$_/ }
+ $conf->config('emaildecline-exclude')
+ ) {
+ my @templ = $conf->config('declinetemplate');
+ my $template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @templ ],
+ ) or return "($perror) can't create template: $Text::Template::ERROR";
+ $template->compile()
+ or return "($perror) can't compile template: $Text::Template::ERROR";
+
+ my $templ_hash = { error => $transaction->error_message };
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'),
+ 'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ],
+ 'subject' => 'Your payment could not be processed',
+ 'body' => [ $template->fill_in(HASH => $templ_hash) ],
+ );
+
+ $perror .= " (also received error sending decline notification: $error)"
+ if $error;
+
+ }
+
+ return $perror;
+ }
+
+}
+
+=item remove_cvv
+
+Removes the I<paycvv> field from the database directly.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub remove_cvv {
+ my $self = shift;
+ my $sth = dbh->prepare("UPDATE cust_main SET paycvv = '' WHERE custnum = ?")
+ or return dbh->errstr;
+ $sth->execute($self->custnum)
+ or return $sth->errstr;
+ $self->paycvv('');
+ '';
+}
+
+=item realtime_refund_bop METHOD [ OPTION => VALUE ... ]
+
+Refunds a realtime credit card, ACH (electronic check) or phone bill transaction
+via a Business::OnlinePayment realtime gateway. See
+L<http://420.am/business-onlinepayment> for supported gateways.
+
+Available methods are: I<CC>, I<ECHECK> and I<LEC>
+
+Available options are: I<amount>, I<reason>, I<paynum>
+
+Most gateways require a reference to an original payment transaction to refund,
+so you probably need to specify a I<paynum>.
+
+I<amount> defaults to the original amount of the payment if not specified.
+
+I<reason> specifies a reason for the refund.
+
+Implementation note: If I<amount> is unspecified or equal to the amount of the
+orignal payment, first an attempt is made to "void" the transaction via
+the gateway (to cancel a not-yet settled transaction) and then if that fails,
+the normal attempt is made to "refund" ("credit") the transaction via the
+gateway is attempted.
+
+#The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
+#I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
+#if set, will override the value from the customer record.
+
+#If an I<invnum> is specified, this payment (if sucessful) is applied to the
+#specified invoice. If you don't specify an I<invnum> you might want to
+#call the B<apply_payments> method.
+
+=cut
+
+#some false laziness w/realtime_bop, not enough to make it worth merging
+#but some useful small subs should be pulled out
+sub realtime_refund_bop {
+ my( $self, $method, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$self $method refund\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ #pre-requisites
+ die "Real-time processing not enabled\n"
+ unless $conf->exists('business-onlinepayment');
+ eval "use Business::OnlinePayment";
+ die $@ if $@;
+
+ #load up config
+ my $bop_config = 'business-onlinepayment';
+ $bop_config .= '-ach'
+ if $method eq 'ECHECK' && $conf->exists($bop_config. '-ach');
+ my ( $processor, $login, $password, $unused_action, @bop_options ) =
+ $conf->config($bop_config);
+ #$action ||= 'normal authorization';
+ pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
+
+ my $cust_pay = '';
+ my $amount = $options{'amount'};
+ my( $pay_processor, $auth, $order_number ) = ( '', '', '' );
+ if ( $options{'paynum'} ) {
+ warn "FS::cust_main::realtime_bop: paynum: $options{paynum}\n" if $DEBUG;
+ $cust_pay = qsearchs('cust_pay', { paynum=>$options{'paynum'} } )
+ or return "Unknown paynum $options{'paynum'}";
+ $amount ||= $cust_pay->paid;
+ $cust_pay->paybatch =~ /^(\w+):(\w*)(:(\w+))?$/
+ or return "Can't parse paybatch for paynum $options{'paynum'}: ".
+ $cust_pay->paybatch;
+ ( $pay_processor, $auth, $order_number ) = ( $1, $2, $4 );
+ return "processor of payment $options{'paynum'} $pay_processor does not".
+ " match current processor $processor"
+ unless $pay_processor eq $processor;
+ }
+ return "neither amount nor paynum specified" unless $amount;
+
+ my %content = (
+ 'type' => $method,
+ 'login' => $login,
+ 'password' => $password,
+ 'order_number' => $order_number,
+ 'amount' => $amount,
+ 'referer' => 'http://cleanwhisker.420.am/',
+ );
+ $content{authorization} = $auth
+ if length($auth); #echeck/ACH transactions have an order # but no auth
+ #(at least with authorize.net)
+
+ #first try void if applicable
+ if ( $cust_pay && $cust_pay->paid == $amount ) { #and check dates?
+ my $void = new Business::OnlinePayment( $processor, @bop_options );
+ $void->content( 'action' => 'void', %content );
+ $void->submit();
+ if ( $void->is_success ) {
+ my $error = $cust_pay->void($options{'reason'});
+ if ( $error ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH voided but database not updated - '.
+ "error voiding payment: $error";
+ warn $e;
+ return $e;
+ }
+ return '';
+ }
+ }
+
+ #massage data
+ my $address = $self->address1;
+ $address .= ", ". $self->address2 if $self->address2;
+
+ my($payname, $payfirst, $paylast);
+ if ( $self->payname && $method ne 'ECHECK' ) {
+ $payname = $self->payname;
+ $payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+ or return "Illegal payname $payname";
+ ($payfirst, $paylast) = ($1, $2);
+ } else {
+ $payfirst = $self->getfield('first');
+ $paylast = $self->getfield('last');
+ $payname = "$payfirst $paylast";
+ }
+
+ if ( $method eq 'CC' ) {
+
+ $content{card_number} = $self->payinfo;
+ $self->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ $content{expiration} = "$2/$1";
+
+ #$content{cvv2} = $self->paycvv
+ # if defined $self->dbdef_table->column('paycvv')
+ # && length($self->paycvv);
+
+ #$content{recurring_billing} = 'YES'
+ # if qsearch('cust_pay', { 'custnum' => $self->custnum,
+ # 'payby' => 'CARD',
+ # 'payinfo' => $self->payinfo, } );
+
+ } elsif ( $method eq 'ECHECK' ) {
+ ( $content{account_number}, $content{routing_code} ) =
+ split('@', $self->payinfo);
+ $content{bank_name} = $self->payname;
+ $content{account_type} = 'CHECKING';
+ $content{account_name} = $payname;
+ $content{customer_org} = $self->company ? 'B' : 'I';
+ $content{customer_ssn} = $self->ss;
+ } elsif ( $method eq 'LEC' ) {
+ $content{phone} = $self->payinfo;
+ }
+
+ #then try refund
+ my $refund = new Business::OnlinePayment( $processor, @bop_options );
+ $refund->content(
+ 'action' => 'credit',
+ 'customer_id' => $self->custnum,
+ 'last_name' => $paylast,
+ 'first_name' => $payfirst,
+ 'name' => $payname,
+ 'address' => $address,
+ 'city' => $self->city,
+ 'state' => $self->state,
+ 'zip' => $self->zip,
+ 'country' => $self->country,
+ %content, #after
+ );
+ $refund->submit();
+
+ return "$processor error: ". $refund->error_message
+ unless $refund->is_success();
+
+ my %method2payby = (
+ 'CC' => 'CARD',
+ 'ECHECK' => 'CHEK',
+ 'LEC' => 'LECB',
+ );
+
+ my $paybatch = "$processor:". $refund->authorization;
+ $paybatch .= ':'. $refund->order_number
+ if $refund->can('order_number') && $refund->order_number;
+
+ while ( $cust_pay && $cust_pay->unappled < $amount ) {
+ my @cust_bill_pay = $cust_pay->cust_bill_pay;
+ last unless @cust_bill_pay;
+ my $cust_bill_pay = pop @cust_bill_pay;
+ my $error = $cust_bill_pay->delete;
+ last if $error;
+ }
+
+ my $cust_refund = new FS::cust_refund ( {
+ 'custnum' => $self->custnum,
+ 'paynum' => $options{'paynum'},
+ 'refund' => $amount,
+ '_date' => '',
+ 'payby' => $method2payby{$method},
+ 'payinfo' => $self->payinfo,
+ 'paybatch' => $paybatch,
+ 'reason' => $options{'reason'} || 'card or ACH refund',
+ } );
+ my $error = $cust_refund->insert;
+ if ( $error ) {
+ $cust_refund->paynum(''); #try again with no specific paynum
+ my $error2 = $cust_refund->insert;
+ if ( $error2 ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH refunded but database not updated - '.
+ "error inserting refund ($processor): $error2".
+ " (previously tried insert with paynum #$options{'paynum'}" .
+ ": $error )";
+ warn $e;
+ return $e;
+ }
+ }
+
+ ''; #no error
+
+}
+
+=item total_owed
+
+Returns the total owed for this customer on all invoices
+(see L<FS::cust_bill/owed>).
+
+=cut
+
+sub total_owed {
+ my $self = shift;
+ $self->total_owed_date(2145859200); #12/31/2037
+}
+
+=item total_owed_date TIME
+
+Returns the total owed for this customer on all invoices with date earlier than
+TIME. TIME is specified as a UNIX timestamp; see L<perlfunc/"time">). Also
+see L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub total_owed_date {
+ my $self = shift;
+ my $time = shift;
+ my $total_bill = 0;
+ foreach my $cust_bill (
+ grep { $_->_date <= $time }
+ qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+ ) {
+ $total_bill += $cust_bill->owed;
+ }
+ sprintf( "%.2f", $total_bill );
+}
+
+=item apply_credits OPTION => VALUE ...
+
+Applies (see L<FS::cust_credit_bill>) unapplied credits (see L<FS::cust_credit>)
+to outstanding invoice balances in chronological order (or reverse
+chronological order if the I<order> option is set to B<newest>) and returns the
+value of any remaining unapplied credits available for refund (see
+L<FS::cust_refund>).
+
+=cut
+
+sub apply_credits {
+ my $self = shift;
+ my %opt = @_;
+
+ return 0 unless $self->total_credited;
+
+ my @credits = sort { $b->_date <=> $a->_date} (grep { $_->credited > 0 }
+ qsearch('cust_credit', { 'custnum' => $self->custnum } ) );
+
+ my @invoices = $self->open_cust_bill;
+ @invoices = sort { $b->_date <=> $a->_date } @invoices
+ if defined($opt{'order'}) && $opt{'order'} eq 'newest';
+
+ my $credit;
+ foreach my $cust_bill ( @invoices ) {
+ my $amount;
+
+ if ( !defined($credit) || $credit->credited == 0) {
+ $credit = pop @credits or last;
+ }
+
+ if ($cust_bill->owed >= $credit->credited) {
+ $amount=$credit->credited;
+ }else{
+ $amount=$cust_bill->owed;
+ }
+
+ my $cust_credit_bill = new FS::cust_credit_bill ( {
+ 'crednum' => $credit->crednum,
+ 'invnum' => $cust_bill->invnum,
+ 'amount' => $amount,
+ } );
+ my $error = $cust_credit_bill->insert;
+ die $error if $error;
+
+ redo if ($cust_bill->owed > 0);
+
+ }
+
+ return $self->total_credited;
+}
+
+=item apply_payments
+
+Applies (see L<FS::cust_bill_pay>) unapplied payments (see L<FS::cust_pay>)
+to outstanding invoice balances in chronological order.
+
+ #and returns the value of any remaining unapplied payments.
+
+=cut
+
+sub apply_payments {
+ my $self = shift;
+
+ #return 0 unless
+
+ my @payments = sort { $b->_date <=> $a->_date } ( grep { $_->unapplied > 0 }
+ qsearch('cust_pay', { 'custnum' => $self->custnum } ) );
+
+ my @invoices = sort { $a->_date <=> $b->_date} (grep { $_->owed > 0 }
+ qsearch('cust_bill', { 'custnum' => $self->custnum } ) );
+
+ my $payment;
+
+ foreach my $cust_bill ( @invoices ) {
+ my $amount;
+
+ if ( !defined($payment) || $payment->unapplied == 0 ) {
+ $payment = pop @payments or last;
+ }
+
+ if ( $cust_bill->owed >= $payment->unapplied ) {
+ $amount = $payment->unapplied;
+ } else {
+ $amount = $cust_bill->owed;
+ }
+
+ my $cust_bill_pay = new FS::cust_bill_pay ( {
+ 'paynum' => $payment->paynum,
+ 'invnum' => $cust_bill->invnum,
+ 'amount' => $amount,
+ } );
+ my $error = $cust_bill_pay->insert;
+ die $error if $error;
+
+ redo if ( $cust_bill->owed > 0);
+
+ }
+
+ return $self->total_unapplied_payments;
+}
+
+=item total_credited
+
+Returns the total outstanding credit (see L<FS::cust_credit>) for this
+customer. See L<FS::cust_credit/credited>.
+
+=cut
+
+sub total_credited {
+ my $self = shift;
+ my $total_credit = 0;
+ foreach my $cust_credit ( qsearch('cust_credit', {
+ 'custnum' => $self->custnum,
+ } ) ) {
+ $total_credit += $cust_credit->credited;
+ }
+ sprintf( "%.2f", $total_credit );
+}
+
+=item total_unapplied_payments
+
+Returns the total unapplied payments (see L<FS::cust_pay>) for this customer.
+See L<FS::cust_pay/unapplied>.
+
+=cut
+
+sub total_unapplied_payments {
+ my $self = shift;
+ my $total_unapplied = 0;
+ foreach my $cust_pay ( qsearch('cust_pay', {
+ 'custnum' => $self->custnum,
+ } ) ) {
+ $total_unapplied += $cust_pay->unapplied;
+ }
+ sprintf( "%.2f", $total_unapplied );
+}
+
+=item balance
+
+Returns the balance for this customer (total_owed minus total_credited
+minus total_unapplied_payments).
+
+=cut
+
+sub balance {
+ my $self = shift;
+ sprintf( "%.2f",
+ $self->total_owed - $self->total_credited - $self->total_unapplied_payments
+ );
+}
+
+=item balance_date TIME
+
+Returns the balance for this customer, only considering invoices with date
+earlier than TIME (total_owed_date minus total_credited minus
+total_unapplied_payments). TIME is specified as a UNIX timestamp; see
+L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+sub balance_date {
+ my $self = shift;
+ my $time = shift;
+ sprintf( "%.2f",
+ $self->total_owed_date($time)
+ - $self->total_credited
+ - $self->total_unapplied_payments
+ );
+}
+
+=item paydate_monthyear
+
+Returns a two-element list consisting of the month and year of this customer's
+paydate (credit card expiration date for CARD customers)
+
+=cut
+
+sub paydate_monthyear {
+ my $self = shift;
+ if ( $self->paydate =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #Pg date format
+ ( $2, $1 );
+ } elsif ( $self->paydate =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $1, $3 );
+ } else {
+ ('', '');
+ }
+}
+
+=item payinfo_masked
+
+Returns a "masked" payinfo field with all but the last four characters replaced
+by 'x'es. Useful for displaying credit cards.
+
+=cut
+
+sub payinfo_masked {
+ my $self = shift;
+ my $payinfo = $self->payinfo;
+ 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+}
+
+=item invoicing_list [ ARRAYREF ]
+
+If an arguement is given, sets these email addresses as invoice recipients
+(see L<FS::cust_main_invoice>). Errors are not fatal and are not reported
+(except as warnings), so use check_invoicing_list first.
+
+Returns a list of email addresses (with svcnum entries expanded).
+
+Note: You can clear the invoicing list by passing an empty ARRAYREF. You can
+check it without disturbing anything by passing nothing.
+
+This interface may change in the future.
+
+=cut
+
+sub invoicing_list {
+ my( $self, $arrayref ) = @_;
+ if ( $arrayref ) {
+ my @cust_main_invoice;
+ if ( $self->custnum ) {
+ @cust_main_invoice =
+ qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+ } else {
+ @cust_main_invoice = ();
+ }
+ foreach my $cust_main_invoice ( @cust_main_invoice ) {
+ #warn $cust_main_invoice->destnum;
+ unless ( grep { $cust_main_invoice->address eq $_ } @{$arrayref} ) {
+ #warn $cust_main_invoice->destnum;
+ my $error = $cust_main_invoice->delete;
+ warn $error if $error;
+ }
+ }
+ if ( $self->custnum ) {
+ @cust_main_invoice =
+ qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+ } else {
+ @cust_main_invoice = ();
+ }
+ my %seen = map { $_->address => 1 } @cust_main_invoice;
+ foreach my $address ( @{$arrayref} ) {
+ next if exists $seen{$address} && $seen{$address};
+ $seen{$address} = 1;
+ my $cust_main_invoice = new FS::cust_main_invoice ( {
+ 'custnum' => $self->custnum,
+ 'dest' => $address,
+ } );
+ my $error = $cust_main_invoice->insert;
+ warn $error if $error;
+ }
+ }
+ if ( $self->custnum ) {
+ map { $_->address }
+ qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
+ } else {
+ ();
+ }
+}
+
+=item check_invoicing_list ARRAYREF
+
+Checks these arguements as valid input for the invoicing_list method. If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub check_invoicing_list {
+ my( $self, $arrayref ) = @_;
+ foreach my $address ( @{$arrayref} ) {
+ my $cust_main_invoice = new FS::cust_main_invoice ( {
+ 'custnum' => $self->custnum,
+ 'dest' => $address,
+ } );
+ my $error = $self->custnum
+ ? $cust_main_invoice->check
+ : $cust_main_invoice->checkdest
+ ;
+ return $error if $error;
+ }
+ '';
+}
+
+=item set_default_invoicing_list
+
+Sets the invoicing list to all accounts associated with this customer,
+overwriting any previous invoicing list.
+
+=cut
+
+sub set_default_invoicing_list {
+ my $self = shift;
+ $self->invoicing_list($self->all_emails);
+}
+
+=item all_emails
+
+Returns the email addresses of all accounts provisioned for this customer.
+
+=cut
+
+sub all_emails {
+ my $self = shift;
+ my %list;
+ foreach my $cust_pkg ( $self->all_pkgs ) {
+ my @cust_svc = qsearch('cust_svc', { 'pkgnum' => $cust_pkg->pkgnum } );
+ my @svc_acct =
+ map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
+ grep { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
+ @cust_svc;
+ $list{$_}=1 foreach map { $_->email } @svc_acct;
+ }
+ keys %list;
+}
+
+=item invoicing_list_addpost
+
+Adds postal invoicing to this customer. If this customer is already configured
+to receive postal invoices, does nothing.
+
+=cut
+
+sub invoicing_list_addpost {
+ my $self = shift;
+ return if grep { $_ eq 'POST' } $self->invoicing_list;
+ my @invoicing_list = $self->invoicing_list;
+ push @invoicing_list, 'POST';
+ $self->invoicing_list(\@invoicing_list);
+}
+
+=item referral_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ]
+
+Returns an array of customers referred by this customer (referral_custnum set
+to this custnum). If DEPTH is given, recurses up to the given depth, returning
+customers referred by customers referred by this customer and so on, inclusive.
+The default behavior is DEPTH 1 (no recursion).
+
+=cut
+
+sub referral_cust_main {
+ my $self = shift;
+ my $depth = @_ ? shift : 1;
+ my $exclude = @_ ? shift : {};
+
+ my @cust_main =
+ map { $exclude->{$_->custnum}++; $_; }
+ grep { ! $exclude->{ $_->custnum } }
+ qsearch( 'cust_main', { 'referral_custnum' => $self->custnum } );
+
+ if ( $depth > 1 ) {
+ push @cust_main,
+ map { $_->referral_cust_main($depth-1, $exclude) }
+ @cust_main;
+ }
+
+ @cust_main;
+}
+
+=item referral_cust_main_ncancelled
+
+Same as referral_cust_main, except only returns customers with uncancelled
+packages.
+
+=cut
+
+sub referral_cust_main_ncancelled {
+ my $self = shift;
+ grep { scalar($_->ncancelled_pkgs) } $self->referral_cust_main;
+}
+
+=item referral_cust_pkg [ DEPTH ]
+
+Like referral_cust_main, except returns a flat list of all unsuspended (and
+uncancelled) packages for each customer. The number of items in this list may
+be useful for comission calculations (perhaps after a C<grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } @commission_worthy_pkgparts> } $cust_main-> ).
+
+=cut
+
+sub referral_cust_pkg {
+ my $self = shift;
+ my $depth = @_ ? shift : 1;
+
+ map { $_->unsuspended_pkgs }
+ grep { $_->unsuspended_pkgs }
+ $self->referral_cust_main($depth);
+}
+
+=item credit AMOUNT, REASON
+
+Applies a credit to this customer. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub credit {
+ my( $self, $amount, $reason ) = @_;
+ my $cust_credit = new FS::cust_credit {
+ 'custnum' => $self->custnum,
+ 'amount' => $amount,
+ 'reason' => $reason,
+ };
+ $cust_credit->insert;
+}
+
+=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
+
+Creates a one-time charge for this customer. If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub charge {
+ my ( $self, $amount ) = ( shift, shift );
+ my $pkg = @_ ? shift : 'One-time charge';
+ my $comment = @_ ? shift : '$'. sprintf("%.2f",$amount);
+ my $taxclass = @_ ? shift : '';
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $part_pkg = new FS::part_pkg ( {
+ 'pkg' => $pkg,
+ 'comment' => $comment,
+ 'setup' => $amount,
+ 'freq' => 0,
+ 'recur' => '0',
+ 'disabled' => 'Y',
+ 'taxclass' => $taxclass,
+ } );
+
+ my $error = $part_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $pkgpart = $part_pkg->pkgpart;
+ my %type_pkgs = ( 'typenum' => $self->agent->typenum, 'pkgpart' => $pkgpart );
+ unless ( qsearchs('type_pkgs', \%type_pkgs ) ) {
+ my $type_pkgs = new FS::type_pkgs \%type_pkgs;
+ $error = $type_pkgs->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ 'custnum' => $self->custnum,
+ 'pkgpart' => $pkgpart,
+ } );
+
+ $error = $cust_pkg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item cust_bill
+
+Returns all the invoices (see L<FS::cust_bill>) for this customer.
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+}
+
+=item open_cust_bill
+
+Returns all the open (owed > 0) invoices (see L<FS::cust_bill>) for this
+customer.
+
+=cut
+
+sub open_cust_bill {
+ my $self = shift;
+ grep { $_->owed > 0 } $self->cust_bill;
+}
+
+=item cust_credit
+
+Returns all the credits (see L<FS::cust_credit>) for this customer.
+
+=cut
+
+sub cust_credit {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay
+
+Returns all the payments (see L<FS::cust_pay>) for this customer.
+
+=cut
+
+sub cust_pay {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay_void
+
+Returns all voided payments (see L<FS::cust_pay_void>) for this customer.
+
+=cut
+
+sub cust_pay_void {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay_void', { 'custnum' => $self->custnum } )
+}
+
+
+=item cust_refund
+
+Returns all the refunds (see L<FS::cust_refund>) for this customer.
+
+=cut
+
+sub cust_refund {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
+}
+
+=item select_for_update
+
+Selects this record with the SQL "FOR UPDATE" command. This can be useful as
+a mutex.
+
+=cut
+
+sub select_for_update {
+ my $self = shift;
+ qsearch('cust_main', { 'custnum' => $self->custnum }, '*', 'FOR UPDATE' );
+}
+
+=item name
+
+Returns a name string for this customer, either "Company (Last, First)" or
+"Last, First".
+
+=cut
+
+sub name {
+ my $self = shift;
+ my $name = $self->get('last'). ', '. $self->first;
+ $name = $self->company. " ($name)" if $self->company;
+ $name;
+}
+
+=item status
+
+Returns a status string for this customer, currently:
+
+=over 4
+
+=item prospect - No packages have ever been ordered
+
+=item active - One or more recurring packages is active
+
+=item suspended - All non-cancelled recurring packages are suspended
+
+=item cancelled - All recurring packages are cancelled
+
+=back
+
+=cut
+
+sub status {
+ my $self = shift;
+ for my $status (qw( prospect active suspended cancelled )) {
+ my $method = $status.'_sql';
+ my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g;
+ my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
+ $sth->execute( ($self->custnum) x $numnum ) or die $sth->errstr;
+ return $status if $sth->fetchrow_arrayref->[0];
+ }
+}
+
+=item statuscolor
+
+Returns a hex triplet color string for this customer's status.
+
+=cut
+
+my %statuscolor = (
+ 'prospect' => '000000',
+ 'active' => '00CC00',
+ 'suspended' => 'FF9900',
+ 'cancelled' => 'FF0000',
+);
+sub statuscolor {
+ my $self = shift;
+ $statuscolor{$self->status};
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item prospect_sql
+
+Returns an SQL expression identifying prospective cust_main records (customers
+with no packages ever ordered)
+
+=cut
+
+sub prospect_sql { "
+ 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )
+"; }
+
+=item active_sql
+
+Returns an SQL expression identifying active cust_main records.
+
+=cut
+
+sub active_sql { "
+ 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
+ )
+"; }
+
+=item susp_sql
+=item suspended_sql
+
+Returns an SQL expression identifying suspended cust_main records.
+
+=cut
+
+sub suspended_sql { susp_sql(@_); }
+sub susp_sql { "
+ 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ )
+ AND 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 )
+ )
+"; }
+
+=item cancel_sql
+=item cancelled_sql
+
+Returns an SQL expression identifying cancelled cust_main records.
+
+=cut
+
+sub cancelled_sql { cancel_sql(@_); }
+sub cancel_sql { "
+ 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )
+ AND 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ )
+"; }
+
+=item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ]
+
+Performs a fuzzy (approximate) search and returns the matching FS::cust_main
+records. Currently, only I<last> or I<company> may be specified (the
+appropriate ship_ field is also searched if applicable).
+
+Additional options are the same as FS::Record::qsearch
+
+=cut
+
+sub fuzzy_search {
+ my( $self, $fuzzy, $hash, @opt) = @_;
+ #$self
+ $hash ||= {};
+ my @cust_main = ();
+
+ check_and_rebuild_fuzzyfiles();
+ foreach my $field ( keys %$fuzzy ) {
+ my $sub = \&{"all_$field"};
+ my %match = ();
+ $match{$_}=1 foreach ( amatch($fuzzy->{$field}, ['i'], @{ &$sub() } ) );
+
+ foreach ( keys %match ) {
+ push @cust_main, qsearch('cust_main', { %$hash, $field=>$_}, @opt);
+ push @cust_main, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt)
+ if defined dbdef->table('cust_main')->column('ship_last');
+ }
+ }
+
+ my %saw = ();
+ @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+
+ @cust_main;
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item check_and_rebuild_fuzzyfiles
+
+=cut
+
+sub check_and_rebuild_fuzzyfiles {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ -e "$dir/cust_main.last" && -e "$dir/cust_main.company"
+ or &rebuild_fuzzyfiles;
+}
+
+=item rebuild_fuzzyfiles
+
+=cut
+
+sub rebuild_fuzzyfiles {
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ #last
+
+ open(LASTLOCK,">>$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ flock(LASTLOCK,LOCK_EX)
+ or die "can't lock $dir/cust_main.last: $!";
+
+ my @all_last = map $_->getfield('last'), qsearch('cust_main', {});
+ push @all_last,
+ grep $_, map $_->getfield('ship_last'), qsearch('cust_main',{})
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ open (LASTCACHE,">$dir/cust_main.last.tmp")
+ or die "can't open $dir/cust_main.last.tmp: $!";
+ print LASTCACHE join("\n", @all_last), "\n";
+ close LASTCACHE or die "can't close $dir/cust_main.last.tmp: $!";
+
+ rename "$dir/cust_main.last.tmp", "$dir/cust_main.last";
+ close LASTLOCK;
+
+ #company
+
+ open(COMPANYLOCK,">>$dir/cust_main.company")
+ or die "can't open $dir/cust_main.company: $!";
+ flock(COMPANYLOCK,LOCK_EX)
+ or die "can't lock $dir/cust_main.company: $!";
+
+ my @all_company = grep $_ ne '', map $_->company, qsearch('cust_main',{});
+ push @all_company,
+ grep $_ ne '', map $_->ship_company, qsearch('cust_main', {})
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ open (COMPANYCACHE,">$dir/cust_main.company.tmp")
+ or die "can't open $dir/cust_main.company.tmp: $!";
+ print COMPANYCACHE join("\n", @all_company), "\n";
+ close COMPANYCACHE or die "can't close $dir/cust_main.company.tmp: $!";
+
+ rename "$dir/cust_main.company.tmp", "$dir/cust_main.company";
+ close COMPANYLOCK;
+
+}
+
+=item all_last
+
+=cut
+
+sub all_last {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ open(LASTCACHE,"<$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ my @array = map { chomp; $_; } <LASTCACHE>;
+ close LASTCACHE;
+ \@array;
+}
+
+=item all_company
+
+=cut
+
+sub all_company {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ open(COMPANYCACHE,"<$dir/cust_main.company")
+ or die "can't open $dir/cust_main.last: $!";
+ my @array = map { chomp; $_; } <COMPANYCACHE>;
+ close COMPANYCACHE;
+ \@array;
+}
+
+=item append_fuzzyfiles LASTNAME COMPANY
+
+=cut
+
+sub append_fuzzyfiles {
+ my( $last, $company ) = @_;
+
+ &check_and_rebuild_fuzzyfiles;
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ if ( $last ) {
+
+ open(LAST,">>$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ flock(LAST,LOCK_EX)
+ or die "can't lock $dir/cust_main.last: $!";
+
+ print LAST "$last\n";
+
+ flock(LAST,LOCK_UN)
+ or die "can't unlock $dir/cust_main.last: $!";
+ close LAST;
+ }
+
+ if ( $company ) {
+
+ open(COMPANY,">>$dir/cust_main.company")
+ or die "can't open $dir/cust_main.company: $!";
+ flock(COMPANY,LOCK_EX)
+ or die "can't lock $dir/cust_main.company: $!";
+
+ print COMPANY "$company\n";
+
+ flock(COMPANY,LOCK_UN)
+ or die "can't unlock $dir/cust_main.company: $!";
+
+ close COMPANY;
+ }
+
+ 1;
+}
+
+=item batch_import
+
+=cut
+
+sub batch_import {
+ my $param = shift;
+ #warn join('-',keys %$param);
+ my $fh = $param->{filehandle};
+ my $agentnum = $param->{agentnum};
+ my $refnum = $param->{refnum};
+ my $pkgpart = $param->{pkgpart};
+ my @fields = @{$param->{fields}};
+
+ eval "use Date::Parse;";
+ die $@ if $@;
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+ #warn $csv;
+ #warn $fh;
+
+ my $imported = 0;
+ #my $columns;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #while ( $columns = $csv->getline($fh) ) {
+ my $line;
+ while ( defined($line=<$fh>) ) {
+
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ my @columns = $csv->fields();
+ #warn join('-',@columns);
+
+ my %cust_main = (
+ agentnum => $agentnum,
+ refnum => $refnum,
+ country => $conf->config('countrydefault') || 'US',
+ payby => 'BILL', #default
+ paydate => '12/2037', #default
+ );
+ my $billtime = time;
+ my %cust_pkg = ( pkgpart => $pkgpart );
+ foreach my $field ( @fields ) {
+ if ( $field =~ /^cust_pkg\.(setup|bill|susp|expire|cancel)$/ ) {
+ #$cust_pkg{$1} = str2time( shift @$columns );
+ if ( $1 eq 'setup' ) {
+ $billtime = str2time(shift @columns);
+ } else {
+ $cust_pkg{$1} = str2time( shift @columns );
+ }
+ } else {
+ #$cust_main{$field} = shift @$columns;
+ $cust_main{$field} = shift @columns;
+ }
+ }
+
+ my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ) if $pkgpart;
+ my $cust_main = new FS::cust_main ( \%cust_main );
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash'; #this part is important
+ $hash{$cust_pkg} = [] if $pkgpart;
+ my $error = $cust_main->insert( \%hash );
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert customer for $line: $error";
+ }
+
+ #false laziness w/bill.cgi
+ $error = $cust_main->bill( 'time' => $billtime );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't bill customer for $line: $error";
+ }
+
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ $error = $cust_main->collect();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't collect customer for $line: $error";
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless $imported;
+
+ ''; #no error
+
+}
+
+=item batch_charge
+
+=cut
+
+sub batch_charge {
+ my $param = shift;
+ #warn join('-',keys %$param);
+ my $fh = $param->{filehandle};
+ my @fields = @{$param->{fields}};
+
+ eval "use Date::Parse;";
+ die $@ if $@;
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+ #warn $csv;
+ #warn $fh;
+
+ my $imported = 0;
+ #my $columns;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #while ( $columns = $csv->getline($fh) ) {
+ my $line;
+ while ( defined($line=<$fh>) ) {
+
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ my @columns = $csv->fields();
+ #warn join('-',@columns);
+
+ my %row = ();
+ foreach my $field ( @fields ) {
+ $row{$field} = shift @columns;
+ }
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $row{'custnum'} } );
+ unless ( $cust_main ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "unknown custnum $row{'custnum'}";
+ }
+
+ if ( $row{'amount'} > 0 ) {
+ my $error = $cust_main->charge($row{'amount'}, $row{'pkg'});
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $imported++;
+ } elsif ( $row{'amount'} < 0 ) {
+ my $error = $cust_main->credit( sprintf( "%.2f", 0-$row{'amount'} ),
+ $row{'pkg'} );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $imported++;
+ } else {
+ #hmm?
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless $imported;
+
+ ''; #no error
+
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+The delete method should possibly take an FS::cust_main object reference
+instead of a scalar customer number.
+
+Bill and collect options should probably be passed as references instead of a
+list.
+
+There should probably be a configuration file with a list of allowed credit
+card types.
+
+No multiple currency support (probably a larger project than just this module).
+
+payinfo_masked false laziness with cust_pay.pm and cust_refund.pm
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_pkg>, L<FS::cust_bill>, L<FS::cust_credit>
+L<FS::agent>, L<FS::part_referral>, L<FS::cust_main_county>,
+L<FS::cust_main_invoice>, L<FS::UID>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
new file mode 100644
index 0000000..ef2793a
--- /dev/null
+++ b/FS/FS/cust_main_county.pm
@@ -0,0 +1,290 @@
+package FS::cust_main_county;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $conf
+ @cust_main_county %cust_main_county $countyflag );
+use Exporter;
+use FS::Record qw( qsearch );
+
+@ISA = qw( FS::Record );
+@EXPORT_OK = qw( regionselector );
+
+@cust_main_county = ();
+$countyflag = '';
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::cust_main_county'} = sub {
+ $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::cust_main_county - Object methods for cust_main_county objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_main_county;
+
+ $record = new FS::cust_main_county \%hash;
+ $record = new FS::cust_main_county { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ ($county_html, $state_html, $country_html) =
+ FS::cust_main_county::regionselector( $county, $state, $country );
+
+=head1 DESCRIPTION
+
+An FS::cust_main_county object represents a tax rate, defined by locale.
+FS::cust_main_county inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item taxnum - primary key (assigned automatically for new tax rates)
+
+=item state
+
+=item county
+
+=item country
+
+=item tax - percentage
+
+=item taxclass
+
+=item exempt_amount
+
+=item taxname - if defined, printed on invoices instead of "Tax"
+
+=item setuptax - if 'Y', this tax does not apply to setup fees
+
+=item recurtax - if 'Y', this tax does not apply to recurring fees
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax rate. To add the tax rate to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_main_county'; }
+
+=item insert
+
+Adds this tax rate to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this tax rate from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid tax rate. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->exempt_amount(0) unless $self->exempt_amount;
+
+ $self->ut_numbern('taxnum')
+ || $self->ut_anything('state')
+ || $self->ut_textn('county')
+ || $self->ut_text('country')
+ || $self->ut_float('tax')
+ || $self->ut_textn('taxclass') # ...
+ || $self->ut_money('exempt_amount')
+ || $self->ut_textn('taxname')
+ || $self->ut_enum('setuptax', [ '', 'Y' ] )
+ || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->SUPER::check
+ ;
+
+}
+
+sub taxname {
+ my $self = shift;
+ if ( $self->dbdef_table->column('taxname') ) {
+ return $self->setfield('taxname', $_[0]) if @_;
+ return $self->getfield('taxname');
+ }
+ return '';
+}
+
+sub setuptax {
+ my $self = shift;
+ if ( $self->dbdef_table->column('setuptax') ) {
+ return $self->setfield('setuptax', $_[0]) if @_;
+ return $self->getfield('setuptax');
+ }
+ return '';
+}
+
+sub recurtax {
+ my $self = shift;
+ if ( $self->dbdef_table->column('recurtax') ) {
+ return $self->setfield('recurtax', $_[0]) if @_;
+ return $self->getfield('recurtax');
+ }
+ return '';
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item regionselector [ COUNTY STATE COUNTRY [ PREFIX [ ONCHANGE ] ] ]
+
+=cut
+
+sub regionselector {
+ my ( $selected_county, $selected_state, $selected_country,
+ $prefix, $onchange ) = @_;
+
+ $prefix = '' unless defined $prefix;
+
+ $countyflag = 0;
+
+# unless ( @cust_main_county ) { #cache
+ @cust_main_county = qsearch('cust_main_county', {} );
+ foreach my $c ( @cust_main_county ) {
+ $countyflag=1 if $c->county;
+ #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
+ $cust_main_county{$c->country}{$c->state}{$c->county} = 1;
+ }
+# }
+ $countyflag=1 if $selected_county;
+
+ my $script_html = <<END;
+ <SCRIPT>
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+ function ${prefix}country_changed(what) {
+ country = what.options[what.selectedIndex].text;
+ for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
+ what.form.${prefix}state.options[i] = null;
+END
+ #what.form.${prefix}state.options[0] = new Option('', '', false, true);
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ my $text = $state || '(n/a)';
+ $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+
+ $script_html .= <<END;
+ }
+ function ${prefix}state_changed(what) {
+END
+
+ if ( $countyflag ) {
+ $script_html .= <<END;
+ state = what.options[what.selectedIndex].text;
+ country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
+ for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
+ what.form.${prefix}county.options[i] = null;
+END
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ $script_html .= "\nif ( state == \"$state\" ) {\n";
+ #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
+ foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
+ my $text = $county || '(n/a)';
+ $script_html .=
+ qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+ $script_html .= "}\n";
+ }
+ }
+
+ $script_html .= <<END;
+ }
+ </SCRIPT>
+END
+
+ my $county_html = $script_html;
+ if ( $countyflag ) {
+ $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$onchange">!;
+ $county_html .= '</SELECT>';
+ } else {
+ $county_html .=
+ qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$selected_county">!;
+ }
+
+ my $state_html = qq!<SELECT NAME="${prefix}state" !.
+ qq!onChange="${prefix}state_changed(this); $onchange">!;
+ foreach my $state ( sort keys %{ $cust_main_county{$selected_country} } ) {
+ my $text = $state || '(n/a)';
+ my $selected = $state eq $selected_state ? 'SELECTED' : '';
+ $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
+ }
+ $state_html .= '</SELECT>';
+
+ $state_html .= '</SELECT>';
+
+ my $country_html = qq!<SELECT NAME="${prefix}country" !.
+ qq!onChange="${prefix}country_changed(this); $onchange">!;
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ foreach my $country (
+ sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
+ keys %cust_main_county
+ ) {
+ my $selected = $country eq $selected_country ? ' SELECTED' : '';
+ $country_html .= "\n<OPTION$selected>$country</OPTION>"
+ }
+ $country_html .= '</SELECT>';
+
+ ($county_html, $state_html, $country_html);
+
+}
+
+=back
+
+=head1 BUGS
+
+regionselector? putting web ui components in here? they should probably live
+somewhere else...
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm
new file mode 100644
index 0000000..add0cca
--- /dev/null
+++ b/FS/FS/cust_main_invoice.pm
@@ -0,0 +1,177 @@
+package FS::cust_main_invoice;
+
+use strict;
+use vars qw(@ISA $conf);
+use Exporter;
+use FS::Record qw( qsearchs );
+use FS::Conf;
+use FS::cust_main;
+use FS::svc_acct;
+use FS::Msgcat qw(gettext);
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_main_invoice - Object methods for cust_main_invoice records
+
+=head1 SYNOPSIS
+
+ use FS::cust_main_invoice;
+
+ $record = new FS::cust_main_invoice \%hash;
+ $record = new FS::cust_main_invoice { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $email_address = $record->address;
+
+=head1 DESCRIPTION
+
+An FS::cust_main_invoice object represents an invoice destination. FS::cust_main_invoice inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item destnum - primary key
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item dest - Invoice destination: If numeric, a svcnum (see L<FS::svc_acct>), if string, a literal email address, or `POST' to enable mailing (the default if no cust_main_invoice records exist)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice destination. To add the invoice destination to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_main_invoice'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ return "Can't change custnum!" unless $old->custnum == $new->custnum;
+
+ $new->SUPER::replace($old);
+}
+
+
+=item check
+
+Checks all fields to make sure this is a valid invoice destination. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error = $self->ut_numbern('destnum')
+ || $self->ut_number('custnum')
+ || $self->checkdest;
+ ;
+ return $error if $error;
+
+ return "Unknown customer"
+ unless qsearchs('cust_main',{ 'custnum' => $self->custnum });
+
+ $self->SUPER::check;
+}
+
+=item checkdest
+
+Checks the dest field only.
+
+#If it finds that the account ends in the
+#same domain configured as the B<domain> configuration file, it will change the
+#invoice destination from an email address to a service number (see
+#L<FS::svc_acct>).
+
+=cut
+
+sub checkdest {
+ my $self = shift;
+
+ my $error = $self->ut_text('dest');
+ return $error if $error;
+
+ if ( $self->dest eq 'POST' ) {
+ #contemplate our navel
+ } elsif ( $self->dest =~ /^(\d+)$/ ) {
+ return "Unknown local account (specified by svcnum: ". $self->dest. ")"
+ unless qsearchs( 'svc_acct', { 'svcnum' => $self->dest } );
+ } elsif ( $self->dest =~ /^([\w\.\-\&\+]+)\@(([\w\.\-]+\.)+\w+)$/ ) {
+ my($user, $domain) = ($1, $2);
+ $self->dest("$1\@$2");
+ } else {
+ return gettext("illegal_email_invoice_address");
+ }
+
+ ''; #no error
+}
+
+=item address
+
+Returns the literal email address for this record (or `POST').
+
+=cut
+
+sub address {
+ my $self = shift;
+ if ( $self->dest =~ /^(\d+)$/ ) {
+ my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } )
+ or return undef;
+ $svc_acct->email;
+ } else {
+ $self->dest;
+ }
+}
+
+=back
+
+=head1 VERSION
+
+$Id: cust_main_invoice.pm,v 1.14 2003-08-05 00:20:42 khoff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
new file mode 100644
index 0000000..d901c78
--- /dev/null
+++ b/FS/FS/cust_pay.pm
@@ -0,0 +1,484 @@
+package FS::cust_pay;
+
+use strict;
+use vars qw( @ISA $conf $unsuspendauto );
+use Date::Format;
+use Business::CreditCard;
+use Text::Template;
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::Misc qw(send_email);
+use FS::cust_bill;
+use FS::cust_bill_pay;
+use FS::cust_pay_refund;
+use FS::cust_main;
+use FS::cust_pay_void;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $unsuspendauto = $conf->exists('unsuspendauto');
+} );
+
+=head1 NAME
+
+FS::cust_pay - Object methods for cust_pay objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_pay;
+
+ $record = new FS::cust_pay \%hash;
+ $record = new FS::cust_pay { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay object represents a payment; the transfer of money from a
+customer. FS::cust_pay inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item paynum - primary key (assigned automatically for new payments)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item paid - Amount of this payment
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH),
+`LECB' (phone bill billing), `BILL' (billing), or `COMP' (free)
+
+=item payinfo - card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
+
+=item paybatch - text field for tracking card processing
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new payment. To add the payment to the databse, see L<"insert">.
+
+=cut
+
+sub table { 'cust_pay'; }
+
+=item insert
+
+Adds this payment to the database.
+
+For backwards-compatibility and convenience, if the additional field invnum
+is defined, an FS::cust_bill_pay record for the full amount of the payment
+will be created. In this case, custnum is optional.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $self->invnum ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
+ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unknown cust_bill.invnum: ". $self->invnum;
+ };
+ $self->custnum($cust_bill->custnum );
+ }
+
+ my $cust_main = $self->cust_main;
+ my $old_balance = $cust_main->balance;
+
+ my $error = $self->check;
+ return $error if $error;
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting $self: $error";
+ }
+
+ if ( $self->invnum ) {
+ my $cust_bill_pay = new FS::cust_bill_pay {
+ 'invnum' => $self->invnum,
+ 'paynum' => $self->paynum,
+ 'amount' => $self->paid,
+ '_date' => $self->_date,
+ };
+ $error = $cust_bill_pay->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting $cust_bill_pay: $error";
+ }
+ }
+
+ if ( $self->paybatch =~ /^webui-/ ) {
+ my @cust_pay = qsearch('cust_pay', {
+ 'custnum' => $self->custnum,
+ 'paybatch' => $self->paybatch,
+ } );
+ if ( scalar(@cust_pay) > 1 ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "a payment with webui token ". $self->paybatch. " already exists";
+ }
+ }
+
+ #false laziness w/ cust_credit::insert
+ if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
+ my @errors = $cust_main->unsuspend;
+ #return
+ # side-fx with nested transactions? upstack rolls back?
+ warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
+ join(' / ', @errors)
+ if @errors;
+ }
+ #eslaf
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ #my $cust_main = $self->cust_main;
+ if ( $conf->exists('payment_receipt_email')
+ && grep { $_ ne 'POST' } $cust_main->invoicing_list
+ ) {
+
+ my $receipt_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ],
+ ) or do {
+ warn "can't create payment receipt template: $Text::Template::ERROR";
+ return '';
+ };
+
+ my @invoicing_list = grep { $_ ne 'POST' } $cust_main->invoicing_list;
+
+ my $payby = $self->payby;
+ my $payinfo = $self->payinfo;
+ $payby =~ s/^BILL$/Check/ if $payinfo;
+ $payinfo = $self->payinfo_masked if $payby eq 'CARD';
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'), #??? well as good as any
+ 'to' => \@invoicing_list,
+ 'subject' => 'Payment receipt',
+ 'body' => [ $receipt_template->fill_in( HASH => {
+ 'date' => time2str("%a %B %o, %Y", $self->_date),
+ 'name' => $cust_main->name,
+ 'paynum' => $self->paynum,
+ 'paid' => sprintf("%.2f", $self->paid),
+ 'payby' => ucfirst(lc($payby)),
+ 'payinfo' => $payinfo,
+ 'balance' => $cust_main->balance,
+ } ) ],
+ );
+ if ( $error ) {
+ warn "can't send payment receipt: $error";
+ }
+
+ }
+
+ '';
+
+}
+
+=item void [ REASON ]
+
+Voids this payment: deletes the payment and all associated applications and
+adds a record of the voided payment to the FS::cust_pay_void table.
+
+=cut
+
+sub void {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $cust_pay_void = new FS::cust_pay_void ( {
+ map { $_ => $self->get($_) } $self->fields
+ } );
+ $cust_pay_void->reason(shift) if scalar(@_);
+ my $error = $cust_pay_void->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $error = $self->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Deletes this payment and all associated applications (see L<FS::cust_bill_pay>),
+unless the closed flag is set. In most cases, you want to use the void
+method instead to leave a record of the deleted payment.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed payment" if $self->closed =~ /^Y/i;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $app ( $self->cust_bill_pay, $self->cust_pay_refund ) {
+ my $error = $app->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $conf->config('deletepayments') ne '' ) {
+
+ my $cust_main = $self->cust_main;
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'), #??? well as good as any
+ 'to' => $conf->config('deletepayments'),
+ 'subject' => 'FREESIDE NOTIFICATION: Payment deleted',
+ 'body' => [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you that the following payment has been deleted:\n",
+ "\n",
+ 'paynum: '. $self->paynum. "\n",
+ 'custnum: '. $self->custnum.
+ " (". $cust_main->last. ", ". $cust_main->first. ")\n",
+ 'paid: $'. sprintf("%.2f", $self->paid). "\n",
+ 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
+ 'payby: '. $self->payby. "\n",
+ 'payinfo: '. $self->payinfo. "\n",
+ 'paybatch: '. $self->paybatch. "\n",
+ ],
+ );
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't send payment deletion notification: $error";
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace OLD_RECORD
+
+You probably shouldn't modify payments...
+
+=item check
+
+Checks all fields to make sure this is a valid payment. If there is an error,
+returns the error, otherwise returns false. Called by the insert method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('paynum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_money('paid')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('paybatch')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "paid must be > 0 " if $self->paid <= 0;
+
+ return "unknown cust_main.custnum: ". $self->custnum
+ unless $self->invnum
+ || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP)$/ or return "Illegal payby";
+ $self->payby($1);
+
+ #false laziness with cust_refund::check
+ if ( $self->payby eq 'CARD' ) {
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+ $self->payinfo($payinfo);
+ if ( $self->payinfo ) {
+ $self->payinfo =~ /^(\d{13,16})$/
+ or return "Illegal (mistyped?) credit card number (payinfo)";
+ $self->payinfo($1);
+ validate($self->payinfo) or return "Illegal credit card number";
+ return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
+ } else {
+ $self->payinfo('N/A');
+ }
+
+ } else {
+ $error = $self->ut_textn('payinfo');
+ return $error if $error;
+ }
+
+ $self->SUPER::check;
+}
+
+=item cust_bill_pay
+
+Returns all applications to invoices (see L<FS::cust_bill_pay>) for this
+payment.
+
+=cut
+
+sub cust_bill_pay {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date
+ || $a->invnum <=> $b->invnum }
+ qsearch( 'cust_bill_pay', { 'paynum' => $self->paynum } )
+ ;
+}
+
+=item cust_pay_refund
+
+Returns all applications of refunds (see L<FS::cust_pay_refund>) to this
+payment.
+
+=cut
+
+sub cust_pay_refund {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay_refund', { 'paynum' => $self->paynum } )
+ ;
+}
+
+
+=item unapplied
+
+Returns the amount of this payment that is still unapplied; which is
+paid minus all payment applications (see L<FS::cust_bill_pay>) and refund
+applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unapplied {
+ my $self = shift;
+ my $amount = $self->paid;
+ $amount -= $_->amount foreach ( $self->cust_bill_pay );
+ $amount -= $_->amount foreach ( $self->cust_pay_refund );
+ sprintf("%.2f", $amount );
+}
+
+=item unrefunded
+
+Returns the amount of this payment that has not been refuned; which is
+paid minus all refund applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unrefunded {
+ my $self = shift;
+ my $amount = $self->paid;
+ $amount -= $_->amount foreach ( $self->cust_pay_refund );
+ sprintf("%.2f", $amount );
+}
+
+
+=item cust_main
+
+Returns the parent customer object (see L<FS::cust_main>).
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=item payinfo_masked
+
+Returns a "masked" payinfo field with all but the last four characters replaced
+by 'x'es. Useful for displaying credit cards.
+
+=cut
+
+sub payinfo_masked {
+ my $self = shift;
+ my $payinfo = $self->payinfo;
+ 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods. payinfo_masked false laziness with cust_main.pm
+and cust_refund.pm
+
+=head1 SEE ALSO
+
+L<FS::cust_bill_pay>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm
new file mode 100644
index 0000000..8059f1c
--- /dev/null
+++ b/FS/FS/cust_pay_batch.pm
@@ -0,0 +1,397 @@
+package FS::cust_pay_batch;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(dbh qsearchs);
+use Business::CreditCard;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_pay_batch - Object methods for batch cards
+
+=head1 SYNOPSIS
+
+ use FS::cust_pay_batch;
+
+ $record = new FS::cust_pay_batch \%hash;
+ $record = new FS::cust_pay_batch { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay_batch object represents a credit card transaction ready to be
+batched (sent to a processor). FS::cust_pay_batch inherits from FS::Record.
+Typically called by the collect method of an FS::cust_main object. The
+following fields are currently supported:
+
+=over 4
+
+=item paybatchnum - primary key (automatically assigned)
+
+=item cardnum
+
+=item exp - card expiration
+
+=item amount
+
+=item invnum - invoice
+
+=item custnum - customer
+
+=item payname - name on card
+
+=item first - name
+
+=item last - name
+
+=item address1
+
+=item address2
+
+=item city
+
+=item state
+
+=item zip
+
+=item country
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_pay_batch'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item replace OLD_RECORD
+
+#inactive
+#
+#Replaces the OLD_RECORD with this one in the database. If there is an error,
+#returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ return "Can't (yet?) replace batched transactions!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid transaction. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('paybatchnum')
+ || $self->ut_numbern('trancode') #depriciated
+ || $self->ut_number('cardnum')
+ || $self->ut_money('amount')
+ || $self->ut_number('invnum')
+ || $self->ut_number('custnum')
+ || $self->ut_text('address1')
+ || $self->ut_textn('address2')
+ || $self->ut_text('city')
+ || $self->ut_textn('state')
+ ;
+
+ return $error if $error;
+
+ $self->getfield('last') =~ /^([\w \,\.\-\']+)$/ or return "Illegal last name";
+ $self->setfield('last',$1);
+
+ $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name";
+ $self->first($1);
+
+ my $cardnum = $self->cardnum;
+ $cardnum =~ s/\D//g;
+ $cardnum =~ /^(\d{13,16})$/
+ or return "Illegal credit card number";
+ $cardnum = $1;
+ $self->cardnum($cardnum);
+ validate($cardnum) or return "Illegal credit card number";
+ return "Unknown card type" if cardtype($cardnum) eq "Unknown";
+
+ if ( $self->exp eq '' ) {
+ return "Expriation date required"; #unless
+ $self->exp('');
+ } else {
+ if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) {
+ $self->exp("$1-$2-$3");
+ } elsif ( $self->exp =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ ) {
+ if ( length($2) == 4 ) {
+ $self->exp("$2-$1-01");
+ } elsif ( $2 > 98 ) { #should pry change to check for "this year"
+ $self->exp("19$2-$1-01");
+ } else {
+ $self->exp("20$2-$1-01");
+ }
+ } else {
+ return "Illegal expiration date";
+ }
+ }
+
+ if ( $self->payname eq '' ) {
+ $self->payname( $self->first. " ". $self->getfield('last') );
+ } else {
+ $self->payname =~ /^([\w \,\.\-\']+)$/
+ or return "Illegal billing name";
+ $self->payname($1);
+ }
+
+ #$self->zip =~ /^\s*(\w[\w\-\s]{3,8}\w)\s*$/
+ # or return "Illegal zip: ". $self->zip;
+ #$self->zip($1);
+
+ $self->country =~ /^(\w\w)$/ or return "Illegal country: ". $self->country;
+ $self->country($1);
+
+ $error = $self->ut_zip('zip', $self->country);
+ return $error if $error;
+
+ #check invnum, custnum, ?
+
+ $self->SUPER::check;
+}
+
+=item cust_main
+
+Returns the customer (see L<FS::cust_main>) for this batched credit card
+payment.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item import_results
+
+=cut
+
+sub import_results {
+ use Time::Local;
+ use FS::cust_pay;
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+#
+ my $param = shift;
+ my $fh = $param->{'filehandle'};
+ my $format = $param->{'format'};
+ my $paybatch = $param->{'paybatch'};
+
+ my @fields;
+ my $end_condition;
+ my $end_hook;
+ my $hook;
+ my $approved_condition;
+ my $declined_condition;
+
+ if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) {
+
+ @fields = (
+ 'paybatchnum', # Reference#: Invoice number of the transaction
+ 'paid', # Amount: Amount of the transaction. Dollars and cents
+ # with no decimal entered.
+ '', # Card Type: 0 - MCrd, 1 - Visa, 2 - AMEX, 3 - Discover,
+ # 4 - Insignia, 5 - Diners/EnRoute, 6 - JCB
+ '_date', # Transaction Date: Date the Transaction was processed
+ 'time', # Transaction Time: Time the transaction was processed
+ 'payinfo', # Card Number: Card number for the transaction
+ '', # Expiry Date: Expiry date of the card
+ '', # Auth#: Authorization number entered for force post
+ # transaction
+ 'type', # Transaction Type: 0 - purchase, 40 - refund,
+ # 20 - force post
+ 'result', # Processing Result: 3 - Approval,
+ # 4 - Declined/Amount over limit,
+ # 5 - Invalid/Expired/stolen card,
+ # 6 - Comm Error
+ '', # Terminal ID: Terminal ID used to process the transaction
+ );
+
+ $end_condition = sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0BC';
+ };
+
+ $end_hook = sub {
+ my( $hash, $total) = @_;
+ $total = sprintf("%.2f", $total);
+ my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100 );
+ return "Our total $total does not match bank total $batch_total!"
+ if $total != $batch_total;
+ '';
+ };
+
+ $hook = sub {
+ my $hash = shift;
+ $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
+ $hash->{'_date'} = timelocal( substr($hash->{'time'}, 4, 2),
+ substr($hash->{'time'}, 2, 2),
+ substr($hash->{'time'}, 0, 2),
+ substr($hash->{'_date'}, 6, 2),
+ substr($hash->{'_date'}, 4, 2)-1,
+ substr($hash->{'_date'}, 0, 4)-1900, );
+ };
+
+ $approved_condition = sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && $hash->{'result'} == 3;
+ };
+
+ $declined_condition = sub {
+ my $hash = shift;
+ $hash->{'type'} eq '0' && ( $hash->{'result'} == 4
+ || $hash->{'result'} == 5 );
+ };
+
+
+ } else {
+ return "Unknown format $format";
+ }
+
+ my $csv = new Text::CSV_XS;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $total = 0;
+ my $line;
+ while ( defined($line=<$fh>) ) {
+
+ next if $line =~ /^\s*$/; #skip blank lines
+
+ $csv->parse($line) or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't parse: ". $csv->error_input();
+ };
+
+ my @values = $csv->fields();
+ my %hash;
+ foreach my $field ( @fields ) {
+ my $value = shift @values;
+ next unless $field;
+ $hash{$field} = $value;
+ }
+
+ if ( &{$end_condition}(\%hash) ) {
+ my $error = &{$end_hook}(\%hash, $total);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ last;
+ }
+
+ my $cust_pay_batch =
+ qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'} } );
+ unless ( $cust_pay_batch ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "unknown paybatchnum $hash{'paybatchnum'}\n";
+ }
+ my $custnum = $cust_pay_batch->custnum,
+
+ my $error = $cust_pay_batch->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error removing paybatchnum $hash{'paybatchnum'}: $error\n";
+ }
+
+ &{$hook}(\%hash);
+
+ if ( &{$approved_condition}(\%hash) ) {
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'custnum' => $custnum,
+ 'payby' => 'CARD',
+ 'paybatch' => $paybatch,
+ map { $_ => $hash{$_} } (qw( paid _date payinfo )),
+ } );
+ $error = $cust_pay->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error adding payment paybatchnum $hash{'paybatchnum'}: $error\n";
+ }
+ $total += $hash{'paid'};
+
+ $cust_pay->cust_main->apply_payments;
+
+ } elsif ( &{$declined_condition}(\%hash) ) {
+
+ #this should be configurable... if anybody else ever uses batches
+ $cust_pay_batch->cust_main->suspend;
+
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+There should probably be a configuration file with a list of allowed credit
+card types.
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay_refund.pm b/FS/FS/cust_pay_refund.pm
new file mode 100644
index 0000000..af25f17
--- /dev/null
+++ b/FS/FS/cust_pay_refund.pm
@@ -0,0 +1,177 @@
+package FS::cust_pay_refund;
+
+use strict;
+use vars qw( @ISA ); #$conf );
+use FS::UID qw( getotaker );
+use FS::Record qw( qsearchs ); # qsearch );
+use FS::cust_main;
+use FS::cust_pay;
+use FS::cust_refund;
+
+@ISA = qw( FS::Record );
+
+#ask FS::UID to run this stuff for us later
+#FS::UID->install_callback( sub {
+# $conf = new FS::Conf;
+#} );
+
+=head1 NAME
+
+FS::cust_pay_refund - Object methods for cust_pay_refund records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pay_refund;
+
+ $record = new FS::cust_pay_refund \%hash;
+ $record = new FS::cust_pay_refund { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay_refund object represents application of a refund (see
+L<FS::cust_refund>) to an payment (see L<FS::cust_pay>). FS::cust_pay_refund
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item payrefundnum - primary key
+
+=item paynum - credit being applied
+
+=item refundnum - invoice to which credit is applied (see L<FS::cust_bill>)
+
+=item amount - amount of the credit applied
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cust_pay_refund. To add the cust_pay_refund to the database,
+see L<"insert">.
+
+=cut
+
+sub table { 'cust_pay_refund'; }
+
+=item insert
+
+Adds this cust_pay_refund to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't apply refund to closed payment"
+ if $self->cust_pay->closed =~ /^Y/i;
+ return "Can't apply closed refund"
+ if $self->cust_refund->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Application of refunds to payments may not be modified.
+
+=cut
+
+sub replace {
+ return "Can't modify application of a refund to payment!"
+}
+
+=item check
+
+Checks all fields to make sure this is a valid refund application to a payment.
+If there is an error, returns the error, otherwise returns false. Called by
+the insert and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('payrefundnum')
+ || $self->ut_number('paynum')
+ || $self->ut_number('refundnum')
+ || $self->ut_numbern('_date')
+ || $self->ut_money('amount')
+ ;
+ return $error if $error;
+
+ return "amount must be > 0" if $self->amount <= 0;
+
+ return "Unknown payment"
+ unless my $cust_pay =
+ qsearchs( 'cust_pay', { 'paynum' => $self->paynum } );
+
+ return "Unknown refund"
+ unless my $cust_refund =
+ qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } );
+
+ $self->_date(time) unless $self->_date;
+
+ return 'Cannot apply ($'. $self->amount. ') more than'.
+ ' remaining value of refund ($'. $cust_refund->unapplied. ')'
+ unless $self->amount <= $cust_refund->unapplied;
+
+ return "Cannot apply more than remaining value of payment"
+ unless $self->amount <= $cust_pay->unapplied;
+
+ $self->SUPER::check;
+}
+
+=item sub cust_credit
+
+Returns the credit (see L<FS::cust_credit>)
+
+=cut
+
+sub cust_credit {
+ my $self = shift;
+ qsearchs( 'cust_credit', { 'crednum' => $self->crednum } );
+}
+
+=item cust_bill
+
+Returns the invoice (see L<FS::cust_bill>)
+
+=cut
+
+sub cust_bill {
+ my $self = shift;
+ qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
+}
+
+=back
+
+=head1 BUGS
+
+The delete method.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, L<FS::cust_credit>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm
new file mode 100644
index 0000000..7267929
--- /dev/null
+++ b/FS/FS/cust_pay_void.pm
@@ -0,0 +1,196 @@
+package FS::cust_pay_void;
+use strict;
+use vars qw( @ISA );
+use Business::CreditCard;
+use FS::UID qw(getotaker);
+use FS::Record qw(qsearchs); # dbh qsearch );
+#use FS::cust_bill;
+#use FS::cust_bill_pay;
+#use FS::cust_pay_refund;
+#use FS::cust_main;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_pay_void - Object methods for cust_pay_void objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_pay_void;
+
+ $record = new FS::cust_pay_void \%hash;
+ $record = new FS::cust_pay_void { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay_void object represents a voided payment. The following fields
+are currently supported:
+
+=over 4
+
+=item paynum - primary key (assigned automatically for new payments)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item paid - Amount of this payment
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH),
+`LECB' (phone bill billing), `BILL' (billing), or `COMP' (free)
+
+=item payinfo - card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
+
+=item paybatch - text field for tracking card processing
+
+=item closed - books closed flag, empty or `Y'
+
+=item void_date
+
+=item reason
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new payment. To add the payment to the databse, see L<"insert">.
+
+=cut
+
+sub table { 'cust_pay_void'; }
+
+=item insert
+
+Adds this voided payment to the database.
+
+=item delete
+
+Currently unimplemented.
+
+=cut
+
+sub delete {
+ return "Can't delete voided payments!";
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented.
+
+=cut
+
+sub replace {
+ return "Can't modify voided payments!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid voided payment. If there is an
+error, returns the error, otherwise returns false. Called by the insert
+method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('paynum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_money('paid')
+ || $self->ut_number('_date')
+ || $self->ut_textn('paybatch')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ || $self->ut_numbern('void_date')
+ || $self->ut_textn('reason')
+ ;
+ return $error if $error;
+
+ return "paid must be > 0 " if $self->paid <= 0;
+
+ return "unknown cust_main.custnum: ". $self->custnum
+ unless $self->invnum
+ || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->void_date(time) unless $self->void_date;
+
+ $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP)$/ or return "Illegal payby";
+ $self->payby($1);
+
+ #false laziness with cust_refund::check
+ if ( $self->payby eq 'CARD' ) {
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+ $self->payinfo($payinfo);
+ if ( $self->payinfo ) {
+ $self->payinfo =~ /^(\d{13,16})$/
+ or return "Illegal (mistyped?) credit card number (payinfo)";
+ $self->payinfo($1);
+ validate($self->payinfo) or return "Illegal credit card number";
+ return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
+ } else {
+ $self->payinfo('N/A');
+ }
+
+ } else {
+ $error = $self->ut_textn('payinfo');
+ return $error if $error;
+ }
+
+ $self->otaker(getotaker);
+
+ $self->SUPER::check;
+}
+
+=item cust_main
+
+Returns the parent customer object (see L<FS::cust_main>).
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=item payinfo_masked
+
+Returns a "masked" payinfo field with all but the last four characters replaced
+by 'x'es. Useful for displaying credit cards.
+
+=cut
+
+sub payinfo_masked {
+ my $self = shift;
+ my $payinfo = $self->payinfo;
+ 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+}
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods.
+
+=head1 SEE ALSO
+
+L<FS::cust_pay>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
new file mode 100644
index 0000000..d2a48e9
--- /dev/null
+++ b/FS/FS/cust_pkg.pm
@@ -0,0 +1,973 @@
+package FS::cust_pkg;
+
+use strict;
+use vars qw(@ISA $disable_agentcheck @SVCDB_CANCEL_SEQ $DEBUG);
+use FS::UID qw( getotaker dbh );
+use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw( send_email );
+use FS::cust_svc;
+use FS::part_pkg;
+use FS::cust_main;
+use FS::type_pkgs;
+use FS::pkg_svc;
+use FS::cust_bill_pkg;
+
+# need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend,
+# setup }
+# because they load configuraion by setting FS::UID::callback (see TODO)
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_www;
+use FS::svc_forward;
+
+# for sending cancel emails in sub cancel
+use FS::Conf;
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+
+$disable_agentcheck = 0;
+
+# The order in which to unprovision services.
+@SVCDB_CANCEL_SEQ = qw( svc_external
+ svc_www
+ svc_forward
+ svc_acct
+ svc_domain
+ svc_broadband );
+
+sub _cache {
+ my $self = shift;
+ my ( $hashref, $cache ) = @_;
+ #if ( $hashref->{'pkgpart'} ) {
+ if ( $hashref->{'pkg'} ) {
+ # #@{ $self->{'_pkgnum'} } = ();
+ # my $subcache = $cache->subcache('pkgpart', 'part_pkg');
+ # $self->{'_pkgpart'} = $subcache;
+ # #push @{ $self->{'_pkgnum'} },
+ # FS::part_pkg->new_or_cached($hashref, $subcache);
+ $self->{'_pkgpart'} = FS::part_pkg->new($hashref);
+ }
+ if ( exists $hashref->{'svcnum'} ) {
+ #@{ $self->{'_pkgnum'} } = ();
+ my $subcache = $cache->subcache('svcnum', 'cust_svc', $hashref->{pkgnum});
+ $self->{'_svcnum'} = $subcache;
+ #push @{ $self->{'_pkgnum'} },
+ FS::cust_svc->new_or_cached($hashref, $subcache) if $hashref->{svcnum};
+ }
+}
+
+=head1 NAME
+
+FS::cust_pkg - Object methods for cust_pkg objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg;
+
+ $record = new FS::cust_pkg \%hash;
+ $record = new FS::cust_pkg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->cancel;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $part_pkg = $record->part_pkg;
+
+ @labels = $record->labels;
+
+ $seconds = $record->seconds_since($timestamp);
+
+ $error = FS::cust_pkg::order( $custnum, \@pkgparts );
+ $error = FS::cust_pkg::order( $custnum, \@pkgparts, \@remove_pkgnums ] );
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg object represents a customer billing item. FS::cust_pkg
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgnum - primary key (assigned automatically for new billing items)
+
+=item custnum - Customer (see L<FS::cust_main>)
+
+=item pkgpart - Billing item definition (see L<FS::part_pkg>)
+
+=item setup - date
+
+=item bill - date (next bill date)
+
+=item last_bill - last bill date
+
+=item susp - date
+
+=item expire - date
+
+=item cancel - date
+
+=item otaker - order taker (assigned automatically if null, see L<FS::UID>)
+
+=item manual_flag - If this field is set to 1, disables the automatic
+unsuspension of this package when using the B<unsuspendauto> config file.
+
+=back
+
+Note: setup, bill, susp, expire and cancel are specified as UNIX timestamps;
+see L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for
+conversion functions.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new billing item. To add the item to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_pkg'; }
+
+=item insert
+
+Adds this billing item to the database ("Orders" the item). If there is an
+error, returns the error, otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ # custnum might not have have been defined in sub check (for one-shot new
+ # customers), so check it here instead
+ # (is this still necessary with transactions?)
+
+ my $error = $self->ut_number('custnum');
+ return $error if $error;
+
+ my $cust_main = $self->cust_main;
+ return "Unknown custnum: ". $self->custnum unless $cust_main;
+
+ unless ( $disable_agentcheck ) {
+ my $agent = qsearchs( 'agent', { 'agentnum' => $cust_main->agentnum } );
+ my $pkgpart_href = $agent->pkgpart_hashref;
+ return "agent ". $agent->agentnum.
+ " can't purchase pkgpart ". $self->pkgpart
+ unless $pkgpart_href->{ $self->pkgpart };
+ }
+
+ $self->SUPER::insert;
+
+}
+
+=item delete
+
+This method now works but you probably shouldn't use it.
+
+You don't want to delete billing items, because there would then be no record
+the customer ever purchased the item. Instead, see the cancel method.
+
+=cut
+
+#sub delete {
+# return "Can't delete cust_pkg records!";
+#}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+Currently, custnum, setup, bill, susp, expire, and cancel may be changed.
+
+Changing pkgpart may have disasterous effects. See the order subroutine.
+
+setup and bill are normally updated by calling the bill method of a customer
+object (see L<FS::cust_main>).
+
+suspend is normally updated by the suspend and unsuspend methods.
+
+cancel is normally updated by the cancel method (and also the order subroutine
+in some cases).
+
+=cut
+
+sub replace {
+ my( $new, $old ) = ( shift, shift );
+
+ #return "Can't (yet?) change pkgpart!" if $old->pkgpart != $new->pkgpart;
+ return "Can't change otaker!" if $old->otaker ne $new->otaker;
+
+ #allow this *sigh*
+ #return "Can't change setup once it exists!"
+ # if $old->getfield('setup') &&
+ # $old->getfield('setup') != $new->getfield('setup');
+
+ #some logic for bill, susp, cancel?
+
+ $new->SUPER::replace($old);
+}
+
+=item check
+
+Checks all fields to make sure this is a valid billing item. If there is an
+error, returns the error, otherwise returns false. Called by the insert and
+replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('pkgnum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_number('pkgpart')
+ || $self->ut_numbern('setup')
+ || $self->ut_numbern('bill')
+ || $self->ut_numbern('susp')
+ || $self->ut_numbern('cancel')
+ ;
+ return $error if $error;
+
+ if ( $self->custnum ) {
+ return "Unknown customer ". $self->custnum unless $self->cust_main;
+ }
+
+ return "Unknown pkgpart: ". $self->pkgpart
+ unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+
+ $self->otaker(getotaker) unless $self->otaker;
+ $self->otaker =~ /^([\w\.\-]{0,16})$/ or return "Illegal otaker";
+ $self->otaker($1);
+
+ if ( $self->dbdef_table->column('manual_flag') ) {
+ $self->manual_flag('') if $self->manual_flag eq ' ';
+ $self->manual_flag =~ /^([01]?)$/
+ or return "Illegal manual_flag ". $self->manual_flag;
+ $self->manual_flag($1);
+ }
+
+ $self->SUPER::check;
+}
+
+=item cancel [ OPTION => VALUE ... ]
+
+Cancels and removes all services (see L<FS::cust_svc> and L<FS::part_svc>)
+in this package, then cancels the package itself (sets the cancel field to
+now).
+
+Available options are: I<quiet>
+
+I<quiet> can be set true to supress email cancellation notices.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub cancel {
+ my( $self, %options ) = @_;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my %svc;
+ foreach my $cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
+ ) {
+ push @{ $svc{$cust_svc->part_svc->svcdb} }, $cust_svc;
+ }
+
+ foreach my $svcdb (@SVCDB_CANCEL_SEQ) {
+ foreach my $cust_svc (@{ $svc{$svcdb} }) {
+ my $error = $cust_svc->cancel;
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error cancelling cust_svc: $error";
+ }
+ }
+ }
+
+ unless ( $self->getfield('cancel') ) {
+ my %hash = $self->hash;
+ $hash{'cancel'} = time;
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ my $conf = new FS::Conf;
+ my @invoicing_list = grep { $_ ne 'POST' } $self->cust_main->invoicing_list;
+ if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) {
+ my $conf = new FS::Conf;
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'),
+ 'to' => \@invoicing_list,
+ 'subject' => $conf->config('cancelsubject'),
+ 'body' => [ map "$_\n", $conf->config('cancelmessage') ],
+ );
+ #should this do something on errors?
+ }
+
+ ''; #no errors
+
+}
+
+=item suspend
+
+Suspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this
+package, then suspends the package itself (sets the susp field to now).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub suspend {
+ my $self = shift;
+ my $error ;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
+ ) {
+ my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
+
+ $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Illegal svcdb value in part_svc!";
+ };
+ my $svcdb = $1;
+ require "FS/$svcdb.pm";
+
+ my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
+ if ($svc) {
+ $error = $svc->suspend;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ unless ( $self->getfield('susp') ) {
+ my %hash = $self->hash;
+ $hash{'susp'} = time;
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+}
+
+=item unsuspend
+
+Unsuspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this
+package, then unsuspends the package itself (clears the susp field).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub unsuspend {
+ my $self = shift;
+ my($error);
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_svc (
+ qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } )
+ ) {
+ my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
+
+ $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Illegal svcdb value in part_svc!";
+ };
+ my $svcdb = $1;
+ require "FS/$svcdb.pm";
+
+ my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
+ if ($svc) {
+ $error = $svc->unsuspend;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ unless ( ! $self->getfield('susp') ) {
+ my %hash = $self->hash;
+ my $inactive = time - $hash{'susp'};
+ $hash{'susp'} = '';
+ $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
+ if $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} );
+ my $new = new FS::cust_pkg ( \%hash );
+ $error = $new->replace($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+}
+
+=item last_bill
+
+Returns the last bill date, or if there is no last bill date, the setup date.
+Useful for billing metered services.
+
+=cut
+
+sub last_bill {
+ my $self = shift;
+ if ( $self->dbdef_table->column('last_bill') ) {
+ return $self->setfield('last_bill', $_[0]) if @_;
+ return $self->getfield('last_bill') if $self->getfield('last_bill');
+ }
+ my $cust_bill_pkg = qsearchs('cust_bill_pkg', { 'pkgnum' => $self->pkgnum,
+ 'edate' => $self->bill, } );
+ $cust_bill_pkg ? $cust_bill_pkg->sdate : $self->setup || 0;
+}
+
+=item part_pkg
+
+Returns the definition for this billing item, as an FS::part_pkg object (see
+L<FS::part_pkg>).
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ #exists( $self->{'_pkgpart'} )
+ $self->{'_pkgpart'}
+ ? $self->{'_pkgpart'}
+ : qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item cust_svc [ SVCPART ]
+
+Returns the services for this package, as FS::cust_svc objects (see
+L<FS::cust_svc>). If a svcpart is specified, return only the matching
+services.
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+
+ if ( @_ ) {
+ return qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum,
+ 'svcpart' => shift, } );
+ }
+
+ #if ( $self->{'_svcnum'} ) {
+ # values %{ $self->{'_svcnum'}->cache };
+ #} else {
+ map { $_->[0] }
+ sort { $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] }
+ map {
+ my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
+ 'svcpart' => $_->svcpart } );
+ [ $_,
+ $pkg_svc ? $pkg_svc->primary_svc : '',
+ $pkg_svc ? $pkg_svc->quantity : 0,
+ ];
+ }
+ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } );
+ #}
+
+}
+
+=item num_cust_svc [ SVCPART ]
+
+Returns the number of provisioned services for this package. If a svcpart is
+specified, counts only the matching services.
+
+=cut
+
+sub num_cust_svc {
+ my $self = shift;
+ my $sql = 'SELECT COUNT(*) FROM cust_svc WHERE pkgnum = ?';
+ $sql .= ' AND svcpart = ?' if @_;
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute($self->pkgnum, @_) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item available_part_svc
+
+Returns a list FS::part_svc objects representing services included in this
+package but not yet provisioned. Each FS::part_svc object also has an extra
+field, I<num_avail>, which specifies the number of available services.
+
+=cut
+
+sub available_part_svc {
+ my $self = shift;
+ grep { $_->num_avail > 0 }
+ map {
+ my $part_svc = $_->part_svc;
+ $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking
+ $_->quantity - $self->num_cust_svc($_->svcpart);
+ $part_svc;
+ }
+ $self->part_pkg->pkg_svc;
+}
+
+=item labels
+
+Returns a list of lists, calling the label method for all services
+(see L<FS::cust_svc>) of this billing item.
+
+=cut
+
+sub labels {
+ my $self = shift;
+ map { [ $_->label ] } $self->cust_svc;
+}
+
+=item cust_main
+
+Returns the parent customer object (see L<FS::cust_main>).
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=item seconds_since TIMESTAMP
+
+Returns the number of seconds all accounts (see L<FS::svc_acct>) in this
+package have been online since TIMESTAMP, according to the session monitor.
+
+TIMESTAMP is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub seconds_since {
+ my($self, $since) = @_;
+ my $seconds = 0;
+
+ foreach my $cust_svc (
+ grep { $_->part_svc->svcdb eq 'svc_acct' } $self->cust_svc
+ ) {
+ $seconds += $cust_svc->seconds_since($since);
+ }
+
+ $seconds;
+
+}
+
+=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+Returns the numbers of seconds all accounts (see L<FS::svc_acct>) in this
+package have been online between TIMESTAMP_START (inclusive) and TIMESTAMP_END
+(exclusive).
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+
+=cut
+
+sub seconds_since_sqlradacct {
+ my($self, $start, $end) = @_;
+
+ my $seconds = 0;
+
+ foreach my $cust_svc (
+ grep {
+ my $part_svc = $_->part_svc;
+ $part_svc->svcdb eq 'svc_acct'
+ && scalar($part_svc->part_export('sqlradius'));
+ } $self->cust_svc
+ ) {
+ $seconds += $cust_svc->seconds_since_sqlradacct($start, $end);
+ }
+
+ $seconds;
+
+}
+
+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
+in this package for sessions ending between TIMESTAMP_START (inclusive) and
+TIMESTAMP_END
+(exclusive).
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+sub attribute_since_sqlradacct {
+ my($self, $start, $end, $attrib) = @_;
+
+ my $sum = 0;
+
+ foreach my $cust_svc (
+ grep {
+ my $part_svc = $_->part_svc;
+ $part_svc->svcdb eq 'svc_acct'
+ && scalar($part_svc->part_export('sqlradius'));
+ } $self->cust_svc
+ ) {
+ $sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
+ }
+
+ $sum;
+
+}
+
+=item transfer DEST_PKGNUM | DEST_CUST_PKG, [ OPTION => VALUE ... ]
+
+Transfers as many services as possible from this package to another package.
+
+The destination package can be specified by pkgnum by passing an FS::cust_pkg
+object. The destination package must already exist.
+
+Services are moved only if the destination allows services with the correct
+I<svcpart> (not svcdb), unless the B<change_svcpart> option is set true. Use
+this option with caution! No provision is made for export differences
+between the old and new service definitions. Probably only should be used
+when your exports for all service definitions of a given svcdb are identical.
+(attempt a transfer without it first, to move all possible svcpart-matching
+services)
+
+Any services that can't be moved remain in the original package.
+
+Returns an error, if there is one; otherwise, returns the number of services
+that couldn't be moved.
+
+=cut
+
+sub transfer {
+ my ($self, $dest_pkgnum, %opt) = @_;
+
+ my $remaining = 0;
+ my $dest;
+ my %target;
+
+ if (ref ($dest_pkgnum) eq 'FS::cust_pkg') {
+ $dest = $dest_pkgnum;
+ $dest_pkgnum = $dest->pkgnum;
+ } else {
+ $dest = qsearchs('cust_pkg', { pkgnum => $dest_pkgnum });
+ }
+
+ return ('Package does not exist: '.$dest_pkgnum) unless $dest;
+
+ foreach my $pkg_svc ( $dest->part_pkg->pkg_svc ) {
+ $target{$pkg_svc->svcpart} = $pkg_svc->quantity;
+ }
+
+ foreach my $cust_svc ($dest->cust_svc) {
+ $target{$cust_svc->svcpart}--;
+ }
+
+ my %svcpart2svcparts = ();
+ if ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) {
+ warn "change_svcpart option received, creating alternates list\n" if $DEBUG;
+ foreach my $svcpart ( map { $_->svcpart } $self->cust_svc ) {
+ next if exists $svcpart2svcparts{$svcpart};
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ $svcpart2svcparts{$svcpart} = [
+ map { $_->[0] }
+ sort { $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] }
+ map {
+ my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $dest->pkgpart,
+ 'svcpart' => $_ } );
+ [ $_,
+ $pkg_svc ? $pkg_svc->primary_svc : '',
+ $pkg_svc ? $pkg_svc->quantity : 0,
+ ];
+ }
+
+ grep { $_ != $svcpart }
+ map { $_->svcpart }
+ qsearch('part_svc', { 'svcdb' => $part_svc->svcdb } )
+ ];
+ warn "alternates for svcpart $svcpart: ".
+ join(', ', @{$svcpart2svcparts{$svcpart}}). "\n"
+ if $DEBUG;
+ }
+ }
+
+ foreach my $cust_svc ($self->cust_svc) {
+ if($target{$cust_svc->svcpart} > 0) {
+ $target{$cust_svc->svcpart}--;
+ my $new = new FS::cust_svc {
+ svcnum => $cust_svc->svcnum,
+ svcpart => $cust_svc->svcpart,
+ pkgnum => $dest_pkgnum,
+ };
+ my $error = $new->replace($cust_svc);
+ return $error if $error;
+ } elsif ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) {
+ if ( $DEBUG ) {
+ warn "looking for alternates for svcpart ". $cust_svc->svcpart. "\n";
+ warn "alternates to consider: ".
+ join(', ', @{$svcpart2svcparts{$cust_svc->svcpart}}). "\n";
+ }
+ my @alternate = grep {
+ warn "considering alternate svcpart $_: ".
+ "$target{$_} available in new package\n"
+ if $DEBUG;
+ $target{$_} > 0;
+ } @{$svcpart2svcparts{$cust_svc->svcpart}};
+ if ( @alternate ) {
+ warn "alternate(s) found\n" if $DEBUG;
+ my $change_svcpart = $alternate[0];
+ $target{$change_svcpart}--;
+ my $new = new FS::cust_svc {
+ svcnum => $cust_svc->svcnum,
+ svcpart => $change_svcpart,
+ pkgnum => $dest_pkgnum,
+ };
+ my $error = $new->replace($cust_svc);
+ return $error if $error;
+ } else {
+ $remaining++;
+ }
+ } else {
+ $remaining++
+ }
+ }
+ return $remaining;
+}
+
+=item reexport
+
+This method is deprecated. See the I<depend_jobnum> option to the insert and
+order_pkgs methods in FS::cust_main for a better way to defer provisioning.
+
+=cut
+
+sub reexport {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_svc ( $self->cust_svc ) {
+ #false laziness w/svc_Common::insert
+ my $svc_x = $cust_svc->svc_x;
+ foreach my $part_export ( $cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_insert($svc_x);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item order CUSTNUM, PKGPARTS_ARYREF, [ REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF ] ]
+
+CUSTNUM is a customer (see L<FS::cust_main>)
+
+PKGPARTS is a list of pkgparts specifying the the billing item definitions (see
+L<FS::part_pkg>) to order for this customer. Duplicates are of course
+permitted.
+
+REMOVE_PKGNUMS is an optional list of pkgnums specifying the billing items to
+remove for this customer. The services (see L<FS::cust_svc>) are moved to the
+new billing items. An error is returned if this is not possible (see
+L<FS::pkg_svc>). An empty arrayref is equivalent to not specifying this
+parameter.
+
+RETURN_CUST_PKG_ARRAYREF, if specified, will be filled in with the
+newly-created cust_pkg objects.
+
+=cut
+
+sub order {
+ my ($custnum, $pkgparts, $remove_pkgnum, $return_cust_pkg) = @_;
+
+ my $conf = new FS::Conf;
+
+ # Transactionize this whole mess
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error;
+ my $cust_main = qsearchs('cust_main', { custnum => $custnum });
+ return "Customer not found: $custnum" unless $cust_main;
+
+ # Create the new packages.
+ my $cust_pkg;
+ foreach (@$pkgparts) {
+ $cust_pkg = new FS::cust_pkg { custnum => $custnum,
+ pkgpart => $_ };
+ $error = $cust_pkg->insert;
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ push @$return_cust_pkg, $cust_pkg;
+ }
+ # $return_cust_pkg now contains refs to all of the newly
+ # created packages.
+
+ # Transfer services and cancel old packages.
+ foreach my $old_pkgnum (@$remove_pkgnum) {
+ my $old_pkg = qsearchs ('cust_pkg', { pkgnum => $old_pkgnum });
+
+ foreach my $new_pkg (@$return_cust_pkg) {
+ $error = $old_pkg->transfer($new_pkg);
+ if ($error and $error == 0) {
+ # $old_pkg->transfer failed.
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ if ( $error > 0 && $conf->exists('cust_pkg-change_svcpart') ) {
+ warn "trying transfer again with change_svcpart option\n" if $DEBUG;
+ foreach my $new_pkg (@$return_cust_pkg) {
+ $error = $old_pkg->transfer($new_pkg, 'change_svcpart'=>1 );
+ if ($error and $error == 0) {
+ # $old_pkg->transfer failed.
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ if ($error > 0) {
+ # Transfers were successful, but we went through all of the
+ # new packages and still had services left on the old package.
+ # We can't cancel the package under the circumstances, so abort.
+ $dbh->rollback if $oldAutoCommit;
+ return "Unable to transfer all services from package ".$old_pkg->pkgnum;
+ }
+ $error = $old_pkg->cancel;
+ if ($error) {
+ $dbh->rollback;
+ return $error;
+ }
+ }
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=back
+
+=head1 BUGS
+
+sub order is not OO. Perhaps it should be moved to FS::cust_main and made so?
+
+In sub order, the @pkgparts array (passed by reference) is clobbered.
+
+Also in sub order, no money is adjusted. Once FS::part_pkg defines a standard
+method to pass dates to the recur_prog expression, it should do so.
+
+FS::svc_acct, FS::svc_domain, FS::svc_www, FS::svc_ip and FS::svc_forward are
+loaded via 'use' at compile time, rather than via 'require' in sub { setup,
+suspend, unsuspend, cancel } because they use %FS::UID::callback to load
+configuration values. Probably need a subroutine which decides what to do
+based on whether or not we've fetched the user yet, rather than a hash. See
+FS::UID and the TODO.
+
+Now that things are transactional should the check in the insert method be
+moved to check ?
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>, L<FS::part_pkg>, L<FS::cust_svc>,
+L<FS::pkg_svc>, schema.html from the base documentation
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm
new file mode 100644
index 0000000..106ccd3
--- /dev/null
+++ b/FS/FS/cust_refund.pm
@@ -0,0 +1,317 @@
+package FS::cust_refund;
+
+use strict;
+use vars qw( @ISA );
+use Business::CreditCard;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::UID qw(getotaker);
+use FS::cust_credit;
+use FS::cust_credit_refund;
+use FS::cust_pay_refund;
+use FS::cust_main;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_refund - Object method for cust_refund objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_refund;
+
+ $record = new FS::cust_refund \%hash;
+ $record = new FS::cust_refund { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_refund represents a refund: the transfer of money to a customer;
+equivalent to a negative payment (see L<FS::cust_pay>). FS::cust_refund
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item refundnum - primary key (assigned automatically for new refunds)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item refund - Amount of the refund
+
+=item reason - Reason for the refund
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH),
+`LECB' (Phone bill billing), `BILL' (billing), or `COMP' (free)
+
+=item payinfo - card number, P.O.#, or comp issuer (4-8 lowercase alphanumerics; think username)
+
+=item paybatch - text field for tracking card processing
+
+=item otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new refund. To add the refund to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_refund'; }
+
+=item insert
+
+Adds this refund to the database.
+
+For backwards-compatibility and convenience, if the additional field crednum is
+defined, an FS::cust_credit_refund record for the full amount of the refund
+will be created. Or (this time for convenience and consistancy), if the
+additional field paynum is defined, an FS::cust_pay_refund record for the full
+amount of the refund will be created. In both cases, custnum is optional.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $self->crednum ) {
+ my $cust_credit = qsearchs('cust_credit', { 'crednum' => $self->crednum } )
+ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unknown cust_credit.crednum: ". $self->crednum;
+ };
+ $self->custnum($cust_credit->custnum);
+ } elsif ( $self->paynum ) {
+ my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } )
+ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Unknown cust_pay.paynum: ". $self->paynum;
+ };
+ $self->custnum($cust_pay->custnum);
+ }
+
+ my $error = $self->check;
+ return $error if $error;
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $self->crednum ) {
+ my $cust_credit_refund = new FS::cust_credit_refund {
+ 'crednum' => $self->crednum,
+ 'refundnum' => $self->refundnum,
+ 'amount' => $self->refund,
+ '_date' => $self->_date,
+ };
+ $error = $cust_credit_refund->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ #$self->custnum($cust_credit_refund->cust_credit->custnum);
+ } elsif ( $self->paynum ) {
+ my $cust_pay_refund = new FS::cust_pay_refund {
+ 'paynum' => $self->paynum,
+ 'refundnum' => $self->refundnum,
+ 'amount' => $self->refund,
+ '_date' => $self->_date,
+ };
+ $error = $cust_pay_refund->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't delete closed refund" if $self->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented (accounting reasons).
+
+=cut
+
+sub replace {
+ return "Can't (yet?) modify cust_refund records!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid refund. If there is an error,
+returns the error, otherwise returns false. Called by the insert method.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('refundnum')
+ || $self->ut_numbern('custnum')
+ || $self->ut_money('refund')
+ || $self->ut_text('reason')
+ || $self->ut_numbern('_date')
+ || $self->ut_textn('paybatch')
+ || $self->ut_enum('closed', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "refund must be > 0 " if $self->refund <= 0;
+
+ $self->_date(time) unless $self->_date;
+
+ return "unknown cust_main.custnum: ". $self->custnum
+ unless $self->crednum
+ || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+ $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP)$/ or return "Illegal payby";
+ $self->payby($1);
+
+ #false laziness with cust_pay::check
+ if ( $self->payby eq 'CARD' ) {
+ my $payinfo = $self->payinfo;
+ $payinfo =~ s/\D//g;
+ $self->payinfo($payinfo);
+ if ( $self->payinfo ) {
+ $self->payinfo =~ /^(\d{13,16})$/
+ or return "Illegal (mistyped?) credit card number (payinfo)";
+ $self->payinfo($1);
+ validate($self->payinfo) or return "Illegal credit card number";
+ return "Unknown card type" if cardtype($self->payinfo) eq "Unknown";
+ } else {
+ $self->payinfo('N/A');
+ }
+
+ } else {
+ $error = $self->ut_textn('payinfo');
+ return $error if $error;
+ }
+
+ $self->otaker(getotaker);
+
+ $self->SUPER::check;
+}
+
+=item cust_credit_refund
+
+Returns all applications to credits (see L<FS::cust_credit_refund>) for this
+refund.
+
+=cut
+
+sub cust_credit_refund {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } )
+ ;
+}
+
+=item cust_pay_refund
+
+Returns all applications to payments (see L<FS::cust_pay_refund>) for this
+refund.
+
+=cut
+
+sub cust_pay_refund {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } )
+ ;
+}
+
+=item unapplied
+
+Returns the amount of this refund that is still unapplied; which is
+amount minus all credit applications (see L<FS::cust_credit_refund>) and
+payment applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unapplied {
+ my $self = shift;
+ my $amount = $self->refund;
+ $amount -= $_->amount foreach ( $self->cust_credit_refund );
+ $amount -= $_->amount foreach ( $self->cust_pay_refund );
+ sprintf("%.2f", $amount );
+}
+
+
+
+=item payinfo_masked
+
+Returns a "masked" payinfo field with all but the last four characters replaced
+by 'x'es. Useful for displaying credit cards.
+
+=cut
+
+
+sub payinfo_masked {
+ my $self = shift;
+ my $payinfo = $self->payinfo;
+ 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
+}
+
+
+=back
+
+=head1 BUGS
+
+Delete and replace methods. payinfo_masked false laziness with cust_main.pm
+and cust_pay.pm
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_credit>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm
new file mode 100644
index 0000000..65f8d58
--- /dev/null
+++ b/FS/FS/cust_svc.pm
@@ -0,0 +1,615 @@
+package FS::cust_svc;
+
+use strict;
+use vars qw( @ISA $ignore_quantity );
+use Carp qw( cluck );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_pkg;
+use FS::part_pkg;
+use FS::part_svc;
+use FS::pkg_svc;
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_forward;
+use FS::svc_broadband;
+use FS::svc_external;
+use FS::domain_record;
+use FS::part_export;
+
+@ISA = qw( FS::Record );
+
+$ignore_quantity = 0;
+
+sub _cache {
+ my $self = shift;
+ my ( $hashref, $cache ) = @_;
+ if ( $hashref->{'username'} ) {
+ $self->{'_svc_acct'} = FS::svc_acct->new($hashref, '');
+ }
+ if ( $hashref->{'svc'} ) {
+ $self->{'_svcpart'} = FS::part_svc->new($hashref);
+ }
+}
+
+=head1 NAME
+
+FS::cust_svc - Object method for cust_svc objects
+
+=head1 SYNOPSIS
+
+ use FS::cust_svc;
+
+ $record = new FS::cust_svc \%hash
+ $record = new FS::cust_svc { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ ($label, $value) = $record->label;
+
+=head1 DESCRIPTION
+
+An FS::cust_svc represents a service. FS::cust_svc inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatically for new services)
+
+=item pkgnum - Package (see L<FS::cust_pkg>)
+
+=item svcpart - Service definition (see L<FS::part_svc>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new service. To add the refund to the database, see L<"insert">.
+Services are normally created by creating FS::svc_ objects (see
+L<FS::svc_acct>, L<FS::svc_domain>, and L<FS::svc_forward>, among others).
+
+=cut
+
+sub table { 'cust_svc'; }
+
+=item insert
+
+Adds this service to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this service from the database. If there is an error, returns the
+error, otherwise returns false. Note that this only removes the cust_svc
+record - you should probably use the B<cancel> method instead.
+
+=item cancel
+
+Cancels the relevant service by calling the B<cancel> method of the associated
+FS::svc_XXX object (i.e. an FS::svc_acct object or FS::svc_domain object),
+deleting the FS::svc_XXX record and then deleting this record.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub cancel {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $part_svc = $self->part_svc;
+
+ $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+ $dbh->rollback if $oldAutoCommit;
+ return "Illegal svcdb value in part_svc!";
+ };
+ my $svcdb = $1;
+ require "FS/$svcdb.pm";
+
+ my $svc = $self->svc_x;
+ if ($svc) {
+ my $error = $svc->cancel;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error canceling service: $error";
+ }
+ $error = $svc->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error deleting service: $error";
+ }
+ }
+
+ my $error = $self->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Error deleting cust_svc: $error";
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no errors
+
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $new->svcpart != $old->svcpart ) {
+ my $svc_x = $new->svc_x;
+ my $new_svc_x = ref($svc_x)->new({$svc_x->hash, svcpart=>$new->svcpart });
+ my $error = $new_svc_x->replace($svc_x);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+ }
+
+ my $error = $new->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid service. If there is an error,
+returns the error, otehrwise returns false. Called by the insert and
+replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_numbern('pkgnum')
+ || $self->ut_number('svcpart')
+ ;
+ return $error if $error;
+
+ my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
+ return "Unknown svcpart" unless $part_svc;
+
+ if ( $self->pkgnum ) {
+ my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+ return "Unknown pkgnum" unless $cust_pkg;
+ my $pkg_svc = qsearchs( 'pkg_svc', {
+ 'pkgpart' => $cust_pkg->pkgpart,
+ 'svcpart' => $self->svcpart,
+ });
+ # or new FS::pkg_svc ( { 'pkgpart' => $cust_pkg->pkgpart,
+ # 'svcpart' => $self->svcpart,
+ # 'quantity' => 0 } );
+ my $quantity = $pkg_svc ? $pkg_svc->quantity : 0;
+
+ my @cust_svc = qsearch('cust_svc', {
+ 'pkgnum' => $self->pkgnum,
+ 'svcpart' => $self->svcpart,
+ });
+ return "Already ". scalar(@cust_svc). " ". $part_svc->svc.
+ " services for pkgnum ". $self->pkgnum
+ if scalar(@cust_svc) >= $quantity && !$ignore_quantity;
+ }
+
+ $self->SUPER::check;
+}
+
+=item part_svc
+
+Returns the definition for this service, as a FS::part_svc object (see
+L<FS::part_svc>).
+
+=cut
+
+sub part_svc {
+ my $self = shift;
+ $self->{'_svcpart'}
+ ? $self->{'_svcpart'}
+ : qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
+}
+
+=item cust_pkg
+
+Returns the definition for this service, as a FS::part_svc object (see
+L<FS::part_svc>).
+
+=cut
+
+sub cust_pkg {
+ my $self = shift;
+ qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=item label
+
+Returns a list consisting of:
+- The name of this service (from part_svc)
+- A meaningful identifier (username, domain, or mail alias)
+- The table name (i.e. svc_domain) for this service
+
+=cut
+
+sub label {
+ my $self = shift;
+ my $svcdb = $self->part_svc->svcdb;
+ my $svc_x = $self->svc_x
+ or die "can't find $svcdb.svcnum ". $self->svcnum;
+ my $tag;
+ if ( $svcdb eq 'svc_acct' ) {
+ $tag = $svc_x->email;
+ } elsif ( $svcdb eq 'svc_forward' ) {
+ if ( $svc_x->srcsvc ) {
+ my $svc_acct = $svc_x->srcsvc_acct;
+ $tag = $svc_acct->email;
+ } else {
+ $tag = $svc_x->src;
+ }
+ $tag .= '->';
+ if ( $svc_x->dstsvc ) {
+ my $svc_acct = $svc_x->dstsvc_acct;
+ $tag .= $svc_acct->email;
+ } else {
+ $tag .= $svc_x->dst;
+ }
+ } elsif ( $svcdb eq 'svc_domain' ) {
+ $tag = $svc_x->getfield('domain');
+ } elsif ( $svcdb eq 'svc_www' ) {
+ my $domain = qsearchs( 'domain_record', { 'recnum' => $svc_x->recnum } );
+ $tag = $domain->zone;
+ } elsif ( $svcdb eq 'svc_broadband' ) {
+ $tag = $svc_x->ip_addr;
+ } elsif ( $svcdb eq 'svc_external' ) {
+ my $conf = new FS::Conf;
+ if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) {
+ $tag = sprintf('%010d', $svc_x->id). '-'. $svc_x->title;
+ } else {
+ $tag = $svc_x->id. ': '. $svc_x->title;
+ }
+ } else {
+ cluck "warning: asked for label of unsupported svcdb; using svcnum";
+ $tag = $svc_x->getfield('svcnum');
+ }
+ $self->part_svc->svc, $tag, $svcdb;
+}
+
+=item svc_x
+
+Returns the FS::svc_XXX object for this service (i.e. an FS::svc_acct object or
+FS::svc_domain object, etc.)
+
+=cut
+
+sub svc_x {
+ my $self = shift;
+ my $svcdb = $self->part_svc->svcdb;
+ if ( $svcdb eq 'svc_acct' && $self->{'_svc_acct'} ) {
+ $self->{'_svc_acct'};
+ } else {
+ #require "FS/$svcdb.pm";
+ qsearchs( $svcdb, { 'svcnum' => $self->svcnum } );
+ }
+}
+
+=item seconds_since TIMESTAMP
+
+See L<FS::svc_acct/seconds_since>. Equivalent to
+$cust_svc->svc_x->seconds_since, but more efficient. Meaningless for records
+where B<svcdb> is not "svc_acct".
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+sub seconds_since {
+ my($self, $since) = @_;
+ my $dbh = dbh;
+ my $sth = $dbh->prepare(' SELECT SUM(logout-login) FROM session
+ WHERE svcnum = ?
+ AND login >= ?
+ AND logout IS NOT NULL'
+ ) or die $dbh->errstr;
+ $sth->execute($self->svcnum, $since) or die $sth->errstr;
+ $sth->fetchrow_arrayref->[0];
+}
+
+=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+See L<FS::svc_acct/seconds_since_sqlradacct>. Equivalent to
+$cust_svc->svc_x->seconds_since_sqlradacct, but more efficient. Meaningless
+for records where B<svcdb> is not "svc_acct".
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+sub seconds_since_sqlradacct {
+ my($self, $start, $end) = @_;
+
+ my $svc_x = $self->svc_x;
+
+ my @part_export = $self->part_svc->part_export('sqlradius');
+ push @part_export, $self->part_svc->part_export('sqlradius_withdomain');
+ die "no sqlradius or sqlradius_withdomain export configured for this".
+ "service type"
+ unless @part_export;
+ #or return undef;
+
+ my $seconds = 0;
+ foreach my $part_export ( @part_export ) {
+
+ next if $part_export->option('ignore_accounting');
+
+ my $dbh = DBI->connect( map { $part_export->option($_) }
+ qw(datasrc username password) )
+ or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+ #select a unix time conversion function based on database type
+ my $str2time;
+ if ( $dbh->{Driver}->{Name} =~ /^mysql(PP)?$/ ) {
+ $str2time = 'UNIX_TIMESTAMP(';
+ } elsif ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+ $str2time = 'EXTRACT( EPOCH FROM ';
+ } else {
+ warn "warning: unknown database type ". $dbh->{Driver}->{Name}.
+ "; guessing how to convert to UNIX timestamps";
+ $str2time = 'extract(epoch from ';
+ }
+
+ my $username;
+ if ( $part_export->exporttype eq 'sqlradius' ) {
+ $username = $svc_x->username;
+ } elsif ( $part_export->exporttype eq 'sqlradius_withdomain' ) {
+ $username = $svc_x->email;
+ } else {
+ die 'unknown exporttype '. $part_export->exporttype;
+ }
+
+ my $query;
+
+ #find closed sessions completely within the given range
+ my $sth = $dbh->prepare("SELECT SUM(acctsessiontime)
+ FROM radacct
+ WHERE UserName = ?
+ AND $str2time AcctStartTime) >= ?
+ AND $str2time AcctStopTime ) < ?
+ AND $str2time AcctStopTime ) > 0
+ AND AcctStopTime IS NOT NULL"
+ ) or die $dbh->errstr;
+ $sth->execute($username, $start, $end) or die $sth->errstr;
+ my $regular = $sth->fetchrow_arrayref->[0];
+
+ #find open sessions which start in the range, count session start->range end
+ $query = "SELECT SUM( ? - $str2time AcctStartTime ) )
+ FROM radacct
+ WHERE UserName = ?
+ AND $str2time AcctStartTime ) >= ?
+ AND $str2time AcctStartTime ) < ?
+ AND ( ? - $str2time AcctStartTime ) ) < 86400
+ AND ( $str2time AcctStopTime ) = 0
+ OR AcctStopTime IS NULL )";
+ $sth = $dbh->prepare($query) or die $dbh->errstr;
+ $sth->execute($end, $username, $start, $end, $end)
+ or die $sth->errstr. " executing query $query";
+ my $start_during = $sth->fetchrow_arrayref->[0];
+
+ #find closed sessions which start before the range but stop during,
+ #count range start->session end
+ $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? )
+ FROM radacct
+ WHERE UserName = ?
+ AND $str2time AcctStartTime ) < ?
+ AND $str2time AcctStopTime ) >= ?
+ AND $str2time AcctStopTime ) < ?
+ AND $str2time AcctStopTime ) > 0
+ AND AcctStopTime IS NOT NULL"
+ ) or die $dbh->errstr;
+ $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr;
+ my $end_during = $sth->fetchrow_arrayref->[0];
+
+ #find closed (not anymore - or open) sessions which start before the range
+ # but stop after, or are still open, count range start->range end
+ # don't count open sessions (probably missing stop record)
+ $sth = $dbh->prepare("SELECT COUNT(*)
+ FROM radacct
+ WHERE UserName = ?
+ AND $str2time AcctStartTime ) < ?
+ AND ( $str2time AcctStopTime ) >= ?
+ )"
+ # OR AcctStopTime = 0
+ # OR AcctStopTime IS NULL )"
+ ) or die $dbh->errstr;
+ $sth->execute($username, $start, $end ) or die $sth->errstr;
+ my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0];
+
+ $seconds += $regular + $end_during + $start_during + $entire_range;
+
+ }
+
+ $seconds;
+
+}
+
+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+See L<FS::svc_acct/attribute_since_sqlradacct>. Equivalent to
+$cust_svc->svc_x->attribute_since_sqlradacct, but more efficient. Meaningless
+for records where B<svcdb> is not "svc_acct".
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+#(false laziness w/seconds_since_sqlradacct above)
+sub attribute_since_sqlradacct {
+ my($self, $start, $end, $attrib) = @_;
+
+ my $svc_x = $self->svc_x;
+
+ my @part_export = $self->part_svc->part_export('sqlradius');
+ push @part_export, $self->part_svc->part_export('sqlradius_withdomain');
+ die "no sqlradius or sqlradius_withdomain export configured for this".
+ "service type"
+ unless @part_export;
+ #or return undef;
+
+ my $sum = 0;
+
+ foreach my $part_export ( @part_export ) {
+
+ next if $part_export->option('ignore_accounting');
+
+ my $dbh = DBI->connect( map { $part_export->option($_) }
+ qw(datasrc username password) )
+ or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+ #select a unix time conversion function based on database type
+ my $str2time;
+ if ( $dbh->{Driver}->{Name} =~ /^mysql(PP)?$/ ) {
+ $str2time = 'UNIX_TIMESTAMP(';
+ } elsif ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+ $str2time = 'EXTRACT( EPOCH FROM ';
+ } else {
+ warn "warning: unknown database type ". $dbh->{Driver}->{Name}.
+ "; guessing how to convert to UNIX timestamps";
+ $str2time = 'extract(epoch from ';
+ }
+
+ my $username;
+ if ( $part_export->exporttype eq 'sqlradius' ) {
+ $username = $svc_x->username;
+ } elsif ( $part_export->exporttype eq 'sqlradius_withdomain' ) {
+ $username = $svc_x->email;
+ } else {
+ die 'unknown exporttype '. $part_export->exporttype;
+ }
+
+ my $sth = $dbh->prepare("SELECT SUM($attrib)
+ FROM radacct
+ WHERE UserName = ?
+ AND $str2time AcctStopTime ) >= ?
+ AND $str2time AcctStopTime ) < ?
+ AND AcctStopTime IS NOT NULL"
+ ) or die $dbh->errstr;
+ $sth->execute($username, $start, $end) or die $sth->errstr;
+
+ $sum += $sth->fetchrow_arrayref->[0];
+
+ }
+
+ $sum;
+
+}
+
+=item get_session_history_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+See L<FS::svc_acct/get_session_history_sqlradacct>. Equivalent to
+$cust_svc->svc_x->get_session_history_sqlradacct, but more efficient.
+Meaningless for records where B<svcdb> is not "svc_acct".
+
+=cut
+
+sub get_session_history {
+ my($self, $start, $end, $attrib) = @_;
+
+ #$attrib ???
+
+ my @part_export = $self->part_svc->part_export('sqlradius');
+ push @part_export, $self->part_svc->part_export('sqlradius_withdomain');
+ die "no sqlradius or sqlradius_withdomain export configured for this".
+ "service type"
+ unless @part_export;
+ #or return undef;
+
+ my @sessions = ();
+
+ foreach my $part_export ( @part_export ) {
+ push @sessions, $part_export->usage_sessions( $self->svc_x, $start, $end );
+ }
+
+ \@sessions;
+
+}
+
+=item pkg_svc
+
+Returns the pkg_svc record for for this service, if applicable.
+
+=cut
+
+sub pkg_svc {
+ my $self = shift;
+ my $cust_pkg = $self->cust_pkg;
+ return undef unless $cust_pkg;
+
+ qsearchs( 'pkg_svc', { 'svcpart' => $self->svcpart,
+ 'pkgpart' => $cust_pkg->pkgpart,
+ }
+ );
+}
+
+=back
+
+=head1 BUGS
+
+Behaviour of changing the svcpart of cust_svc records is undefined and should
+possibly be prohibited, and pkg_svc records are not checked.
+
+pkg_svc records are not checked in general (here).
+
+Deleting this record doesn't check or delete the svc_* record associated
+with this record.
+
+In seconds_since_sqlradacct, specifying a DATASRC/USERNAME/PASSWORD instead of
+a DBI database handle is not yet implemented.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_pkg>, L<FS::part_svc>, L<FS::pkg_svc>,
+schema.html from the base documentation
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm
new file mode 100644
index 0000000..da0de00
--- /dev/null
+++ b/FS/FS/cust_tax_exempt.pm
@@ -0,0 +1,132 @@
+package FS::cust_tax_exempt;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_tax_exempt - Object methods for cust_tax_exempt records
+
+=head1 SYNOPSIS
+
+ use FS::cust_tax_exempt;
+
+ $record = new FS::cust_tax_exempt \%hash;
+ $record = new FS::cust_tax_exempt { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_tax_exempt object represents a historical record of a customer tax
+exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item exemptnum - primary key
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item taxnum - tax rate (see L<FS::cust_main_county>)
+
+=item year
+
+=item month
+
+=item amount
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new exemption record. To add the example to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_tax_exempt'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('exemptnum')
+ || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum')
+ || $self->ut_number('year') #check better
+ || $self->ut_number('month') #check better
+ || $self->ut_money('amount')
+ || $self->SUPER::check
+ ;
+}
+
+=back
+
+=head1 BUGS
+
+Texas tax is a royal pain in the ass.
+
+=head1 SEE ALSO
+
+L<FS::cust_main_county>, L<FS::cust_main>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm
new file mode 100644
index 0000000..2a30594
--- /dev/null
+++ b/FS/FS/domain_record.pm
@@ -0,0 +1,417 @@
+package FS::domain_record;
+
+use strict;
+use vars qw( @ISA $noserial_hack $DEBUG );
+use FS::Conf;
+#use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearchs dbh );
+use FS::svc_domain;
+use FS::svc_www;
+
+@ISA = qw(FS::Record);
+
+$DEBUG = 1;
+
+=head1 NAME
+
+FS::domain_record - Object methods for domain_record records
+
+=head1 SYNOPSIS
+
+ use FS::domain_record;
+
+ $record = new FS::domain_record \%hash;
+ $record = new FS::domain_record { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::domain_record object represents an entry in a DNS zone.
+FS::domain_record inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item recnum - primary key
+
+=item svcnum - Domain (see L<FS::svc_domain>) of this entry
+
+=item reczone - partial (or full) zone for this entry
+
+=item recaf - address family for this entry, currently only `IN' is recognized.
+
+=item rectype - record type for this entry (A, MX, etc.)
+
+=item recdata - data for this entry
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new entry. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'domain_record'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( $self->rectype eq '_mstr' ) { #delete all other records
+ foreach my $domain_record ( reverse $self->svc_domain->domain_record ) {
+ my $error = $domain_record->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ unless ( $self->rectype =~ /^(SOA|_mstr)$/ ) {
+ my $error = $self->increment_serial;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $conf = new FS::Conf;
+ if ( $self->rectype =~ /^A$/ && ! $conf->exists('disable_autoreverse') ) {
+ my $reverse = $self->reverse_record;
+ if ( $reverse && ! $reverse->recnum ) {
+ my $error = $reverse->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error adding corresponding reverse-ARPA record: $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a domain record which has a website!"
+ if qsearchs( 'svc_www', { 'recnum' => $self->recnum } );
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ unless ( $self->rectype =~ /^(SOA|_mstr)$/ ) {
+ my $error = $self->increment_serial;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $conf = new FS::Conf;
+ if ( $self->rectype =~ /^A$/ && ! $conf->exists('disable_autoreverse') ) {
+ my $reverse = $self->reverse_record;
+ if ( $reverse && $reverse->recnum && $reverse->recdata eq $self->zone.'.' ){
+ my $error = $reverse->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error removing corresponding reverse-ARPA record: $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::replace(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ unless ( $self->rectype eq 'SOA' ) {
+ my $error = $self->increment_serial;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('recnum')
+ || $self->ut_number('svcnum')
+ ;
+ return $error if $error;
+
+ return "Unknown svcnum (in svc_domain)"
+ unless qsearchs('svc_domain', { 'svcnum' => $self->svcnum } );
+
+ $self->reczone =~ /^(@|[a-z0-9\.\-\*]+)$/i
+ or return "Illegal reczone: ". $self->reczone;
+ $self->reczone($1);
+
+ $self->recaf =~ /^(IN)$/ or return "Illegal recaf: ". $self->recaf;
+ $self->recaf($1);
+
+ $self->rectype =~ /^(SOA|NS|MX|A|PTR|CNAME|_mstr)$/
+ or return "Illegal rectype (only SOA NS MX A PTR CNAME recognized): ".
+ $self->rectype;
+ $self->rectype($1);
+
+ return "Illegal reczone for ". $self->rectype. ": ". $self->reczone
+ if $self->rectype !~ /^MX$/i && $self->reczone =~ /\*/;
+
+ if ( $self->rectype eq 'SOA' ) {
+ my $recdata = $self->recdata;
+ $recdata =~ s/\s+/ /g;
+ $recdata =~ /^([a-z0-9\.\-]+ [\w\-\+]+\.[a-z0-9\.\-]+ \( ((\d+|((\d+[WDHMS])+)) ){5}\))$/i
+ or return "Illegal data for SOA record: $recdata";
+ $self->recdata($1);
+ } elsif ( $self->rectype eq 'NS' ) {
+ $self->recdata =~ /^([a-z0-9\.\-]+)$/i
+ or return "Illegal data for NS record: ". $self->recdata;
+ $self->recdata($1);
+ } elsif ( $self->rectype eq 'MX' ) {
+ $self->recdata =~ /^(\d+)\s+([a-z0-9\.\-]+)$/i
+ or return "Illegal data for MX record: ". $self->recdata;
+ $self->recdata("$1 $2");
+ } elsif ( $self->rectype eq 'A' ) {
+ $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/
+ or return "Illegal data for A record: ". $self->recdata;
+ $self->recdata($1);
+ } elsif ( $self->rectype eq 'PTR' ) {
+ $self->recdata =~ /^([a-z0-9\.\-]+)$/i
+ or return "Illegal data for PTR record: ". $self->recdata;
+ $self->recdata($1);
+ } elsif ( $self->rectype eq 'CNAME' ) {
+ $self->recdata =~ /^([a-z0-9\.\-]+|\@)$/i
+ or return "Illegal data for CNAME record: ". $self->recdata;
+ $self->recdata($1);
+ } elsif ( $self->rectype eq '_mstr' ) {
+ $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/
+ or return "Illegal data for _master pseudo-record: ". $self->recdata;
+ } else {
+ die "ack!";
+ }
+
+ $self->SUPER::check;
+}
+
+=item increment_serial
+
+=cut
+
+sub increment_serial {
+ return '' if $noserial_hack;
+ my $self = shift;
+
+ my $soa = qsearchs('domain_record', {
+ svcnum => $self->svcnum,
+ reczone => '@',
+ recaf => 'IN',
+ rectype => 'SOA', } )
+ || qsearchs('domain_record', {
+ svcnum => $self->svcnum,
+ reczone => $self->svc_domain->domain.'.',
+ recaf => 'IN',
+ rectype => 'SOA',
+ } )
+ or return "soa record not found; can't increment serial";
+
+ my $data = $soa->recdata;
+ $data =~ s/(\(\D*)(\d+)/$1.($2+1)/e; #well, it works.
+
+ my %hash = $soa->hash;
+ $hash{recdata} = $data;
+ my $new = new FS::domain_record \%hash;
+ $new->replace($soa);
+}
+
+=item svc_domain
+
+Returns the domain (see L<FS::svc_domain>) for this record.
+
+=cut
+
+sub svc_domain {
+ my $self = shift;
+ qsearchs('svc_domain', { svcnum => $self->svcnum } );
+}
+
+=item zone
+
+Returns the canonical zone name.
+
+=cut
+
+sub zone {
+ my $self = shift;
+ my $zone = $self->reczone; # or die ?
+ if ( $zone =~ /\.$/ ) {
+ $zone =~ s/\.$//;
+ } else {
+ my $svc_domain = $self->svc_domain; # or die ?
+ $zone .= '.'. $svc_domain->domain;
+ $zone =~ s/^\@\.//;
+ }
+ $zone;
+}
+
+=item reverse_record
+
+Returns the corresponding reverse-ARPA record as another FS::domain_record
+object. If the specific record does not exist in the database but the
+reverse-ARPA zone itself does, an appropriate new record is created. If no
+reverse-ARPA zone is available at all, returns false.
+
+(You can test whether or not record itself exists in the database or is a new
+object that might need to be inserted by checking the recnum field)
+
+Mostly used by the insert and delete methods - probably should see them for
+examples.
+
+=cut
+
+sub reverse_record {
+ my $self = shift;
+ warn "reverse_record called\n" if $DEBUG;
+ #should support classless reverse-ARPA ala rfc2317 too
+ $self->recdata =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
+ or return '';
+ my $domain = "$3.$2.$1.in-addr.arpa";
+ my $ptr_reczone = $4;
+ warn "reverse_record: searching for domain: $domain\n" if $DEBUG;
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ or return '';
+ warn "reverse_record: found domain: $domain\n" if $DEBUG;
+ my %hash = (
+ 'svcnum' => $svc_domain->svcnum,
+ 'reczone' => $ptr_reczone,
+ 'recaf' => 'IN',
+ 'rectype' => 'PTR',
+ );
+ qsearchs('domain_record', \%hash )
+ or new FS::domain_record { %hash, 'recdata' => $self->zone.'.' };
+}
+
+=back
+
+=head1 BUGS
+
+The data validation doesn't check everything it could. In particular,
+there is no protection against bad data that passes the regex, duplicate
+SOA records, forgetting the trailing `.', impossible IP addersses, etc. Of
+course, it's still better than editing the zone files directly. :)
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/export_svc.pm b/FS/FS/export_svc.pm
new file mode 100644
index 0000000..d1153c0
--- /dev/null
+++ b/FS/FS/export_svc.pm
@@ -0,0 +1,283 @@
+package FS::export_svc;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::part_export;
+use FS::part_svc;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::export_svc - Object methods for export_svc records
+
+=head1 SYNOPSIS
+
+ use FS::export_svc;
+
+ $record = new FS::export_svc \%hash;
+ $record = new FS::export_svc { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::export_svc object links a service definition (see L<FS::part_svc>) to
+an export (see L<FS::part_export>). FS::export_svc inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item exportsvcnum - primary key
+
+=item exportnum - export (see L<FS::part_export>)
+
+=item svcpart - service definition (see L<FS::part_svc>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'export_svc'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ return $error if $error;
+
+ #check for duplicates!
+ my @checks = ();
+ my $svcdb = $self->part_svc->svcdb;
+ if ( $svcdb eq 'svc_acct' ) {
+
+ if ( $self->part_export->nodomain =~ /^Y/i ) {
+ push @checks, {
+ label => 'usernames',
+ method => 'username',
+ sortby => sub { $a cmp $b },
+ };
+ } else {
+ push @checks, {
+ label => 'username@domain',
+ method => 'email',
+ sortby => sub {
+ my($auser, $adomain) = split('@', $a);
+ my($buser, $bdomain) = split('@', $b);
+ $adomain cmp $bdomain || $auser cmp $buser;
+ },
+ };
+ }
+
+ unless ( $self->part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+ push @checks, {
+ label => 'uids',
+ method => 'uid',
+ sortby => sub { $a <=> $b },
+ };
+ }
+
+ } elsif ( $svcdb eq 'svc_domain' ) {
+ push @checks, {
+ label => 'domains',
+ method => 'domain',
+ sortby => sub { $a cmp $b },
+ };
+ } else {
+ warn "WARNING: No duplicate checking done on merge of $svcdb exports";
+ }
+
+ foreach my $check ( @checks ) {
+ my @current_svc = $self->part_export->svc_x;
+ #warn "current: ". scalar(@current_svc). " $current_svc[0]\n";
+ my @new_svc = $self->part_svc->svc_x;
+ #warn "new: ". scalar(@new_svc). " $new_svc[0]\n";
+ my $method = $check->{'method'};
+ my %cur_svc = map { $_->$method() => $_ } @current_svc;
+ my @dup_svc = grep { $cur_svc{$_->$method()} } @new_svc;
+ #my @diff_customer = grep {
+ # $_->cust_pkg->custnum != $cur_svc{$_->$method()}->cust_pkg->custnum
+ # } @dup_svc;
+
+
+
+ if ( @dup_svc ) { #aye, that's the rub
+ #error out for now, eventually accept different options of adjustments
+ # to make to allow us to continue forward
+ $dbh->rollback if $oldAutoCommit;
+
+ my @diff_customer_svc = grep {
+ my $cust_pkg = $_->cust_svc->cust_pkg;
+ my $custnum = $cust_pkg ? $cust_pkg->custnum : 0;
+ my $other_cust_pkg = $cur_svc{$_->$method()}->cust_svc->cust_pkg;
+ my $other_custnum = $other_cust_pkg ? $other_cust_pkg->custnum : 0;
+ $custnum != $other_custnum;
+ } @dup_svc;
+
+ my $label = $check->{'label'};
+ my $sortby = $check->{'sortby'};
+ return "Can't export ".
+ $self->part_svc->svcpart.':'.$self->part_svc->svc. " service to ".
+ $self->part_export->exportnum.':'.$self->part_export->exporttype.
+ ' on '. $self->part_export->machine.
+ ' : '. scalar(@dup_svc). " duplicate $label".
+ ' ('. scalar(@diff_customer_svc). " from different customers)".
+ #": ". join(', ', sort $sortby map { $_->$method() } @dup_svc )
+ ": ". join(', ', sort $sortby map { $_->$method() } @diff_customer_svc )
+ ;
+ }
+ }
+
+ #end of duplicate check, whew
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+# if ( $self->part_svc->svcdb eq 'svc_acct' ) {
+#
+# if ( $self->part_export->nodomain =~ /^Y/i ) {
+#
+# select username from svc_acct where svcpart = $svcpart
+# group by username having count(*) > 1;
+#
+# } else {
+#
+# select username, domain
+# from svc_acct
+# join svc_domain on ( svc_acct.domsvc = svc_domain.svcnum )
+# group by username, domain having count(*) > 1;
+#
+# }
+#
+# } elsif ( $self->part_svc->svcdb eq 'svc_domain' ) {
+#
+# #similar but easier domain checking one
+#
+# } #etc.?
+#
+# my @services =
+# map { $_->part_svc }
+# grep { $_->svcpart != $self->svcpart }
+# $self->part_export->export_svc;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('exportsvcnum')
+ || $self->ut_number('exportnum')
+ || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum')
+ || $self->ut_number('svcpart')
+ || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
+ || $self->SUPER::check
+ ;
+}
+
+=item part_export
+
+Returns the FS::part_export object (see L<FS::part_export>).
+
+=cut
+
+sub part_export {
+ my $self = shift;
+ qsearchs( 'part_export', { 'exportnum' => $self->exportnum } );
+}
+
+=item part_svc
+
+Returns the FS::part_svc object (see L<FS::part_svc>).
+
+=cut
+
+sub part_svc {
+ my $self = shift;
+ qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_export>, L<FS::part_svc>, L<FS::Record>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm
new file mode 100644
index 0000000..855b8b2
--- /dev/null
+++ b/FS/FS/msgcat.pm
@@ -0,0 +1,132 @@
+package FS::msgcat;
+
+use strict;
+use vars qw( @ISA );
+use Exporter;
+use FS::UID;
+use FS::Record qw( qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::msgcat - Object methods for message catalog entries
+
+=head1 SYNOPSIS
+
+ use FS::msgcat;
+
+ $record = new FS::msgcat \%hash;
+ $record = new FS::msgcat { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::msgcat object represents an message catalog entry. FS::msgcat inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item msgnum - primary key
+
+=item msgcode - Error code
+
+=item locale - Locale
+
+=item msg - Message
+
+=back
+
+If you just want to B<use> message catalogs, see L<FS::Msgcat>.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'msgcat'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('msgnum')
+ || $self->ut_text('msgcode')
+ || $self->ut_text('msg')
+ ;
+ return $error if $error;
+
+ $self->locale =~ /^([\w\@]+)$/ or return "illegal locale: ". $self->locale;
+ $self->locale($1);
+
+ $self->SUPER::check
+}
+
+=back
+
+=head1 BUGS
+
+i18n/l10n, eek
+
+=head1 SEE ALSO
+
+L<FS::Msgcat>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/nas.pm b/FS/FS/nas.pm
new file mode 100644
index 0000000..2d17df8
--- /dev/null
+++ b/FS/FS/nas.pm
@@ -0,0 +1,154 @@
+package FS::nas;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(qsearchs); #qsearch);
+use FS::UID qw( dbh );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::nas - Object methods for nas records
+
+=head1 SYNOPSIS
+
+ use FS::nas;
+
+ $record = new FS::nas \%hash;
+ $record = new FS::nas {
+ 'nasnum' => 1,
+ 'nasip' => '10.4.20.23',
+ 'nasfqdn' => 'box1.brc.nv.us.example.net',
+ };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->heartbeat($timestamp);
+
+=head1 DESCRIPTION
+
+An FS::nas object represents an Network Access Server on your network, such as
+a terminal server or equivalent. FS::nas inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item nasnum - primary key
+
+=item nas - NAS name
+
+=item nasip - NAS ip address
+
+=item nasfqdn - NAS fully-qualified domain name
+
+=item last - timestamp indicating the last instant the NAS was in a known
+ state (used by the session monitoring).
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new NAS. To add the NAS to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'nas'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('nasnum')
+ || $self->ut_text('nas')
+ || $self->ut_ip('nasip')
+ || $self->ut_domain('nasfqdn')
+ || $self->ut_numbern('last')
+ || $self->SUPER::check
+ ;
+}
+
+=item heartbeat TIMESTAMP
+
+Updates the timestamp for this nas
+
+=cut
+
+sub heartbeat {
+ my($self, $timestamp) = @_;
+ my $dbh = dbh;
+ my $sth =
+ $dbh->prepare("UPDATE nas SET last = ? WHERE nasnum = ? AND last < ?");
+ $sth->execute($timestamp, $self->nasnum, $timestamp) or die $sth->errstr;
+ $self->last($timestamp);
+}
+
+=back
+
+=head1 VERSION
+
+$Id: nas.pm,v 1.7 2003-08-05 00:20:43 khoff Exp $
+
+=head1 BUGS
+
+heartbeat method uses SQL directly and doesn't update history tables.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm
new file mode 100644
index 0000000..4774b8d
--- /dev/null
+++ b/FS/FS/part_bill_event.pm
@@ -0,0 +1,186 @@
+package FS::part_bill_event;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_bill_event - Object methods for part_bill_event records
+
+=head1 SYNOPSIS
+
+ use FS::part_bill_event;
+
+ $record = new FS::part_bill_event \%hash;
+ $record = new FS::part_bill_event { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_bill_event object represents an invoice event definition -
+a callback which is triggered when an invoice is a certain amount of time
+overdue. FS::part_bill_event inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item eventpart - primary key
+
+=item payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, or COMP
+
+=item event - event name
+
+=item eventcode - event action
+
+=item seconds - how long after the invoice date events of this type are triggered
+
+=item weight - ordering for events with identical seconds
+
+=item plan - eventcode plan
+
+=item plandata - additional plan data
+
+=item disabled - Disabled flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice event definition. To add the example to the database,
+see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_bill_event'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid invoice event definition. If
+there is an error, returns the error, otherwise returns false. Called by the
+insert and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ $self->weight(0) unless $self->weight;
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('safe-part_bill_event') ) {
+ my $error = $self->ut_anything('eventcode');
+ return $error if $error;
+
+ my $c = $self->eventcode;
+
+ $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|realtime_card_cybercash|batch_card|send)\(\);\s*$/
+
+ or $c =~ /^\s*\$cust_bill\->send\(\'\w+\'\);\s*$/
+
+ or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/
+
+ or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/
+
+ or do {
+ #log
+ return "illegal eventcode: $c";
+ };
+
+ }
+
+ my $error = $self->ut_numbern('eventpart')
+ || $self->ut_enum('payby', [qw( CARD DCRD CHEK DCHK LECB BILL COMP )] )
+ || $self->ut_text('event')
+ || $self->ut_anything('eventcode')
+ || $self->ut_number('seconds')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->ut_number('weight')
+ || $self->ut_textn('plan')
+ || $self->ut_anything('plandata')
+ ;
+ #|| $self->ut_snumber('seconds')
+ return $error if $error;
+
+ #quelle kludge
+ if ( $self->plandata =~ /^(agent_)?templatename\s+(.*)$/m ) {
+ my $name= $2;
+
+ foreach my $file (qw( template latex latexnotes )) {
+ unless ( $conf->exists("invoice_${file}_$name") ) {
+ $conf->set(
+ "invoice_${file}_$name" =>
+ join("\n", $conf->config("invoice_$file") )
+ );
+ }
+ }
+ }
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Alas.
+
+=head1 SEE ALSO
+
+L<FS::cust_bill>, L<FS::cust_bill_event>, L<FS::Record>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm
new file mode 100644
index 0000000..bd12389
--- /dev/null
+++ b/FS/FS/part_export.pm
@@ -0,0 +1,590 @@
+package FS::part_export;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG %exports );
+use Exporter;
+use Tie::IxHash;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::part_svc;
+use FS::part_export_option;
+use FS::export_svc;
+
+@ISA = qw(FS::Record);
+@EXPORT_OK = qw(export_info);
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::part_export - Object methods for part_export records
+
+=head1 SYNOPSIS
+
+ use FS::part_export;
+
+ $record = new FS::part_export \%hash;
+ $record = new FS::part_export { 'column' => 'value' };
+
+ #($new_record, $options) = $template_recored->clone( $svcpart );
+
+ $error = $record->insert( { 'option' => 'value' } );
+ $error = $record->insert( \%options );
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_export object represents an export of Freeside data to an external
+provisioning system. FS::part_export inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item exportnum - primary key
+
+=item machine - Machine name
+
+=item exporttype - Export type
+
+=item nodomain - blank or "Y" : usernames are exported to this service with no domain
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new export. To add the export to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_export'; }
+
+=cut
+
+#=item clone SVCPART
+#
+#An alternate constructor. Creates a new export by duplicating an existing
+#export. The given svcpart is assigned to the new export.
+#
+#Returns a list consisting of the new export object and a hashref of options.
+#
+#=cut
+#
+#sub clone {
+# my $self = shift;
+# my $class = ref($self);
+# my %hash = $self->hash;
+# $hash{'exportnum'} = '';
+# $hash{'svcpart'} = shift;
+# ( $class->new( \%hash ),
+# { map { $_->optionname => $_->optionvalue }
+# qsearch('part_export_option', { 'exportnum' => $self->exportnum } )
+# }
+# );
+#}
+
+=item insert HASHREF
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If a hash reference of options is supplied, part_export_option records are
+created (see L<FS::part_export_option>).
+
+=cut
+
+#false laziness w/queue.pm
+sub insert {
+ my $self = shift;
+ my $options = shift;
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $optionname ( keys %{$options} ) {
+ my $part_export_option = new FS::part_export_option ( {
+ 'exportnum' => $self->exportnum,
+ 'optionname' => $optionname,
+ 'optionvalue' => $options->{$optionname},
+ } );
+ $error = $part_export_option->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+#foreign keys would make this much less tedious... grr dumb mysql
+sub delete {
+ my $self = shift;
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $part_export_option ( $self->part_export_option ) {
+ my $error = $part_export_option->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $export_svc ( $self->export_svc ) {
+ my $error = $export_svc->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace OLD_RECORD HASHREF
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+If a hash reference of options is supplied, part_export_option records are
+created or modified (see L<FS::part_export_option>).
+
+=cut
+
+sub replace {
+ my $self = shift;
+ my $old = shift;
+ my $options = shift;
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $optionname ( keys %{$options} ) {
+ my $old = qsearchs( 'part_export_option', {
+ 'exportnum' => $self->exportnum,
+ 'optionname' => $optionname,
+ } );
+ my $new = new FS::part_export_option ( {
+ 'exportnum' => $self->exportnum,
+ 'optionname' => $optionname,
+ 'optionvalue' => $options->{$optionname},
+ } );
+ $new->optionnum($old->optionnum) if $old;
+ my $error = $old ? $new->replace($old) : $new->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ #remove extraneous old options
+ foreach my $opt (
+ grep { !exists $options->{$_->optionname} } $old->part_export_option
+ ) {
+ my $error = $opt->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+};
+
+=item check
+
+Checks all fields to make sure this is a valid export. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('exportnum')
+ || $self->ut_domain('machine')
+ || $self->ut_alpha('exporttype')
+ ;
+ return $error if $error;
+
+ $self->nodomain =~ /^(Y?)$/ or return "Illegal nodomain: ". $self->nodomain;
+ $self->nodomain($1);
+
+ $self->deprecated(1); #BLAH
+
+ #check exporttype?
+
+ $self->SUPER::check;
+}
+
+#=item part_svc
+#
+#Returns the service definition (see L<FS::part_svc>) for this export.
+#
+#=cut
+#
+#sub part_svc {
+# my $self = shift;
+# qsearchs('part_svc', { svcpart => $self->svcpart } );
+#}
+
+sub part_svc {
+ use Carp;
+ croak "FS::part_export::part_svc deprecated";
+ #confess "FS::part_export::part_svc deprecated";
+}
+
+=item svc_x
+
+Returns a list of associated FS::svc_* records.
+
+=cut
+
+sub svc_x {
+ my $self = shift;
+ map { $_->svc_x } $self->cust_svc;
+}
+
+=item cust_svc
+
+Returns a list of associated FS::cust_svc records.
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ $self->export_svc;
+}
+
+=item export_svc
+
+Returns a list of associated FS::export_svc records.
+
+=cut
+
+sub export_svc {
+ my $self = shift;
+ qsearch('export_svc', { 'exportnum' => $self->exportnum } );
+}
+
+=item part_export_option
+
+Returns all options as FS::part_export_option objects (see
+L<FS::part_export_option>).
+
+=cut
+
+sub part_export_option {
+ my $self = shift;
+ qsearch('part_export_option', { 'exportnum' => $self->exportnum } );
+}
+
+=item options
+
+Returns a list of option names and values suitable for assigning to a hash.
+
+=cut
+
+sub options {
+ my $self = shift;
+ map { $_->optionname => $_->optionvalue } $self->part_export_option;
+}
+
+=item option OPTIONNAME
+
+Returns the option value for the given name, or the empty string.
+
+=cut
+
+sub option {
+ my $self = shift;
+ my $part_export_option =
+ qsearchs('part_export_option', {
+ exportnum => $self->exportnum,
+ optionname => shift,
+ } );
+ $part_export_option ? $part_export_option->optionvalue : '';
+}
+
+=item rebless
+
+Reblesses the object into the FS::part_export::EXPORTTYPE class, where
+EXPORTTYPE is the object's I<exporttype> field. There should be better docs
+on how to create new exports (and they should live in their own files and be
+autoloaded-on-demand), but until then, see L</NEW EXPORT CLASSES>.
+
+=cut
+
+sub rebless {
+ my $self = shift;
+ my $exporttype = $self->exporttype;
+ my $class = ref($self). "::$exporttype";
+ eval "use $class;";
+ die $@ if $@;
+ bless($self, $class);
+}
+
+=item export_insert SVC_OBJECT
+
+=cut
+
+sub export_insert {
+ my $self = shift;
+ $self->rebless;
+ $self->_export_insert(@_);
+}
+
+#sub AUTOLOAD {
+# my $self = shift;
+# $self->rebless;
+# my $method = $AUTOLOAD;
+# #$method =~ s/::(\w+)$/::_$1/; #infinite loop prevention
+# $method =~ s/::(\w+)$/_$1/; #infinite loop prevention
+# $self->$method(@_);
+#}
+
+=item export_replace NEW OLD
+
+=cut
+
+sub export_replace {
+ my $self = shift;
+ $self->rebless;
+ $self->_export_replace(@_);
+}
+
+=item export_delete
+
+=cut
+
+sub export_delete {
+ my $self = shift;
+ $self->rebless;
+ $self->_export_delete(@_);
+}
+
+=item export_suspend
+
+=cut
+
+sub export_suspend {
+ my $self = shift;
+ $self->rebless;
+ $self->_export_suspend(@_);
+}
+
+=item export_unsuspend
+
+=cut
+
+sub export_unsuspend {
+ my $self = shift;
+ $self->rebless;
+ $self->_export_unsuspend(@_);
+}
+
+#fallbacks providing useful error messages intead of infinite loops
+sub _export_insert {
+ my $self = shift;
+ return "_export_insert: unknown export type ". $self->exporttype;
+}
+
+sub _export_replace {
+ my $self = shift;
+ return "_export_replace: unknown export type ". $self->exporttype;
+}
+
+sub _export_delete {
+ my $self = shift;
+ return "_export_delete: unknown export type ". $self->exporttype;
+}
+
+#call svcdb-specific fallbacks
+
+sub _export_suspend {
+ my $self = shift;
+ #warn "warning: _export_suspened unimplemented for". ref($self);
+ my $svc_x = shift;
+ my $new = $svc_x->clone_suspended;
+ $self->_export_replace( $new, $svc_x );
+}
+
+sub _export_unsuspend {
+ my $self = shift;
+ #warn "warning: _export_unsuspend unimplemented for ". ref($self);
+ my $svc_x = shift;
+ my $old = $svc_x->clone_kludge_unsuspend;
+ $self->_export_replace( $svc_x, $old );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item export_info [ SVCDB ]
+
+Returns a hash reference of the exports for the given I<svcdb>, or if no
+I<svcdb> is specified, for all exports. The keys of the hash are
+I<exporttype>s and the values are again hash references containing information
+on the export:
+
+ 'desc' => 'Description',
+ 'options' => {
+ 'option' => { label=>'Option Label' },
+ 'option2' => { label=>'Another label' },
+ },
+ 'nodomain' => 'Y', #or ''
+ 'notes' => 'Additional notes',
+
+=cut
+
+sub export_info {
+ #warn $_[0];
+ return $exports{$_[0]} || {} if @_;
+ #{ map { %{$exports{$_}} } keys %exports };
+ my $r = { map { %{$exports{$_}} } keys %exports };
+}
+
+#=item exporttype2svcdb EXPORTTYPE
+#
+#Returns the applicable I<svcdb> for an I<exporttype>.
+#
+#=cut
+#
+#sub exporttype2svcdb {
+# my $exporttype = $_[0];
+# foreach my $svcdb ( keys %exports ) {
+# return $svcdb if grep { $exporttype eq $_ } keys %{$exports{$svcdb}};
+# }
+# '';
+#}
+
+foreach my $INC ( @INC ) {
+ foreach my $file ( glob("$INC/FS/part_export/*.pm") ) {
+ warn "attempting to load export info from $file\n" if $DEBUG;
+ $file =~ /\/(\w+)\.pm$/ or do {
+ warn "unrecognized file in $INC/FS/part_export/: $file\n";
+ next;
+ };
+ my $mod = $1;
+ my $info = eval "use FS::part_export::$mod; ".
+ "\\%FS::part_export::$mod\::info;";
+ if ( $@ ) {
+ die "error using FS::part_export::$mod (skipping): $@\n" if $@;
+ next;
+ }
+ unless ( keys %$info ) {
+ warn "no %info hash found in FS::part_export::$mod, skipping\n"
+ unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
+ next;
+ }
+ warn "got export info from FS::part_export::$mod: $info\n" if $DEBUG;
+ no strict 'refs';
+ foreach my $svc (
+ ref($info->{'svc'}) ? @{$info->{'svc'}} : $info->{'svc'}
+ ) {
+ unless ( $svc ) {
+ warn "blank svc for FS::part_export::$mod (skipping)\n";
+ next;
+ }
+ $exports{$svc}->{$mod} = $info;
+ }
+ }
+}
+
+=back
+
+=head1 NEW EXPORT CLASSES
+
+A module should be added in FS/FS/part_export/ (an example may be found in
+eg/export_template.pm)
+
+=head1 BUGS
+
+Hmm... cust_export class (not necessarily a database table...) ... ?
+
+deprecated column...
+
+=head1 SEE ALSO
+
+L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_acct>,
+L<FS::svc_domain>,
+L<FS::svc_forward>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/acct_sql.pm b/FS/FS/part_export/acct_sql.pm
new file mode 100644
index 0000000..dfc37d0
--- /dev/null
+++ b/FS/FS/part_export/acct_sql.pm
@@ -0,0 +1,177 @@
+package FS::part_export::acct_sql;
+
+use vars qw(@ISA %info @saltset);
+use Tie::IxHash;
+#use Digest::MD5 qw(md5_hex);
+use FS::Record; #qw(qsearchs);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'datasrc' => { label => 'DBI data source' },
+ 'username' => { label => 'Database username' },
+ 'password' => { label => 'Database password' },
+ 'table' => { label => 'Database table' },
+ 'schema' => { label =>
+ 'Database schema mapping to Freeside methods.',
+ type => 'textarea',
+ },
+ 'primary_key' => { label => 'Database primary key' },
+;
+
+tie my %postfix_courierimap_mailbox_map, 'Tie::IxHash',
+ 'username' => 'email',
+ 'password' => '_password',
+ 'crypt' => 'crypt_password',
+ 'name' => 'finger',
+ 'maildir' => 'virtual_maildir',
+ 'domain' => 'domain',
+ 'svcnum' => 'svcnum',
+;
+my $postfix_courierimap_mailbox_map =
+ join('\n', map "$_ $postfix_courierimap_mailbox_map{$_}",
+ keys %postfix_courierimap_mailbox_map );
+
+tie my %postfix_courierimap_alias_map, 'Tie::IxHash',
+ 'address' => 'email',
+ 'goto' => 'email',
+ 'domain' => 'domain',
+ 'svcnum' => 'svcnum',
+;
+my $postfix_courierimap_alias_map =
+ join('\n', map "$_ $postfix_courierimap_alias_map{$_}",
+ keys %postfix_courierimap_alias_map );
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export of accounts to SQL databases '.
+ '(Postfix+Courier IMAP, others?)',
+ 'options' => \%options,
+ 'nodomain' => '',
+ 'notes' => <<END
+Export accounts (svc_acct records) to SQL databases. Written for
+Postfix+Courier IMAP but intended to be generally useful for generic SQL
+exports, eventually.
+
+<BR><BR>In contrast to sqlmail, this is newer and less well tested, and
+currently less flexible. It is intended to export just svc_acct records only,
+rather than a single export for svc_acct, svc_forward and svc_domain records,
+to export in "default" formats rather than configure the MTA or POP/IMAP server
+for a Freeside-specific schema, and possibly to be configured for different
+mail server setups through some subclassing rather than options.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI><INPUT TYPE="button" VALUE="postfix_courierimap_mailbox" onClick='
+ this.form.table.value = "mailbox";
+ this.form.schema.value = "$postfix_courierimap_mailbox_map";
+ this.form.primary_key.value = "username";
+ '>
+ <LI><INPUT TYPE="button" VALUE="postfix_courierimap_alias" onClick='
+ this.form.table.value = "alias";
+ this.form.schema.value = "$postfix_courierimap_alias_map";
+ this.form.primary_key.value = "address";
+ '>
+</UL>
+END
+);
+
+sub _map {
+ my $self = shift;
+ map { /^\s*(\S+)\s*(\S+)\s*$/ } split("\n", $self->option('schema') );
+}
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+
+ my %map = $self->_map;
+
+ my %record = map { my $value = $map{$_};
+ $_ => $svc_acct->$value();
+ } keys %map;
+
+ my $err_or_queue =
+ $self->acct_sql_queue(
+ $svc_acct->svcnum,
+ 'insert',
+ $self->option('table'),
+ %record
+ );
+ return $err_or_queue unless ref($err_or_queue);
+
+ '';
+
+}
+
+sub _export_replace {
+}
+
+sub _export_delete {
+ my ( $self, $svc_acct ) = (shift, shift);
+ my %map = $self->_map;
+ my $keymap = $map{$self->option('primary_key')};
+ my $err_or_queue = $self->acct_sql_queue(
+ $svc_acct->svcnum,
+ 'delete',
+ $self->option('table'),
+ $self->option('primary_key') => $svc_acct->$keymap(),
+ );
+ return $err_or_queue unless ref($err_or_queue);
+ '';
+}
+
+sub acct_sql_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::acct_sql::acct_sql_$method",
+ };
+ $queue->insert(
+ $self->option('datasrc'),
+ $self->option('username'),
+ $self->option('password'),
+ @_,
+ ) or $queue;
+}
+
+sub acct_sql_insert { #subroutine, not method
+ my $dbh = acct_sql_connect(shift, shift, shift);
+ my( $table, %record ) = @_;
+
+ my $sth = $dbh->prepare(
+ "INSERT INTO $table ( ". join(", ", keys %record).
+ " ) VALUES ( ". join(", ", map '?', keys %record ). " )"
+ ) or die $dbh->errstr;
+
+ $sth->execute( map $record{$_}, keys %record )
+ or die "can't insert into $table table: ". $sth->errstr;
+
+ $dbh->disconnect;
+}
+
+sub acct_sql_delete { #subroutine, not method
+ my $dbh = acct_sql_connect(shift, shift, shift);
+ my( $table, %record ) = @_;
+
+ my $sth = $dbh->prepare(
+ "DELETE FROM $table WHERE ". join(' AND ', map "$_ = ? ", keys %record )
+ ) or die $dbh->errstr;
+
+ $sth->execute( map $record{$_}, keys %record )
+ or die "can't delete from $table table: ". $sth->errstr;
+
+ $dbh->disconnect;
+}
+
+sub acct_sql_connect {
+ #my($datasrc, $username, $password) = @_;
+ #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
+ DBI->connect(@_) or die $DBI::errstr;
+}
+
+1;
+
+
diff --git a/FS/FS/part_export/apache.pm b/FS/FS/part_export/apache.pm
new file mode 100644
index 0000000..35b00cc
--- /dev/null
+++ b/FS/FS/part_export/apache.pm
@@ -0,0 +1,47 @@
+package FS::part_export::apache;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'httpd_conf' => { label=>'httpd.conf snippet location',
+ default=>'/etc/apache/httpd-freeside.conf', },
+ 'restart' => { label=>'Apache restart command',
+ default=>'apachectl graceful',
+ },
+ 'template' => {
+ label => 'Template',
+ type => 'textarea',
+ default => <<'END',
+<VirtualHost $domain> #generic
+#<VirtualHost ip.addr> #preferred, http://httpd.apache.org/docs/dns-caveats.html
+DocumentRoot /var/www/$zone
+ServerName $zone
+ServerAlias *.$zone
+#BandWidthModule On
+#LargeFileLimit 4096 12288
+#FrontpageEnable on
+</VirtualHost>
+
+END
+ },
+;
+
+%info = (
+ 'svc' => 'svc_www',
+ 'desc' => 'Export an Apache httpd.conf file snippet.',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Batch export of an httpd.conf snippet from a template. Typically used with
+something like <code>Include /etc/apache/httpd-freeside.conf</code> in
+httpd.conf. <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
+must be installed. Run bin/apache.export to export the files.
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/artera_turbo.pm b/FS/FS/part_export/artera_turbo.pm
new file mode 100644
index 0000000..af4c790
--- /dev/null
+++ b/FS/FS/part_export/artera_turbo.pm
@@ -0,0 +1,155 @@
+package FS::part_export::artera_turbo;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::Record qw(qsearch);
+use FS::part_export;
+use FS::cust_svc;
+use FS::svc_external;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'rid' => { 'label' => 'Reseller ID (RID)' },
+ 'username' => { 'label' => 'Reseller username', },
+ 'password' => { 'label' => 'Reseller password', },
+ 'pid' => { 'label' => 'Artera Product ID', },
+ 'priceid' => { 'label' => 'Artera Price ID', },
+ 'agent_aid' => { 'label' => 'Export agentnum values to Artera AID',
+ 'type' => 'checkbox',
+ },
+ 'aid' => { 'label' => 'Artera Agent ID to use if not using agentnum values', },
+ 'production' => { 'label' => 'Production mode (leave unchecked for staging)',
+ 'type' => 'checkbox',
+ },
+ 'debug' => { 'label' => 'Enable debug logging',
+ 'type' => 'checkbox',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_external',
+ #'svc' => [qw( svc_acct svc_forward )],
+ 'desc' =>
+ 'Real-time export to Artera Turbo Reseller API',
+ 'options' => \%options,
+ #'nodomain' => 'Y',
+ 'notes' => <<'END'
+Real-time export to <a href="http://www.arteraturbo.com/">Artera Turbo</a>
+Reseller API. Requires installation of
+<a href="http://search.cpan.org/dist/Net-Artera">Net::Artera</a>
+from CPAN. You probably also want to:
+<UL>
+ <LI>In the configuraiton UI section: set the <B>svc_external-skip_manual</B> and <B>svc_external-display_type</B> configuration values.
+ <LI>In the message catalog: set <B>svc_external-id</B> to <I>Artera Serial Number</I> and set <B>svc_external-title</B> to <I>Artera Key Code</I>.
+</UL>
+END
+);
+
+sub rebless { shift; }
+
+sub _new_Artera {
+ my $self = shift;
+
+ my $artera = new Net::Artera (
+ map { $_ => $self->option($_) }
+ qw( rid username password production )
+ );
+}
+
+
+sub _export_insert {
+ my($self, $svc_external) = (shift, shift);
+
+ # want the ASN (serial) and AKC (key code) right away
+
+ eval "use Net::Artera;";
+ return $@ if $@;
+ $Net::Artera::DEBUG = 1 if $self->option('debug');
+ my $artera = $self->_new_Artera;
+
+ my $cust_pkg = $svc_external->cust_svc->cust_pkg;
+ my $part_pkg = $cust_pkg->part_pkg;
+ my @svc_acct = grep { $_->table eq 'svc_acct' }
+ map { $_->svc_x }
+ sort { my $svcpart = $part_pkg->svcpart('svc_acct');
+ ($b->svcpart==$svcpart) cmp ($a->svcpart==$svcpart); }
+ qsearch('cust_svc', { 'pkgnum' => $cust_pkg->pkgnum } );
+ my $email = scalar(@svc_acct) ? $svc_acct[0]->email : '';
+
+ my $cust_main = $cust_pkg->cust_main;
+
+ my $result = $artera->newOrder(
+ 'pid' => $self->option('pid'),
+ 'priceid' => $self->option('priceid'),
+ 'email' => $email,
+ 'cname' => $cust_main->name,
+ 'ref' => $svc_external->svcnum,
+ 'aid' => ( $self->option('agent_aid')
+ ? $cust_main->agentnum
+ : $self->option('aid') ),
+ 'add1' => $cust_main->address1,
+ 'add2' => $cust_main->address2,
+ 'add3' => $cust_main->city,
+ 'add4' => $cust_main->state,
+ 'zip' => $cust_main->zip,
+ 'cid' => $cust_main->country,
+ 'phone' => $cust_main->daytime || $cust_main->night,
+ 'fax' => $cust_main->fax,
+ );
+
+ if ( $result->{'id'} == 1 ) {
+ my $new = new FS::svc_external { $svc_external->hash };
+ $new->id($result->{'ASN'});
+ $new->title($result->{'AKC'});
+ $new->replace($svc_external);
+ } else {
+ $result->{'message'} || 'No response from Artera';
+ }
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change serial number with Artera"
+ if $old->id != $new->id && $old->id;
+ return "can't change key code with Artera"
+ if $old->title ne $new->title && $old->title;
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_external ) = (shift, shift);
+ $self->statusChange(17, $svc_external);
+}
+
+sub _export_suspend {
+ my( $self, $svc_external ) = (shift, shift);
+ $self->statusChange(16, $svc_external);
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_external ) = (shift, shift);
+ $self->statusChange(15, $svc_external);
+}
+
+sub statusChange {
+ my( $self, $status, $svc_external ) = @_;
+
+ eval "use Net::Artera;";
+ return $@ if $@;
+ $Net::Artera::DEBUG = 1 if $self->option('debug');
+ my $artera = $self->_new_Artera;
+
+ my $result = $artera->statusChange(
+ 'asn' => sprintf('%010d', $svc_external->id),
+ 'akc' => $svc_external->title,
+ 'statusid' => $status,
+ );
+
+ $result->{'id'} == 1
+ ? ''
+ : $result->{'message'};
+}
+
+1;
+
diff --git a/FS/FS/part_export/bind.pm b/FS/FS/part_export/bind.pm
new file mode 100644
index 0000000..1ef7b65
--- /dev/null
+++ b/FS/FS/part_export/bind.pm
@@ -0,0 +1,35 @@
+package FS::part_export::bind;
+
+use vars qw(@ISA %info %options);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie %options, 'Tie::IxHash',
+ 'named_conf' => { label => 'named.conf location',
+ default=> '/etc/bind/named.conf' },
+ 'zonepath' => { label => 'path to zone files',
+ default=> '/etc/bind/', },
+ 'bind_release' => { label => 'ISC BIND Release',
+ type => 'select',
+ options => [qw(BIND8 BIND9)],
+ default => 'BIND8' },
+ 'bind9_minttl' => { label => 'The minttl required by bind9 and RFC1035.',
+ default => '1D' },
+ 'reload' => { label => 'Optional reload command. If not specified, defaults to "ndc" under BIND8 and "rndc" under BIND9.', },
+;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Batch export to BIND named',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Batch export of BIND zone and configuration files to a primary nameserver.
+<a href="http://search.cpan.org/search?dist=File-Rsync">File::Rsync</a>
+must be installed. Run bin/bind.export to export the files.
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/bind_slave.pm b/FS/FS/part_export/bind_slave.pm
new file mode 100644
index 0000000..c89325f
--- /dev/null
+++ b/FS/FS/part_export/bind_slave.pm
@@ -0,0 +1,28 @@
+package FS::part_export::bind_slave;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie my %options, 'Tie::IxHash',
+ 'master' => { label=> 'Master IP address(s) (semicolon-separated)' },
+ %FS::part_export::bind::options,
+;
+delete $options{'zonepath'};
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' =>'Batch export to slave BIND named',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Batch export of BIND configuration file to a secondary nameserver. Zones are
+slaved from the listed masters.
+<a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
+must be installed. Run bin/bind.export to export the files.
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/bsdshell.pm b/FS/FS/part_export/bsdshell.pm
new file mode 100644
index 0000000..7b5feb2
--- /dev/null
+++ b/FS/FS/part_export/bsdshell.pm
@@ -0,0 +1,25 @@
+package FS::part_export::bsdshell;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::passwdfile;
+
+@ISA = qw(FS::part_export::passwdfile);
+
+tie my %options, 'Tie::IxHash', %FS::part_export::passwdfile::options;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' =>
+ 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+MD5 crypt requires installation of
+<a href="http://search.cpan.org/dist/Crypt-PasswdMD5">Crypt::PasswdMD5</a>
+from CPAN. Run bin/bsdshell.export to export the files.
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm
new file mode 100644
index 0000000..6da2017
--- /dev/null
+++ b/FS/FS/part_export/communigate_pro.pm
@@ -0,0 +1,178 @@
+package FS::part_export::communigate_pro;
+
+use vars qw(@ISA %info %options);
+use Tie::IxHash;
+use FS::part_export;
+use FS::queue;
+
+@ISA = qw(FS::part_export);
+
+tie %options, 'Tie::IxHash',
+ 'port' => { label=>'Port number', default=>'106', },
+ 'login' => { label=>'The administrator account name. The name can contain a domain part.', },
+ 'password' => { label=>'The administrator account password.', },
+ 'accountType' => { label=>'Type for newly-created accounts',
+ type=>'select',
+ options=>[qw( MultiMailbox TextMailbox MailDirMailbox )],
+ default=>'MultiMailbox',
+ },
+ 'externalFlag' => { label=> 'Create accounts with an external (visible for legacy mailers) INBOX.',
+ type=>'checkbox',
+ },
+ 'AccessModes' => { label=>'Access modes',
+ default=>'Mail POP IMAP PWD WebMail WebSite',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to a CommuniGate Pro mail server',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Real time export to a
+<a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
+mail server. The
+<a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
+must be installed as CGP::CLI.
+END
+);
+
+sub rebless { shift; }
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ $svc_acct->email;
+}
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+ my @options = ( $svc_acct->svcnum, 'CreateAccount',
+ 'accountName' => $self->export_username($svc_acct),
+ 'accountType' => $self->option('accountType'),
+ 'AccessModes' => $self->option('AccessModes'),
+ 'RealName' => $svc_acct->finger,
+ 'Password' => $svc_acct->_password,
+ );
+ push @options, 'MaxAccountSize' => $svc_acct->quota if $svc_acct->quota;
+ push @options, 'externalFlag' => $self->option('externalFlag')
+ if $self->option('externalFlag');
+
+ $self->communigate_pro_queue( @options );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't (yet) change username with CommuniGate Pro"
+ if $old->username ne $new->username;
+ return "can't (yet) change domain with CommuniGate Pro"
+ if $self->export_username($old) ne $self->export_username($new);
+ return "can't (yet) change GECOS with CommuniGate Pro"
+ if $old->finger ne $new->finger;
+ return "can't (yet) change quota with CommuniGate Pro"
+ if $old->quota ne $new->quota;
+ return '' unless $old->username ne $new->username
+ || $old->_password ne $new->_password
+ || $old->finger ne $new->finger
+ || $old->quota ne $new->quota;
+
+ return '' if '*SUSPENDED* '. $old->_password eq $new->_password;
+
+ #my $err_or_queue = $self->communigate_pro_queue( $new->svcnum,'RenameAccount',
+ # $old->email, $new->email );
+ #return $err_or_queue unless ref($err_or_queue);
+ #my $jobnum = $err_or_queue->jobnum;
+
+ $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword',
+ $self->export_username($new), $new->_password )
+ if $new->_password ne $old->_password;
+
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount',
+ $self->export_username($svc_acct),
+ );
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
+ 'accountName' => $self->export_username($svc_acct),
+ 'AccessModes' => 'Mail',
+ );
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
+ 'accountName' => $self->export_username($svc_acct),
+ 'AccessModes' => $self->option('AccessModes'),
+ );
+}
+
+sub communigate_pro_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my @kludge_methods = qw(CreateAccount UpdateAccountSettings);
+ my $sub = 'communigate_pro_command';
+ $sub = $method if grep { $method eq $_ } @kludge_methods;
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::communigate_pro::$sub",
+ };
+ $queue->insert(
+ $self->machine,
+ $self->option('port'),
+ $self->option('login'),
+ $self->option('password'),
+ $method,
+ @_,
+ );
+
+}
+
+sub CreateAccount {
+ my( $machine, $port, $login, $password, $method, %args ) = @_;
+ my $accountName = delete $args{'accountName'};
+ my $accountType = delete $args{'accountType'};
+ my $externalFlag = delete $args{'externalFlag'};
+ $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
+ my @args = ( accountName => $accountName,
+ accountType => $accountType,
+ settings => \%args,
+ );
+ #externalFlag => $externalFlag,
+ push @args, externalFlag => $externalFlag if $externalFlag;
+
+ communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+
+}
+
+sub UpdateAccountSettings {
+ my( $machine, $port, $login, $password, $method, %args ) = @_;
+ my $accountName = delete $args{'accountName'};
+ $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
+ @args = ( $accountName, \%args );
+ communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+}
+
+sub communigate_pro_command { #subroutine, not method
+ my( $machine, $port, $login, $password, $method, @args ) = @_;
+
+ eval "use CGP::CLI";
+
+ my $cli = new CGP::CLI( {
+ 'PeerAddr' => $machine,
+ 'PeerPort' => $port,
+ 'login' => $login,
+ 'password' => $password,
+ } ) or die "Can't login to CGPro: $CGP::ERR_STRING\n";
+
+ $cli->$method(@args) or die "CGPro error: ". $cli->getErrMessage;
+
+ $cli->Logout or die "Can't logout of CGPro: $CGP::ERR_STRING\n";
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm
new file mode 100644
index 0000000..6a1bf60
--- /dev/null
+++ b/FS/FS/part_export/communigate_pro_singledomain.pm
@@ -0,0 +1,37 @@
+package FS::part_export::communigate_pro_singledomain;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::communigate_pro;
+
+@ISA = qw(FS::part_export::communigate_pro);
+
+tie my %options, 'Tie::IxHash', %FS::part_export::communigate_pro::options,
+ 'domain' => { label=>'Domain', },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' =>
+ 'Real-time export to a CommuniGate Pro mail server, one domain only',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+Real time export to a
+<a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
+mail server. This is an unusual export to CommuniGate Pro that forces all
+accounts into a single domain. As CommuniGate Pro supports multipledomains,
+unless you have a specific reason for using this export, you probably want to
+use the communigate_pro export instead. The
+<a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
+must be installed as CGP::CLI.
+END
+);
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ $svc_acct->username. '@'. $self->option('domain');
+}
+
+1;
+
diff --git a/FS/FS/part_export/cp.pm b/FS/FS/part_export/cp.pm
new file mode 100644
index 0000000..a295c57
--- /dev/null
+++ b/FS/FS/part_export/cp.pm
@@ -0,0 +1,160 @@
+package FS::part_export::cp;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'port' => { label=>'Port number' },
+ 'username' => { label=>'Username' },
+ 'password' => { label=>'Password' },
+ 'domain' => { label=>'Domain' },
+ 'workgroup' => { label=>'Default Workgroup' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to Critical Path Account Provisioning Protocol',
+ 'options'=> \%options,
+ 'notes' => <<'END'
+Real-time export to
+<a href="http://www.cp.net/">Critial Path Account Provisioning Protocol</a>.
+Requires installation of
+<a href="http://search.cpan.org/dist/Net-APP">Net::APP</a>
+from CPAN.
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->cp_queue( $svc_acct->svcnum, 'create_mailbox',
+ 'Mailbox' => $svc_acct->username,
+ 'Password' => $svc_acct->_password,
+ 'Workgroup' => $self->option('workgroup'),
+ 'Domain' => $svc_acct->domain,
+ );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change domain with Critical Path"
+ if $old->domain ne $new->domain;
+ return '' unless $old->username ne $new->username
+ || $old->_password ne $new->_password;
+ $self->cp_queue( $new->svcnum, 'replace', $new->domain,
+ $old->username, $new->username, $old->_password, $new->_password );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->cp_queue( $svc_acct->svcnum, 'delete_mailbox',
+ 'Mailbox' => $svc_acct->username,
+ 'Domain' => $svc_acct->domain,
+ );
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status',
+ 'Mailbox' => $svc_acct->username,
+ 'Domain' => $svc_acct->domain,
+ 'OTHER' => 'T',
+ 'OTHER_SUSPEND' => 'T',
+ );
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status',
+ 'Mailbox' => $svc_acct->username,
+ 'Domain' => $svc_acct->domain,
+ 'PAYMENT' => 'F',
+ 'OTHER' => 'F',
+ 'OTHER_SUSPEND' => 'F',
+ 'OTHER_BOUNCE' => 'F',
+ );
+}
+
+sub cp_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => 'FS::part_export::cp::cp_command',
+ };
+ $queue->insert(
+ $self->machine,
+ $self->option('port'),
+ $self->option('username'),
+ $self->option('password'),
+ $self->option('domain'),
+ $method,
+ @_,
+ );
+}
+
+sub cp_command { #subroutine, not method
+ my($host, $port, $username, $password, $login_domain, $method, @args) = @_;
+
+ #quelle hack
+ if ( $method eq 'replace' ) {
+
+ my( $domain, $old_username, $new_username, $old_password, $new_password)
+ = @args;
+
+ if ( $old_username ne $new_username ) {
+ cp_command($host, $port, $username, $password, 'rename_mailbox',
+ Domain => $domain,
+ Old_Mailbox => $old_username,
+ New_Mailbox => $new_username,
+ );
+ }
+
+ #my $other = 'F';
+ if ( $new_password =~ /^\*SUSPENDED\* (.*)$/ ) {
+ $new_password = $1;
+ # $other = 'T';
+ }
+ #cp_command($host, $port, $username, $password, $login_domain,
+ # 'set_mailbox_status',
+ # Domain => $domain,
+ # Mailbox => $new_username,
+ # Other => $other,
+ # Other_Bounce => $other,
+ #);
+
+ if ( $old_password ne $new_password ) {
+ cp_command($host, $port, $username, $password, $login_domain,
+ 'change_mailbox',
+ Domain => $domain,
+ Mailbox => $new_username,
+ Password => $new_password,
+ );
+ }
+
+ return;
+ }
+ #eof quelle hack
+
+ eval "use Net::APP;";
+
+ my $app = new Net::APP (
+ "$host:$port",
+ User => $username,
+ Password => $password,
+ Domain => $login_domain,
+ Timeout => 60,
+ #Debug => 1,
+ ) or die "$@\n";
+
+ $app->$method( @args );
+
+ die $app->message."\n" unless $app->ok;
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/cyrus.pm b/FS/FS/part_export/cyrus.pm
new file mode 100644
index 0000000..84c9e5a
--- /dev/null
+++ b/FS/FS/part_export/cyrus.pm
@@ -0,0 +1,120 @@
+package FS::part_export::cyrus;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'server' => { label=>'IMAP server' },
+ 'username' => { label=>'Admin username' },
+ 'password' => { label=>'Admin password' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to Cyrus IMAP server',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+Integration with
+<a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>.
+Cyrus::IMAP::Admin should be installed locally and the connection to the
+server secured. <B>svc_acct.quota</B>, if available, is used to set the
+Cyrus quota.
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+ $self->cyrus_queue( $svc_acct->svcnum, 'insert',
+ $svc_acct->username, $svc_acct->quota );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change username using Cyrus"
+ if $old->username ne $new->username;
+ return '';
+# #return '' unless $old->_password ne $new->_password;
+# $self->cyrus_queue( $new->svcnum,
+# 'replace', $new->username, $new->_password );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->cyrus_queue( $svc_acct->svcnum, 'delete',
+ $svc_acct->username );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub cyrus_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::cyrus::cyrus_$method",
+ };
+ $queue->insert(
+ $self->option('server'),
+ $self->option('username'),
+ $self->option('password'),
+ @_
+ );
+}
+
+sub cyrus_insert { #subroutine, not method
+ my $client = cyrus_connect(shift, shift, shift);
+ my( $username, $quota ) = @_;
+ my $rc = $client->create("user.$username");
+ my $error = $client->error;
+ die "creating user.$username: $error" if $error;
+
+ $rc = $client->setacl("user.$username", $username => 'all' );
+ $error = $client->error;
+ die "setacl user.$username: $error" if $error;
+
+ if ( $quota ) {
+ $rc = $client->setquota("user.$username", 'STORAGE' => $quota );
+ $error = $client->error;
+ die "setquota user.$username: $error" if $error;
+ }
+
+}
+
+sub cyrus_delete { #subroutine, not method
+ my ( $server, $admin_username, $password_username, $username ) = @_;
+ my $client = cyrus_connect($server, $admin_username, $password_username);
+
+ my $rc = $client->setacl("user.$username", $admin_username => 'all' );
+ my $error = $client->error;
+ die $error if $error;
+
+ $rc = $client->delete("user.$username");
+ $error = $client->error;
+ die $error if $error;
+}
+
+sub cyrus_connect {
+
+ my( $server, $admin_username, $admin_password ) = @_;
+
+ eval "use Cyrus::IMAP::Admin;";
+
+ my $client = Cyrus::IMAP::Admin->new($server);
+ $client->authenticate(
+ -user => $admin_username,
+ -mechanism => "login",
+ -password => $admin_password,
+ );
+ $client;
+
+}
+
+#sub cyrus_replace { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm
new file mode 100644
index 0000000..0ba5617
--- /dev/null
+++ b/FS/FS/part_export/domain_shellcommands.pm
@@ -0,0 +1,161 @@
+package FS::part_export::domain_shellcommands;
+
+use strict;
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'useradd' => { label=>'Insert command',
+ default=>'',
+ },
+ 'userdel' => { label=>'Delete command',
+ default=>'',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Run remote commands via SSH, for domains (qmail, ISPMan).',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Run remote commands via SSH, for domains. You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="qmail catchall .qmail-domain-default maintenance" onClick='
+ this.form.useradd.value = "[ \"$uid\" -a \"$gid\" -a \"$dir\" -a \"$qdomain\" ] && [ -e $dir/.qmail-$qdomain-default ] || { touch $dir/.qmail-$qdomain-default; chown $uid:$gid $dir/.qmail-$qdomain-default; }";
+ this.form.userdel.value = "";
+ this.form.usermod.value = "";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+ this.form.useradd.value = "/usr/local/ispman/bin/ispman.addDomain -d $domain changeme";
+ this.form.userdel.value = "/usr/local/ispman/bin/ispman.deleteDomain -d $domain";
+ this.form.usermod.value = "";
+ '>
+</UL>
+The following variables are available for interpolation (prefixed with <code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+ <LI><code>$domain</code>
+ <LI><code>$qdomain</code> - domain with periods replaced by colons
+ <LI><code>$uid</code> - of catchall account
+ <LI><code>$gid</code> - of catchall account
+ <LI><code>$dir</code> - home directory of catchall account
+ <LI>All other fields in
+ <a href="../docs/schema.html#svc_domain">svc_domain</a> are also available.
+</UL>
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self) = shift;
+ $self->_export_command('useradd', @_);
+}
+
+sub _export_delete {
+ my($self) = shift;
+ $self->_export_command('userdel', @_);
+}
+
+sub _export_command {
+ my ( $self, $action, $svc_domain) = (shift, shift, shift);
+ my $command = $self->option($action);
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_domain->getfield($_) foreach $svc_domain->fields;
+ }
+ ( $qdomain = $domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
+
+ if ( $svc_domain->catchall ) {
+ no strict 'refs';
+ my $svc_acct = $svc_domain->catchall_svc_acct;
+ ${$_} = $svc_acct->getfield($_) foreach qw(uid gid dir);
+ } else {
+ no strict 'refs';
+ ${$_} = '' foreach qw(uid gid dir);
+ }
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $svc_domain->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+sub _export_replace {
+ my($self, $new, $old ) = (shift, shift, shift);
+ my $command = $self->option('usermod');
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+ }
+ ( $old_qdomain = $old_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
+ ( $new_qdomain = $new_domain ) =~ s/\./:/g; #see dot-qmail(5): EXTENSION ADDRESSES
+
+ if ( $old->catchall ) {
+ no strict 'refs';
+ my $svc_acct = $old->catchall_svc_acct;
+ ${"old_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir);
+ } else {
+ ${"old_$_"} = '' foreach qw(uid gid dir);
+ }
+ if ( $new->catchall ) {
+ no strict 'refs';
+ my $svc_acct = $new->catchall_svc_acct;
+ ${"new_$_"} = $svc_acct->getfield($_) foreach qw(uid gid dir);
+ } else {
+ ${"new_$_"} = '' foreach qw(uid gid dir);
+ }
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $new->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub shellcommands_queue {
+ my( $self, $svcnum ) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::domain_shellcommands::ssh_cmd",
+ };
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+#sub shellcommands_insert { #subroutine, not method
+#}
+#sub shellcommands_replace { #subroutine, not method
+#}
+#sub shellcommands_delete { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/forward_shellcommands.pm b/FS/FS/part_export/forward_shellcommands.pm
new file mode 100644
index 0000000..fe30435
--- /dev/null
+++ b/FS/FS/part_export/forward_shellcommands.pm
@@ -0,0 +1,159 @@
+package FS::part_export::forward_shellcommands;
+
+use strict;
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'useradd' => { label=>'Insert command',
+ default=>'',
+ },
+ 'userdel' => { label=>'Delete command',
+ default=>'',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_forward',
+ 'desc' => 'Run remote commands via SSH, for forwards',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Run remote commands via SSH, for forwards. You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="text vpopmail maintenance" onClick='
+ this.form.useradd.value = "[ -d /home/vpopmail/domains/$domain/$username ] && { echo \"$destination\" > /home/vpopmail/domains/$domain/$username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$domain/$username/.qmail; }";
+ this.form.userdel.value = "rm /home/vpopmail/domains/$domain/$username/.qmail";
+ this.form.usermod.value = "mv /home/vpopmail/domains/$old_domain/$old_username/.qmail /home/vpopmail/domains/$new_domain/$new_username; [ \"$old_destination\" != \"$new_destination\" ] && { echo \"$new_destination\" > /home/vpopmail/domains/$new_domain/$new_username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$new_domain/$new_username/.qmail; }";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+ this.form.useradd.value = "";
+ this.form.userdel.value = "";
+ this.form.usermod.value = "";
+ '>
+</UL>
+The following variables are available for interpolation (prefixed with
+<code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+ <LI><code>$username</code>
+ <LI><code>$domain</code>
+ <LI><code>$destination</code> - forward destination
+ <LI>All other fields in <a href="../docs/schema.html#svc_forward">svc_forward</a> are also available.
+</UL>
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self) = shift;
+ $self->_export_command('useradd', @_);
+}
+
+sub _export_delete {
+ my($self) = shift;
+ $self->_export_command('userdel', @_);
+}
+
+sub _export_command {
+ my ( $self, $action, $svc_forward ) = (shift, shift, shift);
+ my $command = $self->option($action);
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_forward->getfield($_) foreach $svc_forward->fields;
+ }
+
+ my $svc_acct = $svc_forward->srcsvc_acct;
+ $username = $svc_acct->username;
+ $domain = $svc_acct->domain;
+ if ($svc_forward->dstsvc_acct) {
+ $destination = $svc_forward->dstsvc_acct->email;
+ } else {
+ $destination = $svc_forward->dst;
+ }
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $svc_forward->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ my $command = $self->option('usermod');
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+ }
+
+ my $old_svc_acct = $old->srcsvc_acct;
+ $old_username = $old_svc_acct->username;
+ $old_domain = $old_svc_acct->domain;
+ if ($old->dstsvc_acct) {
+ $old_destination = $old->dstsvc_acct->email;
+ } else {
+ $old_destination = $old->dst;
+ }
+
+ my $new_svc_acct = $new->srcsvc_acct;
+ $new_username = $new_svc_acct->username;
+ $new_domain = $new_svc_acct->domain;
+ if ($new->dstsvc) {
+ $new_destination = $new->dstsvc_acct->email;
+ } else {
+ $new_destination = $new->dst;
+ }
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $new->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub shellcommands_queue {
+ my( $self, $svcnum ) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::forward_shellcommands::ssh_cmd",
+ };
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+#sub shellcommands_insert { #subroutine, not method
+#}
+#sub shellcommands_replace { #subroutine, not method
+#}
+#sub shellcommands_delete { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/http.pm b/FS/FS/part_export/http.pm
new file mode 100644
index 0000000..0be2a0f
--- /dev/null
+++ b/FS/FS/part_export/http.pm
@@ -0,0 +1,134 @@
+package FS::part_export::http;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'method' => { label =>'Method',
+ type =>'select',
+ #options =>[qw(POST GET)],
+ options =>[qw(POST)],
+ default =>'POST' },
+ 'url' => { label => 'URL', default => 'http://', },
+ 'insert_data' => {
+ label => 'Insert data',
+ type => 'textarea',
+ default => join("\n",
+ 'DomainName $svc_x->domain',
+ 'Email ( grep { $_ ne "POST" } $svc_x->cust_svc->cust_pkg->cust_main->invoicing_list)[0]',
+ 'test 1',
+ 'reseller $svc_x->cust_svc->cust_pkg->part_pkg->pkg =~ /reseller/i',
+ ),
+ },
+ 'delete_data' => {
+ label => 'Delete data',
+ type => 'textarea',
+ default => join("\n",
+ ),
+ },
+ 'replace_data' => {
+ label => 'Replace data',
+ type => 'textarea',
+ default => join("\n",
+ ),
+ },
+;
+
+%info = (
+ 'svc' => 'svc_domain',
+ 'desc' => 'Send an HTTP or HTTPS GET or POST request',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Send an HTTP or HTTPS GET or POST to the specified URL. For HTTPS support,
+<a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
+or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a>
+is required.
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my $self = shift;
+ $self->_export_command('insert', @_);
+}
+
+sub _export_delete {
+ my $self = shift;
+ $self->_export_command('delete', @_);
+}
+
+sub _export_command {
+ my( $self, $action, $svc_x ) = ( shift, shift, shift );
+
+ return unless $self->option("${action}_data");
+
+ $self->http_queue( $svc_x->svcnum,
+ $self->option('method'),
+ $self->option('url'),
+ map {
+ /^\s*(\S+)\s+(.*)$/ or /()()/;
+ my( $field, $value_expression ) = ( $1, $2 );
+ my $value = eval $value_expression;
+ die $@ if $@;
+ ( $field, $value );
+ } split(/\n/, $self->option("${action}_data") )
+ );
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = ( shift, shift, shift );
+
+ return unless $self->option('replace_data');
+
+ $self->http_queue( $svc_x->svcnum,
+ $self->option('method'),
+ $self->option('url'),
+ map {
+ /^\s*(\S+)\s+(.*)$/ or /()()/;
+ my( $field, $value_expression ) = ( $1, $2 );
+ die $@ if $@;
+ ( $field, $value );
+ } split(/\n/, $self->option('replace_data') )
+ );
+
+}
+
+sub http_queue {
+ my($self, $svcnum) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::http::http",
+ };
+ $queue->insert( @_ );
+}
+
+sub http {
+ my($method, $url, @data) = @_;
+
+ $method = lc($method);
+
+ eval "use LWP::UserAgent;";
+ die "using LWP::UserAgent: $@" if $@;
+ eval "use HTTP::Request::Common;";
+ die "using HTTP::Request::Common: $@" if $@;
+
+ my $ua = LWP::UserAgent->new;
+
+ #my $response = $ua->$method(
+ # $url, \%data,
+ # 'Content-Type'=>'application/x-www-form-urlencoded'
+ #);
+ my $req = HTTP::Request::Common::POST( $url, \@data );
+ my $response = $ua->request($req);
+
+ die $response->error_as_HTML if $response->is_error;
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/infostreet.pm b/FS/FS/part_export/infostreet.pm
new file mode 100644
index 0000000..309e7ce
--- /dev/null
+++ b/FS/FS/part_export/infostreet.pm
@@ -0,0 +1,277 @@
+package FS::part_export::infostreet;
+
+use vars qw(@ISA %info %infostreet2cust_main $DEBUG);
+use Tie::IxHash;
+use FS::UID qw(dbh);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'url' => { label=>'XML-RPC Access URL', },
+ 'login' => { label=>'InfoStreet login', },
+ 'password' => { label=>'InfoStreet password', },
+ 'groupID' => { label=>'InfoStreet groupID', },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to InfoStreet streetSmartAPI',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+Real-time export to
+<a href="http://www.infostreet.com/">InfoStreet</a> streetSmartAPI.
+Requires installation of
+<a href="http://search.cpan.org/dist/Frontier-Client">Frontier::Client</a> from CPAN.
+END
+);
+
+$DEBUG = 0;
+
+%infostreet2cust_main = (
+ 'firstName' => 'first',
+ 'lastName' => 'last',
+ 'address1' => 'address1',
+ 'address2' => 'address2',
+ 'city' => 'city',
+ 'state' => 'state',
+ 'zipCode' => 'zip',
+ 'country' => 'country',
+ 'phoneNumber' => 'daytime',
+ 'faxNumber' => 'night', #noment-request...
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my( $self, $svc_acct ) = (shift, shift);
+ my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $err_or_queue = $self->infostreet_err_or_queue( $svc_acct->svcnum,
+ 'createUser', $svc_acct->username, $svc_acct->_password );
+ return $err_or_queue unless ref($err_or_queue);
+ my $jobnum = $err_or_queue->jobnum;
+
+ my %contact_info = ( map {
+ $_ => $cust_main->getfield( $infostreet2cust_main{$_} );
+ } keys %infostreet2cust_main );
+
+ my @emails = grep { $_ ne 'POST' } $cust_main->invoicing_list;
+ $contact_info{'email'} = $emails[0] if @emails;
+
+ #this one is kinda noment-specific
+ $contact_info{'organization'} = $cust_main->agent->agent;
+
+ $err_or_queue = $self->infostreet_queueContact( $svc_acct->svcnum,
+ $svc_acct->username, %contact_info );
+ return $err_or_queue unless ref($err_or_queue);
+
+ # If a quota has been specified set the quota because it is not the default
+ $err_or_queue = $self->infostreet_queueSetQuota( $svc_acct->svcnum,
+ $svc_acct->username, $svc_acct->quota ) if $svc_acct->quota;
+ return $err_or_queue unless ref($err_or_queue);
+
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ return $error if $error;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't change username with InfoStreet"
+ if $old->username ne $new->username;
+
+ # If the quota has changed then do the export to setQuota
+ my $err_or_queue = $self->infostreet_queueSetQuota( $new->svcnum, $new->username, $new->quota )
+ if ( $old->quota != $new->quota );
+ return $err_or_queue unless ref($err_or_queue);
+
+
+ return '' unless $old->_password ne $new->_password;
+ $self->infostreet_queue( $new->svcnum,
+ 'passwd', $new->username, $new->_password );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->infostreet_queue( $svc_acct->svcnum,
+ 'purgeAccount,releaseUsername', $svc_acct->username );
+}
+
+sub _export_suspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->infostreet_queue( $svc_acct->svcnum,
+ 'setStatus', $svc_acct->username, 'DISABLED' );
+}
+
+sub _export_unsuspend {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->infostreet_queue( $svc_acct->svcnum,
+ 'setStatus', $svc_acct->username, 'ACTIVE' );
+}
+
+sub infostreet_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => 'FS::part_export::infostreet::infostreet_command',
+ };
+ $queue->insert(
+ $self->option('url'),
+ $self->option('login'),
+ $self->option('password'),
+ $self->option('groupID'),
+ $method,
+ @_,
+ );
+}
+
+#ick false laziness
+sub infostreet_err_or_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => 'FS::part_export::infostreet::infostreet_command',
+ };
+ $queue->insert(
+ $self->option('url'),
+ $self->option('login'),
+ $self->option('password'),
+ $self->option('groupID'),
+ $method,
+ @_,
+ ) or $queue;
+}
+
+sub infostreet_queueContact {
+ my( $self, $svcnum ) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => 'FS::part_export::infostreet::infostreet_setContact',
+ };
+ $queue->insert(
+ $self->option('url'),
+ $self->option('login'),
+ $self->option('password'),
+ $self->option('groupID'),
+ @_,
+ ) or $queue;
+}
+
+sub infostreet_setContact {
+ my($url, $is_username, $is_password, $groupID, $username, %contact_info) = @_;
+ my $accountID = infostreet_command($url, $is_username, $is_password, $groupID,
+ 'getAccountID', $username);
+ foreach my $field ( keys %contact_info ) {
+ infostreet_command($url, $is_username, $is_password, $groupID,
+ 'setContactField', [ 'int'=>$accountID ], $field, $contact_info{$field} );
+ }
+
+}
+
+sub infostreet_queueSetQuota {
+
+ my( $self, $svcnum) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => 'FS::part_export::infostreet::infostreet_setQuota',
+ };
+
+ $queue->insert(
+ $self->option('url'),
+ $self->option('login'),
+ $self->option('password'),
+ $self->option('groupID'),
+ @_,
+ ) or $queue;
+
+}
+
+sub infostreet_setQuota {
+ my($url, $is_username, $is_password, $groupID, $username, $quota) = @_;
+ infostreet_command($url, $is_username, $is_password, $groupID, 'setQuota', $username, [ 'int'=> $quota ] );
+}
+
+
+sub infostreet_command { #subroutine, not method
+ my($url, $username, $password, $groupID, $method, @args) = @_;
+
+ warn "[FS::part_export::infostreet] $method ".join(' ', @args)."\n" if $DEBUG;
+
+ #quelle hack
+ if ( $method =~ /,/ ) {
+ foreach my $part ( split(/,\s*/, $method) ) {
+ infostreet_command($url, $username, $password, $groupID, $part, @args);
+ }
+ return;
+ }
+
+ eval "use Frontier::Client;";
+ die $@ if $@;
+
+ eval 'sub Frontier::RPC2::String::repr {
+ my $self = shift;
+ my $value = $$self;
+ $value =~ s/([&<>\"])/$Frontier::RPC2::char_entities{$1}/ge;
+ $value;
+ }';
+ die $@ if $@;
+
+ my $conn = Frontier::Client->new( url => $url );
+ my $key_result = $conn->call( 'authenticate', $username, $password, $groupID);
+ my %key_result = _infostreet_parse($key_result);
+ die $key_result{error} unless $key_result{success};
+ my $key = $key_result{data};
+
+ #my $result = $conn->call($method, $key, @args);
+ my $result = $conn->call( $method, $key,
+ map {
+ if ( ref($_) ) {
+ my( $type, $value) = @{$_};
+ $conn->$type($value);
+ } else {
+ $conn->string($_);
+ }
+ } @args );
+ my %result = _infostreet_parse($result);
+ die $result{error} unless $result{success};
+
+ $result->{data};
+
+}
+
+#sub infostreet_command_byid { #subroutine, not method;
+# my($url, $username, $password, $groupID, $method, @args ) = @_;
+#
+# infostreet_command
+#
+#}
+
+sub _infostreet_parse { #subroutine, not method
+ my $arg = shift;
+ map {
+ my $value = $arg->{$_};
+ #warn ref($value);
+ $value = $value->value()
+ if ref($value) && $value->isa('Frontier::RPC2::DataType');
+ $_=>$value;
+ } keys %$arg;
+}
+
+1;
+
diff --git a/FS/FS/part_export/ldap.pm b/FS/FS/part_export/ldap.pm
new file mode 100644
index 0000000..823d99d
--- /dev/null
+++ b/FS/FS/part_export/ldap.pm
@@ -0,0 +1,294 @@
+package FS::part_export::ldap;
+
+use vars qw(@ISA %info @saltset);
+use Tie::IxHash;
+use FS::Record qw( dbh );
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'dn' => { label=>'Root DN' },
+ 'password' => { label=>'Root DN password' },
+ 'userdn' => { label=>'User DN' },
+ 'attributes' => { label=>'Attributes',
+ type=>'textarea',
+ default=>join("\n",
+ 'uid $username',
+ 'mail $username\@$domain',
+ 'uidno $uid',
+ 'gidno $gid',
+ 'cn $first',
+ 'sn $last',
+ 'mailquota $quota',
+ 'vmail',
+ 'location',
+ 'mailtag',
+ 'mailhost',
+ 'mailmessagestore $dir',
+ 'userpassword $crypt_password',
+ 'hint',
+ 'answer $sec_phrase',
+ 'objectclass top,person,inetOrgPerson',
+ ),
+ },
+ 'radius' => { label=>'Export RADIUS attributes', type=>'checkbox', },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to LDAP',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Real-time export to arbitrary LDAP attributes. Requires installation of
+<a href="http://search.cpan.org/dist/Net-LDAP">Net::LDAP</a> from CPAN.
+END
+);
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+
+ #false laziness w/shellcommands.pm
+ {
+ no strict 'refs';
+ ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
+ ${$_} = $svc_acct->$_() foreach qw( domain );
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+ ${$_} = $cust_main->getfield($_) foreach qw(first last);
+ }
+ }
+ $crypt_password = ''; #surpress "used only once" warnings
+ $crypt_password = '{crypt}'. crypt( $svc_acct->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))] );
+
+ my $username_attrib;
+ my %attrib = map { /^\s*(\w+)\s+(.*\S)\s*$/;
+ $username_attrib = $1 if $2 eq '$username';
+ ( $1 => eval(qq("$2")) ); }
+ grep { /^\s*(\w+)\s+(.*\S)\s*$/ }
+ split("\n", $self->option('attributes'));
+
+ if ( $self->option('radius') ) {
+ foreach my $table (qw(reply check)) {
+ my $method = "radius_$table";
+ my %radius = $svc_acct->$method();
+ foreach my $radius ( keys %radius ) {
+ ( my $ldap = $radius ) =~ s/\-//g;
+ $attrib{$ldap} = $radius{$radius};
+ }
+ }
+ }
+
+ my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'insert',
+ #$svc_acct->username,
+ $username_attrib,
+ %attrib );
+ return $err_or_queue unless ref($err_or_queue);
+
+ #groups with LDAP?
+ #my @groups = $svc_acct->radius_groups;
+ #if ( @groups ) {
+ # my $err_or_queue = $self->ldap_queue(
+ # $svc_acct->svcnum, 'usergroup_insert',
+ # $svc_acct->username, @groups );
+ # return $err_or_queue unless ref($err_or_queue);
+ #}
+
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ return "can't (yet?) change username with ldap"
+ if $old->username ne $new->username;
+
+ return "ldap replace unimplemented";
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $jobnum = '';
+ #if ( $old->username ne $new->username ) {
+ # my $err_or_queue = $self->ldap_queue( $new->svcnum, 'rename',
+ # $new->username, $old->username );
+ # unless ( ref($err_or_queue) ) {
+ # $dbh->rollback if $oldAutoCommit;
+ # return $err_or_queue;
+ # }
+ # $jobnum = $err_or_queue->jobnum;
+ #}
+
+ foreach my $table (qw(reply check)) {
+ my $method = "radius_$table";
+ my %new = $new->$method();
+ my %old = $old->$method();
+ if ( grep { !exists $old{$_} #new attributes
+ || $new{$_} ne $old{$_} #changed
+ } keys %new
+ ) {
+ my $err_or_queue = $self->ldap_queue( $new->svcnum, 'insert',
+ $table, $new->username, %new );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ my @del = grep { !exists $new{$_} } keys %old;
+ if ( @del ) {
+ my $err_or_queue = $self->ldap_queue( $new->svcnum, 'attrib_delete',
+ $table, $new->username, @del );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+ }
+
+ # (sorta) false laziness with FS::svc_acct::replace
+ my @oldgroups = @{$old->usergroup}; #uuuh
+ my @newgroups = $new->radius_groups;
+ my @delgroups = ();
+ foreach my $oldgroup ( @oldgroups ) {
+ if ( grep { $oldgroup eq $_ } @newgroups ) {
+ @newgroups = grep { $oldgroup ne $_ } @newgroups;
+ next;
+ }
+ push @delgroups, $oldgroup;
+ }
+
+ if ( @delgroups ) {
+ my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_delete',
+ $new->username, @delgroups );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ if ( @newgroups ) {
+ my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_insert',
+ $new->username, @newgroups );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ return "ldap delete unimplemented";
+ my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'delete',
+ $svc_acct->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub ldap_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::ldap::ldap_$method",
+ };
+ $queue->insert(
+ $self->machine,
+ $self->option('dn'),
+ $self->option('password'),
+ $self->option('userdn'),
+ @_,
+ ) or $queue;
+}
+
+sub ldap_insert { #subroutine, not method
+ my $ldap = ldap_connect(shift, shift, shift);
+ my( $userdn, $username_attrib, %attrib ) = @_;
+
+ $userdn = "$username_attrib=$attrib{$username_attrib}, $userdn"
+ if $username_attrib;
+ #icky hack, but should be unsurprising to the LDAPers
+ foreach my $key ( grep { $attrib{$_} =~ /,/ } keys %attrib ) {
+ $attrib{$key} = [ split(/,/, $attrib{$key}) ];
+ }
+
+ my $status = $ldap->add( $userdn, attrs => [ %attrib ] );
+ die 'LDAP error: '. $status->error. "\n" if $status->is_error;
+
+ $ldap->unbind;
+}
+
+#sub ldap_delete { #subroutine, not method
+# my $dbh = ldap_connect(shift, shift, shift);
+# my $username = shift;
+#
+# foreach my $table (qw( radcheck radreply usergroup )) {
+# my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
+# $sth->execute($username)
+# or die "can't delete from $table table: ". $sth->errstr;
+# }
+# $dbh->disconnect;
+#}
+
+sub ldap_connect {
+ my( $machine, $dn, $password ) = @_;
+ my %bind_options;
+ $bind_options{password} = $password if length($password);
+
+ eval "use Net::LDAP";
+ die $@ if $@;
+
+ my $ldap = Net::LDAP->new($machine) or die $@;
+ my $status = $ldap->bind( $dn, %bind_options );
+ die 'LDAP error: '. $status->error. "\n" if $status->is_error;
+
+ $ldap;
+}
+
+1;
+
diff --git a/FS/FS/part_export/null.pm b/FS/FS/part_export/null.pm
new file mode 100644
index 0000000..0145af3
--- /dev/null
+++ b/FS/FS/part_export/null.pm
@@ -0,0 +1,13 @@
+package FS::part_export::null;
+
+use vars qw(@ISA);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+sub rebless { shift; }
+
+sub _export_insert {}
+sub _export_replace {}
+sub _export_delete {}
+
diff --git a/FS/FS/part_export/passwdfile.pm b/FS/FS/part_export/passwdfile.pm
new file mode 100644
index 0000000..2978d25
--- /dev/null
+++ b/FS/FS/part_export/passwdfile.pm
@@ -0,0 +1,18 @@
+package FS::part_export::passwdfile;
+
+use strict;
+use vars qw(@ISA %options);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie %options, 'Tie::IxHash',
+ 'crypt' => { label=>'Password encryption',
+ type=>'select', options=>[qw(crypt md5)],
+ default=>'crypt',
+ },
+;
+
+1;
+
diff --git a/FS/FS/part_export/postfix.pm b/FS/FS/part_export/postfix.pm
new file mode 100644
index 0000000..4fd19ee
--- /dev/null
+++ b/FS/FS/part_export/postfix.pm
@@ -0,0 +1,32 @@
+package FS::part_export::postfix;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'aliases' => { label=>'aliases file location', default=>'/etc/aliases' },
+ 'virtual' => { label=>'virtual file location', default=>'/etc/postfix/virtual' },
+ 'mydomain' => { label=>'local domain', default=>'' },
+ 'newaliases' => { label=>'newaliases command', default=>'newaliases' },
+ 'postmap' => { label=>'postmap command',
+ default=>'postmap hash:/etc/postfix/virtual', },
+ 'reload' => { label=>'reload command',
+ default=>'postfix reload' },
+;
+
+%info = (
+ 'svc' => 'svc_forward',
+ 'desc' => 'Postfix text files',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Batch export of Postfix aliases and virtual files.
+<a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
+must be installed. Run bin/postfix.export to export the files.
+END
+);
+
+1;
diff --git a/FS/FS/part_export/router.pm b/FS/FS/part_export/router.pm
new file mode 100644
index 0000000..648a437
--- /dev/null
+++ b/FS/FS/part_export/router.pm
@@ -0,0 +1,190 @@
+package FS::part_export::router;
+
+=head1 FS::part_export::router
+
+This export connects to a router and transmits commands via telnet or SSH.
+It requires the following custom router fields:
+
+=over 4
+
+=item admin_address - IP address (or hostname) to connect
+
+=item admin_user - username for admin access
+
+=item admin_password - password for admin access
+
+=back
+
+The export itself needs the following options:
+
+=over 4
+
+=item insert, replace, delete - command strings (to be interpolated)
+
+=item Prompt - prompt string to expect from router after successful login
+
+=item Timeout - time to wait for prompt string
+
+=back
+
+(Prompt and Timeout are required only for telnet connections.)
+
+=cut
+
+use vars qw(@ISA %info @saltset);
+use Tie::IxHash;
+use String::ShellQuote;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'protocol' => {
+ label=>'Protocol',
+ type =>'select',
+ options => [qw(telnet ssh)],
+ default => 'telnet'},
+ 'insert' => {label=>'Insert command', default=>'' },
+ 'delete' => {label=>'Delete command', default=>'' },
+ 'replace' => {label=>'Replace command', default=>'' },
+ 'Timeout' => {label=>'Time to wait for prompt', default=>'20' },
+ 'Prompt' => {label=>'Prompt string', default=>'#' }
+;
+
+%info = (
+ 'svc' => 'svc_broadband',
+ 'desc' => 'Send a command to a router.',
+ 'options' => \%options,
+ 'notes' => 'Installation of Net::Telnet from CPAN is required for telnet connections. ( more detailed description from Kristian / fire2wire? )',
+);
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self) = shift;
+ $self->_export_command('insert', @_);
+}
+
+sub _export_delete {
+ my($self) = shift;
+ $self->_export_command('delete', @_);
+}
+
+sub _export_suspend {
+ my($self) = shift;
+ $self->_export_command('suspend', @_);
+}
+
+sub _export_unsuspend {
+ my($self) = shift;
+ $self->_export_command('unsuspend', @_);
+}
+
+sub _export_command {
+ my ( $self, $action, $svc_broadband) = (shift, shift, shift);
+ my $command = $self->option($action);
+ return '' if $command =~ /^\s*$/;
+
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_broadband->getfield($_) foreach $svc_broadband->fields;
+ }
+ # fetch router info
+ my $router = $svc_broadband->addr_block->router;
+ my %r;
+ $r{$_} = $router->getfield($_) foreach $router->virtual_fields;
+ #warn qq("$command");
+ #warn eval(qq("$command"));
+
+ warn "admin_address: '$r{admin_address}'";
+
+ if ($r{admin_address} ne '') {
+ $self->router_queue( $svc_broadband->svcnum, $self->option('protocol'),
+ user => $r{admin_user},
+ password => $r{admin_password},
+ host => $r{admin_address},
+ Timeout => $self->option('Timeout'),
+ Prompt => $self->option('Prompt'),
+ command => eval(qq("$command")),
+ );
+ } else {
+ return '';
+ }
+}
+
+sub _export_replace {
+
+ # We don't handle the case of a svc_broadband moving between routers.
+ # If you want to do that, reprovision the service.
+
+ my($self, $new, $old ) = (shift, shift, shift);
+ my $command = $self->option('replace');
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+ }
+
+ my $router = $new->addr_block->router;
+ my %r;
+ $r{$_} = $router->getfield($_) foreach $router->virtual_fields;
+
+ if ($r{admin_address} ne '') {
+ $self->router_queue( $new->svcnum, $self->option('protocol'),
+ user => $r{admin_user},
+ password => $r{admin_password},
+ host => $r{admin_address},
+ Timeout => $self->option('Timeout'),
+ Prompt => $self->option('Prompt'),
+ command => eval(qq("$command")),
+ );
+ } else {
+ return '';
+ }
+}
+
+#a good idea to queue anything that could fail or take any time
+sub router_queue {
+ #warn join ':', @_;
+ my( $self, $svcnum, $protocol ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ };
+ $queue->job ("FS::part_export::router::".$protocol."_cmd");
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+sub telnet_cmd {
+ eval 'use Net::Telnet;';
+ die $@ if $@;
+
+ warn join(', ', @_);
+
+ my %arg = @_;
+
+ my $t = new Net::Telnet (Timeout => $arg{Timeout},
+ Prompt => $arg{Prompt});
+ $t->open($arg{host});
+ $t->login($arg{user}, $arg{password});
+ my @error = $t->cmd($arg{command});
+ die @error if (grep /^ERROR/, @error);
+}
+
+#sub router_insert { #subroutine, not method
+#}
+#sub router_replace { #subroutine, not method
+#}
+#sub router_delete { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm
new file mode 100644
index 0000000..4f201cf
--- /dev/null
+++ b/FS/FS/part_export/shellcommands.pm
@@ -0,0 +1,338 @@
+package FS::part_export::shellcommands;
+
+use vars qw(@ISA %info @saltset);
+use Tie::IxHash;
+use String::ShellQuote;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'useradd' => { label=>'Insert command',
+ default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username'
+ #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir'
+ },
+ 'useradd_stdin' => { label=>'Insert command STDIN',
+ type =>'textarea',
+ default=>'',
+ },
+ 'userdel' => { label=>'Delete command',
+ default=>'userdel -r $username',
+ #default=>'rm -rf $dir',
+ },
+ 'userdel_stdin' => { label=>'Delete command STDIN',
+ type =>'textarea',
+ default=>'',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username',
+ #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '.
+ # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '.
+ # 'find . -depth -print | cpio -pdm $new_dir; '.
+ # 'chmod u-t $new_dir; chown -R $uid.$gid $new_dir; '.
+ # 'rm -rf $old_dir'.
+ #')'
+ },
+ 'usermod_stdin' => { label=>'Modify command STDIN',
+ type =>'textarea',
+ default=>'',
+ },
+ 'usermod_pwonly' => { label=>'Disallow username changes',
+ type =>'checkbox',
+ },
+ 'suspend' => { label=>'Suspension command',
+ default=>'usermod -L $username',
+ },
+ 'suspend_stdin' => { label=>'Suspension command STDIN',
+ default=>'',
+ },
+ 'unsuspend' => { label=>'Unsuspension command',
+ default=>'usermod -U $username',
+ },
+ 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
+ default=>'',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' =>
+ 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+Run remote commands via SSH. Usernames are considered unique (also see
+shellcommands_withdomain). You probably want this if the commands you are
+running will not accept a domain as a parameter. You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="Linux" onClick='
+ this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
+ this.form.useradd_stdin.value = "";
+ this.form.userdel.value = "userdel -r $username";
+ this.form.userdel_stdin.value="";
+ this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username";
+ this.form.usermod_stdin.value = "";
+ this.form.suspend.value = "usermod -L $username";
+ this.form.suspend_stdin.value="";
+ this.form.unsuspend.value = "usermod -U $username";
+ this.form.unsuspend_stdin.value="";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="FreeBSD before 4.10 / 5.3" onClick='
+ this.form.useradd.value = "lockf /etc/passwd.lock pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
+ this.form.useradd_stdin.value = "$_password\n";
+ this.form.userdel.value = "lockf /etc/passwd.lock pw userdel $username -r"; this.form.userdel_stdin.value="";
+ this.form.usermod.value = "lockf /etc/passwd.lock pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -c $new_finger -h 0";
+ this.form.usermod_stdin.value = "$new__password\n"; this.form.suspend.value = "lockf /etc/passwd.lock pw lock $username";
+ this.form.suspend_stdin.value="";
+ this.form.unsuspend.value = "lockf /etc/passwd.lock pw unlock $username"; this.form.unsuspend_stdin.value="";
+ '>
+ Note: On FreeBSD versions before 5.3 and 4.10 (4.10 is after 4.9, not
+ 4.1!), due to deficient locking in pw(1), you must disable the chpass(1),
+ chsh(1), chfn(1), passwd(1), and vipw(1) commands, or replace them with
+ wrappers that prepend "lockf /etc/passwd.lock". Alternatively, apply the
+ patch in
+ <A HREF="http://www.freebsd.org/cgi/query-pr.cgi?pr=23501">FreeBSD PR#23501</A>
+ and use the "FreeBSD 4.10 / 5.3 or later" button below.
+ <LI>
+ <INPUT TYPE="button" VALUE="FreeBSD 4.10 / 5.3 or later" onClick='
+ this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -g $gid -c $finger -h 0";
+ this.form.useradd_stdin.value = "$_password\n";
+ this.form.userdel.value = "pw userdel $username -r";
+ this.form.userdel_stdin.value="";
+ this.form.usermod.value = "pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -c $new_finger -h 0";
+ this.form.usermod_stdin.value = "$new__password\n";
+ this.form.suspend.value = "pw lock $username";
+ this.form.suspend_stdin.value="";
+ this.form.unsuspend.value = "pw unlock $username";
+ this.form.unsuspend_stdin.value="";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="NetBSD/OpenBSD" onClick='
+ this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
+ this.form.useradd_stdin.value = "";
+ this.form.userdel.value = "userdel -r $username";
+ this.form.userdel_stdin.value="";
+ this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username";
+ this.form.usermod_stdin.value = "";
+ this.form.suspend.value = "";
+ this.form.suspend_stdin.value="";
+ this.form.unsuspend.value = "";
+ this.form.unsuspend_stdin.value="";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="Just maintain directories (use with sysvshell or bsdshell)" onClick='
+ this.form.useradd.value = "cp -pr /etc/skel $dir; chown -R $uid.$gid $dir"; this.form.useradd_stdin.value = "";
+ this.form.usermod.value = "[ -d $old_dir ] && mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $new_uid.$new_gid $new_dir; rm -rf $old_dir )";
+ this.form.usermod_stdin.value = "";
+ this.form.userdel.value = "rm -rf $dir";
+ this.form.userdel_stdin.value="";
+ this.form.suspend.value = "";
+ this.form.suspend_stdin.value="";
+ this.form.unsuspend.value = "";
+ this.form.unsuspend_stdin.value="";
+ '>
+</UL>
+
+The following variables are available for interpolation (prefixed with new_ or
+old_ for replace operations):
+<UL>
+ <LI><code>$username</code>
+ <LI><code>$_password</code>
+ <LI><code>$quoted_password</code> - unencrypted password quoted for the shell
+ <LI><code>$crypt_password</code> - encrypted password
+ <LI><code>$uid</code>
+ <LI><code>$gid</code>
+ <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$dir</code> - home directory
+ <LI><code>$shell</code>
+ <LI><code>$quota</code>
+ <LI><code>@radius_groups</code>
+ <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
+</UL>
+END
+);
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self) = shift;
+ $self->_export_command('useradd', @_);
+}
+
+sub _export_delete {
+ my($self) = shift;
+ $self->_export_command('userdel', @_);
+}
+
+sub _export_suspend {
+ my($self) = shift;
+ $self->_export_command_or_super('suspend', @_);
+}
+
+sub _export_unsuspend {
+ my($self) = shift;
+ $self->_export_command_or_super('unsuspend', @_);
+}
+
+sub _export_command_or_super {
+ my($self, $action) = (shift, shift);
+ if ( $self->option($action) =~ /^\s*$/ ) {
+ my $method = "SUPER::_export_$action";
+ $self->$method(@_);
+ } else {
+ $self->_export_command($action, @_);
+ }
+};
+
+
+sub _export_command {
+ my ( $self, $action, $svc_acct) = (shift, shift, shift);
+ my $command = $self->option($action);
+ return '' if $command =~ /^\s*$/;
+ my $stdin = $self->option($action."_stdin");
+
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
+
+ my $count = 1;
+ foreach my $acct_snarf ( $svc_acct->acct_snarf ) {
+ ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) )
+ foreach qw( machine username _password );
+ $count++;
+ }
+ }
+
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ if ( $cust_pkg ) {
+ $email = ( grep { $_ ne 'POST' } $cust_pkg->cust_main->invoicing_list )[0];
+ } else {
+ $email = '';
+ }
+
+ $finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
+ ($first, $last ) = ( $1, $2 );
+ $first = shell_quote $first;
+ $last = shell_quote $last;
+ $finger = shell_quote $finger;
+ $quoted_password = shell_quote $_password;
+ $domain = $svc_acct->domain;
+
+ #eventually should check a "password-encoding" field
+ if ( length($svc_acct->_password) == 13
+ || $svc_acct->_password =~ /^\$(1|2a?)\$/ ) {
+ $crypt_password = shell_quote $svc_acct->_password;
+ } else {
+ $crypt_password = crypt(
+ $svc_acct->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ }
+
+ @radius_groups = $svc_acct->radius_groups;
+
+ $self->shellcommands_queue( $svc_acct->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ stdin_string => eval(qq("$stdin")),
+ );
+}
+
+sub _export_replace {
+ my($self, $new, $old ) = (shift, shift, shift);
+ my $command = $self->option('usermod');
+ my $stdin = $self->option('usermod_stdin');
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+ }
+ $new_finger =~ /^(.*)\s+(\S+)$/ or $finger =~ /^((.*))$/;
+ ($new_first, $new_last ) = ( $1, $2 );
+ $new_first = shell_quote $new_first;
+ $new_last = shell_quote $new_last;
+ $new_finger = shell_quote $new_finger;
+ $quoted_new__password = shell_quote $new__password; #old, wrong?
+ $new_quoted_password = shell_quote $new__password; #new, better?
+ $old_domain = $old->domain;
+ $new_domain = $new->domain;
+
+ #eventuall should check a "password-encoding" field
+ if ( length($new->_password) == 13
+ || $new->_password =~ /^\$(1|2a?)\$/ ) {
+ $new_crypt_password = shell_quote $new->_password;
+ } else {
+ $new_crypt_password =
+ crypt( $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ }
+
+ @old_radius_groups = $old->radius_groups;
+ @new_radius_groups = $new->radius_groups;
+
+ if ( $self->option('usermod_pwonly') ) {
+ my $error = '';
+ if ( $old_username ne $new_username ) {
+ $error ||= "can't change username";
+ }
+ if ( $old_domain ne $new_domain ) {
+ $error ||= "can't change domain";
+ }
+ if ( $old_uid != $new_uid ) {
+ $error ||= "can't change uid";
+ }
+ if ( $old_dir ne $new_dir ) {
+ $error ||= "can't change dir";
+ }
+ if ( join("\n", sort @old_radius_groups) ne
+ join("\n", sort @new_radius_groups) ) {
+ $error ||= "can't change RADIUS groups";
+ }
+ return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')'
+ if $error;
+ }
+ $self->shellcommands_queue( $new->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ stdin_string => eval(qq("$stdin")),
+ );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub shellcommands_queue {
+ my( $self, $svcnum ) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::shellcommands::ssh_cmd",
+ };
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+#sub shellcommands_insert { #subroutine, not method
+#}
+#sub shellcommands_replace { #subroutine, not method
+#}
+#sub shellcommands_delete { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/shellcommands_withdomain.pm b/FS/FS/part_export/shellcommands_withdomain.pm
new file mode 100644
index 0000000..89ee95f
--- /dev/null
+++ b/FS/FS/part_export/shellcommands_withdomain.pm
@@ -0,0 +1,105 @@
+package FS::part_export::shellcommands_withdomain;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::shellcommands;
+
+@ISA = qw(FS::part_export::shellcommands);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'useradd' => { label=>'Insert command',
+ #default=>''
+ },
+ 'useradd_stdin' => { label=>'Insert command STDIN',
+ type =>'textarea',
+ #default=>"$_password\n$_password\n",
+ },
+ 'userdel' => { label=>'Delete command',
+ #default=>'',
+ },
+ 'userdel_stdin' => { label=>'Delete command STDIN',
+ type =>'textarea',
+ #default=>'',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'',
+ },
+ 'usermod_stdin' => { label=>'Modify command STDIN',
+ type =>'textarea',
+ #default=>"$_password\n$_password\n",
+ },
+ 'usermod_pwonly' => { label=>'Disallow username changes',
+ type =>'checkbox',
+ },
+ 'suspend' => { label=>'Suspension command',
+ default=>'',
+ },
+ 'suspend_stdin' => { label=>'Suspension command STDIN',
+ default=>'',
+ },
+ 'unsuspend' => { label=>'Unsuspension command',
+ default=>'',
+ },
+ 'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
+ default=>'',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export via remote SSH (vpopmail, ISPMan)',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Run remote commands via SSH. username@domain (rather than just usernames) are
+considered unique (also see shellcommands). You probably want this if the
+commands you are running will accept a domain as a parameter, and will allow
+the same username with different domains. You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI><INPUT TYPE="button" VALUE="vpopmail" onClick='
+ this.form.useradd.value = "/home/vpopmail/bin/vadduser $username\\\@$domain $quoted_password";
+ this.form.useradd_stdin.value = "";
+ this.form.userdel.value = "/home/vpopmail/bin/vdeluser $username\\\@$domain";
+ this.form.userdel_stdin.value="";
+ this.form.usermod.value = "/home/vpopmail/bin/vpasswd $new_username\\\@$new_domain $new_quoted_password";
+ this.form.usermod_stdin.value = "";
+ this.form.usermod_pwonly.checked = true;
+ '>
+ <LI><INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+ this.form.useradd.value = "/usr/local/ispman/bin/ispman.addUser -d $domain -f $first -l $last -q $quota -p $quoted_password $username";
+ this.form.useradd_stdin.value = "";
+ this.form.userdel.value = "/usr/local/ispman/bin/ispman.delUser -d $domain $username";
+ this.form.userdel_stdin.value="";
+ this.form.usermod.value = "/usr/local/ispman/bin/ispman.passwd.user $new_username\\\@$new_domain $new_quoted_password";
+ this.form.usermod_stdin.value = "";
+ this.form.usermod_pwonly.checked = true;
+ '>
+</UL>
+
+The following variables are available for interpolation (prefixed with
+<code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+ <LI><code>$username</code>
+ <LI><code>$domain</code>
+ <LI><code>$_password</code>
+ <LI><code>$quoted_password</code> - unencrypted password quoted for the shell
+ <LI><code>$crypt_password</code> - encrypted password
+ <LI><code>$uid</code>
+ <LI><code>$gid</code>
+ <LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$first</code> - First name of GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$last</code> - Last name of GECOS, already quoted for the shell (do not add additional quotes)
+ <LI><code>$dir</code> - home directory
+ <LI><code>$shell</code>
+ <LI><code>$quota</code>
+ <LI><code>@radius_groups</code>
+ <LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
+</UL>
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm
new file mode 100644
index 0000000..6d61e0e
--- /dev/null
+++ b/FS/FS/part_export/sqlmail.pm
@@ -0,0 +1,220 @@
+package FS::part_export::sqlmail;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use Digest::MD5 qw(md5_hex);
+use FS::Record qw(qsearchs);
+use FS::part_export;
+use FS::svc_domain;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'datasrc' => { label => 'DBI data source' },
+ 'username' => { label => 'Database username' },
+ 'password' => { label => 'Database password' },
+ 'server_type' => {
+ label => 'Server type',
+ type => 'select',
+ options => [qw(dovecot_plain dovecot_crypt dovecot_digest_md5 courier_plain
+ courier_crypt)],
+ default => ['dovecot_plain'], },
+ 'svc_acct_table' => { label => 'User Table', default => 'user_acct' },
+ 'svc_forward_table' => { label => 'Forward Table', default => 'forward' },
+ 'svc_domain_table' => { label => 'Domain Table', default => 'domain' },
+ 'svc_acct_fields' => { label => 'svc_acct Export Fields',
+ default => 'username _password domsvc svcnum' },
+ 'svc_forward_fields' => { label => 'svc_forward Export Fields',
+ default => 'domain svcnum catchall' },
+ 'svc_domain_fields' => { label => 'svc_domain Export Fields',
+ default => 'srcsvc dstsvc dst' },
+ 'resolve_dstsvc' => { label => q{Resolve svc_forward.dstsvc to an email address and store it in dst. (Doesn't require that you also export dstsvc.)},
+ type => 'checkbox' },
+;
+
+%info = (
+ 'svc' => [qw( svc_acct svc_domain svc_forward )],
+ 'desc' => 'Real-time export to SQL-backed mail server',
+ 'options' => \%options,
+ 'nodomain' => '',
+ 'notes' => <<'END'
+Database schema can be made to work with Courier IMAP, Exim and Dovecot.
+Others could work but are untested. (more detailed description from
+Kristian / fire2wire? )
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc) = (shift, shift);
+ # this is a svc_something.
+
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+ my $export_table = $self->option($svcdb . '_table')
+ or die('Export table not defined for svcdb: ' . $svcdb);
+ my @export_fields = split(/\s+/, $self->option($svcdb . '_fields'));
+ my $svchash = update_values($self, $svc, $svcdb);
+
+ foreach my $key (keys(%$svchash)) {
+ unless (grep { $key eq $_ } @export_fields) {
+ delete $svchash->{$key};
+ }
+ }
+
+ my $error = $self->sqlmail_queue( $svc->svcnum, 'insert',
+ $self->option('server_type'), $export_table,
+ (map { ($_, $svchash->{$_}); } keys(%$svchash)));
+ return $error if $error;
+ '';
+
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $svcdb = $new->cust_svc->part_svc->svcdb;
+ my $export_table = $self->option($svcdb . '_table')
+ or die('Export table not defined for svcdb: ' . $svcdb);
+ my @export_fields = split(/\s+/, $self->option($svcdb . '_fields'));
+ my $svchash = update_values($self, $new, $svcdb);
+
+ foreach my $key (keys(%$svchash)) {
+ unless (grep { $key eq $_ } @export_fields) {
+ delete $svchash->{$key};
+ }
+ }
+
+ my $error = $self->sqlmail_queue( $new->svcnum, 'replace',
+ $old->svcnum, $self->option('server_type'), $export_table,
+ (map { ($_, $svchash->{$_}); } keys(%$svchash)));
+ return $error if $error;
+ '';
+
+}
+
+sub _export_delete {
+ my( $self, $svc ) = (shift, shift);
+
+ my $svcdb = $svc->cust_svc->part_svc->svcdb;
+ my $table = $self->option($svcdb . '_table')
+ or die('Export table not defined for svcdb: ' . $svcdb);
+
+ $self->sqlmail_queue( $svc->svcnum, 'delete', $table,
+ $svc->svcnum );
+}
+
+sub sqlmail_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::sqlmail::sqlmail_$method",
+ };
+ $queue->insert(
+ $self->option('datasrc'),
+ $self->option('username'),
+ $self->option('password'),
+ @_,
+ );
+}
+
+sub sqlmail_insert { #subroutine, not method
+ my $dbh = sqlmail_connect(shift, shift, shift);
+ my( $server_type, $table ) = (shift, shift);
+
+ my %attrs = @_;
+
+ map { $attrs{$_} = $attrs{$_} ? qq!'$attrs{$_}'! : 'NULL'; } keys(%attrs);
+ my $query = sprintf("INSERT INTO %s (%s) values (%s)",
+ $table, join(",", keys(%attrs)),
+ join(',', values(%attrs)));
+
+ $dbh->do($query) or die $dbh->errstr;
+ $dbh->disconnect;
+
+ '';
+}
+
+sub sqlmail_delete { #subroutine, not method
+ my $dbh = sqlmail_connect(shift, shift, shift);
+ my( $table, $svcnum ) = @_;
+
+ $dbh->do("DELETE FROM $table WHERE svcnum = $svcnum") or die $dbh->errstr;
+ $dbh->disconnect;
+
+ '';
+}
+
+sub sqlmail_replace {
+ my $dbh = sqlmail_connect(shift, shift, shift);
+ my($oldsvcnum, $server_type, $table) = (shift, shift, shift);
+
+ my %attrs = @_;
+ map { $attrs{$_} = $attrs{$_} ? qq!'$attrs{$_}'! : 'NULL'; } keys(%attrs);
+
+ my $query = "SELECT COUNT(*) FROM $table WHERE svcnum = $oldsvcnum";
+ my $result = $dbh->selectrow_arrayref($query) or die $dbh->errstr;
+
+ if (@$result[0] == 0) {
+ $query = sprintf("INSERT INTO %s (%s) values (%s)",
+ $table, join(",", keys(%attrs)),
+ join(',', values(%attrs)));
+ $dbh->do($query) or die $dbh->errstr;
+ } else {
+ $query = sprintf('UPDATE %s SET %s WHERE svcnum = %s',
+ $table, join(', ', map {"$_ = $attrs{$_}"} keys(%attrs)),
+ $oldsvcnum);
+ $dbh->do($query) or die $dbh->errstr;
+ }
+
+ $dbh->disconnect;
+
+ '';
+}
+
+sub sqlmail_connect {
+ DBI->connect(@_) or die $DBI::errstr;
+}
+
+sub update_values {
+
+ # Update records to conform to a particular server_type.
+
+ my ($self, $svc, $svcdb) = (shift,shift,shift);
+ my $svchash = { %{$svc->hashref} } or return ''; # We need a copy.
+
+ if ($svcdb eq 'svc_acct') {
+ if ($self->option('server_type') eq 'courier_crypt') {
+ my $salt = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
+ $svchash->{_password} = crypt($svchash->{_password}, $salt);
+
+ } elsif ($self->option('server_type') eq 'dovecot_plain') {
+ $svchash->{_password} = '{PLAIN}' . $svchash->{_password};
+
+ } elsif ($self->option('server_type') eq 'dovecot_crypt') {
+ my $salt = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
+ $svchash->{_password} = '{CRYPT}' . crypt($svchash->{_password}, $salt);
+
+ } elsif ($self->option('server_type') eq 'dovecot_digest_md5') {
+ my $svc_domain = qsearchs('svc_domain', { svcnum => $svc->domsvc });
+ die('Unable to lookup svc_domain with domsvc: ' . $svc->domsvc)
+ unless ($svc_domain);
+
+ my $domain = $svc_domain->domain;
+ my $md5hash = '{DIGEST-MD5}' . md5_hex(join(':', $svchash->{username},
+ $domain, $svchash->{_password}));
+ $svchash->{_password} = $md5hash;
+ }
+ } elsif ($svcdb eq 'svc_forward') {
+ if ($self->option('resolve_dstsvc') && $svc->dstsvc_acct) {
+ $svchash->{dst} = $svc->dstsvc_acct->username . '@' .
+ $svc->dstsvc_acct->svc_domain->domain;
+ }
+ }
+
+ return($svchash);
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm
new file mode 100644
index 0000000..5eddd3a
--- /dev/null
+++ b/FS/FS/part_export/sqlradius.pm
@@ -0,0 +1,444 @@
+package FS::part_export::sqlradius;
+
+use vars qw(@ISA %info %options $notes1 $notes2);
+use Tie::IxHash;
+use FS::Record qw( dbh );
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie %options, 'Tie::IxHash',
+ 'datasrc' => { label=>'DBI data source ' },
+ 'username' => { label=>'Database username' },
+ 'password' => { label=>'Database password' },
+ 'ignore_accounting' => {
+ type => 'checkbox',
+ label=>'Ignore accounting records from this database'
+ },
+;
+
+$notes1 = <<'END';
+Real-time export of radcheck, radreply and usergroup tables to any SQL database
+for <a href="http://www.freeradius.org/">FreeRADIUS</a>,
+<a href="http://radius.innercite.com/">ICRADIUS</a>
+or <a href="http://www.open.com.au/radiator/">Radiator</a>.
+END
+
+$notes2 = <<'END';
+An existing RADIUS database will be updated in realtime, but you can use
+<a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a>
+to delete the entire RADIUS database and repopulate the tables from the
+Freeside database. See the
+<a href="http://search.cpan.org/dist/DBI/DBI.pm#connect">DBI documentation</a>
+and the
+<a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a>
+for the exact syntax of a DBI data source.
+<ul>
+ <li>Using FreeRADIUS 0.9.0 with the PostgreSQL backend, the db_postgresql.sql schema and postgresql.conf queries contain incompatible changes. This is fixed in 0.9.1. Only new installs with 0.9.0 and PostgreSQL are affected - upgrades and other database backends and versions are unaffected.
+ <li>Using ICRADIUS, add a dummy "op" column to your database:
+ <blockquote><code>
+ ALTER&nbsp;TABLE&nbsp;radcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+ ALTER&nbsp;TABLE&nbsp;radreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+ ALTER&nbsp;TABLE&nbsp;radgroupcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+ ALTER&nbsp;TABLE&nbsp;radgroupreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='
+ </code></blockquote>
+ <li>Using Radiator, see the
+ <a href="http://www.open.com.au/radiator/faq.html#38">Radiator FAQ</a>
+ for configuration information.
+</ul>
+END
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS, Radiator)',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => $notes1.
+ 'This export does not export RADIUS realms (see also '.
+ 'sqlradius_withdomain). '.
+ $notes2
+);
+
+sub rebless { shift; }
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ $svc_acct->username;
+}
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+
+ foreach my $table (qw(reply check)) {
+ my $method = "radius_$table";
+ my %attrib = $svc_acct->$method();
+ next unless keys %attrib;
+ my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
+ $table, $self->export_username($svc_acct), %attrib );
+ return $err_or_queue unless ref($err_or_queue);
+ }
+ my @groups = $svc_acct->radius_groups;
+ if ( @groups ) {
+ my $err_or_queue = $self->sqlradius_queue(
+ $svc_acct->svcnum, 'usergroup_insert',
+ $self->export_username($svc_acct), @groups );
+ return $err_or_queue unless ref($err_or_queue);
+ }
+ '';
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $jobnum = '';
+ if ( $self->export_username($old) ne $self->export_username($new) ) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename',
+ $self->export_username($new), $self->export_username($old) );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ $jobnum = $err_or_queue->jobnum;
+ }
+
+ foreach my $table (qw(reply check)) {
+ my $method = "radius_$table";
+ my %new = $new->$method();
+ my %old = $old->$method();
+ if ( grep { !exists $old{$_} #new attributes
+ || $new{$_} ne $old{$_} #changed
+ } keys %new
+ ) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert',
+ $table, $self->export_username($new), %new );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ my @del = grep { !exists $new{$_} } keys %old;
+ if ( @del ) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'attrib_delete',
+ $table, $self->export_username($new), @del );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+ }
+
+ # (sorta) false laziness with FS::svc_acct::replace
+ my @oldgroups = @{$old->usergroup}; #uuuh
+ my @newgroups = $new->radius_groups;
+ my @delgroups = ();
+ foreach my $oldgroup ( @oldgroups ) {
+ if ( grep { $oldgroup eq $_ } @newgroups ) {
+ @newgroups = grep { $oldgroup ne $_ } @newgroups;
+ next;
+ }
+ push @delgroups, $oldgroup;
+ }
+
+ if ( @delgroups ) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_delete',
+ $self->export_username($new), @delgroups );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ if ( @newgroups ) {
+ my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_insert',
+ $self->export_username($new), @newgroups );
+ unless ( ref($err_or_queue) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $err_or_queue;
+ }
+ if ( $jobnum ) {
+ my $error = $err_or_queue->depend_insert( $jobnum );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'delete',
+ $self->export_username($svc_acct) );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub sqlradius_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::sqlradius::sqlradius_$method",
+ };
+ $queue->insert(
+ $self->option('datasrc'),
+ $self->option('username'),
+ $self->option('password'),
+ @_,
+ ) or $queue;
+}
+
+sub sqlradius_insert { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my( $table, $username, %attributes ) = @_;
+
+ foreach my $attribute ( keys %attributes ) {
+
+ my $s_sth = $dbh->prepare(
+ "SELECT COUNT(*) FROM rad$table WHERE UserName = ? AND Attribute = ?"
+ ) or die $dbh->errstr;
+ $s_sth->execute( $username, $attribute ) or die $s_sth->errstr;
+
+ if ( $s_sth->fetchrow_arrayref->[0] ) {
+
+ my $u_sth = $dbh->prepare(
+ "UPDATE rad$table SET Value = ? WHERE UserName = ? AND Attribute = ?"
+ ) or die $dbh->errstr;
+ $u_sth->execute($attributes{$attribute}, $username, $attribute)
+ or die $u_sth->errstr;
+
+ } else {
+
+ my $i_sth = $dbh->prepare(
+ "INSERT INTO rad$table ( UserName, Attribute, op, Value ) ".
+ "VALUES ( ?, ?, ?, ? )"
+ ) or die $dbh->errstr;
+ $i_sth->execute(
+ $username,
+ $attribute,
+ ( $attribute =~ /Password/i ? '==' : ':=' ),
+ $attributes{$attribute},
+ ) or die $i_sth->errstr;
+
+ }
+
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_usergroup_insert { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my( $username, @groups ) = @_;
+
+ my $sth = $dbh->prepare(
+ "INSERT INTO usergroup ( UserName, GroupName ) VALUES ( ?, ? )"
+ ) or die $dbh->errstr;
+ foreach my $group ( @groups ) {
+ $sth->execute( $username, $group )
+ or die "can't insert into groupname table: ". $sth->errstr;
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_usergroup_delete { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my( $username, @groups ) = @_;
+
+ my $sth = $dbh->prepare(
+ "DELETE FROM usergroup WHERE UserName = ? AND GroupName = ?"
+ ) or die $dbh->errstr;
+ foreach my $group ( @groups ) {
+ $sth->execute( $username, $group )
+ or die "can't delete from groupname table: ". $sth->errstr;
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_rename { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my($new_username, $old_username) = @_;
+ foreach my $table (qw(radreply radcheck usergroup )) {
+ my $sth = $dbh->prepare("UPDATE $table SET Username = ? WHERE UserName = ?")
+ or die $dbh->errstr;
+ $sth->execute($new_username, $old_username)
+ or die "can't update $table: ". $sth->errstr;
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_attrib_delete { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my( $table, $username, @attrib ) = @_;
+
+ foreach my $attribute ( @attrib ) {
+ my $sth = $dbh->prepare(
+ "DELETE FROM rad$table WHERE UserName = ? AND Attribute = ?" )
+ or die $dbh->errstr;
+ $sth->execute($username,$attribute)
+ or die "can't delete from rad$table table: ". $sth->errstr;
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_delete { #subroutine, not method
+ my $dbh = sqlradius_connect(shift, shift, shift);
+ my $username = shift;
+
+ foreach my $table (qw( radcheck radreply usergroup )) {
+ my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
+ $sth->execute($username)
+ or die "can't delete from $table table: ". $sth->errstr;
+ }
+ $dbh->disconnect;
+}
+
+sub sqlradius_connect {
+ #my($datasrc, $username, $password) = @_;
+ #DBI->connect($datasrc, $username, $password) or die $DBI::errstr;
+ DBI->connect(@_) or die $DBI::errstr;
+}
+
+#--
+
+=item usage_sessions TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ SQL_SELECT ] ] ]
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+SVC_ACCT, if specified, limits the results to the specified account.
+
+IP, if specified, limits the results to the specified IP address.
+
+#SQL_SELECT defaults to * if unspecified. It can be useful to set it to
+#SUM(acctsessiontime) or SUM(AcctInputOctets), etc.
+
+Returns an array of hash references
+Returns an arrayref of hashrefs with the following fields:
+
+=over 4
+
+=item username
+
+=item framedipaddress
+
+=item acctstarttime
+
+=item acctstoptime
+
+=item acctsessiontime
+
+=item acctinputoctets
+
+=item acctoutputoctets
+
+=back
+
+=cut
+
+#some false laziness w/cust_svc::seconds_since_sqlradacct
+
+sub usage_sessions {
+ my( $self, $start, $end ) = splice(@_, 0, 3);
+ my $svc_acct = @_ ? shift : '';
+ my $ip = @_ ? shift : '';
+ #my $select = @_ ? shift : '*';
+
+ $end ||= 2147483647;
+
+ return [] if $self->option('ignore_accounting');
+
+ my $dbh = sqlradius_connect( map $self->option($_),
+ qw( datasrc username password ) );
+
+ #select a unix time conversion function based on database type
+ my $str2time;
+ if ( $dbh->{Driver}->{Name} =~ /^mysql(PP)?$/ ) {
+ $str2time = 'UNIX_TIMESTAMP(';
+ } elsif ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+ $str2time = 'EXTRACT( EPOCH FROM ';
+ } else {
+ warn "warning: unknown database type ". $dbh->{Driver}->{Name}.
+ "; guessing how to convert to UNIX timestamps";
+ $str2time = 'extract(epoch from ';
+ }
+
+ my @fields = (
+ qw( username realm framedipaddress
+ acctsessiontime acctinputoctets acctoutputoctets
+ ),
+ "$str2time acctstarttime ) as acctstarttime",
+ "$str2time acctstoptime ) as acctstoptime",
+ );
+
+ my @param = ();
+ my $where = '';
+
+ if ( $svc_acct ) {
+ my $username = $self->export_username($svc_acct);
+ if ( $svc_acct =~ /^([^@]+)\@([^@]+)$/ ) {
+ $where = '( UserName = ? OR ( UserName = ? AND Realm = ? ) ) AND';
+ push @param, $username, $1, $2;
+ } else {
+ $where = 'UserName = ? AND';
+ push @param, $username;
+ }
+ }
+
+ if ( length($ip) ) {
+ $where .= ' FramedIPAddress = ? AND';
+ push @param, $ip;
+ }
+
+ push @param, $start, $end;
+
+ my $sth = $dbh->prepare('SELECT '. join(', ', @fields).
+ " FROM radacct
+ WHERE $where
+ $str2time AcctStopTime ) >= ?
+ AND $str2time AcctStopTime ) <= ?
+ ORDER BY AcctStartTime DESC
+ ") or die $dbh->errstr;
+ $sth->execute(@param) or die $sth->errstr;
+
+ [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ];
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/sqlradius_withdomain.pm b/FS/FS/part_export/sqlradius_withdomain.pm
new file mode 100644
index 0000000..6130e5e
--- /dev/null
+++ b/FS/FS/part_export/sqlradius_withdomain.pm
@@ -0,0 +1,28 @@
+package FS::part_export::sqlradius_withdomain;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::sqlradius;
+
+tie my %options, 'Tie::IxHash', %FS::part_export::sqlradius::options;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS, Radiator) with realms',
+ 'options' => \%options,
+ 'nodomain' => '',
+ 'notes' => $FS::part_export::sqlradius::notes1.
+ 'This export exports domains to RADIUS realms (see also '.
+ 'sqlradius). '.
+ $FS::part_export::sqlradius::notes2
+);
+
+@ISA = qw(FS::part_export::sqlradius);
+
+sub export_username {
+ my($self, $svc_acct) = (shift, shift);
+ $svc_acct->email;
+}
+
+1;
+
diff --git a/FS/FS/part_export/sysvshell.pm b/FS/FS/part_export/sysvshell.pm
new file mode 100644
index 0000000..244c3bf
--- /dev/null
+++ b/FS/FS/part_export/sysvshell.pm
@@ -0,0 +1,25 @@
+package FS::part_export::sysvshell;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::passwdfile;
+
+@ISA = qw(FS::part_export::passwdfile);
+
+tie my %options, 'Tie::IxHash', %FS::part_export::passwdfile::options;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' =>
+ 'Batch export of /etc/passwd and /etc/shadow files (Linux, Solaris)',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+MD5 crypt requires installation of
+<a href="http://search.cpan.org/dist/Crypt-PasswdMD5">Crypt::PasswdMD5</a>
+from CPAN. Run bin/sysvshell.export to export the files.
+END
+);
+
+1;
+
diff --git a/FS/FS/part_export/textradius.pm b/FS/FS/part_export/textradius.pm
new file mode 100644
index 0000000..65936ea
--- /dev/null
+++ b/FS/FS/part_export/textradius.pm
@@ -0,0 +1,191 @@
+package FS::part_export::textradius;
+
+use vars qw(@ISA %info $prefix);
+use Fcntl qw(:flock);
+use Tie::IxHash;
+use FS::UID qw(datasrc);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'users' => { label=>'users file location', default=>'/etc/raddb/users' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' =>
+ 'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)',
+ 'options' => \%options,
+ 'notes' => <<'END'
+This will edit a text RADIUS users file in place on a remote server.
+Requires installation of
+<a href="http://search.cpan.org/dist/RADIUS-UserFile">RADIUS::UserFile</a>
+from CPAN. If using RADIUS::UserFile 1.01, make sure to apply
+<a href="http://rt.cpan.org/NoAuth/Bug.html?id=1210">this patch</a>. Also
+make sure <a href="http://rsync.samba.org/">rsync</a> is installed on the
+remote machine, and <a href="../docs/ssh.html">SSH is setup for unattended
+operation</a>.
+END
+);
+
+$prefix = "/usr/local/etc/freeside/export.";
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+ $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'insert',
+ $svc_acct->username, $svc_acct->radius_check, '-', $svc_acct->radius_reply);
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ return "can't (yet?) change username with textradius"
+ if $old->username ne $new->username;
+ #return '' unless $old->_password ne $new->_password;
+ $err_or_queue = $self->textradius_queue( $new->svcnum, 'insert',
+ $new->username, $new->radius_check, '-', $new->radius_reply);
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'delete',
+ $svc_acct->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+#a good idea to queue anything that could fail or take any time
+sub textradius_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::textradius::textradius_$method",
+ };
+ $queue->insert(
+ $self->option('user')||'root',
+ $self->machine,
+ $self->option('users'),
+ @_,
+ ) or $queue;
+}
+
+sub textradius_insert { #subroutine, not method
+ my( $user, $host, $users, $username, @attributes ) = @_;
+
+ #silly arg processing
+ my($att, @check);
+ push @check, $att while @attributes && ($att=shift @attributes) ne '-';
+ my %check = @check;
+ my %reply = @attributes;
+
+ my $file = textradius_download($user, $host, $users);
+
+ eval "use RADIUS::UserFile;";
+ die $@ if $@;
+
+ my $userfile = new RADIUS::UserFile(
+ File => $file,
+ Who => [ $username ],
+ Check_Items => [ keys %check ],
+ ) or die "error parsing $file";
+
+ $userfile->remove($username);
+ $userfile->add(
+ Who => $username,
+ Attributes => { %check, %reply },
+ Comment => 'user added by Freeside',
+ ) or die "error adding to $file";
+
+ $userfile->update( Who => [ $username ] )
+ or die "error updating $file";
+
+ textradius_upload($user, $host, $users);
+
+}
+
+sub textradius_delete { #subroutine, not method
+ my( $user, $host, $users, $username ) = @_;
+
+ my $file = textradius_download($user, $host, $users);
+
+ eval "use RADIUS::UserFile;";
+ die $@ if $@;
+
+ my $userfile = new RADIUS::UserFile(
+ File => $file,
+ Who => [ $username ],
+ ) or die "error parsing $file";
+
+ $userfile->remove($username);
+
+ $userfile->update( Who => [ $username ] )
+ or die "error updating $file";
+
+ textradius_upload($user, $host, $users);
+}
+
+sub textradius_download {
+ my( $user, $host, $users ) = @_;
+
+ my $dir = $prefix. datasrc;
+ mkdir $dir, 0700 or die $! unless -d $dir;
+ $dir .= "/$host";
+ mkdir $dir, 0700 or die $! unless -d $dir;
+
+ my $dest = "$dir/users";
+
+ eval "use File::Rsync;";
+ die $@ if $@;
+ my $rsync = File::Rsync->new({ rsh => 'ssh' });
+
+ open(LOCK, "+>>$dest.lock")
+ and flock(LOCK,LOCK_EX)
+ or die "can't open $dest.lock: $!";
+
+ $rsync->exec( {
+ src => "$user\@$host:$users",
+ dest => $dest,
+ } ); # true/false return value from exec is not working, alas
+ if ( $rsync->err ) {
+ die "error downloading $user\@$host:$users : ".
+ 'exit status: '. $rsync->status. ', '.
+ 'STDERR: '. join(" / ", $rsync->err). ', '.
+ 'STDOUT: '. join(" / ", $rsync->out);
+ }
+
+ $dest;
+}
+
+sub textradius_upload {
+ my( $user, $host, $users ) = @_;
+
+ my $dir = $prefix. datasrc. "/$host";
+
+ eval "use File::Rsync;";
+ die $@ if $@;
+ my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+ #dry_run => 1,
+ });
+ $rsync->exec( {
+ src => "$dir/users",
+ dest => "$user\@$host:$users",
+ } ); # true/false return value from exec is not working, alas
+ if ( $rsync->err ) {
+ die "error uploading to $user\@$host:$users : ".
+ 'exit status: '. $rsync->status. ', '.
+ 'STDERR: '. join(" / ", $rsync->err). ', '.
+ 'STDOUT: '. join(" / ", $rsync->out);
+ }
+
+ flock(LOCK,LOCK_UN);
+ close LOCK;
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm
new file mode 100644
index 0000000..0fc8266
--- /dev/null
+++ b/FS/FS/part_export/vpopmail.pm
@@ -0,0 +1,254 @@
+package FS::part_export::vpopmail;
+
+use vars qw(@ISA %info @saltset $exportdir);
+use Fcntl qw(:flock);
+use Tie::IxHash;
+use File::Path;
+use FS::UID qw( datasrc );
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ #'machine' => { label=>'vpopmail machine', },
+ 'dir' => { label=>'directory', }, # ?more info? default?
+ 'uid' => { label=>'vpopmail uid' },
+ 'gid' => { label=>'vpopmail gid' },
+ 'restart' => { label=> 'vpopmail restart command',
+ default=> 'cd /home/vpopmail/domains; for domain in *; do /home/vpopmail/bin/vmkpasswd $domain; done; /var/qmail/bin/qmail-newu; killall -HUP qmail-send',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ 'desc' => 'Real-time export to vpopmail text files',
+ 'options' => \%options,
+ 'notes' => <<'END'
+This export is currently unmaintained. See shellcommands_withdomain for an
+export that uses vpopmail CLI commands instead.<BR>
+<BR>
+Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text
+files. <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
+must be installed, and you will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>
+to <b>vpopmail</b>@<i>export.host</i>.
+END
+);
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_acct) = (shift, shift);
+ $self->vpopmail_queue( $svc_acct->svcnum, 'insert',
+ $svc_acct->username,
+ crypt($svc_acct->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]),
+ $svc_acct->domain,
+ $svc_acct->quota,
+ $svc_acct->finger,
+ );
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+
+ my $cpassword = crypt(
+ $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+
+ return "can't change username with vpopmail"
+ if $old->username ne $new->username;
+
+ #no.... if mail can't be preserved, better to disallow username changes
+ #if ($old->username ne $new->username || $old->domain ne $new->domain ) {
+ # vpopmail_queue( $svc_acct->svcnum, 'delete',
+ # $old->username, $old->domain
+ # );
+ # vpopmail_queue( $svc_acct->svcnum, 'insert',
+ # $new->username,
+ # $cpassword,
+ # $new->domain,
+ # );
+
+ return '' unless $old->_password ne $new->_password;
+
+ $self->vpopmail_queue( $new->svcnum, 'replace',
+ $new->username, $cpassword, $new->domain, $new->quota, $new->finger );
+}
+
+sub _export_delete {
+ my( $self, $svc_acct ) = (shift, shift);
+ $self->vpopmail_queue( $svc_acct->svcnum, 'delete',
+ $svc_acct->username, $svc_acct->domain );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub vpopmail_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+
+ my $exportdir = "/usr/local/etc/freeside/export." . datasrc;
+ mkdir $exportdir, 0700 or die $! unless -d $exportdir;
+ $exportdir .= "/vpopmail";
+ mkdir $exportdir, 0700 or die $! unless -d $exportdir;
+ $exportdir .= '/'. $self->machine;
+ mkdir $exportdir, 0700 or die $! unless -d $exportdir;
+ mkdir "$exportdir/domains", 0700 or die $! unless -d "$exportdir/domains";
+
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::vpopmail::vpopmail_$method",
+ };
+ $queue->insert(
+ $exportdir,
+ $self->machine,
+ $self->option('dir'),
+ $self->option('uid'),
+ $self->option('gid'),
+ $self->option('restart'),
+ @_
+ );
+}
+
+sub vpopmail_insert { #subroutine, not method
+ my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
+ my( $username, $password, $domain, $quota, $finger ) = @_;
+
+ mkdir "$exportdir/domains/$domain", 0700 or die $!
+ unless -d "$exportdir/domains/$domain";
+
+ (open(VPASSWD, ">>$exportdir/domains/$domain/vpasswd")
+ and flock(VPASSWD,LOCK_EX)
+ ) or die "can't open vpasswd file for $username\@$domain: ".
+ "$exportdir/domains/$domain/vpasswd: $!";
+ print VPASSWD join(":",
+ $username,
+ $password,
+ '1',
+ '0',
+ $finger,
+ "$dir/domains/$domain/$username",
+ $quota ? $quota.'S' : 'NOQUOTA',
+ ), "\n";
+
+ flock(VPASSWD,LOCK_UN);
+ close(VPASSWD);
+
+ for my $mkdir (
+ grep { ! -d $_ } map { "$exportdir/domains/$domain/$username$_" }
+ ( '', qw( /Maildir /Maildir/cur /Maildir/new /Maildir/tmp ) )
+ ) {
+ mkdir $mkdir, 0700 or die "can't mkdir $mkdir: $!";
+ }
+
+ vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
+
+}
+
+sub vpopmail_replace { #subroutine, not method
+ my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
+ my( $username, $password, $domain, $quota, $finger ) = @_;
+
+ (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
+ and flock(VPASSWD,LOCK_EX)
+ ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
+
+ open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
+ or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
+
+ while (<VPASSWD>) {
+ my ($mailbox, $pw, $vuid, $vgid, $vfinger, $vdir, $vquota, @rest) =
+ split(':', $_);
+ if ( $username ne $mailbox ) {
+ print VPASSWDTMP $_;
+ next
+ }
+ print VPASSWDTMP join (':',
+ $mailbox,
+ $password,
+ '1',
+ '0',
+ $finger,
+ "$dir/domains/$domain/$username", #$vdir
+ $quota ? $quota.'S' : 'NOQUOTA',
+ ), "\n";
+ }
+
+ close(VPASSWDTMP);
+
+ rename "$exportdir/domains/$domain/vpasswd.tmp", "$exportdir/domains/$domain/vpasswd"
+ or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
+
+ flock(VPASSWD,LOCK_UN);
+ close(VPASSWD);
+
+ vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
+
+}
+
+sub vpopmail_delete { #subroutine, not method
+ my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
+ my( $username, $domain ) = @_;
+
+ (open(VPASSWD, "$exportdir/domains/$domain/vpasswd")
+ and flock(VPASSWD,LOCK_EX)
+ ) or die "can't open $exportdir/domains/$domain/vpasswd: $!";
+
+ open(VPASSWDTMP, ">$exportdir/domains/$domain/vpasswd.tmp")
+ or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!";
+
+ while (<VPASSWD>) {
+ my ($mailbox, $rest) = split(':', $_);
+ print VPASSWDTMP $_ unless $username eq $mailbox;
+ }
+
+ close(VPASSWDTMP);
+
+ rename "$exportdir/domains/$domain/vpasswd.tmp",
+ "$exportdir/domains/$domain/vpasswd"
+ or die "Can't rename $exportdir/domains/$domain/vpasswd.tmp: $!";
+
+ flock(VPASSWD,LOCK_UN);
+ close(VPASSWD);
+
+ rmtree "$exportdir/domains/$domain/$username"
+ or die "can't rmtree $exportdir/domains/$domain/$username: $!";
+
+ vpopmail_sync( $exportdir, $machine, $dir, $uid, $gid, $restart );
+}
+
+sub vpopmail_sync {
+ my( $exportdir, $machine, $dir, $uid, $gid, $restart ) = splice @_,0,6;
+
+ chdir $exportdir;
+# my @args = ( $rsync, "-rlpt", "-e", $ssh, "domains/",
+# "vpopmail\@$machine:$dir/domains/" );
+# system {$args[0]} @args;
+
+ eval "use File::Rsync;";
+ die $@ if $@;
+
+ my $rsync = File::Rsync->new({ rsh => 'ssh' });
+
+ $rsync->exec( {
+ recursive => 1,
+ perms => 1,
+ times => 1,
+ src => "$exportdir/domains/",
+ dest => "vpopmail\@$machine:$dir/domains/",
+ } ); # true/false return value from exec is not working, alas
+ if ( $rsync->err ) {
+ die "error uploading to vpopmail\@$machine:$dir/domains/ : ".
+ 'exit status: '. $rsync->status. ', '.
+ 'STDERR: '. join(" / ", $rsync->err). ', '.
+ 'STDOUT: '. join(" / ", $rsync->out);
+ }
+
+ eval "use Net::SSH qw(ssh);";
+ die $@ if $@;
+
+ ssh("vpopmail\@$machine", $restart) if $restart;
+}
+
+1;
+
diff --git a/FS/FS/part_export/www_shellcommands.pm b/FS/FS/part_export/www_shellcommands.pm
new file mode 100644
index 0000000..0e50d60
--- /dev/null
+++ b/FS/FS/part_export/www_shellcommands.pm
@@ -0,0 +1,166 @@
+package FS::part_export::www_shellcommands;
+
+use strict;
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'user' => { label=>'Remote username', default=>'root' },
+ 'useradd' => { label=>'Insert command',
+ default=>'mkdir $homedir/$zone; chown $username $homedir/$zone; ln -s $homedir/$zone /var/www/$zone',
+ },
+ 'userdel' => { label=>'Delete command',
+ default=>'[ -n "$zone" ] && rm -rf /var/www/$zone; rm -rf $homedir/$zone',
+ },
+ 'usermod' => { label=>'Modify command',
+ default=>'[ -n "$old_zone" ] && rm /var/www/$old_zone; [ "$old_zone" != "$new_zone" -a -n "$new_zone" ] && ( mv $old_homedir/$old_zone $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone ); [ "$old_username" != "$new_username" ] && chown -R $new_username $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone',
+ },
+;
+
+%info = (
+ 'svc' => 'svc_www',
+ 'desc' => 'Run remote commands via SSH, for virtual web sites (directory maintenance, FrontPage, ISPMan)',
+ 'options' => \%options,
+ 'notes' => <<'END'
+Run remote commands via SSH, for virtual web sites. You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+ <LI>
+ <INPUT TYPE="button" VALUE="Maintain directories" onClick='
+ this.form.user.value = "root";
+ this.form.useradd.value = "mkdir $homedir/$zone; chown $username $homedir/$zone; ln -s $homedir/$zone /var/www/$zone";
+ this.form.userdel.value = "[ -n \"$zone\" ] && rm -rf /var/www/$zone; rm -rf $homedir/$zone";
+ this.form.usermod.value = "[ -n \"$old_zone\" ] && rm /var/www/$old_zone; [ \"$old_zone\" != \"$new_zone\" -a -n \"$new_zone\" ] && ( mv $old_homedir/$old_zone $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone ); [ \"$old_username\" != \"$new_username\" ] && chown -R $new_username $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="FrontPage extensions" onClick='
+ this.form.user.value = "root";
+ this.form.useradd.value = "/usr/local/frontpage/version5.0/bin/owsadm.exe -o install -p 80 -m $zone -xu $username -xg www-data -s /etc/apache/httpd.conf -u $username -pw $_password";
+ this.form.userdel.value = "/usr/local/frontpage/version5.0/bin/owsadm.exe -o uninstall -p 80 -m $zone -s /etc/apache/httpd.conf";
+ this.form.usermod.value = "";
+ '>
+ <LI>
+ <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+ this.form.user.value = "root";
+ this.form.useradd.value = "/usr/local/ispman/bin/ispman.addvhost -d $domain $bare_zone";
+ this.form.userdel.value = "/usr/local/ispman/bin/ispman.deletevhost -d $domain $bare_zone";
+ this.form.usermod.value = "";
+ '></UL>
+The following variables are available for interpolation (prefixed with
+<code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+ <LI><code>$zone</code> - fully-qualified zone of this virtual host
+ <LI><code>$bare_zone</code> - just the zone of this virtual host, without the domain portion
+ <LI><code>$domain</code> - base domain
+ <LI><code>$username</code>
+ <LI><code>$homedir</code>
+ <LI>All other fields in <a href="../docs/schema.html#svc_www">svc_www</a>
+ are also available.
+</UL>
+END
+);
+
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self) = shift;
+ $self->_export_command('useradd', @_);
+}
+
+sub _export_delete {
+ my($self) = shift;
+ $self->_export_command('userdel', @_);
+}
+
+sub _export_command {
+ my ( $self, $action, $svc_www) = (shift, shift, shift);
+ my $command = $self->option($action);
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${$_} = $svc_www->getfield($_) foreach $svc_www->fields;
+ }
+ my $domain_record = $svc_www->domain_record; # or die ?
+ my $zone = $domain_record->zone; # or die ?
+ my $domain = $domain_record->svc_domain->domain;
+ ( my $bare_zone = $zone ) =~ s/\.$domain$//;
+ my $svc_acct = $svc_www->svc_acct; # or die ?
+ my $username = $svc_acct->username;
+ my $_password = $svc_acct->_password;
+ my $homedir = $svc_acct->dir; # or die ?
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $svc_www->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+sub _export_replace {
+ my($self, $new, $old ) = (shift, shift, shift);
+ my $command = $self->option('usermod');
+
+ #set variable for the command
+ no strict 'vars';
+ {
+ no strict 'refs';
+ ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+ ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+ }
+ my $old_domain_record = $old->domain_record; # or die ?
+ my $old_zone = $old_domain_record->zone; # or die ?
+ my $old_domain = $old_domain_record->svc_domain->domain;
+ ( my $old_bare_zone = $old_zone ) =~ s/\.$old_domain$//;
+ my $old_svc_acct = $old->svc_acct; # or die ?
+ my $old_username = $old_svc_acct->username;
+ my $old_homedir = $old_svc_acct->dir; # or die ?
+
+ my $new_domain_record = $new->domain_record; # or die ?
+ my $new_zone = $new_domain_record->zone; # or die ?
+ my $new_domain = $new_domain_record->svc_domain->domain;
+ ( my $new_bare_zone = $new_zone ) =~ s/\.$new_domain$//;
+ my $new_svc_acct = $new->svc_acct; # or die ?
+ my $new_username = $new_svc_acct->username;
+ #my $new__password = $new_svc_acct->_password;
+ my $new_homedir = $new_svc_acct->dir; # or die ?
+
+ #done setting variables for the command
+
+ $self->shellcommands_queue( $new->svcnum,
+ user => $self->option('user')||'root',
+ host => $self->machine,
+ command => eval(qq("$command")),
+ );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub shellcommands_queue {
+ my( $self, $svcnum ) = (shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::www_shellcommands::ssh_cmd",
+ };
+ $queue->insert( @_ );
+}
+
+sub ssh_cmd { #subroutine, not method
+ use Net::SSH '0.08';
+ &Net::SSH::ssh_cmd( { @_ } );
+}
+
+#sub shellcommands_insert { #subroutine, not method
+#}
+#sub shellcommands_replace { #subroutine, not method
+#}
+#sub shellcommands_delete { #subroutine, not method
+#}
+
diff --git a/FS/FS/part_export_option.pm b/FS/FS/part_export_option.pm
new file mode 100644
index 0000000..33b5e5a
--- /dev/null
+++ b/FS/FS/part_export_option.pm
@@ -0,0 +1,134 @@
+package FS::part_export_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::part_export;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_export_option - Object methods for part_export_option records
+
+=head1 SYNOPSIS
+
+ use FS::part_export_option;
+
+ $record = new FS::part_export_option \%hash;
+ $record = new FS::part_export_option { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_export_option object represents an export option.
+FS::part_export_option inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item optionnum - primary key
+
+=item exportnum - export (see L<FS::part_export>)
+
+=item optionname - option name
+
+=item optionvalue - option value
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new export option. To add the export option to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_export_option'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid export option. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('optionnum')
+ || $self->ut_number('exportnum')
+ || $self->ut_alpha('optionname')
+ || $self->ut_anything('optionvalue')
+ ;
+ return $error if $error;
+
+ return "Unknown exportnum: ". $self->exportnum
+ unless qsearchs('part_export', { 'exportnum' => $self->exportnum } );
+
+ #check options & values?
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Possibly.
+
+=head1 SEE ALSO
+
+L<FS::part_export>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
new file mode 100644
index 0000000..df1f02e
--- /dev/null
+++ b/FS/FS/part_pkg.pm
@@ -0,0 +1,335 @@
+package FS::part_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch dbh dbdef );
+use FS::pkg_svc;
+use FS::agent_type;
+use FS::type_pkgs;
+use FS::Conf;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_pkg - Object methods for part_pkg objects
+
+=head1 SYNOPSIS
+
+ use FS::part_pkg;
+
+ $record = new FS::part_pkg \%hash
+ $record = new FS::part_pkg { 'column' => 'value' };
+
+ $custom_record = $template_record->clone;
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ @pkg_svc = $record->pkg_svc;
+
+ $svcnum = $record->svcpart;
+ $svcnum = $record->svcpart( 'svc_acct' );
+
+=head1 DESCRIPTION
+
+An FS::part_pkg object represents a billing item definition. FS::part_pkg
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgpart - primary key (assigned automatically for new billing item definitions)
+
+=item pkg - Text name of this billing item definition (customer-viewable)
+
+=item comment - Text name of this billing item definition (non-customer-viewable)
+
+=item setup - Setup fee expression
+
+=item freq - Frequency of recurring fee
+
+=item recur - Recurring fee expression
+
+=item setuptax - Setup fee tax exempt flag, empty or `Y'
+
+=item recurtax - Recurring fee tax exempt flag, empty or `Y'
+
+=item taxclass - Tax class flag
+
+=item plan - Price plan
+
+=item plandata - Price plan data
+
+=item disabled - Disabled flag, empty or `Y'
+
+=back
+
+setup and recur are evaluated as Safe perl expressions. You can use numbers
+just as you would normally. More advanced semantics are not yet defined.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new billing item definition. To add the billing item definition to
+the database, see L<"insert">.
+
+=cut
+
+sub table { 'part_pkg'; }
+
+=item clone
+
+An alternate constructor. Creates a new billing item definition by duplicating
+an existing definition. A new pkgpart is assigned and `(CUSTOM) ' is prepended
+to the comment field. To add the billing item definition to the database, see
+L<"insert">.
+
+=cut
+
+sub clone {
+ my $self = shift;
+ my $class = ref($self);
+ my %hash = $self->hash;
+ $hash{'pkgpart'} = '';
+ $hash{'comment'} = "(CUSTOM) ". $hash{'comment'}
+ unless $hash{'comment'} =~ /^\(CUSTOM\) /;
+ #new FS::part_pkg ( \%hash ); # ?
+ new $class ( \%hash ); # ?
+}
+
+=item insert
+
+Adds this billing item definition to the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my $conf = new FS::Conf;
+
+ if ( $conf->exists('agent_defaultpkg') ) {
+ foreach my $agent_type ( qsearch('agent_type', {} ) ) {
+ my $type_pkgs = new FS::type_pkgs({
+ 'typenum' => $agent_type->typenum,
+ 'pkgpart' => $self->pkgpart,
+ });
+ my $error = $type_pkgs->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item delete
+
+Currently unimplemented.
+
+=cut
+
+sub delete {
+ return "Can't (yet?) delete package definitions.";
+# check & make sure the pkgpart isn't in cust_pkg or type_pkgs?
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid billing item definition. If
+there is an error, returns the error, otherwise returns false. Called by the
+insert and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ for (qw(setup recur)) { $self->set($_=>0) if $self->get($_) =~ /^\s*$/; }
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('safe-part_pkg') ) {
+
+ my $error = $self->ut_anything('setup')
+ || $self->ut_anything('recur');
+ return $error if $error;
+
+ my $s = $self->setup;
+
+ $s =~ /^\s*\d*\.?\d*\s*$/
+
+ or $s =~ /^my \$d = \$cust_pkg->bill || \$time; \$d += 86400 \* \s*\d+\s*; \$cust_pkg->bill\(\$d\); \$cust_pkg_mod_flag=1; \s*\d*\.?\d*\s*$/
+
+ or do {
+ #log!
+ return "illegal setup: $s";
+ };
+
+ my $r = $self->recur;
+
+ $r =~ /^\s*\d*\.?\d*\s*$/
+
+ #or $r =~ /^\$sdate += 86400 \* \s*\d+\s*; \s*\d*\.?\d*\s*$/
+
+ or $r =~ /^my \$mnow = \$sdate; my \(\$sec,\$min,\$hour,\$mday,\$mon,\$year\) = \(localtime\(\$sdate\) \)\[0,1,2,3,4,5\]; my \$mstart = timelocal\(0,0,0,1,\$mon,\$year\); my \$mend = timelocal\(0,0,0,1, \$mon == 11 \? 0 : \$mon\+1, \$year\+\(\$mon==11\)\); \$sdate = \$mstart; \( \$part_pkg->freq \- 1 \) \* \d*\.?\d* \/ \$part_pkg\-\>freq \+ \d*\.?\d* \/ \$part_pkg\-\>freq \* \(\$mend\-\$mnow\) \/ \(\$mend\-\$mstart\) ;\s*$/
+
+ or $r =~ /^my \$mnow = \$sdate; my \(\$sec,\$min,\$hour,\$mday,\$mon,\$year\) = \(localtime\(\$sdate\) \)\[0,1,2,3,4,5\]; \$sdate = timelocal\(0,0,0,1,\$mon,\$year\); \s*\d*\.?\d*\s*;\s*$/
+
+ or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\(\$cust_pkg\->cust_main\->referral_cust_main_ncancelled\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
+
+ or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\(\$cust_pkg\->cust_main->referral_cust_pkg\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
+
+ or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\( grep \{ my \$pkgpart = \$_\->pkgpart; grep \{ \$_ == \$pkgpart \} \(\s*(\s*\d+,\s*)*\s*\) \} \$cust_pkg\->cust_main->referral_cust_pkg\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
+
+ or $r =~ /^my \$hours = \$cust_pkg\->seconds_since\(\$cust_pkg\->bill \|\| 0\) \/ 3600 \- \s*\d*\.?\d*\s*; \$hours = 0 if \$hours < 0; \s*\d*\.?\d*\s* \+ \s*\d*\.?\d*\s* \* \$hours;\s*$/
+
+ or $r =~ /^my \$min = \$cust_pkg\->seconds_since\(\$cust_pkg\->bill \|\| 0\) \/ 60 \- \s*\d*\.?\d*\s*; \$min = 0 if \$min < 0; \s*\d*\.?\d*\s* \+ \s*\d*\.?\d*\s* \* \$min;\s*$/
+
+ or $r =~ /^my \$last_bill = \$cust_pkg\->last_bill; my \$hours = \$cust_pkg\->seconds_since_sqlradacct\(\$last_bill, \$sdate \) \/ 3600 - \s*\d\.?\d*\s*; \$hours = 0 if \$hours < 0; my \$input = \$cust_pkg\->attribute_since_sqlradacct\(\$last_bill, \$sdate, "AcctInputOctets" \) \/ 1048576; my \$output = \$cust_pkg\->attribute_since_sqlradacct\(\$last_bill, \$sdate, "AcctOutputOctets" \) \/ 1048576; my \$total = \$input \+ \$output \- \s*\d\.?\d*\s*; \$total = 0 if \$total < 0; my \$input = \$input - \s*\d\.?\d*\s*; \$input = 0 if \$input < 0; my \$output = \$output - \s*\d\.?\d*\s*; \$output = 0 if \$output < 0; \s*\d\.?\d*\s* \+ \s*\d\.?\d*\s* \* \$hours \+ \s*\d\.?\d*\s* \* \$input \+ \s*\d\.?\d*\s* \* \$output \+ \s*\d\.?\d*\s* \* \$total *;\s*$/
+
+ or do {
+ #log!
+ return "illegal recur: $r";
+ };
+
+ }
+
+ if ( $self->dbdef_table->column('freq')->type =~ /(int)/i ) {
+ my $error = $self->ut_number('freq');
+ return $error if $error;
+ } else {
+ $self->freq =~ /^(\d+[dw]?)$/
+ or return "Illegal or empty freq: ". $self->freq;
+ $self->freq($1);
+ }
+
+ $self->ut_numbern('pkgpart')
+ || $self->ut_text('pkg')
+ || $self->ut_text('comment')
+ || $self->ut_anything('setup')
+ || $self->ut_anything('recur')
+ || $self->ut_alphan('plan')
+ || $self->ut_anything('plandata')
+ || $self->ut_enum('setuptax', [ '', 'Y' ] )
+ || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->ut_textn('taxclass')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ || $self->SUPER::check
+ ;
+}
+
+=item pkg_svc
+
+Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package
+definition (with non-zero quantity).
+
+=cut
+
+sub pkg_svc {
+ my $self = shift;
+ #sort { $b->primary cmp $a->primary }
+ grep { $_->quantity }
+ qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item svcpart [ SVCDB ]
+
+Returns the svcpart of the primary service definition (see L<FS::part_svc>)
+associated with this billing item definition (see L<FS::pkg_svc>). Returns
+false if there not a primary service definition or exactly one service
+definition with quantity 1, or if SVCDB is specified and does not match the
+svcdb of the service definition,
+
+=cut
+
+sub svcpart {
+ my $self = shift;
+ my $svcdb = scalar(@_) ? shift : '';
+ my @svcdb_pkg_svc =
+ grep { ( $svcdb eq $_->part_svc->svcdb || !$svcdb ) } $self->pkg_svc;
+ my @pkg_svc = ();
+ @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc
+ if dbdef->table('pkg_svc')->column('primary_svc');
+ @pkg_svc = grep {$_->quantity == 1 } @svcdb_pkg_svc
+ unless @pkg_svc;
+ return '' if scalar(@pkg_svc) != 1;
+ $pkg_svc[0]->svcpart;
+}
+
+=item payby
+
+Returns a list of the acceptable payment types for this package. Eventually
+this should come out of a database table and be editable, but currently has the
+following logic instead;
+
+If the package has B<0> setup and B<0> recur, the single item B<BILL> is
+returned, otherwise, the single item B<CARD> is returned.
+
+(CHEK? LEC? Probably shouldn't accept those by default, prone to abuse)
+
+=cut
+
+sub payby {
+ my $self = shift;
+ #if ( $self->setup == 0 && $self->recur == 0 ) {
+ if ( $self->setup =~ /^\s*0+(\.0*)?\s*$/
+ && $self->recur =~ /^\s*0+(\.0*)?\s*$/ ) {
+ ( 'BILL' );
+ } else {
+ ( 'CARD' );
+ }
+}
+
+=back
+
+=head1 BUGS
+
+The delete method is unimplemented.
+
+setup and recur semantics are not yet defined (and are implemented in
+FS::cust_bill. hmm.).
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_pkg>, L<FS::type_pkgs>, L<FS::pkg_svc>, L<Safe>.
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pop_local.pm b/FS/FS/part_pop_local.pm
new file mode 100644
index 0000000..f7d5eac
--- /dev/null
+++ b/FS/FS/part_pop_local.pm
@@ -0,0 +1,117 @@
+package FS::part_pop_local;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record; # qw( qsearchs );
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_pop_local - Object methods for part_pop_local records
+
+=head1 SYNOPSIS
+
+ use FS::part_pop_local;
+
+ $record = new FS::part_pop_local \%hash;
+ $record = new FS::part_pop_local { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_pop_local object represents a local call area. Each
+FS::part_pop_local record maps a NPA/NXX (area code and exchange) to the POP
+(see L<FS::svc_acct_pop>) which is a local call. FS::part_pop_local inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item localnum - primary key (assigned automatically for new accounts)
+
+=item popnum - see L<FS::svc_acct_pop>
+
+=item city
+
+=item state
+
+=item npa - area code
+
+=item nxx - exchange
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new point of presence (if only it were that easy!). To add the
+point of presence to the database, see L<"insert">.
+
+=cut
+
+sub table { 'part_pop_local'; }
+
+=item insert
+
+Adds this point of presence to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Removes this point of presence from the database.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid point of presence. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('localnum')
+ or $self->ut_numbern('popnum')
+ or $self->ut_text('city')
+ or $self->ut_text('state')
+ or $self->ut_number('npa')
+ or $self->ut_number('nxx')
+ or $self->SUPER::check
+ ;
+
+}
+
+=back
+
+=head1 VERSION
+
+$Id: part_pop_local.pm,v 1.2 2003-08-05 00:20:44 khoff Exp $
+
+=head1 BUGS
+
+US/CA-centric.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::svc_acct_pop>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm
new file mode 100644
index 0000000..c0858c0
--- /dev/null
+++ b/FS/FS/part_referral.pm
@@ -0,0 +1,126 @@
+package FS::part_referral;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_referral - Object methods for part_referral objects
+
+=head1 SYNOPSIS
+
+ use FS::part_referral;
+
+ $record = new FS::part_referral \%hash
+ $record = new FS::part_referral { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_referral represents a advertising source - where a customer heard
+of your services. This can be used to track the effectiveness of a particular
+piece of advertising, for example. FS::part_referral inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item refnum - primary key (assigned automatically for new referrals)
+
+=item referral - Text name of this advertising source
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=back
+
+=head1 NOTE
+
+These were called B<referrals> before version 1.4.0 - the name was changed
+so as not to be confused with the new customer-to-customer referrals.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new advertising source. To add the referral to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'part_referral'; }
+
+=item insert
+
+Adds this advertising source to the database. If there is an error, returns
+the error, otherwise returns false.
+
+=item delete
+
+Currently unimplemented.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ return "Can't (yet?) delete part_referral records";
+ #need to make sure no customers have this referral!
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid advertising source. If there is
+an error, returns the error, otherwise returns false. Called by the insert and
+replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error = $self->ut_numbern('refnum')
+ || $self->ut_text('referral')
+ ;
+ return $error if $error;
+
+ if ( $self->dbdef_table->column('disabled') ) {
+ $error = $self->ut_enum('disabled', [ '', 'Y' ] );
+ return $error if $error;
+ }
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+The delete method is unimplemented.
+
+`Advertising source'. Yes, it's a sucky name. The only other ones I could
+come up with were "Marketing channel" and "Heard Abouts" and those are
+definately both worse.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_main>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
new file mode 100644
index 0000000..e7f205d
--- /dev/null
+++ b/FS/FS/part_svc.pm
@@ -0,0 +1,418 @@
+package FS::part_svc;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs fields dbh );
+use FS::part_svc_column;
+use FS::part_export;
+use FS::export_svc;
+use FS::cust_svc;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_svc - Object methods for part_svc objects
+
+=head1 SYNOPSIS
+
+ use FS::part_svc;
+
+ $record = new FS::part_svc \%hash
+ $record = new FS::part_svc { 'column' => 'value' };
+
+ $error = $record->insert;
+ $error = $record->insert( [ 'pseudofield' ] );
+ $error = $record->insert( [ 'pseudofield' ], \%exportnums );
+
+ $error = $new_record->replace($old_record);
+ $error = $new_record->replace($old_record, '1.3-COMPAT', [ 'pseudofield' ] );
+ $error = $new_record->replace($old_record, '1.3-COMPAT', [ 'pseudofield' ], \%exportnums );
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc represents a service definition. FS::part_svc inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item svcpart - primary key (assigned automatically for new service definitions)
+
+=item svc - text name of this service definition
+
+=item svcdb - table used for this service. See L<FS::svc_acct>,
+L<FS::svc_domain>, and L<FS::svc_forward>, among others.
+
+=item disabled - Disabled flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new service definition. To add the service definition to the
+database, see L<"insert">.
+
+=cut
+
+sub table { 'part_svc'; }
+
+=item insert [ EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF ] ]
+
+Adds this service definition to the database. If there is an error, returns
+the error, otherwise returns false.
+
+The following pseudo-fields may be defined, and will be maintained in
+the part_svc_column table appropriately (see L<FS::part_svc_column>).
+
+=over 4
+
+=item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
+
+=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed. For virtual fields, can also be 'X' for excluded.
+
+=back
+
+If you want to add part_svc_column records for fields that do not exist as
+(real or virtual) fields in the I<svcdb> table, make sure to list then in
+EXTRA_FIELDS_ARRAYREF also.
+
+If EXPORTNUMS_HASHREF is specified (keys are exportnums and values are
+boolean), the appopriate export_svc records will be inserted.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my @fields = ();
+ my @exportnums = ();
+ @fields = @{shift(@_)} if @_;
+ if ( @_ ) {
+ my $exportnums = shift;
+ @exportnums = grep $exportnums->{$_}, keys %$exportnums;
+ }
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ # add part_svc_column records
+
+ my $svcdb = $self->svcdb;
+# my @rows = map { /^${svcdb}__(.*)$/; $1 }
+# grep ! /_flag$/,
+# grep /^${svcdb}__/,
+# fields('part_svc');
+ foreach my $field (
+ grep { $_ ne 'svcnum'
+ && defined( $self->getfield($svcdb.'__'.$_.'_flag') )
+ } (fields($svcdb), @fields)
+ ) {
+ my $part_svc_column = $self->part_svc_column($field);
+ my $previous = qsearchs('part_svc_column', {
+ 'svcpart' => $self->svcpart,
+ 'columnname' => $field,
+ } );
+
+ my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
+ if ( uc($flag) =~ /^([DFX])$/ ) {
+ $part_svc_column->setfield('columnflag', $1);
+ $part_svc_column->setfield('columnvalue',
+ $self->getfield($svcdb.'__'.$field)
+ );
+ if ( $previous ) {
+ $error = $part_svc_column->replace($previous);
+ } else {
+ $error = $part_svc_column->insert;
+ }
+ } else {
+ $error = $previous ? $previous->delete : '';
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ }
+
+ # add export_svc records
+
+ foreach my $exportnum ( @exportnums ) {
+ my $export_svc = new FS::export_svc ( {
+ 'exportnum' => $exportnum,
+ 'svcpart' => $self->svcpart,
+ } );
+ $error = $export_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item delete
+
+Currently unimplemented. Set the "disabled" field instead.
+
+=cut
+
+sub delete {
+ return "Can't (yet?) delete service definitions.";
+# check & make sure the svcpart isn't in cust_svc or pkg_svc (in any packages)?
+}
+
+=item replace OLD_RECORD [ '1.3-COMPAT' [ , EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF ] ] ]
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+TODOC: 1.3-COMPAT
+
+TODOC: EXTRA_FIELDS_ARRAYREF (same as insert method)
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ return "Can't change svcdb for an existing service definition!"
+ unless $old->svcdb eq $new->svcdb;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $new->SUPER::replace( $old );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( @_ && $_[0] eq '1.3-COMPAT' ) {
+ shift;
+ my @fields = ();
+ @fields = @{shift(@_)} if @_;
+ my $exportnums = @_ ? shift : '';
+
+ # maintain part_svc_column records
+
+ my $svcdb = $new->svcdb;
+ foreach my $field (
+ grep { $_ ne 'svcnum'
+ && defined( $new->getfield($svcdb.'__'.$_.'_flag') )
+ } (fields($svcdb),@fields)
+ ) {
+ my $part_svc_column = $new->part_svc_column($field);
+ my $previous = qsearchs('part_svc_column', {
+ 'svcpart' => $new->svcpart,
+ 'columnname' => $field,
+ } );
+
+ my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
+ if ( uc($flag) =~ /^([DFX])$/ ) {
+ $part_svc_column->setfield('columnflag', $1);
+ $part_svc_column->setfield('columnvalue',
+ $new->getfield($svcdb.'__'.$field)
+ );
+ if ( $previous ) {
+ $error = $part_svc_column->replace($previous);
+ } else {
+ $error = $part_svc_column->insert;
+ }
+ } else {
+ $error = $previous ? $previous->delete : '';
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ # maintain export_svc records
+
+ if ( $exportnums ) {
+
+ #false laziness w/ edit/process/agent_type.cgi
+ foreach my $part_export ( qsearch('part_export', {}) ) {
+ my $exportnum = $part_export->exportnum;
+ my $hashref = {
+ 'exportnum' => $exportnum,
+ 'svcpart' => $new->svcpart,
+ };
+ my $export_svc = qsearchs('export_svc', $hashref);
+
+ if ( $export_svc && ! $exportnums->{$exportnum} ) {
+ $error = $export_svc->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ } elsif ( ! $export_svc && $exportnums->{$exportnum} ) {
+ $export_svc = new FS::export_svc ( $hashref );
+ $error = $export_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ }
+
+ } else {
+ $dbh->rollback if $oldAutoCommit;
+ return 'non-1.3-COMPAT interface not yet written';
+ #not yet implemented
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item check
+
+Checks all fields to make sure this is a valid service definition. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $recref = $self->hashref;
+
+ my $error;
+ $error=
+ $self->ut_numbern('svcpart')
+ || $self->ut_text('svc')
+ || $self->ut_alpha('svcdb')
+ || $self->ut_enum('disabled', [ '', 'Y' ] )
+ ;
+ return $error if $error;
+
+ my @fields = eval { fields( $recref->{svcdb} ) }; #might die
+ return "Unknown svcdb!" unless @fields;
+
+ $self->SUPER::check;
+}
+
+=item part_svc_column COLUMNNAME
+
+Returns the part_svc_column object (see L<FS::part_svc_column>) for the given
+COLUMNNAME, or a new part_svc_column object if none exists.
+
+=cut
+
+sub part_svc_column {
+ my( $self, $columnname) = @_;
+ $self->svcpart &&
+ qsearchs('part_svc_column', {
+ 'svcpart' => $self->svcpart,
+ 'columnname' => $columnname,
+ }
+ ) or new FS::part_svc_column {
+ 'svcpart' => $self->svcpart,
+ 'columnname' => $columnname,
+ };
+}
+
+=item all_part_svc_column
+
+=cut
+
+sub all_part_svc_column {
+ my $self = shift;
+ qsearch('part_svc_column', { 'svcpart' => $self->svcpart } );
+}
+
+=item part_export [ EXPORTTYPE ]
+
+Returns all exports (see L<FS::part_export>) for this service, or, if an
+export type is specified, only returns exports of the given type.
+
+=cut
+
+sub part_export {
+ my $self = shift;
+ my %search;
+ $search{'exporttype'} = shift if @_;
+ map { qsearchs('part_export', { 'exportnum' => $_->exportnum, %search } ) }
+ qsearch('export_svc', { 'svcpart' => $self->svcpart } );
+}
+
+=item cust_svc
+
+Returns a list of associated FS::cust_svc records.
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ qsearch('cust_svc', { 'svcpart' => $self->svcpart } );
+}
+
+=item svc_x
+
+Returns a list of associated FS::svc_* records.
+
+=cut
+
+sub svc_x {
+ my $self = shift;
+ map { $_->svc_x } $self->cust_svc;
+}
+
+=back
+
+=head1 BUGS
+
+Delete is unimplemented.
+
+The list of svc_* tables is hardcoded. When svc_acct_pop is renamed, this
+should be fixed.
+
+all_part_svc_column method should be documented
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::part_svc_column>, L<FS::part_pkg>, L<FS::pkg_svc>,
+L<FS::cust_svc>, L<FS::svc_acct>, L<FS::svc_forward>, L<FS::svc_domain>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm
new file mode 100644
index 0000000..885155b
--- /dev/null
+++ b/FS/FS/part_svc_column.pm
@@ -0,0 +1,118 @@
+package FS::part_svc_column;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( fields );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_svc_column - Object methods for part_svc_column objects
+
+=head1 SYNOPSIS
+
+ use FS::part_svc_column;
+
+ $record = new FS::part_svc_column \%hash
+ $record = new FS::part_svc_column { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc_column record represents a service definition column
+constraint. FS::part_svc_column inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item columnnum - primary key (assigned automatcially for new records)
+
+=item svcpart - service definition (see L<FS::part_svc>)
+
+=item columnname - column name in part_svc.svcdb table
+
+=item columnvalue - default or fixed value for the column
+
+=item columnflag - null, D, F, X (virtual fields)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new column constraint. To add the column constraint to the database, see L<"insert">.
+
+=cut
+
+sub table { 'part_svc_column'; }
+
+=item insert
+
+Adds this service definition to the database. If there is an error, returns
+the error, otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('columnnum')
+ || $self->ut_number('svcpart')
+ || $self->ut_alpha('columnname')
+ || $self->ut_anything('columnvalue')
+ ;
+ return $error if $error;
+
+ $self->columnflag =~ /^([DFX])$/
+ or return "illegal columnflag ". $self->columnflag;
+ $self->columnflag(uc($1));
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 VERSION
+
+$Id: part_svc_column.pm,v 1.2 2003-08-05 00:20:44 khoff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::part_svc>, L<FS::part_pkg>, L<FS::pkg_svc>,
+L<FS::cust_svc>, L<FS::svc_acct>, L<FS::svc_forward>, L<FS::svc_domain>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_svc_router.pm b/FS/FS/part_svc_router.pm
new file mode 100755
index 0000000..0b23ab5
--- /dev/null
+++ b/FS/FS/part_svc_router.pm
@@ -0,0 +1,32 @@
+package FS::part_svc_router;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw(qsearchs);
+use FS::router;
+use FS::part_svc;
+
+@ISA = qw(FS::Record);
+
+sub table { 'part_svc_router'; }
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
+ || $self->ut_foreign_key('routernum', 'router', 'routernum');
+ return $error if $error;
+ ''; #no error
+}
+
+sub router {
+ my $self = shift;
+ return qsearchs('router', { routernum => $self->routernum });
+}
+
+sub part_svc {
+ my $self = shift;
+ return qsearchs('part_svc', { svcpart => $self->svcpart });
+}
+
+1;
diff --git a/FS/FS/part_virtual_field.pm b/FS/FS/part_virtual_field.pm
new file mode 100755
index 0000000..03c34cc
--- /dev/null
+++ b/FS/FS/part_virtual_field.pm
@@ -0,0 +1,303 @@
+package FS::part_virtual_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch dbdef );
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_virtual_field - Object methods for part_virtual_field records
+
+=head1 SYNOPSIS
+
+ use FS::part_virtual_field;
+
+ $record = new FS::part_virtual_field \%hash;
+ $record = new FS::part_virtual_field { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_virtual_field object represents the definition of a virtual field
+(see the BACKGROUND section). FS::part_virtual_field contains the name and
+base table of the field, as well as validation rules and UI hints about the
+display of the field. The actual data is stored in FS::virtual_field; see
+its manpage for details.
+
+FS::part_virtual_field inherits from FS::Record. The following fields are
+currently supported:
+
+=over 2
+
+=item vfieldpart - primary key (assigned automatically)
+
+=item name - name of the field
+
+=item dbtable - table for which this virtual field is defined
+
+=item check_block - Perl code to validate/normalize data
+
+=item list_source - Perl code to generate a list of values (UI hint)
+
+=item length - expected length of the value (UI hint)
+
+=item label - descriptive label for the field (UI hint)
+
+=item sequence - sort key (UI hint; unimplemented)
+
+=back
+
+=head1 BACKGROUND
+
+"Form is none other than emptiness,
+ and emptiness is none other than form."
+-- Heart Sutra
+
+The virtual field mechanism allows site admins to make trivial changes to
+the Freeside database schema without modifying the code. Specifically, the
+user can add custom-defined 'fields' to the set of data tracked by Freeside
+about objects such as customers and services. These fields are not associated
+with any logic in the core Freeside system, but may be referenced in peripheral
+code such as exports, price calculations, or alternate interfaces, or may just
+be stored in the database for future reference.
+
+This system was originally devised for svc_broadband, which (by necessity)
+comprises such a wide range of access technologies that no static set of fields
+could contain all the information needed by the exports. In an appalling
+display of False Laziness, a parallel mechanism was implemented for the
+router table, to store properties such as passwords to configure routers.
+
+The original system treated svc_broadband custom fields (sb_fields) as records
+in a completely separate table. Any code that accessed or manipulated these
+fields had to be aware that they were I<not> fields in svc_broadband, but
+records in sb_field. For example, code that inserted a svc_broadband with
+several custom fields had to create an FS::svc_broadband object, call its
+insert() method, and then create several FS::sb_field objects and call I<their>
+insert() methods.
+
+This created a problem for exports. The insert method on any FS::svc_Common
+object (including svc_broadband) automatically triggers exports after the
+record has been inserted. However, at this point, the sb_fields had not yet
+been inserted, so the export could not rely on their presence, which was the
+original purpose of sb_fields.
+
+Hence the new system. Virtual fields are appended to the field list of every
+record at the FS::Record level, whether the object is created ex nihilo with
+new() or fetched with qsearch(). The fields() method now returns a list of
+both real and virtual fields. The insert(), replace(), and delete() methods
+now update both the base table and the virtual fields, in a single transaction.
+
+A new method is provided, virtual_fields(), which gives only the virtual
+fields. UI code that dynamically generates form widgets to edit virtual field
+data should use this to figure out what fields are defined. (See below.)
+
+Subclasses may override virtual_fields() to restrict the set of virtual
+fields available. Some discipline and sanity on the part of the programmer
+are required; in particular, this function should probably not depend on any
+fields in the record other than the primary key, since the others may change
+after the object is instantiated. (Making it depend on I<virtual> fields is
+just asking for pain.) One use of this is seen in FS::svc_Common; another
+possibility is field-level access control based on FS::UID::getotaker().
+
+As a trivial case, a subclass may opt out of supporting virtual fields with
+the following code:
+
+sub virtual_fields { () }
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'part_virtual_field'; }
+sub virtual_fields { () }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+If there is an error, returns the error, otherwise returns false.
+Called by the insert and replace methods.
+
+=back
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error = $self->ut_text('name') ||
+ $self->ut_text('dbtable') ||
+ $self->ut_number('length')
+ ;
+ return $error if $error;
+
+ # Make sure it's a real table with a numeric primary key
+ my ($table, $pkey);
+ if($table = $FS::Record::dbdef->table($self->dbtable)) {
+ if($pkey = $table->primary_key) {
+ if($table->column($pkey)->type =~ /int/i) {
+ # this is what it should be
+ } else {
+ $error = "$table.$pkey is not an integer";
+ }
+ } else {
+ $error = "$table does not have a single-field primary key";
+ }
+ } else {
+ $error = "$table does not exist in the schema";
+ }
+ return $error if $error;
+
+ # Possibly some sanity checks for check_block and list_source?
+
+ $self->SUPER::check;
+}
+
+=item list
+
+Evaluates list_source.
+
+=cut
+
+sub list {
+ my $self = shift;
+ return () unless $self->list_source;
+
+ my @opts = eval($self->list_source);
+ if($@) {
+ warn $@;
+ return ();
+ } else {
+ return @opts;
+ }
+}
+
+=item widget UI_TYPE MODE [ VALUE ]
+
+Generates UI code for a widget suitable for editing/viewing the field, based on
+list_source and length.
+
+The only UI_TYPE currently supported is 'HTML', and the only MODE is 'view'.
+Others will be added later.
+
+In HTML, all widgets are assumed to be table rows. View widgets look like
+<TR><TD ALIGN="right">Label</TD><TD BGCOLOR="#ffffff">Value</TD></TR>
+
+(Most of the display style stuff, such as the colors, should probably go into
+a separate module specific to the UI. That can wait, though. The API for
+this function won't change.)
+
+VALUE (optional) is the current value of the field.
+
+=cut
+
+sub widget {
+ my $self = shift;
+ my ($ui_type, $mode, $value) = @_;
+ my $text;
+ my $label = $self->label || $self->name;
+
+ if ($ui_type eq 'HTML') {
+ if ($mode eq 'view') {
+ $text = q!<TR><TD ALIGN="right">! . $label .
+ q!</TD><TD BGCOLOR="#ffffff">! . $value .
+ q!</TD></TR>! . "\n";
+ } elsif ($mode eq 'edit') {
+ $text = q!<TR><TD ALIGN="right">! . $label .
+ q!</TD><TD>!;
+ if ($self->list_source) {
+ $text .= q!<SELECT NAME="! . $self->name .
+ q!" SIZE=1>! . "\n";
+ foreach ($self->list) {
+ $text .= q!<OPTION VALUE="! . $_ . q!"!;
+ $text .= ' SELECTED' if ($_ eq $value);
+ $text .= '>' . $_ . '</OPTION>' . "\n";
+ }
+ } else {
+ $text .= q!<INPUT NAME="! . $self->name .
+ q!" VALUE="! . $value . q!"!;
+ if ($self->length) {
+ $text .= q! SIZE="! . $self->length . q!"!;
+ }
+ $text .= '>';
+ }
+ $text .= q!</TD></TR>! . "\n";
+ } else {
+ return '';
+ }
+ } else {
+ return '';
+ }
+ return $text;
+}
+
+=head1 VERSION
+
+$Id: part_virtual_field.pm,v 1.2 2003-08-05 00:20:45 khoff Exp $
+
+=head1 NOTES
+
+=head2 Semantics of check_block:
+
+This has been changed from the sb_field implementation to make check_blocks
+simpler and more natural to Perl programmers who work on things other than
+Freeside.
+
+The check_block is eval'd with the (proposed) new value of the field in $_,
+and the object to be updated in $self. Its return value is ignored. The
+check_block may change the value of $_ to override the proposed value, or
+call die() (with an appropriate error message) to reject the update entirely;
+the error string will be returned as the output of the check() method.
+
+This makes check_blocks like
+
+C<s/foo/bar/>
+
+do what you expect.
+
+The check_block is expected NOT to do anything freaky to $self, like modifying
+other fields or calling $self->check(). You have been warned.
+
+(FIXME: Rewrite some of the warnings from part_sb_field and insert here.)
+
+=head1 BUGS
+
+None. It's absolutely falwless.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::virtual_field>
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm
new file mode 100644
index 0000000..ea52176
--- /dev/null
+++ b/FS/FS/pkg_svc.pm
@@ -0,0 +1,155 @@
+package FS::pkg_svc;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::part_pkg;
+use FS::part_svc;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::pkg_svc - Object methods for pkg_svc records
+
+=head1 SYNOPSIS
+
+ use FS::pkg_svc;
+
+ $record = new FS::pkg_svc \%hash;
+ $record = new FS::pkg_svc { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $part_pkg = $record->part_pkg;
+
+ $part_svc = $record->part_svc;
+
+=head1 DESCRIPTION
+
+An FS::pkg_svc record links a billing item definition (see L<FS::part_pkg>) to
+a service definition (see L<FS::part_svc>). FS::pkg_svc inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item pkgpart - Billing item definition (see L<FS::part_pkg>)
+
+=item svcpart - Service definition (see L<FS::part_svc>)
+
+=item quantity - Quantity of this service definition that this billing item
+definition includes
+
+=item primary_svc - primary flag, empty or 'Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'pkg_svc'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ return "Can't change pkgpart!" if $old->pkgpart != $new->pkgpart;
+ return "Can't change svcpart!" if $old->svcpart != $new->svcpart;
+
+ $new->SUPER::replace($old);
+}
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error;
+ $error =
+ $self->ut_number('pkgpart')
+ || $self->ut_number('svcpart')
+ || $self->ut_number('quantity')
+ ;
+ return $error if $error;
+
+ return "Unknown pkgpart!" unless $self->part_pkg;
+ return "Unknown svcpart!" unless $self->part_svc;
+
+ if ( $self->dbdef_table->column('primary_svc') ) {
+ $error = $self->ut_enum('primary_svc', [ '', 'Y' ] );
+ return $error if $error;
+ }
+
+ $self->SUPER::check;
+}
+
+=item part_pkg
+
+Returns the FS::part_pkg object (see L<FS::part_pkg>).
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item part_svc
+
+Returns the FS::part_svc object (see L<FS::part_svc>).
+
+=cut
+
+sub part_svc {
+ my $self = shift;
+ qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::part_pkg>, L<FS::part_svc>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/port.pm b/FS/FS/port.pm
new file mode 100644
index 0000000..620030a
--- /dev/null
+++ b/FS/FS/port.pm
@@ -0,0 +1,160 @@
+package FS::port;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::nas;
+use FS::session;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::port - Object methods for port records
+
+=head1 SYNOPSIS
+
+ use FS::port;
+
+ $record = new FS::port \%hash;
+ $record = new FS::port { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $session = $port->session;
+
+=head1 DESCRIPTION
+
+An FS::port object represents an individual port on a NAS. FS::port inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item portnum - primary key
+
+=item ip - IP address of this port
+
+=item nasport - port number on the NAS
+
+=item nasnum - NAS this port is on - see L<FS::nas>
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new port. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'port'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('portnum')
+ || $self->ut_ipn('ip')
+ || $self->ut_numbern('nasport')
+ || $self->ut_number('nasnum');
+ ;
+ return $error if $error;
+ return "Either ip or nasport must be specified"
+ unless $self->ip || $self->nasport;
+ return "Unknown nasnum"
+ unless qsearchs('nas', { 'nasnum' => $self->nasnum } );
+ $self->SUPER::check;
+}
+
+=item session
+
+Returns the currently open session on this port, or if no session is currently
+open, the most recent session. See L<FS::session>.
+
+=cut
+
+sub session {
+ my $self = shift;
+ qsearchs('session', { 'portnum' => $self->portnum }, '*',
+ 'ORDER BY login DESC LIMIT 1' );
+}
+
+=back
+
+=head1 VERSION
+
+$Id: port.pm,v 1.6 2003-08-05 00:20:45 khoff Exp $
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+The session method won't deal well if you have multiple open sessions on a
+port, for example if your RADIUS server drops B<stop> records. Suggestions for
+how to deal with this sort of lossage welcome; should we close the session
+when we get a new session on that port? Tag it as invalid somehow? Close it
+one second after it was opened? *sigh* Maybe FS::session shouldn't let you
+create overlapping sessions, at least folks will find out their logging is
+dropping records.
+
+If you think the above refers multiple user logins you need to read the
+manpages again.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/prepay_credit.pm b/FS/FS/prepay_credit.pm
new file mode 100644
index 0000000..a9d26d1
--- /dev/null
+++ b/FS/FS/prepay_credit.pm
@@ -0,0 +1,127 @@
+package FS::prepay_credit;
+
+use strict;
+use vars qw( @ISA );
+#use FS::Record qw( qsearch qsearchs );
+use FS::Record qw();
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::prepay_credit - Object methods for prepay_credit records
+
+=head1 SYNOPSIS
+
+ use FS::prepay_credit;
+
+ $record = new FS::prepay_credit \%hash;
+ $record = new FS::prepay_credit {
+ 'identifier' => '4198123455512121'
+ 'amount' => '19.95',
+ };
+
+ $record = new FS::prepay_credit {
+ 'identifier' => '4198123455512121'
+ 'seconds' => '7200',
+ };
+
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::table_name object represents an pre--paid credit, such as a pre-paid
+"calling card". FS::prepay_credit inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item field - description
+
+=item identifier - identifier entered by the user to receive the credit
+
+=item amount - amount of the credit
+
+=item seconds - time amount of credit (see L<FS::svc_acct/seconds>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new pre-paid credit. To add the example to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'prepay_credit'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid pre-paid credit. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $identifier = $self->identifier;
+ $identifier =~ s/\W//g; #anything else would just confuse things
+ $self->identifier($identifier);
+
+ $self->ut_numbern('prepaynum')
+ || $self->ut_alpha('identifier')
+ || $self->ut_money('amount')
+ || $self->utnumbern('seconds')
+ || $self->SUPER::check
+ ;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_acct>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm
new file mode 100644
index 0000000..8396fc9
--- /dev/null
+++ b/FS/FS/queue.pm
@@ -0,0 +1,438 @@
+package FS::queue;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $conf $jobnums);
+use Exporter;
+use FS::UID;
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs dbh );
+#use FS::queue;
+use FS::queue_arg;
+use FS::queue_depend;
+use FS::cust_svc;
+
+@ISA = qw(FS::Record);
+@EXPORT_OK = qw( joblisting );
+
+$DEBUG = 0;
+#$DEBUG = 1;
+
+$FS::UID::callback{'FS::queue'} = sub {
+ $conf = new FS::Conf;
+};
+
+$jobnums = '';
+
+=head1 NAME
+
+FS::queue - Object methods for queue records
+
+=head1 SYNOPSIS
+
+ use FS::queue;
+
+ $record = new FS::queue \%hash;
+ $record = new FS::queue { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue object represents an queued job. FS::queue inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item jobnum - primary key
+
+=item job - fully-qualified subroutine name
+
+=item status - job status
+
+=item statustext - freeform text status message
+
+=item _date - UNIX timestamp
+
+=item svcnum - optional link to service (see L<FS::cust_svc>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new job. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'queue'; }
+
+=item insert [ ARGUMENT, ARGUMENT... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If any arguments are supplied, a queue_arg record for each argument is also
+created (see L<FS::queue_arg>).
+
+=cut
+
+#false laziness w/part_export.pm
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $arg ( @_ ) {
+ my $queue_arg = new FS::queue_arg ( {
+ 'jobnum' => $self->jobnum,
+ 'arg' => $arg,
+ } );
+ $error = $queue_arg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ if ( $jobnums ) {
+ warn "jobnums global is active: $jobnums\n" if $DEBUG;
+ push @$jobnums, $self->jobnum;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database. Any corresponding queue_arg records are
+deleted as well
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
+ push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $del ( @del ) {
+ $error = $del->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid job. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('jobnum')
+ || $self->ut_anything('job')
+ || $self->ut_numbern('_date')
+ || $self->ut_enum('status',['', qw( new locked failed )])
+ || $self->ut_anything('statustext')
+ || $self->ut_numbern('svcnum')
+ ;
+ return $error if $error;
+
+ $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
+ $self->svcnum('') if $error;
+
+ $self->status('new') unless $self->status;
+ $self->_date(time) unless $self->_date;
+
+ $self->SUPER::check;
+}
+
+=item args
+
+Returns a list of the arguments associated with this job.
+
+=cut
+
+sub args {
+ my $self = shift;
+ map $_->arg, qsearch( 'queue_arg',
+ { 'jobnum' => $self->jobnum },
+ '',
+ 'ORDER BY argnum'
+ );
+}
+
+=item cust_svc
+
+Returns the FS::cust_svc object associated with this job, if any.
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item queue_depend
+
+Returns the FS::queue_depend objects associated with this job, if any.
+(Dependancies that must complete before this job can be run).
+
+=cut
+
+sub queue_depend {
+ my $self = shift;
+ qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
+}
+
+=item depend_insert OTHER_JOBNUM
+
+Inserts a dependancy for this job - it will not be run until the other job
+specified completes. If there is an error, returns the error, otherwise
+returns false.
+
+When using job dependancies, you should wrap the insertion of all relevant jobs
+in a database transaction.
+
+=cut
+
+sub depend_insert {
+ my($self, $other_jobnum) = @_;
+ my $queue_depend = new FS::queue_depend ( {
+ 'jobnum' => $self->jobnum,
+ 'depend_jobnum' => $other_jobnum,
+ } );
+ $queue_depend->insert;
+}
+
+=item queue_depended
+
+Returns the FS::queue_depend objects that associate other jobs with this job,
+if any. (The jobs that are waiting for this job to complete before they can
+run).
+
+=cut
+
+sub queue_depended {
+ my $self = shift;
+ qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
+}
+
+=item depended_delete
+
+Deletes the other queued jobs (FS::queue objects) that are waiting for this
+job, if any. If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub depended_delete {
+ my $self = shift;
+ my $error;
+ foreach my $job (
+ map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
+ ) {
+ $error = $job->depended_delete;
+ return $error if $error;
+ $error = $job->delete;
+ return $error if $error
+ }
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item joblisting HASHREF NOACTIONS
+
+=cut
+
+sub joblisting {
+ my($hashref, $noactions) = @_;
+
+ use Date::Format;
+ use HTML::Entities;
+ use FS::CGI;
+
+ my @queue = qsearch( 'queue', $hashref );
+ return '' unless scalar(@queue);
+
+ my $p = FS::CGI::popurl(2);
+
+ my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
+ FS::CGI::table(). <<END;
+ <TR>
+ <TH COLSPAN=2>Job</TH>
+ <TH>Args</TH>
+ <TH>Date</TH>
+ <TH>Status</TH>
+END
+ $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
+ $html .= '</TR>';
+
+ my $dangerous = $conf->exists('queue_dangerous_controls');
+
+ my $areboxes = 0;
+
+ foreach my $queue ( sort {
+ $a->getfield('jobnum') <=> $b->getfield('jobnum')
+ } @queue ) {
+ my $queue_hashref = $queue->hashref;
+ my $jobnum = $queue->jobnum;
+
+ my $args;
+ if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
+ $args = encode_entities( join(' ', $queue->args) );
+ } else {
+ $args = '';
+ }
+
+ my $date = time2str( "%a %b %e %T %Y", $queue->_date );
+ my $status = $queue->status;
+ $status .= ': '. $queue->statustext if $queue->statustext;
+ my @queue_depend = $queue->queue_depend;
+ $status .= ' (waiting for '.
+ join(', ', map { $_->depend_jobnum } @queue_depend ).
+ ')'
+ if @queue_depend;
+ my $changable = $dangerous
+ || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
+ if ( $changable ) {
+ $status .=
+ qq! (&nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A>&nbsp;|!.
+ qq!&nbsp;<A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A>&nbsp;)!;
+ }
+ my $cust_svc = $queue->cust_svc;
+
+ $html .= <<END;
+ <TR>
+ <TD>$jobnum</TD>
+ <TD>$queue_hashref->{job}</TD>
+ <TD>$args</TD>
+ <TD>$date</TD>
+ <TD>$status</TD>
+END
+
+ unless ( $hashref->{svcnum} ) {
+ my $account;
+ if ( $cust_svc ) {
+ my $table = $cust_svc->part_svc->svcdb;
+ my $label = ( $cust_svc->label )[1];
+ $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
+ qq!">$label</A>!;
+ } else {
+ $account = '';
+ }
+ $html .= "<TD>$account</TD>";
+ }
+
+ if ( $changable ) {
+ $areboxes=1;
+ $html .=
+ qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
+
+ }
+
+ $html .= '</TR>';
+
+}
+
+ $html .= '</TABLE>';
+
+ if ( $areboxes ) {
+ $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
+ '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
+ }
+
+ $html;
+
+}
+
+=back
+
+=head1 VERSION
+
+$Id: queue.pm,v 1.18 2004-05-04 18:44:48 ivan Exp $
+
+=head1 BUGS
+
+$jobnums global
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm
new file mode 100644
index 0000000..d23ee2a
--- /dev/null
+++ b/FS/FS/queue_arg.pm
@@ -0,0 +1,121 @@
+package FS::queue_arg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::queue_arg - Object methods for queue_arg records
+
+=head1 SYNOPSIS
+
+ use FS::queue_arg;
+
+ $record = new FS::queue_arg \%hash;
+ $record = new FS::queue_arg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue_arg object represents job argument. FS::queue_arg inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item argnum - primary key
+
+=item jobnum - see L<FS::queue>
+
+=item arg - argument
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new argument. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'queue_arg'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid argument. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('argnum')
+ || $self->ut_numbern('jobnum')
+ || $self->ut_anything('arg')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 VERSION
+
+$Id: queue_arg.pm,v 1.2 2003-08-05 00:20:46 khoff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::queue>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/queue_depend.pm b/FS/FS/queue_depend.pm
new file mode 100644
index 0000000..bc910d8
--- /dev/null
+++ b/FS/FS/queue_depend.pm
@@ -0,0 +1,121 @@
+package FS::queue_depend;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::queue;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::queue_depend - Object methods for queue_depend records
+
+=head1 SYNOPSIS
+
+ use FS::queue_depend;
+
+ $record = new FS::queue_depend \%hash;
+ $record = new FS::queue_depend { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue_depend object represents an job dependancy. FS::queue_depend
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item dependnum - primary key
+
+=item jobnum - source jobnum (see L<FS::queue>).
+
+=item depend_jobnum - dependancy jobnum (see L<FS::queue>)
+
+=back
+
+The job specified by B<jobnum> depends on the job specified B<depend_jobnum> -
+the B<jobnum> job will not be run until the B<depend_jobnum> job has completed
+sucessfully (or manually removed).
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new dependancy. To add the dependancy to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'queue_depend'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid dependancy. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('dependnum')
+ || $self->ut_foreign_key('jobnum', 'queue', 'jobnum')
+ || $self->ut_foreign_key('depend_jobnum', 'queue', 'jobnum')
+ || $self->SUPER::check
+ ;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::queue>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/raddb.pm b/FS/FS/raddb.pm
new file mode 100644
index 0000000..efeb739
--- /dev/null
+++ b/FS/FS/raddb.pm
@@ -0,0 +1,1599 @@
+package FS::raddb;
+use vars qw(%attrib);
+
+%attrib = (
+ 'usr_at_zip_output_filter' => 'USR-AT-Zip-Output-Filter',
+ 'ms_filter' => 'MS-Filter',
+ 'annex_compression_protoc' => 'Annex-Compression-Protocol',
+ 'xedia_ssh_privileges' => 'Xedia-SSH-Privileges',
+ 'usr_blocks_received' => 'USR-Blocks-Received',
+ 'shiva_called_number' => 'Shiva-Called-Number',
+ 'annex_filter' => 'Annex-Filter',
+ 'usr_channel_expansion' => 'USR-Channel-Expansion',
+ 'erx_tunnel_tos' => 'ERX-Tunnel-Tos',
+ 'session_timeout' => 'Session-Timeout',
+ 'ascend_route_ipx' => 'Ascend-Route-IPX',
+ 'annex_error_correction_p' => 'Annex-Error-Correction-Prot',
+ 'acc_callback_mode' => 'Acc-Callback-Mode',
+ 'usr_filter_zones' => 'USR-Filter-Zones',
+ 'erx_input_gigapkts' => 'ERX-Input-Gigapkts',
+ 'ascend_session_svr_key' => 'Ascend-Session-Svr-Key',
+ 'bind_l2tp_tunnel_namf' => 'Bind_L2TP_Tunnel_Name',
+ 'ascend_dsl_cir_recv_limi' => 'Ascend-Dsl-CIR-Recv-Limit',
+ 'altiga_secondary_wins_g' => 'Altiga-Secondary-WINS-G',
+ 'ascend_ts_idle_limit' => 'Ascend-TS-Idle-Limit',
+ 'usr_port_tap_priority' => 'USR-Port-Tap-Priority',
+ 'cvpn3000_ipsec_client_fw' => 'CVPN3000-IPSec-Client-Fw-Filter-Name',
+ 'ascend_private_route_req' => 'Ascend-Private-Route-Required',
+ 'ascend_private_route' => 'Ascend-Private-Route',
+ 'prompt' => 'Prompt',
+ 'acct_link_count' => 'Acct-Link-Count',
+ 'bind_auth_service_grq' => 'Bind_Auth_Service_Grp',
+ 'itk_tunnel_ip' => 'ITK-Tunnel-IP',
+ 'login_lat_node' => 'Login-LAT-Node',
+ 'usr_mbi_ct_pri_card_slot' => 'USR-Mbi_Ct_PRI_Card_Slot',
+ 'lac_real_poru' => 'LAC_Real_Port',
+ 'erx_ingress_statistics' => 'ERX-Ingress-Statistics',
+ 'digest_nonce' => 'Digest-Nonce',
+ 'annex_system_disc_reason' => 'Annex-System-Disc-Reason',
+ 'pool_name' => 'Pool-Name',
+ 'altiga_use_client_addres' => 'Altiga-Use-Client-Address-G/U',
+ 'police_bursu' => 'Police_Burst',
+ 'usr_call_arrival_time' => 'USR-Call-Arrival-Time',
+ 'ascend_disconnect_cause' => 'Ascend-Disconnect-Cause',
+ 'ascend_user_acct_time' => 'Ascend-User-Acct-Time',
+ 'chap_challenge' => 'CHAP-Challenge',
+ 'ascend_mpp_idle_percent' => 'Ascend-MPP-Idle-Percent',
+ 'ascend_user_acct_port' => 'Ascend-User-Acct-Port',
+ 'ldap_group' => 'Ldap-Group',
+ 'ascend_numbering_plan_id' => 'Ascend-Numbering-Plan-ID',
+ 'usr_last_number_dialed_o' => 'USR-Last-Number-Dialed-Out',
+ 'pvc_encapsulation_type' => 'PVC-Encapsulation-Type',
+ 'ascend_bir_bridge_group' => 'Ascend-BIR-Bridge-Group',
+ 'ascend_atm_group' => 'Ascend-ATM-Group',
+ 'ascend_fr_svc_addr' => 'Ascend-FR-SVC-Addr',
+ 'x_ascend_send_auth' => 'X-Ascend-Send-Auth',
+ 'le_ip_pool' => 'LE-IP-Pool',
+ 'post_proxy_type' => 'Post-Proxy-Type',
+ 'wispr_session_terminate_' => 'WISPr-Session-Terminate-Time',
+ 'bintec_pppextiftable' => 'BinTec-pppExtIfTable',
+ 'nomadix_subnet' => 'Nomadix-Subnet',
+ 'login_port' => 'Login-Port',
+ 'ms_chap2_response' => 'MS-CHAP2-Response',
+ 'ascend_ipsec_profile' => 'Ascend-IPSEC-Profile',
+ 'usr_compression_algorith' => 'USR-Compression-Algorithm',
+ 'usr_accm_type' => 'USR-ACCM-Type',
+ 'simultaneous_use' => 'Simultaneous-Use',
+ 'cisco_account_info' => 'Cisco-Account-Info',
+ 'framed_protocol' => 'Framed-Protocol',
+ 'erx_tunnel_maximum_sessi' => 'ERX-Tunnel-Maximum-Sessions',
+ 'redcreek_tunneled_wins_t' => 'RedCreek-Tunneled-WINS-Server2',
+ 'ascend_recv_name' => 'Ascend-Recv-Name',
+ 'usr_call_connecting_time' => 'USR-Call-Connecting-Time',
+ 'quintum_h323_gw_id' => 'Quintum-h323-gw-id',
+ 'acct_dyn_ac_ent' => 'Acct-Dyn-Ac-Ent',
+ 'tunnel_remote_name' => 'Tunnel-Remote-Name',
+ 'annex_ppp_trace_level' => 'Annex-PPP-Trace-Level',
+ 'cisco_call_type' => 'Cisco-Call-Type',
+ 'cisco_fax_recipient_coun' => 'Cisco-Fax-Recipient-Count',
+ 'altiga_ipsec_authenticat' => 'Altiga-IPSec-Authentication-G',
+ 'wispr_location_id' => 'WISPr-Location-ID',
+ 'itk_start_delay' => 'ITK-Start-Delay',
+ 'ascend_pre_output_packet' => 'Ascend-Pre-Output-Packets',
+ 'usr_rmmie_firmware_versi' => 'USR-RMMIE-Firmware-Version',
+ 'usr_vts_session_key' => 'USR-VTS-Session-Key',
+ 'ascend_fr_dce_n393' => 'Ascend-FR-DCE-N393',
+ 'login_host' => 'Login-Host',
+ 'usr_reply_script3' => 'USR-Reply-Script3',
+ 'cvpn3000_ipsec_split_tuo' => 'CVPN3000-IPSec-Split-Tunneling-Policy',
+ 'ascend_pppoe_enable' => 'Ascend-PPPoE-Enable',
+ 'annex_primary_dns_server' => 'Annex-Primary-DNS-Server',
+ 'x_ascend_bridge_address' => 'X-Ascend-Bridge-Address',
+ 'usr_number_of_link_naks' => 'USR-Number-of-Link-NAKs',
+ 'altiga_priority_on_sep_g' => 'Altiga-Priority-on-SEP-G/U',
+ 'annex_cli_command' => 'Annex-CLI-Command',
+ 'usr_pw_framed_routing_v2' => 'USR-PW_Framed_Routing_V2',
+ 'session_error_codf' => 'Session_Error_Code',
+ 'annex_user_server_locati' => 'Annex-User-Server-Location',
+ 'cisco_fax_mdn_address' => 'Cisco-Fax-Mdn-Address',
+ 'ascend_calling_subaddres' => 'Ascend-Calling-Subaddress',
+ 'ascend_call_by_call' => 'Ascend-Call-By-Call',
+ 'ascend_first_dest' => 'Ascend-First-Dest',
+ 'annex_tunnel_authen_type' => 'Annex-Tunnel-Authen-Type',
+ 'acct_type' => 'Acct-Type',
+ 'sql_user_name' => 'SQL-User-Name',
+ 'erx_secondary_dns' => 'ERX-Secondary-Dns',
+ 'bridge_grouq' => 'Bridge_Group',
+ 'h323_return_code' => 'h323-return-code',
+ 'annex_host_allow' => 'Annex-Host-Allow',
+ 'cvx_modem_end_recv_line_' => 'CVX-Modem-End-Recv-Line-Lvl',
+ 'sip_method' => 'Sip-Method',
+ 'x_ascend_require_auth' => 'X-Ascend-Require-Auth',
+ 'cvpn3000_sep_card_assign' => 'CVPN3000-SEP-Card-Assignment',
+ 'le_ipsec_deny_action' => 'LE-IPSec-Deny-Action',
+ 'annex_edo' => 'Annex-EDO',
+ 'acct_delay_time' => 'Acct-Delay-Time',
+ 'login_tcp_port' => 'Login-TCP-Port',
+ 'ascend_temporary_rtes' => 'Ascend-Temporary-Rtes',
+ 'versanet_termination_cau' => 'Versanet-Termination-Cause',
+ 'ascend_dialed_number' => 'Ascend-Dialed-Number',
+ 'cvpn3000_ipsec_authentic' => 'CVPN3000-IPSec-Authentication',
+ 'ascend_fr_dlci' => 'Ascend-FR-DLCI',
+ 'annex_modem_disc_reason' => 'Annex-Modem-Disc-Reason',
+ 'x_ascend_receive_secret' => 'X-Ascend-Receive-Secret',
+ 'usr_ospf_addressless_ind' => 'USR-OSPF-Addressless-Index',
+ 'usr_ip_default_route_opt' => 'USR-IP-Default-Route-Option',
+ 'char_noecho' => 'Char-Noecho',
+ 'redcreek_tunneled_search' => 'RedCreek-Tunneled-Search-List',
+ 'ascend_pri_number_type' => 'Ascend-PRI-Number-Type',
+ 'aat_ip_tos_apply_to' => 'AAT-IP-TOS-Apply-To',
+ 'x_ascend_modem_shelfno' => 'X-Ascend-Modem-ShelfNo',
+ 'prefix' => 'Prefix',
+ 'usr_rad_dvmrp_metric' => 'USR-Rad-Dvmrp-Metric',
+ 'x_ascend_call_attempt_li' => 'X-Ascend-Call-Attempt-Limit',
+ 'usr_ip_saa_filter' => 'USR-IP-SAA-Filter',
+ 'itk_prompt' => 'ITK-Prompt',
+ 'ascend_port_redir_protoc' => 'Ascend-Port-Redir-Protocol',
+ 'cvx_modem_tx_packets' => 'CVX-Modem-Tx-Packets',
+ 'usr_tunnel_switch_endpoi' => 'USR-Tunnel-Switch-Endpoint',
+ 'ascend_home_network_name' => 'Ascend-Home-Network-Name',
+ 'acc_customer_id' => 'Acc-Customer-Id',
+ 'message_authenticator' => 'Message-Authenticator',
+ 'cisco_fax_coverpage_flag' => 'Cisco-Fax-Coverpage-Flag',
+ 'usr_multicast_forwarding' => 'USR-Multicast-Forwarding',
+ 'cvpn3000_allow_network_e' => 'CVPN3000-Allow-Network-Extension-Mode',
+ 'ascend_call_direction' => 'Ascend-Call-Direction',
+ 'acc_connect_rx_speed' => 'Acc-Connect-Rx-Speed',
+ 'ascend_force_56' => 'Ascend-Force-56',
+ 'st_service_domain' => 'ST-Service-Domain',
+ 'usr_harc_disconnect_code' => 'USR-HARC-Disconnect-Code',
+ 'shasta_service_profile' => 'Shasta-Service-Profile',
+ 'cisco_maximum_time' => 'Cisco-Maximum-Time',
+ 'usr_tunnel_auth_hostname' => 'USR-Tunnel-Auth-Hostname',
+ 'acc_ip_gateway_pri' => 'Acc-Ip-Gateway-Pri',
+ 'ascend_bridge_address' => 'Ascend-Bridge-Address',
+ 'altiga_pptp_min_authenti' => 'Altiga-PPTP-Min-Authentication-G/U',
+ 'ns_secondary_wins' => 'NS-Secondary-WINS',
+ 'cbbsm_bandwidth' => 'CBBSM-Bandwidth',
+ 'x_ascend_fr_link_mgt' => 'X-Ascend-FR-Link-Mgt',
+ 'altiga_ipsec_banner_g' => 'Altiga-IPSec-Banner-G',
+ 'ascend_handle_ipx' => 'Ascend-Handle-IPX',
+ 'ascend_x25_pad_alias_2' => 'Ascend-X25-Pad-Alias-2',
+ 'st_policy_name' => 'ST-Policy-Name',
+ 'ascend_group' => 'Ascend-Group',
+ 'ascend_dsl_rate_type' => 'Ascend-Dsl-Rate-Type',
+ 'tunnel_contexu' => 'Tunnel_Context',
+ 'ascend_require_auth' => 'Ascend-Require-Auth',
+ 'cvx_modem_local_retrains' => 'CVX-Modem-Local-Retrains',
+ 'cvpn5000_echo' => 'CVPN5000-Echo',
+ 'cvx_secondary_dns' => 'CVX-Secondary-DNS',
+ 'x_ascend_billing_number' => 'X-Ascend-Billing-Number',
+ 'usr_orig_nas_type' => 'USR-Orig-NAS-Type',
+ 'ascend_remote_fw' => 'Ascend-Remote-FW',
+ 'acct_output_packets' => 'Acct-Output-Packets',
+ 'lm_password' => 'LM-Password',
+ 'tunnel_window' => 'Tunnel-Window',
+ 'cisco_avpair' => 'Cisco-AVPair',
+ 'st_service_name' => 'ST-Service-Name',
+ 'shiva_event_flags' => 'Shiva-Event-Flags',
+ 'annex_retrain_requests_s' => 'Annex-Retrain-Requests-Sent',
+ 'ascend_ts_idle_mode' => 'Ascend-TS-Idle-Mode',
+ 'usr_ip_rip_simple_auth_p' => 'USR-IP-RIP-Simple-Auth-Password',
+ 'tunnel_deadtimf' => 'Tunnel_Deadtime',
+ 'state' => 'State',
+ 'usr_keypress_timeout' => 'USR-Keypress-Timeout',
+ 'usr_pw_vpn_neighbor' => 'USR-PW_VPN_Neighbor',
+ 'erx_pppoe_description' => 'ERX-Pppoe-Description',
+ 'ldap_userdn' => 'Ldap-UserDn',
+ 'x_ascend_fr_n391' => 'X-Ascend-FR-N391',
+ 'ascend_calling_id_presen' => 'Ascend-Calling-Id-Presentatn',
+ 'erx_local_loopback_inter' => 'ERX-Local-Loopback-Interface',
+ 'x_ascend_fr_direct' => 'X-Ascend-FR-Direct',
+ 'nas_ip_address' => 'NAS-IP-Address',
+ 'usr_call_end_time' => 'USR-Call-End-Time',
+ 'acct_mcast_out_packett' => 'Acct_Mcast_Out_Packets',
+ 'tunnel_algorithm' => 'Tunnel-Algorithm',
+ 'usr_vpn_encrypter' => 'USR-VPN-Encrypter',
+ 'tunnel_grouq' => 'Tunnel_Group',
+ 'ascend_atm_connect_group' => 'Ascend-ATM-Connect-Group',
+ 'x_ascend_ft1_caller' => 'X-Ascend-FT1-Caller',
+ 'usr_dnis_reauthenticatio' => 'USR-DNIS-ReAuthentication',
+ 'login_callback_number' => 'Login-Callback-Number',
+ 'usr_ip_rip_input_filter' => 'USR-IP-RIP-Input-Filter',
+ 'usr_rmmie_rcv_pwrlvl_330' => 'USR-RMMIE-Rcv-PwrLvl-3300Hz',
+ 'h323_disconnect_cause' => 'h323-disconnect-cause',
+ 'x_ascend_handle_ipx' => 'X-Ascend-Handle-IPX',
+ 'usr_igmp_version' => 'USR-IGMP-Version',
+ 'usr_imsi' => 'USR-IMSI',
+ 'group_name' => 'Group-Name',
+ 'usr_nas_type' => 'USR-NAS-Type',
+ 'context_namf' => 'Context-Name',
+ 'ascend_ip_tos' => 'Ascend-IP-TOS',
+ 'x_ascend_token_immediate' => 'X-Ascend-Token-Immediate',
+ 'tunnel_session_auth_serw' => 'Tunnel_Session_Auth_Service_Grp',
+ 'ms_chap2_cpw' => 'MS-CHAP2-CPW',
+ 'tunnel_session_auth_ctx' => 'Tunnel-Session-Auth-Ctx',
+ 'usr_mobile_numbytes_rxed' => 'USR-Mobile-NumBytes-Rxed',
+ 'usr_mbi_ct_tdm_time_slot' => 'USR-Mbi_Ct_TDM_Time_Slot',
+ 'ascend_x25_nui' => 'Ascend-X25-Nui',
+ 'x_ascend_first_dest' => 'X-Ascend-First-Dest',
+ 'usr_send_password' => 'USR-Send-Password',
+ 'x_ascend_fr_direct_profi' => 'X-Ascend-FR-Direct-Profile',
+ 'x_ascend_fr_t391' => 'X-Ascend-FR-T391',
+ 'altiga_ipsec_sec_associa' => 'Altiga-IPSec-Sec-Association-G/U',
+ 'ip_address_pool_namf' => 'Ip_Address_Pool_Name',
+ 'acct_input_octets' => 'Acct-Input-Octets',
+ 'cvx_modem_begin_modulati' => 'CVX-Modem-Begin-Modulation',
+ 'wispr_session_terminatea' => 'WISPr-Session-Terminate-End-Of-Day',
+ 'cvpn3000_use_client_addr' => 'CVPN3000-Use-Client-Address',
+ 'bridge_group' => 'Bridge-Group',
+ 'annex_sec_profile_index' => 'Annex-Sec-Profile-Index',
+ 'acc_dns_server_pri' => 'Acc-Dns-Server-Pri',
+ 'ms_acct_auth_type' => 'MS-Acct-Auth-Type',
+ 'x_ascend_maximum_call_du' => 'X-Ascend-Maximum-Call-Duration',
+ 'tunnel_password' => 'Tunnel-Password',
+ 'framed_ipv6_prefix' => 'Framed-IPv6-Prefix',
+ 'usr_reply_script5' => 'USR-Reply-Script5',
+ 'shiva_links_in_bundle' => 'Shiva-Links-In-Bundle',
+ 'ascend_fr_profile_name' => 'Ascend-FR-Profile-Name',
+ 'ascend_mtu' => 'Ascend-MTU',
+ 'nokia_charging_id' => 'Nokia-Charging-Id',
+ 'cvpn3000_ms_client_subne' => 'CVPN3000-MS-Client-Subnet-Mask',
+ 'cvpn3000_ipsec_sec_assoc' => 'CVPN3000-IPSec-Sec-Association',
+ 'cisco_ppp_async_map' => 'Cisco-PPP-Async-Map',
+ 'cvpn3000_user_auth_servf' => 'CVPN3000-User-Auth-Server-Port',
+ 'cisco_num_in_multilink' => 'Cisco-Num-In-Multilink',
+ 'wispr_logoff_url' => 'WISPr-Logoff-URL',
+ 'usr_mobile_ip_address' => 'USR-Mobile-IP-Address',
+ 'usr_final_tx_link_data_r' => 'USR-Final-Tx-Link-Data-Rate',
+ 'itk_ppp_compression_prot' => 'ITK-PPP-Compression-Prot',
+ 'ascend_bridge' => 'Ascend-Bridge',
+ 'x_ascend_presession_time' => 'X-Ascend-PreSession-Time',
+ 'aat_client_primary_dns' => 'AAT-Client-Primary-DNS',
+ 'cvpn3000_strip_realm' => 'CVPN3000-Strip-Realm',
+ 'tunnel_cmd_timeout' => 'Tunnel-Cmd-Timeout',
+ 'ascend_multicast_client' => 'Ascend-Multicast-Client',
+ 'cvx_modem_remote_rate_ne' => 'CVX-Modem-Remote-Rate-Negs',
+ 'tunnel_private_group_id' => 'Tunnel-Private-Group-Id',
+ 'usr_rmmie_rcv_tot_pwrlvl' => 'USR-RMMIE-Rcv-Tot-PwrLvl',
+ 'calling_station_id' => 'Calling-Station-Id',
+ 'tunnel_rate_limit_burst' => 'Tunnel-Rate-Limit-Burst',
+ 'usr_device_connected_to' => 'USR-Device-Connected-To',
+ 'aat_source_ip_check' => 'AAT-Source-IP-Check',
+ 'login_lat_service' => 'Login-LAT-Service',
+ 'ascend_h323_fegw_address' => 'Ascend-H323-Fegw-Address',
+ 'usr_called_party_number' => 'USR-Called-Party-Number',
+ 'bintec_ipnatpresettable' => 'BinTec-ipNatPresetTable',
+ 'ascend_remove_seconds' => 'Ascend-Remove-Seconds',
+ 'shiva_user_attributes' => 'Shiva-User-Attributes',
+ 'cisco_fax_dsn_flag' => 'Cisco-Fax-Dsn-Flag',
+ 'x_ascend_route_ipx' => 'X-Ascend-Route-IPX',
+ 'acc_route_policy' => 'Acc-Route-Policy',
+ 'bind_l2tp_flow_controm' => 'Bind_L2TP_Flow_Control',
+ 'erx_qos_profile_name' => 'ERX-Qos-Profile-Name',
+ 'x_ascend_client_gateway' => 'X-Ascend-Client-Gateway',
+ 'pre_proxy_type' => 'Pre-Proxy-Type',
+ 'smb_account_ctrl_text' => 'SMB-Account-CTRL-TEXT',
+ 'x_ascend_data_filter' => 'X-Ascend-Data-Filter',
+ 'usr_rmmie_last_update_ti' => 'USR-RMMIE-Last-Update-Time',
+ 'ascend_atm_direct' => 'Ascend-ATM-Direct',
+ 'ascend_session_type' => 'Ascend-Session-Type',
+ 'x_ascend_fr_linkup' => 'X-Ascend-FR-LinkUp',
+ 'ascend_metric' => 'Ascend-Metric',
+ 'x_ascend_assign_ip_clien' => 'X-Ascend-Assign-IP-Client',
+ 'usr_speed_of_connection' => 'USR-Speed-Of-Connection',
+ 'cvpn3000_require_hw_clie' => 'CVPN3000-Require-HW-Client-Auth',
+ 'session_type' => 'Session-Type',
+ 'acct_input_octets_65' => 'Acct_Input_Octets_64',
+ 'le_nat_outsource_outmap' => 'LE-NAT-Outsource-Outmap',
+ 'cvx_modem_local_rate_neg' => 'CVX-Modem-Local-Rate-Negs',
+ 'mcast_sene' => 'Mcast_Send',
+ 'pppoe_url' => 'PPPOE-URL',
+ 'erx_service_bundle' => 'ERX-Service-Bundle',
+ 'altiga_secondary_dns_g' => 'Altiga-Secondary-DNS-G',
+ 'bg_trans_bpdv' => 'BG_Trans_BPDU',
+ 'cvx_data_filter' => 'CVX-Data-Filter',
+ 'acct_mcast_out_octets' => 'Acct-Mcast-Out-Octets',
+ 'ascend_callback' => 'Ascend-Callback',
+ 'tunnel_client_auth_id' => 'Tunnel-Client-Auth-Id',
+ 'acct_unique_session_id' => 'Acct-Unique-Session-Id',
+ 'usr_port_tap_format' => 'USR-Port-Tap-Format',
+ 'ascend_ckt_type' => 'Ascend-Ckt-Type',
+ 'ascend_ppp_async_map' => 'Ascend-PPP-Async-Map',
+ 'usr_rmmie_rcv_pwrlvl_375' => 'USR-RMMIE-Rcv-PwrLvl-3750Hz',
+ 'usr_acct_reason_code' => 'USR-Acct-Reason-Code',
+ 'ascend_filter' => 'Ascend-Filter',
+ 'h323_redirect_number' => 'h323-redirect-number',
+ 'port_limit' => 'Port-Limit',
+ 'rewrite_rule' => 'Rewrite-Rule',
+ 'tunnel_police_rate' => 'Tunnel-Police-Rate',
+ 'usr_multicast_proxy' => 'USR-Multicast-Proxy',
+ 'ascend_max_shared_users' => 'Ascend-Max-Shared-Users',
+ 'usr_bridging' => 'USR-Bridging',
+ 'cvx_presession_time' => 'CVX-PreSession-Time',
+ 'cvpn5000_vpn_groupinfo' => 'CVPN5000-VPN-GroupInfo',
+ 'autz_type' => 'Autz-Type',
+ 'x_ascend_fr_dlci' => 'X-Ascend-FR-DLCI',
+ 'usr_request_type' => 'USR-Request-Type',
+ 'acc_igmp_admin_state' => 'Acc-Igmp-Admin-State',
+ 'ascend_host_info' => 'Ascend-Host-Info',
+ 'ascend_dhcp_maximum_leas' => 'Ascend-DHCP-Maximum-Leases',
+ 'usr_rmmie_num_of_updates' => 'USR-RMMIE-Num-Of-Updates',
+ 'x_ascend_fr_profile_name' => 'X-Ascend-FR-Profile-Name',
+ 'ascend_fr_direct_profile' => 'Ascend-FR-Direct-Profile',
+ 'x_ascend_bridge' => 'X-Ascend-Bridge',
+ 'tunnel_deadtime' => 'Tunnel-Deadtime',
+ 'ms_chap_error' => 'MS-CHAP-Error',
+ 'framed_route' => 'Framed-Route',
+ 'sip_from' => 'Sip-From',
+ 'expiration' => 'Expiration',
+ 'ascend_backup' => 'Ascend-Backup',
+ 'ascend_pre_output_octets' => 'Ascend-Pre-Output-Octets',
+ 'ascend_calling_id_number' => 'Ascend-Calling-Id-Number-Plan',
+ 'framed_appletalk_zone' => 'Framed-AppleTalk-Zone',
+ 'annex_audit_level' => 'Annex-Audit-Level',
+ 'digest_algorithm' => 'Digest-Algorithm',
+ 'bind_auth_context' => 'Bind-Auth-Context',
+ 'ascend_user_acct_base' => 'Ascend-User-Acct-Base',
+ 'st_secondary_dns_server' => 'ST-Secondary-DNS-Server',
+ 'mcast_receive' => 'Mcast-Receive',
+ 'usr_ds0' => 'USR-DS0',
+ 'aat_atm_traffic_profile' => 'AAT-ATM-Traffic-Profile',
+ 'ms_ras_vendor' => 'MS-RAS-Vendor',
+ 'tunnel_domain' => 'Tunnel-Domain',
+ 'tunnel_max_sessions' => 'Tunnel-Max-Sessions',
+ 'ascend_ip_direct' => 'Ascend-IP-Direct',
+ 'xedia_address_pool' => 'Xedia-Address-Pool',
+ 'idle_timeout' => 'Idle-Timeout',
+ 'tunnel_rate_limit_ratf' => 'Tunnel_Rate_Limit_Rate',
+ 'annex_rate_reneg_req_sen' => 'Annex-Rate-Reneg-Req-Sent',
+ 'usr_initial_tx_link_data' => 'USR-Initial-Tx-Link-Data-Rate',
+ 'tunnel_server_auth_id' => 'Tunnel-Server-Auth-Id',
+ 'cvpn3000_ipsec_banner1' => 'CVPN3000-IPSec-Banner1',
+ 'usr_start_time' => 'USR-Start-Time',
+ 'usr_ip' => 'USR-IP',
+ 'cvpn3000_reqrd_client_fw' => 'CVPN3000-Reqrd-Client-Fw-Vendor-Code',
+ 'altiga_ipsec_secondary_d' => 'Altiga-IPSec-Secondary-Domains-G',
+ 'usr_gateway_ip_address' => 'USR-Gateway-IP-Address',
+ 'ascend_dba_monitor' => 'Ascend-DBA-Monitor',
+ 'ms_link_utilization_thre' => 'MS-Link-Utilization-Threshold',
+ 'st_primary_dns_server' => 'ST-Primary-DNS-Server',
+ 'acc_ace_token_ttl' => 'Acc-Ace-Token-Ttl',
+ 'ms_chap_domain' => 'MS-CHAP-Domain',
+ 'cisco_pre_input_octets' => 'Cisco-Pre-Input-Octets',
+ 'ascend_primary_home_agen' => 'Ascend-Primary-Home-Agent',
+ 'acct_session_time' => 'Acct-Session-Time',
+ 'framed_ip_address' => 'Framed-IP-Address',
+ 'ns_admin_privilege' => 'NS-Admin-Privilege',
+ 'medium_type' => 'Medium-Type',
+ 'acct_output_octets_64' => 'Acct-Output-Octets-64',
+ 'ascend_cir_timer' => 'Ascend-CIR-Timer',
+ 'police_rate' => 'Police-Rate',
+ 'tunnel_functioo' => 'Tunnel_Function',
+ 'quintum_h323_time_and_da' => 'Quintum-h323-time-and-day',
+ 'ip_tos_fiele' => 'IP_TOS_Field',
+ 'erx_framed_ip_route_tag' => 'ERX-Framed-Ip-Route-Tag',
+ 'ms_mppe_send_key' => 'MS-MPPE-Send-Key',
+ 'ascend_maximum_call_dura' => 'Ascend-Maximum-Call-Duration',
+ 'pppoe_motn' => 'PPPOE_MOTM',
+ 'lac_poru' => 'LAC_Port',
+ 'bind_dot1q_slou' => 'Bind_Dot1q_Slot',
+ 'ascend_secondary_home_ag' => 'Ascend-Secondary-Home-Agent',
+ 'usr_ip_call_output_filte' => 'USR-IP-Call-Output-Filter',
+ 'x_ascend_host_info' => 'X-Ascend-Host-Info',
+ 'erx_egress_policy_name' => 'ERX-Egress-Policy-Name',
+ 'erx_ppp_password' => 'ERX-PPP-Password',
+ 'user_name' => 'User-Name',
+ 'usr_number_of_characters' => 'USR-Number-Of-Characters-Lost',
+ 'bind_bypass_bypass' => 'Bind-Bypass-Bypass',
+ 'usr_rad_multicast_routip' => 'USR-Rad-Multicast-Routing-Proto',
+ 'annex_acct_servers' => 'Annex-Acct-Servers',
+ 'cvpn5000_tunnel_throughp' => 'CVPN5000-Tunnel-Throughput',
+ 'usr_chassis_call_channel' => 'USR-Chassis-Call-Channel',
+ 'annex_input_filter' => 'Annex-Input-Filter',
+ 'wispr_billing_class_of_s' => 'WISPr-Billing-Class-Of-Service',
+ 'nas_port_type' => 'NAS-Port-Type',
+ 'cvx_client_assign_dns' => 'CVX-Client-Assign-DNS',
+ 'nomadix_maxbytesdown' => 'Nomadix-MaxBytesDown',
+ 'ascend_endpoint_disc' => 'Ascend-Endpoint-Disc',
+ 'tunnel_police_burst' => 'Tunnel-Police-Burst',
+ 'bind_auth_max_sessions' => 'Bind-Auth-Max-Sessions',
+ 'cvx_identification' => 'CVX-Identification',
+ 'cvpn3000_ipsec_allow_pas' => 'CVPN3000-IPSec-Allow-Passwd-Store',
+ 'ascend_calling_id_type_o' => 'Ascend-Calling-Id-Type-Of-Num',
+ 'x_ascend_fr_dce_n392' => 'X-Ascend-FR-DCE-N392',
+ 'usr_connect_term_reason' => 'USR-Connect-Term-Reason',
+ 'erx_egress_statistics' => 'ERX-Egress-Statistics',
+ 'ascend_fr_dte_n392' => 'Ascend-FR-DTE-N392',
+ 'usr_esn' => 'USR-ESN',
+ 'x_ascend_fr_dte_n392' => 'X-Ascend-FR-DTE-N392',
+ 'itk_modem_init_string' => 'ITK-Modem-Init-String',
+ 'x_ascend_fr_nailed_grp' => 'X-Ascend-FR-Nailed-Grp',
+ 'ascend_bridge_non_pppoe' => 'Ascend-Bridge-Non-PPPoE',
+ 'cvpn3000_ipsec_reqrd_cli' => 'CVPN3000-IPSec-Reqrd-Client-Fw-Cap',
+ 'ascend_ipx_alias' => 'Ascend-IPX-Alias',
+ 'acc_tunnel_port' => 'Acc-Tunnel-Port',
+ 'quintum_h323_return_code' => 'Quintum-h323-return-code',
+ 'cvpn3000_l2tp_encryption' => 'CVPN3000-L2TP-Encryption',
+ 'acct_input_gigawords' => 'Acct-Input-Gigawords',
+ 'bind_dot1q_poru' => 'Bind_Dot1q_Port',
+ 'altiga_primary_wins_g' => 'Altiga-Primary-WINS-G',
+ 'ascend_maximum_channels' => 'Ascend-Maximum-Channels',
+ 'x_ascend_home_agent_pass' => 'X-Ascend-Home-Agent-Password',
+ 'x_ascend_ppp_async_map' => 'X-Ascend-PPP-Async-Map',
+ 'usr_rmmie_manufacturer_i' => 'USR-RMMIE-Manufacturer-ID',
+ 'usr_retrains_requested' => 'USR-Retrains-Requested',
+ 'x_ascend_metric' => 'X-Ascend-Metric',
+ 'acc_apsm_oversubscribed' => 'Acc-Apsm-Oversubscribed',
+ 'usr_originate_answer_mod' => 'USR-Originate-Answer-Mode',
+ 'erx_atm_pcr' => 'ERX-Atm-PCR',
+ 'itk_nas_name' => 'ITK-NAS-Name',
+ 'usr_ipx_routing' => 'USR-IPX-Routing',
+ 'usr_tunneled_mlpp' => 'USR-Tunneled-MLPP',
+ 'usr_send_script5' => 'USR-Send-Script5',
+ 'ascend_traffic_shaper' => 'Ascend-Traffic-Shaper',
+ 'ascend_client_secondarya' => 'Ascend-Client-Secondary-DNS',
+ 'ascend_bacp_enable' => 'Ascend-BACP-Enable',
+ 'usr_call_terminate_in_gm' => 'USR-Call-Terminate-in-GMT',
+ 'login_time' => 'Login-Time',
+ 'bg_path_cosu' => 'BG_Path_Cost',
+ 'aat_require_auth' => 'AAT-Require-Auth',
+ 'cvpn3000_reqrd_client_fy' => 'CVPN3000-Reqrd-Client-Fw-Description',
+ 'ascend_call_type' => 'Ascend-Call-Type',
+ 'erx_address_pool_name' => 'ERX-Address-Pool-Name',
+ 'cvpn3000_ipsec_backup_sf' => 'CVPN3000-IPSec-Backup-Server-List',
+ 'h323_incoming_conf_id' => 'h323-incoming-conf-id',
+ 'user_profile' => 'User-Profile',
+ 'ip_host_adds' => 'Ip_Host_Addr',
+ 'ns_primary_wins' => 'NS-Primary-WINS',
+ 'packet_type' => 'Packet-Type',
+ 'bind_auth_max_sessiont' => 'Bind_Auth_Max_Sessions',
+ 'altiga_allow_alpha_only_' => 'Altiga-Allow-Alpha-Only-Passwords-G',
+ 'usr_security_resp_limit' => 'USR-Security-Resp-Limit',
+ 'ip_address_pool_name' => 'Ip-Address-Pool-Name',
+ 'ascend_ipx_node_addr' => 'Ascend-IPX-Node-Addr',
+ 'ascend_cbcp_trunk_group' => 'Ascend-CBCP-Trunk-Group',
+ 'ascend_menu_selector' => 'Ascend-Menu-Selector',
+ 'ascend_assign_ip_global_' => 'Ascend-Assign-IP-Global-Pool',
+ 'usr_ds0s' => 'USR-DS0s',
+ 'usr_actual_voltage' => 'USR-Actual-Voltage',
+ 'quintum_h323_call_type' => 'Quintum-h323-call-type',
+ 'annex_sw_version' => 'Annex-SW-Version',
+ 'ascend_receive_secret' => 'Ascend-Receive-Secret',
+ 'bintec_qospolicytable' => 'BinTec-qosPolicyTable',
+ 'usr_ip_rip_policies' => 'USR-IP-RIP-Policies',
+ 'redcreek_tunneled_ip_add' => 'RedCreek-Tunneled-IP-Addr',
+ 'ascend_pw_warntime' => 'Ascend-PW-Warntime',
+ 'x_ascend_inc_channel_cou' => 'X-Ascend-Inc-Channel-Count',
+ 'usr_blocks_resent' => 'USR-Blocks-Resent',
+ 'usr_fallback_enabled' => 'USR-Fallback-Enabled',
+ 'arap_challenge_response' => 'ARAP-Challenge-Response',
+ 'tunnel_session_auth' => 'Tunnel-Session-Auth',
+ 'usr_sync_async_mode' => 'USR-Sync-Async-Mode',
+ 'itk_dialout_type' => 'ITK-Dialout-Type',
+ 'extreme_netlogin_url' => 'Extreme-Netlogin-Url',
+ 'client_port_dnis' => 'Client-Port-DNIS',
+ 'digest_realm' => 'Digest-Realm',
+ 'ascend_ppp_vj_1172' => 'Ascend-PPP-VJ-1172',
+ 'ascend_fr_n391' => 'Ascend-FR-N391',
+ 'ascend_remote_addr' => 'Ascend-Remote-Addr',
+ 'client_port_id' => 'Client-Port-Id',
+ 'digest_body_digest' => 'Digest-Body-Digest',
+ 'le_ipsec_active_profile' => 'LE-IPSec-Active-Profile',
+ 'digest_cnonce' => 'Digest-CNonce',
+ 'usr_port_tap_facility' => 'USR-Port-Tap-Facility',
+ 'usr_callback_type' => 'USR-Callback-Type',
+ 'client_dns_prj' => 'Client_DNS_Pri',
+ 'digest_response' => 'Digest-Response',
+ 'login_lat_group' => 'Login-LAT-Group',
+ 'x_ascend_call_type' => 'X-Ascend-Call-Type',
+ 'ascend_route_ip' => 'Ascend-Route-IP',
+ 'usr_rad_multicast_routio' => 'USR-Rad-Multicast-Routing-RtLim',
+ 'usr_pw_vpn_id' => 'USR-PW_VPN_ID',
+ 'cvx_modem_end_modulation' => 'CVX-Modem-End-Modulation',
+ 'cvpn3000_pptp_mppc_compr' => 'CVPN3000-PPTP-MPPC-Compression',
+ 'cisco_pre_output_octets' => 'Cisco-Pre-Output-Octets',
+ 'h323_billing_model' => 'h323-billing-model',
+ 'usr_equalization_type' => 'USR-Equalization-Type',
+ 'acc_clearing_cause' => 'Acc-Clearing-Cause',
+ 'altiga_access_hours_g_u' => 'Altiga-Access-Hours-G/U',
+ 'cvpn3000_ipsec_user_grou' => 'CVPN3000-IPSec-User-Group-Lock',
+ 'x_ascend_menu_selector' => 'X-Ascend-Menu-Selector',
+ 'x_ascend_netware_timeout' => 'X-Ascend-Netware-timeout',
+ 'ascend_fr_linkup' => 'Ascend-FR-LinkUp',
+ 'annex_num_in_multilink' => 'Annex-Num-In-Multilink',
+ 'police_burst' => 'Police-Burst',
+ 'altiga_l2tp_min_authenti' => 'Altiga-L2TP-Min-Authentication-G/U',
+ 'ascend_filter_required' => 'Ascend-Filter-Required',
+ 'x_ascend_idle_limit' => 'X-Ascend-Idle-Limit',
+ 'nomadix_logoff_url' => 'Nomadix-Logoff-URL',
+ 'cvpn3000_ms_client_icpt_' => 'CVPN3000-MS-Client-Icpt-DHCP-Conf-Msg',
+ 'ip_tos_field' => 'IP-TOS-Field',
+ 'ascend_ip_tos_apply_to' => 'Ascend-IP-TOS-Apply-To',
+ 'usr_call_event_code' => 'USR-Call-Event-Code',
+ 'usr_et_bridge_output_fil' => 'USR-ET-Bridge-Output-Filter',
+ 'le_nat_sess_dir_fail_act' => 'LE-NAT-Sess-Dir-Fail-Action',
+ 'usr_rmmie_product_code' => 'USR-RMMIE-Product-Code',
+ 'usr_host_type' => 'USR-Host-Type',
+ 'erx_tunnel_interface_id' => 'ERX-Tunnel-Interface-Id',
+ 'ascend_send_auth' => 'Ascend-Send-Auth',
+ 'shiva_compression_type' => 'Shiva-Compression-Type',
+ 'itk_banner' => 'ITK-Banner',
+ 'ascend_ft1_caller' => 'Ascend-FT1-Caller',
+ 'filter_id' => 'Filter-Id',
+ 'annex_pre_output_octets' => 'Annex-Pre-Output-Octets',
+ 'acct_mcast_in_octett' => 'Acct_Mcast_In_Octets',
+ 'usr_log_filter_packets' => 'USR-Log-Filter-Packets',
+ 'ascend_fr_nailed_grp' => 'Ascend-FR-Nailed-Grp',
+ 'ascend_atm_loopback_cell' => 'Ascend-ATM-Loopback-Cell-Loss',
+ 'usr_at_rtmp_output_filte' => 'USR-AT-RTMP-Output-Filter',
+ 'acc_input_errors' => 'Acc-Input-Errors',
+ 'x_ascend_user_acct_port' => 'X-Ascend-User-Acct-Port',
+ 'erx_secondary_wins' => 'ERX-Secondary-Wins',
+ 'usr_rmmie_serial_number' => 'USR-RMMIE-Serial-Number',
+ 'usr_et_bridge_input_filt' => 'USR-ET-Bridge-Input-Filter',
+ 'ns_primary_dns' => 'NS-Primary-DNS',
+ 'usr_slot_connected_to' => 'USR-Slot-Connected-To',
+ 'shiva_disconnect_reason' => 'Shiva-Disconnect-Reason',
+ 'cvpn5000_client_assignee' => 'CVPN5000-Client-Assigned-IPX',
+ 'cvx_radius_redirect' => 'CVX-Radius-Redirect',
+ 'usr_receive_acc_map' => 'USR-Receive-Acc-Map',
+ 'x_ascend_tunneling_proto' => 'X-Ascend-Tunneling-Protocol',
+ 'itk_acct_serv_ip' => 'ITK-Acct-Serv-IP',
+ 'ascend_fr_type' => 'Ascend-FR-Type',
+ 'ascend_client_assign_dns' => 'Ascend-Client-Assign-DNS',
+ 'annex_retrain_requests_r' => 'Annex-Retrain-Requests-Rcvd',
+ 'x_ascend_assign_ip_globa' => 'X-Ascend-Assign-IP-Global-Pool',
+ 'tunnel_client_endpoint' => 'Tunnel-Client-Endpoint',
+ 'alteon_service_type' => 'Alteon-Service-Type',
+ 'x_ascend_send_secret' => 'X-Ascend-Send-Secret',
+ 'x_ascend_call_filter' => 'X-Ascend-Call-Filter',
+ 'usr_ipx_rip_input_filter' => 'USR-IPX-RIP-Input-Filter',
+ 'x_ascend_maximum_time' => 'X-Ascend-Maximum-Time',
+ 'pvc_profile_name' => 'PVC-Profile-Name',
+ 'usr_framed_ip_address_po' => 'USR-Framed_IP_Address_Pool_Name',
+ 'cvpn3000_ipsec_split_dns' => 'CVPN3000-IPSec-Split-DNS-Names',
+ 'ascend_global_call_id' => 'Ascend-Global-Call-Id',
+ 'usr_initial_rx_link_data' => 'USR-Initial-Rx-Link-Data-Rate',
+ 'st_primary_nbns_server' => 'ST-Primary-NBNS-Server',
+ 'usr_number_of_rings_limi' => 'USR-Number-of-Rings-Limit',
+ 'tunnel_local_name' => 'Tunnel-Local-Name',
+ 'ascend_fr_t392' => 'Ascend-FR-T392',
+ 'annex_pool_id' => 'Annex-Pool-Id',
+ 'ascend_token_immediate' => 'Ascend-Token-Immediate',
+ 'usr_rmmie_firmware_build' => 'USR-RMMIE-Firmware-Build-Date',
+ 'wispr_bandwidth_min_down' => 'WISPr-Bandwidth-Min-Down',
+ 'usr_chassis_call_slot' => 'USR-Chassis-Call-Slot',
+ 'rate_limit_burst' => 'Rate-Limit-Burst',
+ 'cisco_route_ip' => 'Cisco-Route-IP',
+ 'xedia_netbios_server' => 'Xedia-NetBios-Server',
+ 'session_error_msg' => 'Session-Error-Msg',
+ 'dhcp_max_leases' => 'DHCP-Max-Leases',
+ 'acc_vpsm_reject_cause' => 'Acc-Vpsm-Reject-Cause',
+ 'user_category' => 'User-Category',
+ 'x_ascend_multicast_rate_' => 'X-Ascend-Multicast-Rate-Limit',
+ 'cvpn3000_ipsec_auth_on_r' => 'CVPN3000-IPSec-Auth-On-Rekey',
+ 'altiga_min_password_leng' => 'Altiga-Min-Password-Length-G',
+ 'bind_type' => 'Bind-Type',
+ 'ascend_tunneling_protoco' => 'Ascend-Tunneling-Protocol',
+ 'cvx_modem_retx_packets' => 'CVX-Modem-ReTx-Packets',
+ 'usr_framed_ipx_route' => 'USR-Framed-IPX-Route',
+ 'rate_limit_rate' => 'Rate-Limit-Rate',
+ 'ascend_atm_connect_vpi' => 'Ascend-ATM-Connect-Vpi',
+ 'connect_info' => 'Connect-Info',
+ 'usr_port_tap_address' => 'USR-Port-Tap-Address',
+ 'usr_simplified_mnp_level' => 'USR-Simplified-MNP-Levels',
+ 'mcast_receivf' => 'Mcast_Receive',
+ 'annex_begin_modulation' => 'Annex-Begin-Modulation',
+ 'usr_pw_usr_ifilter_ip' => 'USR-PW_USR_IFilter_IP',
+ 'ascend_route_appletalk' => 'Ascend-Route-Appletalk',
+ 'ms_chap_lm_enc_pw' => 'MS-CHAP-LM-Enc-PW',
+ 'altiga_ipsec_over_nat_po' => 'Altiga-IPSec-Over-NAT-Port-Num-G',
+ 'itk_isdn_prot' => 'ITK-ISDN-Prot',
+ 'ascend_callback_delay' => 'Ascend-Callback-Delay',
+ 'session_error_code' => 'Session-Error-Code',
+ 'nomadix_endofsession' => 'Nomadix-EndofSession',
+ 'x_ascend_bacp_enable' => 'X-Ascend-BACP-Enable',
+ 'bg_trans_bpdu' => 'BG-Trans-BPDU',
+ 'bind_int_interface_namf' => 'Bind_Int_Interface_Name',
+ 'foundry_privilege_level' => 'Foundry-Privilege-Level',
+ 'huntgroup_name' => 'Huntgroup-Name',
+ 'x_ascend_ipx_alias' => 'X-Ascend-IPX-Alias',
+ 'tunnel_l2f_second_passwp' => 'Tunnel_L2F_Second_Password',
+ 'xedia_dns_server' => 'Xedia-DNS-Server',
+ 'usr_ipx_wan' => 'USR-IPX-WAN',
+ 'annex_addr_resolution_se' => 'Annex-Addr-Resolution-Servers',
+ 'acct_output_octets_65' => 'Acct_Output_Octets_64',
+ 'menu' => 'Menu',
+ 'erx_tunnel_nas_port_meth' => 'ERX-Tunnel-Nas-Port-Method',
+ 'aat_output_octets_diff' => 'AAT-Output-Octets-Diff',
+ 'x_ascend_fr_direct_dlci' => 'X-Ascend-FR-Direct-DLCI',
+ 'acct_status_type' => 'Acct-Status-Type',
+ 'ascend_port_redir_server' => 'Ascend-Port-Redir-Server',
+ 'telebit_port_name' => 'Telebit-Port-Name',
+ 'acc_dns_server_sec' => 'Acc-Dns-Server-Sec',
+ 'cvx_modem_remote_retrain' => 'CVX-Modem-Remote-Retrains',
+ 'ascend_minimum_channels' => 'Ascend-Minimum-Channels',
+ 'ascend_ipx_route' => 'Ascend-IPX-Route',
+ 'ascend_telnet_profile' => 'Ascend-Telnet-Profile',
+ 'usr_call_connect_in_gmt' => 'USR-Call-Connect-in-GMT',
+ 'usr_cusr_hat_script_rule' => 'USR-CUSR-hat-Script-Rules',
+ 'x_ascend_dba_monitor' => 'X-Ascend-DBA-Monitor',
+ 'response_packet_type' => 'Response-Packet-Type',
+ 'usr_event_id' => 'USR-Event-Id',
+ 'cvpn3000_ipsec_over_udp_' => 'CVPN3000-IPSec-Over-UDP-Port',
+ 'ascend_inc_channel_count' => 'Ascend-Inc-Channel-Count',
+ 'usr_send_script3' => 'USR-Send-Script3',
+ 'annex_pre_input_packets' => 'Annex-Pre-Input-Packets',
+ 'framed_callback_id' => 'Framed-Callback-Id',
+ 'xedia_client_access_netw' => 'Xedia-Client-Access-Network',
+ 'arap_zone_access' => 'ARAP-Zone-Access',
+ 'ascend_port_redir_portnu' => 'Ascend-Port-Redir-Portnum',
+ 'service_type' => 'Service-Type',
+ 'usr_nfas_id' => 'USR-NFAS-ID',
+ 'shiva_calling_number' => 'Shiva-Calling-Number',
+ 'ascend_user_acct_host' => 'Ascend-User-Acct-Host',
+ 'tunnel_session_auth_serv' => 'Tunnel-Session-Auth-Service-Grp',
+ 'juniper_deny_commands' => 'Juniper-Deny-Commands',
+ 'ascend_fr_link_mgt' => 'Ascend-FR-Link-Mgt',
+ 'nokia_imsi' => 'Nokia-IMSI',
+ 'quintum_h323_prompt_id' => 'Quintum-h323-prompt-id',
+ 'cvpn3000_require_individ' => 'CVPN3000-Require-Individual-User-Auth',
+ 'tunnel_retransmiu' => 'Tunnel_Retransmit',
+ 'source_validatioo' => 'Source_Validation',
+ 'sip_to' => 'Sip-To',
+ 'ms_primary_nbns_server' => 'MS-Primary-NBNS-Server',
+ 'quintum_avpair' => 'Quintum-AVPair',
+ 'ascend_transit_number' => 'Ascend-Transit-Number',
+ 'ascend_cache_refresh' => 'Ascend-Cache-Refresh',
+ 'ascend_user_acct_type' => 'Ascend-User-Acct-Type',
+ 'usr_num_fax_pages_proces' => 'USR-Num-Fax-Pages-Processed',
+ 'usr_mic' => 'USR-MIC',
+ 'usr_failure_to_connect_r' => 'USR-Failure-to-Connect-Reason',
+ 'cisco_fax_auth_status' => 'Cisco-Fax-Auth-Status',
+ 'bind_dot1q_vlan_tag_ie' => 'Bind_Dot1q_Vlan_Tag_Id',
+ 'ms_chap2_success' => 'MS-CHAP2-Success',
+ 'erx_tunnel_virtual_route' => 'ERX-Tunnel-Virtual-Router',
+ 'cisco_idle_limit' => 'Cisco-Idle-Limit',
+ 'ascend_pw_lifetime' => 'Ascend-PW-Lifetime',
+ 'cvpn3000_access_hours' => 'CVPN3000-Access-Hours',
+ 'bintec_sapcirctable' => 'BinTec-sapCircTable',
+ 'usr_packet_bus_session' => 'USR-Packet-Bus-Session',
+ 'acct_input_packets_64' => 'Acct-Input-Packets-64',
+ 'ascend_x25_pad_x3_parame' => 'Ascend-X25-Pad-X3-Parameters',
+ 'usr_secondary_nbns_serve' => 'USR-Secondary_NBNS_Server',
+ 'ascend_modem_slotno' => 'Ascend-Modem-SlotNo',
+ 'digest_qop' => 'Digest-QOP',
+ 'usr_characters_received' => 'USR-Characters-Received',
+ 'rate_limit_ratf' => 'Rate_Limit_Rate',
+ 'ms_bap_usage' => 'MS-BAP-Usage',
+ 'cisco_data_filter' => 'Cisco-Data-Filter',
+ 'usr_simplified_v42bis_us' => 'USR-Simplified-V42bis-Usage',
+ 'h323_setup_time' => 'h323-setup-time',
+ 'annex_wan_number' => 'Annex-Wan-Number',
+ 'cvx_vpop_id' => 'CVX-VPOP-ID',
+ 'usr_pw_tunnel_authentica' => 'USR-PW_Tunnel_Authentication',
+ 'le_nat_outsource_inmap' => 'LE-NAT-Outsource-Inmap',
+ 'cvx_modem_begin_recv_lin' => 'CVX-Modem-Begin-Recv-Line-Lvl',
+ 'telebit_login_command' => 'Telebit-Login-Command',
+ 'cisco_command_code' => 'Cisco-Command-Code',
+ 'itk_ppp_auth_type' => 'ITK-PPP-Auth-Type',
+ 'bintec_qosiftable' => 'BinTec-qosIfTable',
+ 'x_ascend_mpp_idle_percen' => 'X-Ascend-MPP-Idle-Percent',
+ 'usr_sap_filter_in' => 'USR-SAP-Filter-In',
+ 'framed_appletalk_link' => 'Framed-AppleTalk-Link',
+ 'tunnel_domaio' => 'Tunnel_Domain',
+ 'usr_ipx' => 'USR-IPX',
+ 'nas_real_poru' => 'NAS_Real_Port',
+ 'shiva_connect_reason' => 'Shiva-Connect-Reason',
+ 'x_ascend_pre_output_octe' => 'X-Ascend-Pre-Output-Octets',
+ 'cisco_ppp_vj_slot_comp' => 'Cisco-PPP-VJ-Slot-Comp',
+ 'freeradius_proxied_to' => 'Freeradius-Proxied-To',
+ 'ascend_atm_vpi' => 'Ascend-ATM-Vpi',
+ 'acc_ml_mlx_admin_state' => 'Acc-ML-MLX-Admin-State',
+ 'cvx_modem_snr' => 'CVX-Modem-SNR',
+ 'usr_igmp_robustness' => 'USR-IGMP-Robustness',
+ 'annex_rate_reneg_req_rcv' => 'Annex-Rate-Reneg-Req-Rcvd',
+ 'add_prefix' => 'Add-Prefix',
+ 'x_ascend_call_by_call' => 'X-Ascend-Call-By-Call',
+ 'usr_last_callers_number_' => 'USR-Last-Callers-Number-ANI',
+ 'postauth_type' => 'PostAuth-Type',
+ 'pvc_circuit_paddinh' => 'PVC_Circuit_Padding',
+ 'usr_at_rtmp_input_filter' => 'USR-AT-RTMP-Input-Filter',
+ 'erx_igmp_enable' => 'ERX-Igmp-Enable',
+ 'bind_bypass_contexu' => 'Bind_Bypass_Context',
+ 'x_ascend_num_in_multilin' => 'X-Ascend-Num-In-Multilink',
+ 'usr_pw_packet' => 'USR-PW_Packet',
+ 'dialback_no' => 'Dialback-No',
+ 'ascend_ip_tos_precedence' => 'Ascend-IP-TOS-Precedence',
+ 'cvpn5000_vpn_password' => 'CVPN5000-VPN-Password',
+ 'annex_cli_filter' => 'Annex-CLI-Filter',
+ 'x_ascend_dial_number' => 'X-Ascend-Dial-Number',
+ 'usr_iwf_call_identifier' => 'USR-IWF-Call-Identifier',
+ 'ms_secondary_dns_server' => 'MS-Secondary-DNS-Server',
+ 'shiva_type_of_service' => 'Shiva-Type-Of-Service',
+ 'bind_ses_context' => 'Bind-Ses-Context',
+ 'acc_reason_code' => 'Acc-Reason-Code',
+ 'ms_chap_cpw_1' => 'MS-CHAP-CPW-1',
+ 'wispr_bandwidth_max_down' => 'WISPr-Bandwidth-Max-Down',
+ 'h323_call_type' => 'h323-call-type',
+ 'bind_bypass_bypast' => 'Bind_Bypass_Bypass',
+ 'usr_number_of_link_timeo' => 'USR-Number-of-Link-Timeouts',
+ 'ascend_fr_08_mode' => 'Ascend-FR-08-Mode',
+ 'usr_calling_party_number' => 'USR-Calling-Party-Number',
+ 'usr_reply_script2' => 'USR-Reply-Script2',
+ 'usr_security_login_limit' => 'USR-Security-Login-Limit',
+ 'cisco_link_compression' => 'Cisco-Link-Compression',
+ 'ascend_vrouter_name' => 'Ascend-VRouter-Name',
+ 'erx_ppp_auth_protocol' => 'ERX-PPP-Auth-Protocol',
+ 'x_ascend_call_block_dura' => 'X-Ascend-Call-Block-Duration',
+ 'usr_modem_setup_time' => 'USR-Modem-Setup-Time',
+ 'pppoe_urm' => 'PPPOE_URL',
+ 'cisco_ip_direct' => 'Cisco-IP-Direct',
+ 'x_ascend_temporary_rtes' => 'X-Ascend-Temporary-Rtes',
+ 'ascend_x25_pad_alias_3' => 'Ascend-X25-Pad-Alias-3',
+ 'annex_multilink_id' => 'Annex-Multilink-Id',
+ 'mcast_maxgroupt' => 'Mcast_MaxGroups',
+ 'configuration_token' => 'Configuration-Token',
+ 'ascend_h323_conference_i' => 'Ascend-H323-Conference-Id',
+ 'ascend_ipx_header_compre' => 'Ascend-IPX-Header-Compression',
+ 'stripped_user_name' => 'Stripped-User-Name',
+ 'usr_ipx_rip_output_filte' => 'USR-IPX-RIP-Output-Filter',
+ 'cisco_call_filter' => 'Cisco-Call-Filter',
+ 'nas_ipv6_address' => 'NAS-IPv6-Address',
+ 'termination_menu' => 'Termination-Menu',
+ 'ascend_shared_profile_en' => 'Ascend-Shared-Profile-Enable',
+ 'port_message' => 'Port-Message',
+ 'erx_ingress_policy_name' => 'ERX-Ingress-Policy-Name',
+ 'acc_service_profile' => 'Acc-Service-Profile',
+ 'ascend_bir_proxy' => 'Ascend-BIR-Proxy',
+ 'aat_ppp_address' => 'AAT-PPP-Address',
+ 'usr_mbi_ct_pri_card_span' => 'USR-Mbi_Ct_PRI_Card_Span_Line',
+ 'ascend_x25_nui_prompt' => 'Ascend-X25-Nui-Prompt',
+ 'itk_modem_pool_id' => 'ITK-Modem-Pool-Id',
+ 'usr_compression_reset_mo' => 'USR-Compression-Reset-Mode',
+ 'usr_unauthenticated_time' => 'USR-Unauthenticated-Time',
+ 'ascend_multicast_gleave_' => 'Ascend-Multicast-GLeave-Delay',
+ 'acc_callback_cbcp_type' => 'Acc-Callback-CBCP-Type',
+ 'medium_typf' => 'Medium_Type',
+ 'login_service' => 'Login-Service',
+ 'itk_username_prompt' => 'ITK-Username-Prompt',
+ 'ascend_dial_number' => 'Ascend-Dial-Number',
+ 'framed_ipv6_route' => 'Framed-IPv6-Route',
+ 'x_ascend_remote_addr' => 'X-Ascend-Remote-Addr',
+ 'usr_call_end_date_time' => 'USR-Call-End-Date-Time',
+ 'bind_dot1q_slot' => 'Bind-Dot1q-Slot',
+ 'le_connect_detail' => 'LE-Connect-Detail',
+ 'annex_user_level' => 'Annex-User-Level',
+ 'tunnel_dnis' => 'Tunnel-DNIS',
+ 'assigned_ip_address' => 'Assigned-IP-Address',
+ 'acc_bridging_support' => 'Acc-Bridging-Support',
+ 'usr_channel' => 'USR-Channel',
+ 'arap_security_data' => 'ARAP-Security-Data',
+ 'bind_auth_service_grp' => 'Bind-Auth-Service-Grp',
+ 'cisco_abort_cause' => 'Cisco-Abort-Cause',
+ 'bg_span_dit' => 'BG_Span_Dis',
+ 'h323_voice_quality' => 'h323-voice-quality',
+ 'lac_real_port_typf' => 'LAC_Real_Port_Type',
+ 'usr_channel_connected_to' => 'USR-Channel-Connected-To',
+ 'ascend_client_assign_win' => 'Ascend-Client-Assign-WINS',
+ 'redcreek_tunneled_gatewa' => 'RedCreek-Tunneled-Gateway',
+ 'usr_number_of_fallbacks' => 'USR-Number-of-Fallbacks',
+ 'nokia_prepaid_ind' => 'Nokia-Prepaid-Ind',
+ 'nomadix_maxbytesup' => 'Nomadix-MaxBytesUp',
+ 'login_hosu' => 'Login-Host',
+ 'ascend_bir_enable' => 'Ascend-BIR-Enable',
+ 'usr_connect_time_limit' => 'USR-Connect-Time-Limit',
+ 'ascend_presession_time' => 'Ascend-PreSession-Time',
+ 'altiga_simultaneous_logi' => 'Altiga-Simultaneous-Logins-G/U',
+ 'cvpn3000_ipsec_default_d' => 'CVPN3000-IPSec-Default-Domain',
+ 'aat_atm_vci' => 'AAT-ATM-VCI',
+ 'extreme_netlogin_url_des' => 'Extreme-Netlogin-Url-Desc',
+ 'itk_auth_serv_ip' => 'ITK-Auth-Serv-IP',
+ 'erx_alternate_cli_vroute' => 'ERX-Alternate-Cli-Vrouter-Name',
+ 'framed_compression' => 'Framed-Compression',
+ 'ascend_svc_enabled' => 'Ascend-SVC-Enabled',
+ 'proxy_state' => 'Proxy-State',
+ 'aat_vrouter_name' => 'AAT-Vrouter-Name',
+ 'usr_rmmie_pwrlvl_farecho' => 'USR-RMMIE-PwrLvl-FarEcho-Canc',
+ 'nas_poru' => 'NAS-Port',
+ 'wispr_location_name' => 'WISPr-Location-Name',
+ 'digest_user_name' => 'Digest-User-Name',
+ 'ascend_modem_shelfno' => 'Ascend-Modem-ShelfNo',
+ 'shasta_user_privilege' => 'Shasta-User-Privilege',
+ 'bind_auth_protocol' => 'Bind-Auth-Protocol',
+ 'ascend_home_agent_passwo' => 'Ascend-Home-Agent-Password',
+ 'acct_interim_interval' => 'Acct-Interim-Interval',
+ 'ascend_history_weigh_typ' => 'Ascend-History-Weigh-Type',
+ 'ms_link_drop_time_limit' => 'MS-Link-Drop-Time-Limit',
+ 'hint' => 'Hint',
+ 'x_ascend_target_util' => 'X-Ascend-Target-Util',
+ 'acc_access_partition' => 'Acc-Access-Partition',
+ 'usr_power_supply_number' => 'USR-Power-Supply-Number',
+ 'x_ascend_multilink_id' => 'X-Ascend-Multilink-ID',
+ 'redcreek_tunneled_domain' => 'RedCreek-Tunneled-DomainName',
+ 'nomadix_bw_down' => 'Nomadix-Bw-Down',
+ 'acc_ipx_compression' => 'Acc-Ipx-Compression',
+ 'quintum_h323_setup_time' => 'Quintum-h323-setup-time',
+ 'cisco_target_util' => 'Cisco-Target-Util',
+ 'acc_ip_gateway_sec' => 'Acc-Ip-Gateway-Sec',
+ 'ascend_dsl_cir_xmit_limi' => 'Ascend-Dsl-CIR-Xmit-Limit',
+ 'ascend_ip_pool_definitio' => 'Ascend-IP-Pool-Definition',
+ 'bind_sub_user_at_contexu' => 'Bind_Sub_User_At_Context',
+ 'itk_dest_no' => 'ITK-Dest-No',
+ 'usr_connect_time' => 'USR-Connect-Time',
+ 'usr_call_start_date_time' => 'USR-Call-Start-Date-Time',
+ 'altiga_l2tp_encryption_g' => 'Altiga-L2TP-Encryption-G',
+ 'ascend_auth_delay' => 'Ascend-Auth-Delay',
+ 'ascend_x25_pad_x3_profil' => 'Ascend-X25-Pad-X3-Profile',
+ 'ascend_access_intercepta' => 'Ascend-Access-Intercept-Log',
+ 'ascend_home_agent_udp_po' => 'Ascend-Home-Agent-UDP-Port',
+ 'bind_tun_context' => 'Bind-Tun-Context',
+ 'dialback_name' => 'Dialback-Name',
+ 'h323_redirect_ip_address' => 'h323-redirect-ip-address',
+ 'annex_keypress_timeout' => 'Annex-Keypress-Timeout',
+ 'x_ascend_home_network_na' => 'X-Ascend-Home-Network-Name',
+ 'ascend_x25_pad_alias_1' => 'Ascend-X25-Pad-Alias-1',
+ 'ascend_call_attempt_limi' => 'Ascend-Call-Attempt-Limit',
+ 'quintum_h323_currency_ty' => 'Quintum-h323-currency-type',
+ 'ms_chap_response' => 'MS-CHAP-Response',
+ 'st_secondary_nbns_server' => 'ST-Secondary-NBNS-Server',
+ 'x_ascend_history_weigh_t' => 'X-Ascend-History-Weigh-Type',
+ 'usr_max_channels' => 'USR-Max-Channels',
+ 'ascend_fr_dte_n393' => 'Ascend-FR-DTE-N393',
+ 'ascend_pre_input_octets' => 'Ascend-Pre-Input-Octets',
+ 'erx_atm_mbs' => 'ERX-Atm-MBS',
+ 'cvpn3000_simultaneous_lo' => 'CVPN3000-Simultaneous-Logins',
+ 'juniper_allow_commands' => 'Juniper-Allow-Commands',
+ 'usr_line_reversals' => 'USR-Line-Reversals',
+ 'itk_users_default_pw' => 'ITK-Users-Default-Pw',
+ 'x_ascend_third_prompt' => 'X-Ascend-Third-Prompt',
+ 'cisco_fax_msg_id' => 'Cisco-Fax-Msg-Id',
+ 'x_ascend_pw_warntime' => 'X-Ascend-PW-Warntime',
+ 'ascend_data_filter' => 'Ascend-Data-Filter',
+ 'framed_address' => 'Framed-Address',
+ 'context_name' => 'Context-Name',
+ 'usr_send_script2' => 'USR-Send-Script2',
+ 'ms_arap_pw_change_reason' => 'MS-ARAP-PW-Change-Reason',
+ 'tunnel_session_auth_cty' => 'Tunnel_Session_Auth_Ctx',
+ 'acct_session_id' => 'Acct-Session-Id',
+ 'annex_port' => 'Annex-Port',
+ 'quintum_h323_call_origin' => 'Quintum-h323-call-origin',
+ 'erx_cli_initial_access_l' => 'ERX-Cli-Initial-Access-Level',
+ 'x_ascend_shared_profile_' => 'X-Ascend-Shared-Profile-Enable',
+ 'tunnel_cmd_timeouu' => 'Tunnel_Cmd_Timeout',
+ 'initial_modulation_type' => 'Initial-Modulation-Type',
+ 'ascend_h323_gatekeeper' => 'Ascend-H323-Gatekeeper',
+ 'x_ascend_fcp_parameter' => 'X-Ascend-FCP-Parameter',
+ 'multi_link_flag' => 'Multi-Link-Flag',
+ 'tunnel_type' => 'Tunnel-Type',
+ 'erx_output_gigapkts' => 'ERX-Output-Gigapkts',
+ 'ascend_idle_limit' => 'Ascend-Idle-Limit',
+ 'ns_user_group' => 'NS-User-Group',
+ 'password_retry' => 'Password-Retry',
+ 'h323_remote_address' => 'h323-remote-address',
+ 'erx_atm_service_category' => 'ERX-Atm-Service-Category',
+ 'acct_input_packets' => 'Acct-Input-Packets',
+ 'h323_disconnect_time' => 'h323-disconnect-time',
+ 'usr_syslog_tap' => 'USR-Syslog-Tap',
+ 'telebit_accounting_info' => 'Telebit-Accounting-Info',
+ 'ascend_billing_number' => 'Ascend-Billing-Number',
+ 'ascend_tunnel_vrouter_na' => 'Ascend-Tunnel-VRouter-Name',
+ 'ms_mppe_encryption_type' => 'MS-MPPE-Encryption-Type',
+ 'quintum_h323_credit_amou' => 'Quintum-h323-credit-amount',
+ 'acc_ace_token' => 'Acc-Ace-Token',
+ 'ascend_assign_ip_pool' => 'Ascend-Assign-IP-Pool',
+ 'annex_end_modulation' => 'Annex-End-Modulation',
+ 'usr_routing_protocol' => 'USR-Routing-Protocol',
+ 'cvx_assign_ip_pool' => 'CVX-Assign-IP-Pool',
+ 'usr_rad_location_type' => 'USR-Rad-Location-Type',
+ 'usr_rmmie_pwrlvl_noise_l' => 'USR-RMMIE-PwrLvl-Noise-Lvl',
+ 'usr_characters_sent' => 'USR-Characters-Sent',
+ 'usr_mp_edo_hiper' => 'USR-MP-EDO-HIPER',
+ 'ascend_x25_nui_password_' => 'Ascend-X25-Nui-Password-Prompt',
+ 'annex_host_restrict' => 'Annex-Host-Restrict',
+ 'user_service_type' => 'User-Service-Type',
+ 'acct_multi_session_id' => 'Acct-Multi-Session-Id',
+ 'ms_chap_cpw_2' => 'MS-CHAP-CPW-2',
+ 'x_ascend_secondary_home_' => 'X-Ascend-Secondary-Home-Agent',
+ 'x_ascend_dialout_allowed' => 'X-Ascend-Dialout-Allowed',
+ 'ascend_connect_progress' => 'Ascend-Connect-Progress',
+ 'x_ascend_ara_pw' => 'X-Ascend-Ara-PW',
+ 'cisco_fax_modem_time' => 'Cisco-Fax-Modem-Time',
+ 'sql_group' => 'Sql-Group',
+ 'annex_multicast_rate_lim' => 'Annex-Multicast-Rate-Limit',
+ 'cvpn3000_user_auth_servg' => 'CVPN3000-User-Auth-Server-Secret',
+ 'ns_mta_md5_password' => 'NS-MTA-MD5-Password',
+ 'annex_addr_resolution_pr' => 'Annex-Addr-Resolution-Protocol',
+ 'callback_number' => 'Callback-Number',
+ 'cvx_multilink_match_info' => 'CVX-Multilink-Match-Info',
+ 'tunnel_max_tunnelt' => 'Tunnel_Max_Tunnels',
+ 'tunnel_local_namf' => 'Tunnel_Local_Name',
+ 'quintum_h323_conf_id' => 'Quintum-h323-conf-id',
+ 'acct_output_packets_64' => 'Acct-Output-Packets-64',
+ 'annex_signal_to_noise_ra' => 'Annex-Signal-to-Noise-Ratio',
+ 'acct_output_packets_65' => 'Acct_Output_Packets_64',
+ 'x_ascend_user_acct_key' => 'X-Ascend-User-Acct-Key',
+ 'erx_dial_out_number' => 'ERX-Dial-Out-Number',
+ 'ascend_modem_portno' => 'Ascend-Modem-PortNo',
+ 'ascend_assign_ip_server' => 'Ascend-Assign-IP-Server',
+ 'ascend_fcp_parameter' => 'Ascend-FCP-Parameter',
+ 'usr_chassis_temp_thresho' => 'USR-Chassis-Temp-Threshold',
+ 'usr_mpip_tunnel_originat' => 'USR-MPIP-Tunnel-Originator',
+ 'tunnel_rate_limit_bursu' => 'Tunnel_Rate_Limit_Burst',
+ 'client_ip_address' => 'Client-IP-Address',
+ 'le_nat_tcp_session_timeo' => 'LE-NAT-TCP-Session-Timeout',
+ 'quintum_h323_redirect_ip' => 'Quintum-h323-redirect-ip-address',
+ 'ms_acct_eap_type' => 'MS-Acct-EAP-Type',
+ 'usr_rmmie_x2_status' => 'USR-RMMIE-x2-Status',
+ 'x_ascend_user_acct_type' => 'X-Ascend-User-Acct-Type',
+ 'shiva_customer_id' => 'Shiva-Customer-Id',
+ 'pvc_encapsulation_typf' => 'PVC_Encapsulation_Type',
+ 'st_acct_vc_connection_id' => 'ST-Acct-VC-Connection-Id',
+ 'lac_real_port' => 'LAC-Real-Port',
+ 'h323_connect_time' => 'h323-connect-time',
+ 'usr_vpn_gw_location_id' => 'USR-VPN-GW-Location-Id',
+ 'old_password' => 'Old-Password',
+ 'x_ascend_if_netmask' => 'X-Ascend-IF-Netmask',
+ 'add_suffix' => 'Add-Suffix',
+ 'lac_port_typf' => 'LAC_Port_Type',
+ 'acc_ip_pool_name' => 'Acc-Ip-Pool-Name',
+ 'usr_terminal_type' => 'USR-Terminal-Type',
+ 'usr_spoofing' => 'USR-Spoofing',
+ 'erx_tunnel_password' => 'ERX-Tunnel-Password',
+ 'ascend_inter_arrival_jit' => 'Ascend-Inter-Arrival-Jitter',
+ 'ascend_call_block_durati' => 'Ascend-Call-Block-Duration',
+ 'itk_channel_binding' => 'ITK-Channel-Binding',
+ 'usr_server_time' => 'USR-Server-Time',
+ 'ascend_assign_ip_client' => 'Ascend-Assign-IP-Client',
+ 'erx_pppoe_max_sessions' => 'ERX-Pppoe-Max-Sessions',
+ 'cvx_multilink_group_numb' => 'CVX-Multilink-Group-Number',
+ 'x_ascend_client_assign_d' => 'X-Ascend-Client-Assign-DNS',
+ 'erx_pppoe_url' => 'ERX-Pppoe-Url',
+ 'police_ratf' => 'Police_Rate',
+ 'ascend_data_svc' => 'Ascend-Data-Svc',
+ 'annex_authen_servers' => 'Annex-Authen-Servers',
+ 'nomadix_bw_up' => 'Nomadix-Bw-Up',
+ 'cvx_modem_data_compressi' => 'CVX-Modem-Data-Compression',
+ 'shiva_link_speed' => 'Shiva-Link-Speed',
+ 'usr_reply_script6' => 'USR-Reply-Script6',
+ 'usr_expansion_algorithm' => 'USR-Expansion-Algorithm',
+ 'cabletron_protocol_calla' => 'Cabletron-Protocol-Callable',
+ 'cisco_data_rate' => 'Cisco-Data-Rate',
+ 'usr_primary_dns_server' => 'USR-Primary_DNS_Server',
+ 'juniper_deny_configurati' => 'Juniper-Deny-Configuration',
+ 'ascend_target_util' => 'Ascend-Target-Util',
+ 'digest_method' => 'Digest-Method',
+ 'altiga_ipsec_split_tunne' => 'Altiga-IPSec-Split-Tunnel-List-G',
+ 'erx_alternate_cli_access' => 'ERX-Alternate-Cli-Access-Level',
+ 'x_ascend_event_type' => 'X-Ascend-Event-Type',
+ 'usr_q931_call_reference_' => 'USR-Q931-Call-Reference-Value',
+ 'usr_mp_mrru' => 'USR-MP-MRRU',
+ 'cvx_ipsvc_mask' => 'CVX-IPSVC-Mask',
+ 'bind_bypass_context' => 'Bind-Bypass-Context',
+ 'usr_rmmie_last_update_ev' => 'USR-RMMIE-Last-Update-Event',
+ 'no_such_attribute' => 'No-Such-Attribute',
+ 'acct_mcast_out_packets' => 'Acct-Mcast-Out-Packets',
+ 'tunnel_medium_type' => 'Tunnel-Medium-Type',
+ 'quintum_h323_remote_addr' => 'Quintum-h323-remote-address',
+ 'acc_callback_delay' => 'Acc-Callback-Delay',
+ 'acct_input_octets_64' => 'Acct-Input-Octets-64',
+ 'ascend_base_channel_coun' => 'Ascend-Base-Channel-Count',
+ 'ascend_atm_connect_vci' => 'Ascend-ATM-Connect-Vci',
+ 'erx_primary_dns' => 'ERX-Primary-Dns',
+ 'altiga_ipsec_over_nat_g' => 'Altiga-IPSec-Over-NAT-G',
+ 'cvx_multicast_rate_limit' => 'CVX-Multicast-Rate-Limit',
+ 'ascend_xmit_rate' => 'Ascend-Xmit-Rate',
+ 'ms_new_arap_password' => 'MS-New-ARAP-Password',
+ 'usr_call_error_code' => 'USR-Call-Error-Code',
+ 'acct_output_octets' => 'Acct-Output-Octets',
+ 'ascend_client_primary_wi' => 'Ascend-Client-Primary-WINS',
+ 'cvpn3000_primary_wins' => 'CVPN3000-Primary-WINS',
+ 'bintec_ipextrttable' => 'BinTec-ipExtRtTable',
+ 'cisco_fax_mdn_flag' => 'Cisco-Fax-Mdn-Flag',
+ 'ascend_destination_nas_p' => 'Ascend-Destination-Nas-Port',
+ 'ascend_num_in_multilink' => 'Ascend-Num-In-Multilink',
+ 'digest_attributes' => 'Digest-Attributes',
+ 'cvpn3000_ipsec_tunnel_ty' => 'CVPN3000-IPSec-Tunnel-Type',
+ 'x_ascend_number_sessions' => 'X-Ascend-Number-Sessions',
+ 'usr_ip_rip_output_filter' => 'USR-IP-RIP-Output-Filter',
+ 'tunnel_police_bursu' => 'Tunnel_Police_Burst',
+ 'redcreek_tunneled_wins_s' => 'RedCreek-Tunneled-WINS-Server1',
+ 'usr_blocks_sent' => 'USR-Blocks-Sent',
+ 'erx_cli_allow_all_vr_acc' => 'ERX-Cli-Allow-All-VR-Access',
+ 'tunnel_police_ratf' => 'Tunnel_Police_Rate',
+ 'usr_ids0_call_type' => 'USR-IDS0-Call-Type',
+ 'acc_ccp_option' => 'Acc-Ccp-Option',
+ 'ascend_client_gateway' => 'Ascend-Client-Gateway',
+ 'cvx_maximum_channels' => 'CVX-Maximum-Channels',
+ 'bg_aging_timf' => 'BG_Aging_Time',
+ 'annex_secondary_dns_serv' => 'Annex-Secondary-DNS-Server',
+ 'le_ipsec_passive_profile' => 'LE-IPSec-Passive-Profile',
+ 'usr_chassis_call_span' => 'USR-Chassis-Call-Span',
+ 'aat_client_primary_wins_' => 'AAT-Client-Primary-WINS-NBNS',
+ 'h323_currency' => 'h323-currency',
+ 'password' => 'Password',
+ 'le_nat_log_options' => 'LE-NAT-Log-Options',
+ 'usr_fallback_limit' => 'USR-Fallback-Limit',
+ 'x_ascend_ppp_address' => 'X-Ascend-PPP-Address',
+ 'suffix' => 'Suffix',
+ 'usr_multicast_receive' => 'USR-Multicast-Receive',
+ 'client_dns_sec' => 'Client-DNS-Sec',
+ 'annex_product_name' => 'Annex-Product-Name',
+ 'cisco_pw_lifetime' => 'Cisco-PW-Lifetime',
+ 'x_ascend_fr_dce_n393' => 'X-Ascend-FR-DCE-N393',
+ 'x_ascend_ts_idle_limit' => 'X-Ascend-TS-Idle-Limit',
+ 'mcast_send' => 'Mcast-Send',
+ 'x_ascend_primary_home_ag' => 'X-Ascend-Primary-Home-Agent',
+ 'tunnel_max_sessiont' => 'Tunnel_Max_Sessions',
+ 'pppoe_motm' => 'PPPOE-MOTM',
+ 'usr_pw_usr_ifilter_ipx' => 'USR-PW_USR_IFilter_IPX',
+ 'ms_ras_version' => 'MS-RAS-Version',
+ 'ascend_source_ip_check' => 'Ascend-Source-IP-Check',
+ 'bintec_ospfiftable' => 'BinTec-ospfIfTable',
+ 'acc_ml_call_threshold' => 'Acc-ML-Call-Threshold',
+ 'x_ascend_modem_slotno' => 'X-Ascend-Modem-SlotNo',
+ 'ascend_menu_item' => 'Ascend-Menu-Item',
+ 'callback_id' => 'Callback-Id',
+ 'framed_ipx_network' => 'Framed-IPX-Network',
+ 'altiga_pptp_encryption_g' => 'Altiga-PPTP-Encryption-G',
+ 'ascend_x25_reverse_charg' => 'Ascend-X25-Reverse-Charging',
+ 'ascend_user_acct_key' => 'Ascend-User-Acct-Key',
+ 'x_ascend_pw_lifetime' => 'X-Ascend-PW-Lifetime',
+ 'user_name_is_star' => 'User-Name-Is-Star',
+ 'nomadix_url_redirection' => 'Nomadix-URL-Redirection',
+ 'framed_pool' => 'Framed-Pool',
+ 'x_ascend_authen_alias' => 'X-Ascend-Authen-Alias',
+ 'cisco_fax_dsn_address' => 'Cisco-Fax-Dsn-Address',
+ 'ms_primary_dns_server' => 'MS-Primary-DNS-Server',
+ 'acc_dialout_auth_usernam' => 'Acc-Dialout-Auth-Username',
+ 'realm' => 'Realm',
+ 'arap_features' => 'ARAP-Features',
+ 'bind_auth_protocom' => 'Bind_Auth_Protocol',
+ 'acc_connect_tx_speed' => 'Acc-Connect-Tx-Speed',
+ 'usr_chassis_temperature' => 'USR-Chassis-Temperature',
+ 'altiga_ipsec_mode_config' => 'Altiga-IPSec-Mode-Config-G',
+ 'ascend_home_agent_ip_add' => 'Ascend-Home-Agent-IP-Addr',
+ 'x_ascend_xmit_rate' => 'X-Ascend-Xmit-Rate',
+ 'cvpn3000_secondary_dns' => 'CVPN3000-Secondary-DNS',
+ 'x_ascend_send_passwd' => 'X-Ascend-Send-Passwd',
+ 'bind_int_contexu' => 'Bind_Int_Context',
+ 'cisco_fax_account_id_ori' => 'Cisco-Fax-Account-Id-Origin',
+ 'le_modem_info' => 'LE-Modem-Info',
+ 'ascend_ipx_peer_mode' => 'Ascend-IPX-Peer-Mode',
+ 'juniper_local_user_name' => 'Juniper-Local-User-Name',
+ 'tunnel_rate_limit_rate' => 'Tunnel-Rate-Limit-Rate',
+ 'quintum_h323_credit_time' => 'Quintum-h323-credit-time',
+ 'acc_modem_modulation_typ' => 'Acc-Modem-Modulation-Type',
+ 'x_ascend_seconds_of_hist' => 'X-Ascend-Seconds-Of-History',
+ 'ascend_dhcp_pool_number' => 'Ascend-DHCP-Pool-Number',
+ 'redcreek_tunneled_ip_net' => 'RedCreek-Tunneled-IP-Netmask',
+ 'x_ascend_callback' => 'X-Ascend-Callback',
+ 'usr_iwf_ip_address' => 'USR-IWF-IP-Address',
+ 'aat_input_octets_diff' => 'AAT-Input-Octets-Diff',
+ 'nas_port_id' => 'NAS-Port-Id',
+ 'le_advice_of_charge' => 'LE-Advice-of-Charge',
+ 'x_ascend_dhcp_pool_numbe' => 'X-Ascend-DHCP-Pool-Number',
+ 'ascend_add_seconds' => 'Ascend-Add-Seconds',
+ 'annex_transmit_speed' => 'Annex-Transmit-Speed',
+ 'usr_port_tap' => 'USR-Port-Tap',
+ 'usr_at_call_input_filter' => 'USR-AT-Call-Input-Filter',
+ 'framed_ipv6_pool' => 'Framed-IPv6-Pool',
+ 'ascend_qos_downstream' => 'Ascend-QOS-Downstream',
+ 'lac_port' => 'LAC-Port',
+ 'tunnel_assignment_id' => 'Tunnel-Assignment-Id',
+ 'acct_mcast_out_octett' => 'Acct_Mcast_Out_Octets',
+ 'ascend_bi_directional_au' => 'Ascend-Bi-Directional-Auth',
+ 'fall_through' => 'Fall-Through',
+ 'cvpn3000_ipsec_ip_compre' => 'CVPN3000-IPSec-IP-Compression',
+ 'cisco_disconnect_cause' => 'Cisco-Disconnect-Cause',
+ 'usr_rad_multicast_routiq' => 'USR-Rad-Multicast-Routing-Bound',
+ 'altiga_tunneling_protoco' => 'Altiga-Tunneling-Protocols-G/U',
+ 'itk_tunnel_prot' => 'ITK-Tunnel-Prot',
+ 'client_dns_sed' => 'Client_DNS_Sec',
+ 'framed_ip_netmask' => 'Framed-IP-Netmask',
+ 'usr_call_reference_numbe' => 'USR-Call-Reference-Number',
+ 'ascend_egress_enabled' => 'Ascend-Egress-Enabled',
+ 'ascend_dsl_rate_mode' => 'Ascend-Dsl-Rate-Mode',
+ 'usr_pw_usr_ofilter_sap' => 'USR-PW_USR_OFilter_SAP',
+ 'bintec_iproutetable' => 'BinTec-ipRouteTable',
+ 'acct_terminate_cause' => 'Acct-Terminate-Cause',
+ 'x_ascend_fr_dte_n393' => 'X-Ascend-FR-DTE-N393',
+ 'ascend_ppp_address' => 'Ascend-PPP-Address',
+ 'erx_maximum_bps' => 'ERX-Maximum-BPS',
+ 'caller_id' => 'Caller-ID',
+ 'bintec_ipfiltertable' => 'BinTec-ipFilterTable',
+ 'x_ascend_base_channel_co' => 'X-Ascend-Base-Channel-Count',
+ 'bind_int_interface_name' => 'Bind-Int-Interface-Name',
+ 'usr_modem_group' => 'USR-Modem-Group',
+ 'cisco_maximum_channels' => 'Cisco-Maximum-Channels',
+ 'erx_ppp_username' => 'ERX-PPP-Username',
+ 'ascend_link_compression' => 'Ascend-Link-Compression',
+ 'annex_retransmitted_pack' => 'Annex-Retransmitted-Packets',
+ 'usr_retrains_granted' => 'USR-Retrains-Granted',
+ 'ascend_dropped_packets' => 'Ascend-Dropped-Packets',
+ 'erx_bearer_type' => 'ERX-Bearer-Type',
+ 'usr_pw_usr_ofilter_ip' => 'USR-PW_USR_OFilter_IP',
+ 'quintum_nas_port' => 'Quintum-NAS-Port',
+ 'x_ascend_pre_output_pack' => 'X-Ascend-Pre-Output-Packets',
+ 'usr_cdma_call_reference_' => 'USR-CDMA-Call-Reference-Number',
+ 'tunnel_function' => 'Tunnel-Function',
+ 'annex_tunnel_authen_mode' => 'Annex-Tunnel-Authen-Mode',
+ 'usr_mp_edo' => 'USR-MP-EDO',
+ 'le_nat_outmap' => 'LE-NAT-Outmap',
+ 'cvpn3000_primary_dns' => 'CVPN3000-Primary-DNS',
+ 'usr_modulation_type' => 'USR-Modulation-Type',
+ 'ascend_calling_id_screen' => 'Ascend-Calling-Id-Screening',
+ 'ascend_maximum_time' => 'Ascend-Maximum-Time',
+ 'user_password' => 'User-Password',
+ 'annex_callback_portlist' => 'Annex-Callback-Portlist',
+ 'cvpn3000_ipsec_split_tun' => 'CVPN3000-IPSec-Split-Tunnel-List',
+ 'annex_pre_output_packets' => 'Annex-Pre-Output-Packets',
+ 'usr_at_call_output_filte' => 'USR-AT-Call-Output-Filter',
+ 'x_ascend_client_primary_' => 'X-Ascend-Client-Primary-DNS',
+ 'tunnel_server_endpoint' => 'Tunnel-Server-Endpoint',
+ 'x_ascend_remove_seconds' => 'X-Ascend-Remove-Seconds',
+ 'cvpn3000_user_auth_serve' => 'CVPN3000-User-Auth-Server-Name',
+ 'arap_password' => 'ARAP-Password',
+ 'x_ascend_assign_ip_serve' => 'X-Ascend-Assign-IP-Server',
+ 'cisco_fax_pages' => 'Cisco-Fax-Pages',
+ 'ms_chap_mppe_keys' => 'MS-CHAP-MPPE-Keys',
+ 'ascend_source_auth' => 'Ascend-Source-Auth',
+ 'group' => 'Group',
+ 'usr_send_script6' => 'USR-Send-Script6',
+ 'le_nat_inmap' => 'LE-NAT-Inmap',
+ 'chap_password' => 'CHAP-Password',
+ 'annex_receive_speed' => 'Annex-Receive-Speed',
+ 'usr_mobileip_home_agent_' => 'USR-MobileIP-Home-Agent-Address',
+ 'bind_l2tp_flow_control' => 'Bind-L2TP-Flow-Control',
+ 'smb_account_ctrl' => 'SMB-Account-CTRL',
+ 'ascend_ip_pool_chaining' => 'Ascend-IP-Pool-Chaining',
+ 'le_admin_group' => 'LE-Admin-Group',
+ 'tunnel_connection_id' => 'Tunnel-Connection-Id',
+ 'tunnel_windox' => 'Tunnel_Window',
+ 'nas_identifier' => 'NAS-Identifier',
+ 'dhcp_max_leaset' => 'DHCP_Max_Leases',
+ 'digest_nonce_count' => 'Digest-Nonce-Count',
+ 'nas_real_port' => 'NAS-Real-Port',
+ 'ms_old_arap_password' => 'MS-Old-ARAP-Password',
+ 'usr_pw_index' => 'USR-PW_Index',
+ 'erx_primary_wins' => 'ERX-Primary-Wins',
+ 'ascend_appletalk_peer_mo' => 'Ascend-Appletalk-Peer-Mode',
+ 'le_ipsec_log_options' => 'LE-IPSec-Log-Options',
+ 'x_ascend_maximum_channel' => 'X-Ascend-Maximum-Channels',
+ 'cvx_ipsvc_aznlvl' => 'CVX-IPSVC-AZNLVL',
+ 'x_ascend_client_secondar' => 'X-Ascend-Client-Secondary-DNS',
+ 'annex_re_chap_timeout' => 'Annex-Re-CHAP-Timeout',
+ 'aat_ip_pool_definition' => 'AAT-IP-Pool-Definition',
+ 'client_dns_pri' => 'Client-DNS-Pri',
+ 'cisco_service_info' => 'Cisco-Service-Info',
+ 'usr_primary_nbns_server' => 'USR-Primary_NBNS_Server',
+ 'aat_atm_direct' => 'AAT-ATM-Direct',
+ 'bind_ses_contexu' => 'Bind_Ses_Context',
+ 'sip_translated_request_u' => 'Sip-Translated-Request-URI',
+ 'acc_acct_on_off_reason' => 'Acc-Acct-On-Off-Reason',
+ 'le_multicast_client' => 'LE-Multicast-Client',
+ 'bind_sub_passwore' => 'Bind_Sub_Password',
+ 'cvpn3000_cisco_ip_phone_' => 'CVPN3000-Cisco-IP-Phone-Bypass',
+ 'ascend_send_passwd' => 'Ascend-Send-Passwd',
+ 'tunnel_remote_namf' => 'Tunnel_Remote_Name',
+ 'cvx_disconnect_cause' => 'CVX-Disconnect-Cause',
+ 'itk_auth_serv_prot' => 'ITK-Auth-Serv-Prot',
+ 'tunnel_context' => 'Tunnel-Context',
+ 'digest_uri' => 'Digest-URI',
+ 'usr_channel_decrement' => 'USR-Channel-Decrement',
+ 'acc_nbns_server_sec' => 'Acc-Nbns-Server-Sec',
+ 'ms_chap_challenge' => 'MS-CHAP-Challenge',
+ 'cisco_assign_ip_pool' => 'Cisco-Assign-IP-Pool',
+ 'ascend_cbcp_mode' => 'Ascend-CBCP-Mode',
+ 'ascend_x25_rpoa' => 'Ascend-X25-Rpoa',
+ 'usr_dtr_false_timeout' => 'USR-DTR-False-Timeout',
+ 'acct_dyn_ac_enu' => 'Acct_Dyn_Ac_Ent',
+ 'usr_physical_state' => 'USR-Physical-State',
+ 'x_ascend_ppp_vj_slot_com' => 'X-Ascend-PPP-VJ-Slot-Comp',
+ 'x_ascend_link_compressio' => 'X-Ascend-Link-Compression',
+ 'ascend_fr_t391' => 'Ascend-FR-T391',
+ 'bind_dot1q_port' => 'Bind-Dot1q-Port',
+ 'ns_secondary_dns' => 'NS-Secondary-DNS',
+ 'altiga_ipsec_tunnel_type' => 'Altiga-IPSec-Tunnel-Type-G',
+ 'lac_port_type' => 'LAC-Port-Type',
+ 'bg_aging_time' => 'BG-Aging-Time',
+ 'erx_atm_scr' => 'ERX-Atm-SCR',
+ 'x_ascend_pre_input_octet' => 'X-Ascend-Pre-Input-Octets',
+ 'cisco_fax_connect_speed' => 'Cisco-Fax-Connect-Speed',
+ 'x_ascend_menu_item' => 'X-Ascend-Menu-Item',
+ 'quintum_h323_voice_quali' => 'Quintum-h323-voice-quality',
+ 'ascend_x25_pad_banner' => 'Ascend-X25-Pad-Banner',
+ 'module_failure_message' => 'Module-Failure-Message',
+ 'h323_gw_id' => 'h323-gw-id',
+ 'h323_preferred_lang' => 'h323-preferred-lang',
+ 'usr_min_compression_size' => 'USR-Min-Compression-Size',
+ 'usr_compression_type' => 'USR-Compression-Type',
+ 'bintec_ipxstaticroutetab' => 'BinTec-ipxStaticRouteTable',
+ 'ascend_dialout_allowed' => 'Ascend-Dialout-Allowed',
+ 'annex_local_username' => 'Annex-Local-Username',
+ 'cisco_pre_input_packets' => 'Cisco-Pre-Input-Packets',
+ 'shiva_function' => 'Shiva-Function',
+ 'ascend_send_secret' => 'Ascend-Send-Secret',
+ 'usr_number_of_blers' => 'USR-Number-of-Blers',
+ 'usr_dte_data_idle_timout' => 'USR-DTE-Data-Idle-Timout',
+ 'usr_card_type' => 'USR-Card-Type',
+ 'x_ascend_connect_progres' => 'X-Ascend-Connect-Progress',
+ 'x_ascend_group' => 'X-Ascend-Group',
+ 'ascend_token_idle' => 'Ascend-Token-Idle',
+ 'erx_qos_profile_interfac' => 'ERX-Qos-Profile-Interface-Type',
+ 'ascend_private_route_tab' => 'Ascend-Private-Route-Table-ID',
+ 'nt_password' => 'NT-Password',
+ 'acct_mcast_in_packets' => 'Acct-Mcast-In-Packets',
+ 'x_ascend_multicast_clien' => 'X-Ascend-Multicast-Client',
+ 'usr_supports_tags' => 'USR-Supports-Tags',
+ 'cvpn3000_authd_user_idle' => 'CVPN3000-Authd-User-Idle-Timeout',
+ 'ascend_number_sessions' => 'Ascend-Number-Sessions',
+ 'x_ascend_add_seconds' => 'X-Ascend-Add-Seconds',
+ 'usr_number_of_upshifts' => 'USR-Number-of-Upshifts',
+ 'proxy_to_realm' => 'Proxy-To-Realm',
+ 'aat_client_secondary_win' => 'AAT-Client-Secondary-WINS-NBNS',
+ 'aat_ip_tos_precedence' => 'AAT-IP-TOS-Precedence',
+ 'acc_callback_num_valid' => 'Acc-Callback-Num-Valid',
+ 'nokia_ggsn_ip_address' => 'Nokia-GGSN-IP-Address',
+ 'acc_access_community' => 'Acc-Access-Community',
+ 'ascend_multicast_rate_li' => 'Ascend-Multicast-Rate-Limit',
+ 'usr_default_dte_data_rat' => 'USR-Default-DTE-Data-Rate',
+ 'usr_rmmie_pwrlvl_nearech' => 'USR-RMMIE-PwrLvl-NearEcho-Canc',
+ 'usr_send_name' => 'USR-Send-Name',
+ 'usr_chassis_slot' => 'USR-Chassis-Slot',
+ 'login_ip_host' => 'Login-IP-Host',
+ 'ascend_netware_timeout' => 'Ascend-Netware-timeout',
+ 'bind_sub_user_at_context' => 'Bind-Sub-User-At-Context',
+ 'vendor_specific' => 'Vendor-Specific',
+ 'ascend_fr_direct_dlci' => 'Ascend-FR-Direct-DLCI',
+ 'ascend_qos_upstream' => 'Ascend-QOS-Upstream',
+ 'aat_user_mac_address' => 'AAT-User-MAC-Address',
+ 'source_validation' => 'Source-Validation',
+ 'x_ascend_token_expiry' => 'X-Ascend-Token-Expiry',
+ 'altiga_ipsec_user_group_' => 'Altiga-IPSec-User-Group-Lock-G',
+ 'ascend_dec_channel_count' => 'Ascend-Dec-Channel-Count',
+ 'assigned_ip_addrest' => 'Assigned_IP_Address',
+ 'usr_local_framed_ip_addr' => 'USR-Local-Framed-IP-Addr',
+ 'usr_service_option' => 'USR-Service-Option',
+ 'usr_transmit_acc_map' => 'USR-Transmit-Acc-Map',
+ 'ascend_fr_direct' => 'Ascend-FR-Direct',
+ 'usr_final_rx_link_data_r' => 'USR-Final-Rx-Link-Data-Rate',
+ 'x_ascend_expect_callback' => 'X-Ascend-Expect-Callback',
+ 'x_ascend_disconnect_caus' => 'X-Ascend-Disconnect-Cause',
+ 'acc_ml_damping_factor' => 'Acc-ML-Damping-Factor',
+ 'framed_netmask' => 'Framed-Netmask',
+ 'usr_connect_speed' => 'USR-Connect-Speed',
+ 'x_ascend_home_agent_ip_a' => 'X-Ascend-Home-Agent-IP-Addr',
+ 'usr_disconnect_cause_ind' => 'USR-Disconnect-Cause-Indicator',
+ 'bg_span_dis' => 'BG-Span-Dis',
+ 'cisco_multilink_id' => 'Cisco-Multilink-ID',
+ 'tunnel_max_tunnels' => 'Tunnel-Max-Tunnels',
+ 'ascend_dsl_downstream_li' => 'Ascend-Dsl-Downstream-Limit',
+ 'ascend_multilink_id' => 'Ascend-Multilink-ID',
+ 'altiga_ipsec_default_dom' => 'Altiga-IPSec-Default-Domain-G',
+ 'ascend_dhcp_reply' => 'Ascend-DHCP-Reply',
+ 'login_ipv6_host' => 'Login-IPv6-Host',
+ 'ascend_x25_cug' => 'Ascend-X25-Cug',
+ 'shiva_network_protocols' => 'Shiva-Network-Protocols',
+ 'cvpn3000_ipsec_mode_conf' => 'CVPN3000-IPSec-Mode-Config',
+ 'extreme_netlogin_vlan' => 'Extreme-Netlogin-Vlan',
+ 'ascend_ara_pw' => 'Ascend-Ara-PW',
+ 'tunnel_l2f_second_passwo' => 'Tunnel-L2F-Second-Password',
+ 'altiga_sep_card_assignme' => 'Altiga-SEP-Card-Assignment-G/U',
+ 'ip_host_addr' => 'Ip-Host-Addr',
+ 'le_ip_gateway' => 'LE-IP-Gateway',
+ 'usr_mobile_numbytes_txed' => 'USR-Mobile-NumBytes-Txed',
+ 'altiga_ipsec_allow_passw' => 'Altiga-IPSec-Allow-Passwd-Store-G/U',
+ 'itk_users_default_entry' => 'ITK-Users-Default-Entry',
+ 'quintum_h323_redirect_nu' => 'Quintum-h323-redirect-number',
+ 'x_ascend_fr_t392' => 'X-Ascend-FR-T392',
+ 'acc_igmp_version' => 'Acc-Igmp-Version',
+ 'cisco_pre_output_packets' => 'Cisco-Pre-Output-Packets',
+ 'tunnel_group' => 'Tunnel-Group',
+ 'x_ascend_home_agent_udp_' => 'X-Ascend-Home-Agent-UDP-Port',
+ 'cvpn3000_tunneling_proto' => 'CVPN3000-Tunneling-Protocols',
+ 'usr_igmp_maximum_respons' => 'USR-IGMP-Maximum-Response-Time',
+ 'bind_sub_password' => 'Bind-Sub-Password',
+ 'eap_message' => 'EAP-Message',
+ 'exec_program' => 'Exec-Program',
+ 'cvpn3000_reqrd_client_fx' => 'CVPN3000-Reqrd-Client-Fw-Product-Code',
+ 'bg_path_cost' => 'BG-Path-Cost',
+ 'usr_modem_training_time' => 'USR-Modem-Training-Time',
+ 'auth_type' => 'Auth-Type',
+ 'itk_acct_serv_prot' => 'ITK-Acct-Serv-Prot',
+ 'x_ascend_ipx_route' => 'X-Ascend-IPX-Route',
+ 'altiga_primary_dns_g' => 'Altiga-Primary-DNS-G',
+ 'ascend_cbcp_enable' => 'Ascend-CBCP-Enable',
+ 'ms_mppe_encryption_polic' => 'MS-MPPE-Encryption-Policy',
+ 'annex_unauthenticated_ti' => 'Annex-Unauthenticated-Time',
+ 'annex_begin_receive_line' => 'Annex-Begin-Receive-Line-Level',
+ 'ascend_atm_direct_profil' => 'Ascend-ATM-Direct-Profile',
+ 'redcreek_tunneled_dns_se' => 'RedCreek-Tunneled-DNS-Server',
+ 'ascend_redirect_number' => 'Ascend-Redirect-Number',
+ 'h323_credit_time' => 'h323-credit-time',
+ 'cvx_idle_limit' => 'CVX-Idle-Limit',
+ 'ascend_appletalk_route' => 'Ascend-Appletalk-Route',
+ 'aat_ip_tos' => 'AAT-IP-TOS',
+ 'cvx_ppp_address' => 'CVX-PPP-Address',
+ 'aat_data_filter' => 'AAT-Data-Filter',
+ 'cvx_primary_dns' => 'CVX-Primary-DNS',
+ 'shiva_link_protocol' => 'Shiva-Link-Protocol',
+ 'x_ascend_fr_circuit_name' => 'X-Ascend-FR-Circuit-Name',
+ 'usr_appletalk' => 'USR-Appletalk',
+ 'client_id' => 'Client-Id',
+ 'tunnel_algorithn' => 'Tunnel_Algorithm',
+ 'aat_assign_ip_pool' => 'AAT-Assign-IP-Pool',
+ 'quintum_h323_incoming_co' => 'Quintum-h323-incoming-conf-id',
+ 'aat_atm_vpi' => 'AAT-ATM-VPI',
+ 'annex_output_filter' => 'Annex-Output-Filter',
+ 'pvc_circuit_padding' => 'PVC-Circuit-Padding',
+ 'usr_ipx_call_output_filt' => 'USR-IPX-Call-Output-Filter',
+ 'usr_rmmie_planned_discon' => 'USR-RMMIE-Planned-Disconnect',
+ 'session_error_msh' => 'Session_Error_Msg',
+ 'usr_rad_multicast_routin' => 'USR-Rad-Multicast-Routing-Ttl',
+ 'h323_time_and_day' => 'h323-time-and-day',
+ 'cvpn3000_ipsec_backup_se' => 'CVPN3000-IPSec-Backup-Servers',
+ 'termination_action' => 'Termination-Action',
+ 'cvpn3000_ipsec_client_fx' => 'CVPN3000-IPSec-Client-Fw-Filter-Opt',
+ 'aat_client_primary_dnt' => 'AAT-Client-Primary-DNS',
+ 'acct_tunnel_packets_lost' => 'Acct-Tunnel-Packets-Lost',
+ 'x_ascend_modem_portno' => 'X-Ascend-Modem-PortNo',
+ 'framed_filter_id' => 'Framed-Filter-Id',
+ 'usr_ccp_algorithm' => 'USR-CCP-Algorithm',
+ 'quintum_h323_preferred_l' => 'Quintum-h323-preferred-lang',
+ 'ascend_fr_link_status_dl' => 'Ascend-FR-Link-Status-DLCI',
+ 'ascend_token_expiry' => 'Ascend-Token-Expiry',
+ 'itk_auth_req_type' => 'ITK-Auth-Req-Type',
+ 'acc_modem_error_protocol' => 'Acc-Modem-Error-Protocol',
+ 'acc_request_type' => 'Acc-Request-Type',
+ 'usr_last_number_dialed_i' => 'USR-Last-Number-Dialed-In-DNIS',
+ 'x_ascend_ipx_peer_mode' => 'X-Ascend-IPX-Peer-Mode',
+ 'ascend_ppp_vj_slot_comp' => 'Ascend-PPP-VJ-Slot-Comp',
+ 'cisco_presession_time' => 'Cisco-PreSession-Time',
+ 'usr_chat_script_name' => 'USR-Chat-Script-Name',
+ 'tunnel_session_auti' => 'Tunnel_Session_Auth',
+ 'ascend_fr_circuit_name' => 'Ascend-FR-Circuit-Name',
+ 'ascend_expect_callback' => 'Ascend-Expect-Callback',
+ 'framed_mtu' => 'Framed-MTU',
+ 'usr_pw_vpn_name' => 'USR-PW_VPN_Name',
+ 'nomadix_ip_upsell' => 'Nomadix-IP-Upsell',
+ 'ascend_nas_port_format' => 'Ascend-NAS-Port-Format',
+ 'usr_dtr_true_timeout' => 'USR-DTR-True-Timeout',
+ 'shasta_vpn_name' => 'Shasta-VPN-Name',
+ 'connect_rate' => 'Connect-Rate',
+ 'ascend_third_prompt' => 'Ascend-Third-Prompt',
+ 'cabletron_protocol_enabl' => 'Cabletron-Protocol-Enable',
+ 'annex_pre_input_octets' => 'Annex-Pre-Input-Octets',
+ 'cvx_modem_error_correcti' => 'CVX-Modem-Error-Correction',
+ 'cvx_ss7_session_id_type' => 'CVX-SS7-Session-ID-Type',
+ 'called_station_id' => 'Called-Station-Id',
+ 'itk_ddi' => 'ITK-DDI',
+ 'usr_pw_cutoff' => 'USR-PW_Cutoff',
+ 'ascend_data_rate' => 'Ascend-Data-Rate',
+ 'acct_input_packets_65' => 'Acct_Input_Packets_64',
+ 'x_ascend_ts_idle_mode' => 'X-Ascend-TS-Idle-Mode',
+ 'ascend_x25_pad_prompt' => 'Ascend-X25-Pad-Prompt',
+ 'x_ascend_dhcp_reply' => 'X-Ascend-DHCP-Reply',
+ 'acc_nbns_server_pri' => 'Acc-Nbns-Server-Pri',
+ 'post_auth_type' => 'Post-Auth-Type',
+ 'ascend_call_filter' => 'Ascend-Call-Filter',
+ 'acc_tunnel_secret' => 'Acc-Tunnel-Secret',
+ 'colubris_avpair' => 'Colubris-AVPair',
+ 'bind_int_context' => 'Bind-Int-Context',
+ 'annex_logical_channel_nu' => 'Annex-Logical-Channel-Number',
+ 'erx_virtual_router_name' => 'ERX-Virtual-Router-Name',
+ 'wispr_redirection_url' => 'WISPr-Redirection-URL',
+ 'bintec_ipextiftable' => 'BinTec-ipExtIfTable',
+ 'crypt_password' => 'Crypt-Password',
+ 'challenge_state' => 'Challenge-State',
+ 'x_ascend_pre_input_packe' => 'X-Ascend-Pre-Input-Packets',
+ 'altiga_ipsec_l2l_keepali' => 'Altiga-IPSec-L2L-Keepalives-G',
+ 'x_ascend_dhcp_maximum_le' => 'X-Ascend-DHCP-Maximum-Leases',
+ 'acc_dialout_auth_passwor' => 'Acc-Dialout-Auth-Password',
+ 'itk_ip_pool' => 'ITK-IP-Pool',
+ 'pvc_profile_namf' => 'PVC_Profile_Name',
+ 'x_ascend_user_acct_host' => 'X-Ascend-User-Acct-Host',
+ 'strip_user_name' => 'Strip-User-Name',
+ 'itk_ppp_client_server_mo' => 'ITK-PPP-Client-Server-Mode',
+ 'usr_mbi_ct_bchannel_used' => 'USR-Mbi_Ct_BChannel_Used',
+ 'x_ascend_route_ip' => 'X-Ascend-Route-IP',
+ 'ascend_seconds_of_histor' => 'Ascend-Seconds-Of-History',
+ 'cvx_data_rate' => 'CVX-Data-Rate',
+ 'ascend_x25_profile_name' => 'Ascend-X25-Profile-Name',
+ 'itk_ftp_auth_ip' => 'ITK-Ftp-Auth-IP',
+ 'cisco_control_info' => 'Cisco-Control-Info',
+ 'cvpn3000_secondary_wins' => 'CVPN3000-Secondary-WINS',
+ 'usr_call_type' => 'USR-Call-Type',
+ 'x_ascend_user_acct_base' => 'X-Ascend-User-Acct-Base',
+ 'acct_mcast_in_packett' => 'Acct_Mcast_In_Packets',
+ 'ns_vsys_name' => 'NS-VSYS-Name',
+ 'acct_output_gigawords' => 'Acct-Output-Gigawords',
+ 'bind_typf' => 'Bind_Type',
+ 'bintec_ipqostable' => 'BinTec-ipQoSTable',
+ 'bintec_ipxstaticservtabl' => 'BinTec-ipxStaticServTable',
+ 'cvpn3000_l2tp_mppc_compr' => 'CVPN3000-L2TP-MPPC-Compression',
+ 'login_lat_port' => 'Login-LAT-Port',
+ 'usr_call_arrival_in_gmt' => 'USR-Call-Arrival-in-GMT',
+ 'acct_mcast_in_octets' => 'Acct-Mcast-In-Octets',
+ 'erx_sa_validate' => 'ERX-Sa-Validate',
+ 'ascend_service_type' => 'Ascend-Service-Type',
+ 'usr_pw_vpn_gateway' => 'USR-PW_VPN_Gateway',
+ 'acc_ip_compression' => 'Acc-Ip-Compression',
+ 'ascend_fr_dce_n392' => 'Ascend-FR-DCE-N392',
+ 'bintec_ipxcirctable' => 'BinTec-ipxCircTable',
+ 'lac_real_port_type' => 'LAC-Real-Port-Type',
+ 'ascend_client_primary_dn' => 'Ascend-Client-Primary-DNS',
+ 'acct_session_start_time' => 'Acct-Session-Start-Time',
+ 'ascend_if_netmask' => 'Ascend-IF-Netmask',
+ 'ms_chap_nt_enc_pw' => 'MS-CHAP-NT-Enc-PW',
+ 'ms_mppe_encryption_types' => 'MS-MPPE-Encryption-Types',
+ 'cisco_fax_process_abort_' => 'Cisco-Fax-Process-Abort-Flag',
+ 'mcast_maxgroups' => 'Mcast-MaxGroups',
+ 'annex_end_receive_line_l' => 'Annex-End-Receive-Line-Level',
+ 'usr_ipx_call_input_filte' => 'USR-IPX-Call-Input-Filter',
+ 'usr_back_channel_data_ra' => 'USR-Back-Channel-Data-Rate',
+ 'ascend_cache_time' => 'Ascend-Cache-Time',
+ 'x_ascend_data_svc' => 'X-Ascend-Data-Svc',
+ 'usr_re_chap_timeout' => 'USR-Re-Chap-Timeout',
+ 'bintec_bibodialtable' => 'BinTec-biboDialTable',
+ 'annex_connect_progress' => 'Annex-Connect-Progress',
+ 'x_ascend_ppp_vj_1172' => 'X-Ascend-PPP-VJ-1172',
+ 'usr_igmp_routing' => 'USR-IGMP-Routing',
+ 'x_ascend_ip_pool_definit' => 'X-Ascend-IP-Pool-Definition',
+ 'h323_prompt_id' => 'h323-prompt-id',
+ 'foundry_command_string' => 'Foundry-Command-String',
+ 'le_terminate_detail' => 'LE-Terminate-Detail',
+ 'cvpn3000_pptp_encryption' => 'CVPN3000-PPTP-Encryption',
+ 'quintum_h323_disconnect_' => 'Quintum-h323-disconnect-time',
+ 'acc_ml_clear_threshold' => 'Acc-ML-Clear-Threshold',
+ 'x_ascend_ip_direct' => 'X-Ascend-IP-Direct',
+ 'usr_ip_call_input_filter' => 'USR-IP-Call-Input-Filter',
+ 'x_ascend_data_rate' => 'X-Ascend-Data-Rate',
+ 'nas_port' => 'NAS-Port',
+ 'ascend_client_secondary_' => 'Ascend-Client-Secondary-WINS',
+ 'ascend_auth_type' => 'Ascend-Auth-Type',
+ 'x_ascend_preempt_limit' => 'X-Ascend-Preempt-Limit',
+ 'cvx_xmit_rate' => 'CVX-Xmit-Rate',
+ 'annex_transmitted_packet' => 'Annex-Transmitted-Packets',
+ 'h323_credit_amount' => 'h323-credit-amount',
+ 'usr_reply_script1' => 'USR-Reply-Script1',
+ 'current_time' => 'Current-Time',
+ 'cisco_xmit_rate' => 'Cisco-Xmit-Rate',
+ 'x_ascend_session_svr_key' => 'X-Ascend-Session-Svr-Key',
+ 'ascend_authen_alias' => 'Ascend-Authen-Alias',
+ 'erx_redirect_vr_name' => 'ERX-Redirect-VR-Name',
+ 'module_success_message' => 'Module-Success-Message',
+ 'acc_dialout_auth_mode' => 'Acc-Dialout-Auth-Mode',
+ 'bind_auth_contexu' => 'Bind_Auth_Context',
+ 'x_ascend_minimum_channel' => 'X-Ascend-Minimum-Channels',
+ 'usr_event_date_time' => 'USR-Event-Date-Time',
+ 'x_ascend_ipx_node_addr' => 'X-Ascend-IPX-Node-Addr',
+ 'cvpn3000_ipsec_over_udp' => 'CVPN3000-IPSec-Over-UDP',
+ 'x_ascend_user_acct_time' => 'X-Ascend-User-Acct-Time',
+ 'cisco_email_server_ack_f' => 'Cisco-Email-Server-Ack-Flag',
+ 'telebit_activate_command' => 'Telebit-Activate-Command',
+ 'acc_output_errors' => 'Acc-Output-Errors',
+ 'juniper_allow_configurat' => 'Juniper-Allow-Configuration',
+ 'bind_l2tp_tunnel_name' => 'Bind-L2TP-Tunnel-Name',
+ 'x_ascend_pri_number_type' => 'X-Ascend-PRI-Number-Type',
+ 'bintec_biboppptable' => 'BinTec-biboPPPTable',
+ 'le_ipsec_outsource_profi' => 'LE-IPSec-Outsource-Profile',
+ 'usr_at_zip_input_filter' => 'USR-AT-Zip-Input-Filter',
+ 'replicate_to_realm' => 'Replicate-To-Realm',
+ 'annex_mrru' => 'Annex-MRRU',
+ 'event_timestamp' => 'Event-Timestamp',
+ 'nokia_sgsn_ip_address' => 'Nokia-SGSN-IP-Address',
+ 'ascend_pre_input_packets' => 'Ascend-Pre-Input-Packets',
+ 'cvpn5000_client_assigned' => 'CVPN5000-Client-Assigned-IP',
+ 'tunnel_dnit' => 'Tunnel_DNIS',
+ 'h323_call_origin' => 'h323-call-origin',
+ 'x_ascend_fr_type' => 'X-Ascend-FR-Type',
+ 'itk_provider_id' => 'ITK-Provider-Id',
+ 'cvx_ppp_log_mask' => 'CVX-PPP-Log-Mask',
+ 'x_ascend_token_idle' => 'X-Ascend-Token-Idle',
+ 'usr_rmmie_pwrlvl_xmit_lv' => 'USR-RMMIE-PwrLvl-Xmit-Lvl',
+ 'usr_igmp_query_interval' => 'USR-IGMP-Query-Interval',
+ 'quintum_h323_billing_mod' => 'Quintum-h323-billing-model',
+ 'ascend_atm_vci' => 'Ascend-ATM-Vci',
+ 'usr_port_tap_output' => 'USR-Port-Tap-Output',
+ 'session' => 'Session',
+ 'itk_welcome_message' => 'ITK-Welcome-Message',
+ 'cvpn3000_ike_keep_alives' => 'CVPN3000-IKE-Keep-Alives',
+ 'ascend_uu_info' => 'Ascend-UU-Info',
+ 'usr_et_bridge_call_outpu' => 'USR-ET-Bridge-Call-Output-Filte',
+ 'usr_secondary_dns_server' => 'USR-Secondary_DNS_Server',
+ 'ms_mppe_recv_key' => 'MS-MPPE-Recv-Key',
+ 'bintec_ripcirctable' => 'BinTec-ripCircTable',
+ 'acc_dial_port_index' => 'Acc-Dial-Port-Index',
+ 'cisco_nas_port' => 'Cisco-NAS-Port',
+ 'itk_username' => 'ITK-Username',
+ 'usr_send_script1' => 'USR-Send-Script1',
+ 'cvpn3000_ipsec_ike_peer_' => 'CVPN3000-IPSec-IKE-Peer-ID-Check',
+ 'ascend_dsl_upstream_limi' => 'Ascend-Dsl-Upstream-Limit',
+ 'x_ascend_dec_channel_cou' => 'X-Ascend-Dec-Channel-Count',
+ 'usr_tunnel_security' => 'USR-Tunnel-Security',
+ 'arap_security' => 'ARAP-Security',
+ 'tunnel_preference' => 'Tunnel-Preference',
+ 'cisco_port_used' => 'Cisco-Port-Used',
+ 'usr_reply_script4' => 'USR-Reply-Script4',
+ 'cvpn5000_client_real_ip' => 'CVPN5000-Client-Real-IP',
+ 'usr_rmmie_status' => 'USR-RMMIE-Status',
+ 'usr_send_script4' => 'USR-Send-Script4',
+ 'quintum_h323_connect_tim' => 'Quintum-h323-connect-time',
+ 'annex_syslog_tap' => 'Annex-Syslog-Tap',
+ 'redcreek_tunneled_hostna' => 'RedCreek-Tunneled-HostName',
+ 'acc_clearing_location' => 'Acc-Clearing-Location',
+ 'ascend_access_intercept_' => 'Ascend-Access-Intercept-LEA',
+ 'annex_disconnect_reason' => 'Annex-Disconnect-Reason',
+ 'usr_at_input_filter' => 'USR-AT-Input-Filter',
+ 'usr_auth_mode' => 'USR-Auth-Mode',
+ 'usr_expected_voltage' => 'USR-Expected-Voltage',
+ 'shiva_session_id' => 'Shiva-Session-Id',
+ 'annex_maximum_call_durat' => 'Annex-Maximum-Call-Duration',
+ 'usr_block_error_count_li' => 'USR-Block-Error-Count-Limit',
+ 'ascend_owner_ip_addr' => 'Ascend-Owner-IP-Addr',
+ 'bind_tun_contexu' => 'Bind_Tun_Context',
+ 'usr_pw_usr_ofilter_ipx' => 'USR-PW_USR_OFilter_IPX',
+ 'framed_routing' => 'Framed-Routing',
+ 'annex_primary_nbns_serve' => 'Annex-Primary-NBNS-Server',
+ 'usr_interface_index' => 'USR-Interface-Index',
+ 'pam_auth' => 'Pam-Auth',
+ 'usr_end_time' => 'USR-End-Time',
+ 'rate_limit_bursu' => 'Rate_Limit_Burst',
+ 'nomadix_expiration' => 'Nomadix-Expiration',
+ 'x_ascend_transit_number' => 'X-Ascend-Transit-Number',
+ 'itk_usergroup' => 'ITK-Usergroup',
+ 'x_ascend_assign_ip_pool' => 'X-Ascend-Assign-IP-Pool',
+ 'annex_secondary_nbns_ser' => 'Annex-Secondary-NBNS-Server',
+ 'bind_dot1q_vlan_tag_id' => 'Bind-Dot1q-Vlan-Tag-Id',
+ 'ms_secondary_nbns_server' => 'MS-Secondary-NBNS-Server',
+ 'tunnel_retransmit' => 'Tunnel-Retransmit',
+ 'acct_tunnel_connection' => 'Acct-Tunnel-Connection',
+ 'x_ascend_backup' => 'X-Ascend-Backup',
+ 'xedia_ppp_echo_interval' => 'Xedia-PPP-Echo-Interval',
+ 'usr_bearer_capabilities' => 'USR-Bearer-Capabilities',
+ 'shiva_acct_serv_switch' => 'Shiva-Acct-Serv-Switch',
+ 'acct_authentic' => 'Acct-Authentic',
+ 'le_nat_other_session_tim' => 'LE-NAT-Other-Session-Timeout',
+ 'cvpn3000_ipsec_banner2' => 'CVPN3000-IPSec-Banner2',
+ 'x_ascend_force_56' => 'X-Ascend-Force-56',
+ 'framed_appletalk_network' => 'Framed-AppleTalk-Network',
+ 'reply_message' => 'Reply-Message',
+ 'class' => 'Class',
+ 'h323_conf_id' => 'h323-conf-id',
+ 'quintum_h323_disconnecta' => 'Quintum-h323-disconnect-cause',
+ 'itk_filter_rule' => 'ITK-Filter-Rule',
+ 'wispr_bandwidth_max_up' => 'WISPr-Bandwidth-Max-Up',
+ 'usr_appletalk_network_ra' => 'USR-Appletalk-Network-Range',
+ 'ascend_cbcp_delay' => 'Ascend-CBCP-Delay',
+ 'usr_dte_ring_no_answer_l' => 'USR-DTE-Ring-No-Answer-Limit',
+ 'pre_acct_type' => 'Pre-Acct-Type',
+ 'usr_local_ip_address' => 'USR-Local-IP-Address',
+ 'ascend_dropped_octets' => 'Ascend-Dropped-Octets',
+ 'ascend_h323_dialed_time' => 'Ascend-H323-Dialed-Time',
+ 'cisco_email_server_addre' => 'Cisco-Email-Server-Address',
+ 'ascend_x25_x121_address' => 'Ascend-X25-X121-Address',
+ 'cvx_multicast_client' => 'CVX-Multicast-Client',
+ 'wispr_bandwidth_min_up' => 'WISPr-Bandwidth-Min-Up',
+ 'usr_at_output_filter' => 'USR-AT-Output-Filter',
+ 'annex_local_ip_address' => 'Annex-Local-IP-Address',
+ 'cisco_ip_pool_definition' => 'Cisco-IP-Pool-Definition',
+ 'cisco_gateway_id' => 'Cisco-Gateway-Id',
+ 'itk_password_prompt' => 'ITK-Password-Prompt',
+ 'annex_domain_name' => 'Annex-Domain-Name',
+ 'foundry_command_exceptio' => 'Foundry-Command-Exception-Flag',
+ 'ascend_preempt_limit' => 'Ascend-Preempt-Limit',
+ 'erx_minimum_bps' => 'ERX-Minimum-BPS',
+ 'aat_mcast_client' => 'AAT-MCast-Client',
+ 'ascend_atm_fault_managem' => 'Ascend-ATM-Fault-Management',
+ 'ascend_event_type' => 'Ascend-Event-Type',
+ 'exec_program_wait' => 'Exec-Program-Wait',
+ 'framed_interface_id' => 'Framed-Interface-Id',
+
+ #NETC.NET.AU (RADIATOR?)
+ 'authentication_type' => 'Authentication-Type',
+
+);
+
+1;
diff --git a/FS/FS/radius_usergroup.pm b/FS/FS/radius_usergroup.pm
new file mode 100644
index 0000000..9bba057
--- /dev/null
+++ b/FS/FS/radius_usergroup.pm
@@ -0,0 +1,131 @@
+package FS::radius_usergroup;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::svc_acct;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::radius_usergroup - Object methods for radius_usergroup records
+
+=head1 SYNOPSIS
+
+ use FS::radius_usergroup;
+
+ $record = new FS::radius_usergroup \%hash;
+ $record = new FS::radius_usergroup { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::radius_usergroup object links an account (see L<FS::svc_acct>) with a
+RADIUS group. FS::radius_usergroup inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item usergroupnum - primary key
+
+=item svcnum - Account (see L<FS::svc_acct>).
+
+=item groupname - group name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'radius_usergroup'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+#inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+#inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+#inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('usergroupnum')
+ || $self->ut_number('svcnum')
+ || $self->ut_foreign_key('svcnum','svc_acct','svcnum')
+ || $self->ut_text('groupname')
+ || $self->SUPER::check
+ ;
+}
+
+=item svc_acct
+
+Returns the account associated with this record (see L<FS::svc_acct>).
+
+=cut
+
+sub svc_acct {
+ my $self = shift;
+ qsearchs('svc_acct', { svcnum => $self->svcnum } );
+}
+
+=back
+
+=head1 BUGS
+
+Don't let 'em get you down.
+
+=head1 SEE ALSO
+
+L<svc_acct>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/router.pm b/FS/FS/router.pm
new file mode 100755
index 0000000..2554ce8
--- /dev/null
+++ b/FS/FS/router.pm
@@ -0,0 +1,144 @@
+package FS::router;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+use FS::addr_block;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::router - Object methods for router records
+
+=head1 SYNOPSIS
+
+ use FS::router;
+
+ $record = new FS::router \%hash;
+ $record = new FS::router { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::router record describes a broadband router, such as a DSLAM or a wireless
+ access point. FS::router inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item routernum - primary key
+
+=item routername - descriptive name for the router
+
+=item svcnum - svcnum of the owning FS::svc_broadband, if appropriate
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'router'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('routernum')
+ || $self->ut_text('routername');
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item addr_block
+
+Returns a list of FS::addr_block objects (address blocks) associated
+with this object.
+
+=cut
+
+sub addr_block {
+ my $self = shift;
+ return qsearch('addr_block', { routernum => $self->routernum });
+}
+
+=item part_svc_router
+
+Returns a list of FS::part_svc_router objects associated with this
+object. This is unlikely to be useful for any purpose other than retrieving
+the associated FS::part_svc objects. See below.
+
+=cut
+
+sub part_svc_router {
+ my $self = shift;
+ return qsearch('part_svc_router', { routernum => $self->routernum });
+}
+
+=item part_svc
+
+Returns a list of FS::part_svc objects associated with this object.
+
+=cut
+
+sub part_svc {
+ my $self = shift;
+ return map { qsearchs('part_svc', { svcpart => $_->svcpart }) }
+ $self->part_svc_router;
+}
+
+=back
+
+=head1 VERSION
+
+$Id:
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+FS::svc_broadband, FS::router, FS::addr_block, FS::part_svc,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/session.pm b/FS/FS/session.pm
new file mode 100644
index 0000000..2ad594c
--- /dev/null
+++ b/FS/FS/session.pm
@@ -0,0 +1,269 @@
+package FS::session;
+
+use strict;
+use vars qw( @ISA $conf $start $stop );
+use FS::UID qw( dbh );
+use FS::Record qw( qsearchs );
+use FS::svc_acct;
+use FS::port;
+use FS::nas;
+
+@ISA = qw(FS::Record);
+
+$FS::UID::callback{'FS::session'} = sub {
+ $conf = new FS::Conf;
+ $start = $conf->exists('session-start') ? $conf->config('session-start') : '';
+ $stop = $conf->exists('session-stop') ? $conf->config('session-stop') : '';
+};
+
+=head1 NAME
+
+FS::session - Object methods for session records
+
+=head1 SYNOPSIS
+
+ use FS::session;
+
+ $record = new FS::session \%hash;
+ $record = new FS::session {
+ 'portnum' => 1,
+ 'svcnum' => 2,
+ 'login' => $timestamp,
+ 'logout' => $timestamp,
+ };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->nas_heartbeat($timestamp);
+
+=head1 DESCRIPTION
+
+An FS::session object represents an user login session. FS::session inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item sessionnum - primary key
+
+=item portnum - NAS port for this session - see L<FS::port>
+
+=item svcnum - User for this session - see L<FS::svc_acct>
+
+=item login - timestamp indicating the beginning of this user session.
+
+=item logout - timestamp indicating the end of this user session. May be null,
+ which indicates a currently open session.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new session. To add the session to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'session'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false. If the `login' field is empty, it is replaced with
+the current time.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ $error = $self->check;
+ return $error if $error;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ if ( qsearchs('session', { 'portnum' => $self->portnum, 'logout' => '' } ) ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "a session on that port is already open!";
+ }
+
+ $self->setfield('login', time()) unless $self->getfield('login');
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->nas_heartbeat($self->getfield('login'));
+
+ #session-starting callback
+ #redundant with heartbeat, yuck
+ my $port = qsearchs('port',{'portnum'=>$self->portnum});
+ my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
+ #kcuy
+ my( $ip, $nasip, $nasfqdn ) = ( $port->ip, $nas->nasip, $nas->nasfqdn );
+ system( eval qq("$start") ) if $start;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false. If the `logout' field is empty,
+it is replaced with the current time.
+
+=cut
+
+sub replace {
+ my($self, $old) = @_;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->setfield('logout', time()) unless $self->getfield('logout');
+
+ $error = $self->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->nas_heartbeat($self->getfield('logout'));
+
+ #session-ending callback
+ #redundant with heartbeat, yuck
+ my $port = qsearchs('port',{'portnum'=>$self->portnum});
+ my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
+ #kcuy
+ my( $ip, $nasip, $nasfqdn ) = ( $port->ip, $nas->nasip, $nas->nasfqdn );
+ system( eval qq("$stop") ) if $stop;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item check
+
+Checks all fields to make sure this is a valid session. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('sessionnum')
+ || $self->ut_number('portnum')
+ || $self->ut_number('svcnum')
+ || $self->ut_numbern('login')
+ || $self->ut_numbern('logout')
+ ;
+ return $error if $error;
+ return "Unknown svcnum"
+ unless qsearchs('svc_acct', { 'svcnum' => $self->svcnum } );
+ $self->SUPER::check;
+}
+
+=item nas_heartbeat
+
+Heartbeats the nas associated with this session (see L<FS::nas>).
+
+=cut
+
+sub nas_heartbeat {
+ my $self = shift;
+ my $port = qsearchs('port',{'portnum'=>$self->portnum});
+ my $nas = qsearchs('nas',{'nasnum'=>$port->nasnum});
+ $nas->heartbeat(shift);
+}
+
+=item svc_acct
+
+Returns the svc_acct record associated with this session (see L<FS::svc_acct>).
+
+=cut
+
+sub svc_acct {
+ my $self = shift;
+ qsearchs('svc_acct', { 'svcnum' => $self->svcnum } );
+}
+
+=back
+
+=head1 VERSION
+
+$Id: session.pm,v 1.8 2003-08-05 00:20:46 khoff Exp $
+
+=head1 BUGS
+
+Maybe you shouldn't be able to insert a session if there's currently an open
+session on that port. Or maybe the open session on that port should be flagged
+as problematic? autoclosed? *sigh*
+
+Hmm, sessions refer to current svc_acct records... probably need to constrain
+deletions to svc_acct records such that no svc_acct records are deleted which
+have a session (even if long-closed).
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm
new file mode 100644
index 0000000..10ff3f9
--- /dev/null
+++ b/FS/FS/svc_Common.pm
@@ -0,0 +1,553 @@
+package FS::svc_Common;
+
+use strict;
+use vars qw( @ISA $noexport_hack $DEBUG );
+use FS::Record qw( qsearch qsearchs fields dbh );
+use FS::cust_svc;
+use FS::part_svc;
+use FS::queue;
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+#$DEBUG = 1;
+
+=head1 NAME
+
+FS::svc_Common - Object method for all svc_ records
+
+=head1 SYNOPSIS
+
+use FS::svc_Common;
+
+@ISA = qw( FS::svc_Common );
+
+=head1 DESCRIPTION
+
+FS::svc_Common is intended as a base class for table-specific classes to
+inherit from, i.e. FS::svc_acct. FS::svc_Common inherits from FS::Record.
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+sub virtual_fields {
+
+ # This restricts the fields based on part_svc_column and the svcpart of
+ # the service. There are four possible cases:
+ # 1. svcpart passed as part of the svc_x hash.
+ # 2. svcpart fetched via cust_svc based on svcnum.
+ # 3. No svcnum or svcpart. In this case, return ALL the fields with
+ # dbtable eq $self->table.
+ # 4. Called via "fields('svc_acct')" or something similar. In this case
+ # there is no $self object.
+
+ my $self = shift;
+ my $svcpart;
+ my @vfields = $self->SUPER::virtual_fields;
+
+ return @vfields unless (ref $self); # Case 4
+
+ if ($self->svcpart) { # Case 1
+ $svcpart = $self->svcpart;
+ } elsif ( $self->svcnum
+ && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} )
+ ) { #Case 2
+ $svcpart = $self->cust_svc->svcpart;
+ } else { # Case 3
+ $svcpart = '';
+ }
+
+ if ($svcpart) { #Cases 1 and 2
+ my %flags = map { $_->columnname, $_->columnflag } (
+ qsearch ('part_svc_column', { svcpart => $svcpart } )
+ );
+ return grep { not ($flags{$_} eq 'X') } @vfields;
+ } else { # Case 3
+ return @vfields;
+ }
+ return ();
+}
+
+=item check
+
+Checks the validity of fields in this record.
+
+At present, this does nothing but call FS::Record::check (which, in turn,
+does nothing but run virtual field checks).
+
+=cut
+
+sub check {
+ my $self = shift;
+ $self->SUPER::check;
+}
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<jobnums>, I<child_objects> and
+I<depend_jobnum>.
+
+If I<jobnum> is set to an array reference, the jobnums of any export jobs will
+be added to the referenced array.
+
+If I<child_objects> is set to an array reference of FS::tablename objects (for
+example, FS::acct_snarf objects), they will have their svcnum fieldsset and
+will be inserted after this record, but before any exports are run.
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+ warn "FS::svc_Common::insert called with options ".
+ join(', ', map { "$_: $options{$_}" } keys %options ). "\n"
+ if $DEBUG;
+
+ my @jobnums = ();
+ local $FS::queue::jobnums = \@jobnums;
+ warn "FS::svc_Common::insert: set \$FS::queue::jobnums to $FS::queue::jobnums"
+ if $DEBUG;
+ my $objects = $options{'child_objects'} || [];
+ my $depend_jobnums = $options{'depend_jobnum'} || [];
+ $depend_jobnums = [ $depend_jobnums ] unless ref($depend_jobnums);
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ return $error if $error;
+
+ my $svcnum = $self->svcnum;
+ my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
+ #unless ( $svcnum ) {
+ if ( !$svcnum or !$cust_svc ) {
+ $cust_svc = new FS::cust_svc ( {
+ #hua?# 'svcnum' => $svcnum,
+ 'svcnum' => $self->svcnum,
+ 'pkgnum' => $self->pkgnum,
+ 'svcpart' => $self->svcpart,
+ } );
+ $error = $cust_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $svcnum = $self->svcnum($cust_svc->svcnum);
+ } else {
+ #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
+ unless ( $cust_svc ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "no cust_svc record found for svcnum ". $self->svcnum;
+ }
+ $self->pkgnum($cust_svc->pkgnum);
+ $self->svcpart($cust_svc->svcpart);
+ }
+
+ $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $object ( @$objects ) {
+ $object->svcnum($self->svcnum);
+ $error = $object->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+
+ warn "FS::svc_Common::insert: \$FS::queue::jobnums is $FS::queue::jobnums"
+ if $DEBUG;
+
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_insert($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "exporting to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ foreach my $depend_jobnum ( @$depend_jobnums ) {
+ warn "inserting dependancies on supplied job $depend_jobnum\n"
+ if $DEBUG;
+ foreach my $jobnum ( @jobnums ) {
+ my $queue = qsearchs('queue', { 'jobnum' => $jobnum } );
+ warn "inserting dependancy for job $jobnum on $depend_jobnum\n"
+ if $DEBUG;
+ my $error = $queue->depend_insert($depend_jobnum);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing job dependancy: $error";
+ }
+ }
+ }
+
+ }
+
+ if ( exists $options{'jobnums'} ) {
+ push @{ $options{'jobnums'} }, @jobnums;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item delete
+
+Deletes this account from the database. If there is an error, returns the
+error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $svcnum = $self->svcnum;
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->SUPER::delete;
+ return $error if $error;
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_delete($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "exporting to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+ }
+
+ return $error if $error;
+
+ my $cust_svc = $self->cust_svc;
+ $error = $cust_svc->delete;
+ return $error if $error;
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub replace {
+ my ($new, $old) = (shift, shift);
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $new->SUPER::replace($old);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+
+ #not quite false laziness, but same pattern as FS::svc_acct::replace and
+ #FS::part_export::sqlradius::_export_replace. List::Compare or something
+ #would be useful but too much of a pain in the ass to deploy
+
+ my @old_part_export = $old->cust_svc->part_svc->part_export;
+ my %old_exportnum = map { $_->exportnum => 1 } @old_part_export;
+ my @new_part_export =
+ $new->svcpart
+ ? qsearchs('part_svc', { svcpart=>$new->svcpart } )->part_export
+ : $new->cust_svc->part_svc->part_export;
+ my %new_exportnum = map { $_->exportnum => 1 } @new_part_export;
+
+ foreach my $delete_part_export (
+ grep { ! $new_exportnum{$_->exportnum} } @old_part_export
+ ) {
+ my $error = $delete_part_export->export_delete($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error deleting, export to ". $delete_part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ foreach my $replace_part_export (
+ grep { $old_exportnum{$_->exportnum} } @new_part_export
+ ) {
+ my $error = $replace_part_export->export_replace($new,$old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting to ". $replace_part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ foreach my $insert_part_export (
+ grep { ! $old_exportnum{$_->exportnum} } @new_part_export
+ ) {
+ my $error = $insert_part_export->export_insert($new);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error inserting export to ". $insert_part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+
+=item setfixed
+
+Sets any fixed fields for this service (see L<FS::part_svc>). If there is an
+error, returns the error, otherwise returns the FS::part_svc object (use ref()
+to test the return). Usually called by the check method.
+
+=cut
+
+sub setfixed {
+ my $self = shift;
+ $self->setx('F');
+}
+
+=item setdefault
+
+Sets all fields to their defaults (see L<FS::part_svc>), overriding their
+current values. If there is an error, returns the error, otherwise returns
+the FS::part_svc object (use ref() to test the return).
+
+=cut
+
+sub setdefault {
+ my $self = shift;
+ $self->setx('D');
+}
+
+sub setx {
+ my $self = shift;
+ my $x = shift;
+
+ my $error;
+
+ $error =
+ $self->ut_numbern('svcnum')
+ ;
+ return $error if $error;
+
+ #get part_svc
+ my $svcpart;
+ if ( $self->get('svcpart') ) {
+ $svcpart = $self->get('svcpart');
+ } elsif ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) {
+ my $cust_svc = $self->cust_svc;
+ return "Unknown svcnum" unless $cust_svc;
+ $svcpart = $cust_svc->svcpart;
+ }
+ my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ return "Unkonwn svcpart" unless $part_svc;
+
+ #set default/fixed/whatever fields from part_svc
+ my $table = $self->table;
+ foreach my $field ( grep { $_ ne 'svcnum' } $self->fields ) {
+ my $part_svc_column = $part_svc->part_svc_column($field);
+ if ( $part_svc_column->columnflag eq $x ) {
+ $self->setfield( $field, $part_svc_column->columnvalue );
+ }
+ }
+
+ $part_svc;
+
+}
+
+=item cust_svc
+
+Returns the cust_svc record associated with this svc_ record, as a FS::cust_svc
+object (see L<FS::cust_svc>).
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item suspend
+
+Runs export_suspend callbacks.
+
+=cut
+
+sub suspend {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_suspend($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item unsuspend
+
+Runs export_unsuspend callbacks.
+
+=cut
+
+sub unsuspend {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #new-style exports!
+ unless ( $noexport_hack ) {
+ foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+ my $error = $part_export->export_unsuspend($self);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error exporting to ". $part_export->exporttype.
+ " (transaction rolled back): $error";
+ }
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
+=item cancel
+
+Stub - returns false (no error) so derived classes don't need to define these
+methods. Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=cut
+
+sub cancel { ''; }
+
+=item clone_suspended
+
+Constructor used by FS::part_export::_export_suspend fallback. Stub returning
+same object for svc_ classes which don't implement a suspension fallback
+(everything except svc_acct at the moment). Document better.
+
+=cut
+
+sub clone_suspended {
+ shift;
+}
+
+=item clone_kludge_unsuspend
+
+Constructor used by FS::part_export::_export_unsuspend fallback. Stub returning
+same object for svc_ classes which don't implement a suspension fallback
+(everything except svc_acct at the moment). Document better.
+
+=cut
+
+sub clone_kludge_unsuspend {
+ shift;
+}
+
+=back
+
+=head1 BUGS
+
+The setfixed method return value.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, schema.html
+from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
new file mode 100644
index 0000000..58442c9
--- /dev/null
+++ b/FS/FS/svc_acct.pm
@@ -0,0 +1,1456 @@
+package FS::svc_acct;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $conf
+ $dir_prefix @shells $usernamemin
+ $usernamemax $passwordmin $passwordmax
+ $username_ampersand $username_letter $username_letterfirst
+ $username_noperiod $username_nounderscore $username_nodash
+ $username_uppercase
+ $welcome_template $welcome_from $welcome_subject $welcome_mimetype
+ $smtpmachine
+ $radius_password $radius_ip
+ $dirhash
+ @saltset @pw_set );
+use Carp;
+use Fcntl qw(:flock);
+use Crypt::PasswdMD5;
+use FS::UID qw( datasrc );
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs fields dbh dbdef );
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::part_svc;
+use FS::svc_acct_pop;
+use FS::cust_main_invoice;
+use FS::svc_domain;
+use FS::raddb;
+use FS::queue;
+use FS::radius_usergroup;
+use FS::export_svc;
+use FS::part_export;
+use FS::Msgcat qw(gettext);
+use FS::svc_forward;
+use FS::svc_www;
+
+@ISA = qw( FS::svc_Common );
+
+$DEBUG = 0;
+#$DEBUG = 1;
+$me = '[FS::svc_acct]';
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::svc_acct'} = sub {
+ $conf = new FS::Conf;
+ $dir_prefix = $conf->config('home');
+ @shells = $conf->config('shells');
+ $usernamemin = $conf->config('usernamemin') || 2;
+ $usernamemax = $conf->config('usernamemax');
+ $passwordmin = $conf->config('passwordmin') || 6;
+ $passwordmax = $conf->config('passwordmax') || 8;
+ $username_letter = $conf->exists('username-letter');
+ $username_letterfirst = $conf->exists('username-letterfirst');
+ $username_noperiod = $conf->exists('username-noperiod');
+ $username_nounderscore = $conf->exists('username-nounderscore');
+ $username_nodash = $conf->exists('username-nodash');
+ $username_uppercase = $conf->exists('username-uppercase');
+ $username_ampersand = $conf->exists('username-ampersand');
+ $dirhash = $conf->config('dirhash') || 0;
+ if ( $conf->exists('welcome_email') ) {
+ $welcome_template = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", $conf->config('welcome_email') ]
+ ) or warn "can't create welcome email template: $Text::Template::ERROR";
+ $welcome_from = $conf->config('welcome_email-from'); # || 'your-isp-is-dum'
+ $welcome_subject = $conf->config('welcome_email-subject') || 'Welcome';
+ $welcome_mimetype = $conf->config('welcome_email-mimetype') || 'text/plain';
+ } else {
+ $welcome_template = '';
+ $welcome_from = '';
+ $welcome_subject = '';
+ $welcome_mimetype = '';
+ }
+ $smtpmachine = $conf->config('smtpmachine');
+ $radius_password = $conf->config('radius-password') || 'Password';
+ $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
+};
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
+
+sub _cache {
+ my $self = shift;
+ my ( $hashref, $cache ) = @_;
+ if ( $hashref->{'svc_acct_svcnum'} ) {
+ $self->{'_domsvc'} = FS::svc_domain->new( {
+ 'svcnum' => $hashref->{'domsvc'},
+ 'domain' => $hashref->{'svc_acct_domain'},
+ 'catchall' => $hashref->{'svc_acct_catchall'},
+ } );
+ }
+}
+
+=head1 NAME
+
+FS::svc_acct - Object methods for svc_acct records
+
+=head1 SYNOPSIS
+
+ use FS::svc_acct;
+
+ $record = new FS::svc_acct \%hash;
+ $record = new FS::svc_acct { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+ %hash = $record->radius;
+
+ %hash = $record->radius_reply;
+
+ %hash = $record->radius_check;
+
+ $domain = $record->domain;
+
+ $svc_domain = $record->svc_domain;
+
+ $email = $record->email;
+
+ $seconds_since = $record->seconds_since($timestamp);
+
+=head1 DESCRIPTION
+
+An FS::svc_acct object represents an account. FS::svc_acct inherits from
+FS::svc_Common. The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatcially for new accounts)
+
+=item username
+
+=item _password - generated if blank
+
+=item sec_phrase - security phrase
+
+=item popnum - Point of presence (see L<FS::svc_acct_pop>)
+
+=item uid
+
+=item gid
+
+=item finger - GECOS
+
+=item dir - set automatically if blank (and uid is not)
+
+=item shell
+
+=item quota - (unimplementd)
+
+=item slipip - IP address
+
+=item seconds -
+
+=item domsvc - svcnum from svc_domain
+
+=item radius_I<Radius_Attribute> - I<Radius-Attribute>
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new account. To add the account to the database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_acct'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this account to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+The additional field I<usergroup> can optionally be defined; if so it should
+contain an arrayref of group names. See L<FS::radius_usergroup>.
+
+The additional field I<child_objects> can optionally be defined; if so it
+should contain an arrayref of FS::tablename objects. They will have their
+svcnum fields set and will be inserted after this record, but before any
+exports are run.
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+(TODOC: L<FS::queue> and L<freeside-queued>)
+
+(TODOC: new exports!)
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ return $error if $error;
+
+ if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) {
+ my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
+ unless ( $cust_svc ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "no cust_svc record found for svcnum ". $self->svcnum;
+ }
+ $self->pkgnum($cust_svc->pkgnum);
+ $self->svcpart($cust_svc->svcpart);
+ }
+
+ $error = $self->_check_duplicate;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ my @jobnums;
+ $error = $self->SUPER::insert(
+ 'jobnums' => \@jobnums,
+ 'child_objects' => $self->child_objects,
+ %options,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $self->usergroup ) {
+ foreach my $groupname ( @{$self->usergroup} ) {
+ my $radius_usergroup = new FS::radius_usergroup ( {
+ svcnum => $self->svcnum,
+ groupname => $groupname,
+ } );
+ my $error = $radius_usergroup->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ #false laziness with sub replace (and cust_main)
+ my $queue = new FS::queue {
+ 'svcnum' => $self->svcnum,
+ 'job' => 'FS::svc_acct::append_fuzzyfiles'
+ };
+ $error = $queue->insert($self->username);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+
+ my $cust_pkg = $self->cust_svc->cust_pkg;
+
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+
+ if ( $conf->exists('emailinvoiceauto') ) {
+ my @invoicing_list = $cust_main->invoicing_list;
+ push @invoicing_list, $self->email;
+ $cust_main->invoicing_list(\@invoicing_list);
+ }
+
+ #welcome email
+ my $to = '';
+ if ( $welcome_template && $cust_pkg ) {
+ my $to = join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list );
+ if ( $to ) {
+ my $wqueue = new FS::queue {
+ 'svcnum' => $self->svcnum,
+ 'job' => 'FS::svc_acct::send_email'
+ };
+ my $error = $wqueue->insert(
+ 'to' => $to,
+ 'from' => $welcome_from,
+ 'subject' => $welcome_subject,
+ 'mimetype' => $welcome_mimetype,
+ 'body' => $welcome_template->fill_in( HASH => {
+ 'custnum' => $self->custnum,
+ 'username' => $self->username,
+ 'password' => $self->_password,
+ 'first' => $cust_main->first,
+ 'last' => $cust_main->getfield('last'),
+ 'pkg' => $cust_pkg->part_pkg->pkg,
+ } ),
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing welcome email: $error";
+ }
+
+ if ( $options{'depend_jobnum'} ) {
+ warn "$me depend_jobnum found; adding to welcome email dependancies"
+ if $DEBUG;
+ if ( ref($options{'depend_jobnum'}) ) {
+ warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ).
+ "to welcome email dependancies"
+ if $DEBUG;
+ push @jobnums, @{ $options{'depend_jobnum'} };
+ } else {
+ warn "$me adding job $options{'depend_jobnum'} ".
+ "to welcome email dependancies"
+ if $DEBUG;
+ push @jobnums, $options{'depend_jobnum'};
+ }
+ }
+
+ foreach my $jobnum ( @jobnums ) {
+ my $error = $wqueue->depend_insert($jobnum);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error queuing welcome email job dependancy: $error";
+ }
+ }
+
+ }
+
+ }
+
+ } # if ( $cust_pkg )
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item delete
+
+Deletes this account from the database. If there is an error, returns the
+error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+(TODOC: new exports!)
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "can't delete system account" if $self->_check_system;
+
+ return "Can't delete an account which is a (svc_forward) source!"
+ if qsearch( 'svc_forward', { 'srcsvc' => $self->svcnum } );
+
+ return "Can't delete an account which is a (svc_forward) destination!"
+ if qsearch( 'svc_forward', { 'dstsvc' => $self->svcnum } );
+
+ return "Can't delete an account with (svc_www) web service!"
+ if qsearch( 'svc_www', { 'usersvc' => $self->svcnum } );
+
+ # what about records in session ? (they should refer to history table)
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $cust_main_invoice (
+ qsearch( 'cust_main_invoice', { 'dest' => $self->svcnum } )
+ ) {
+ unless ( defined($cust_main_invoice) ) {
+ warn "WARNING: something's wrong with qsearch";
+ next;
+ }
+ my %hash = $cust_main_invoice->hash;
+ $hash{'dest'} = $self->email;
+ my $new = new FS::cust_main_invoice \%hash;
+ my $error = $new->replace($cust_main_invoice);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $svc_domain (
+ qsearch( 'svc_domain', { 'catchall' => $self->svcnum } )
+ ) {
+ my %hash = new FS::svc_domain->hash;
+ $hash{'catchall'} = '';
+ my $new = new FS::svc_domain \%hash;
+ my $error = $new->replace($svc_domain);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $radius_usergroup (
+ qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } )
+ ) {
+ my $error = $radius_usergroup->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+The additional field I<usergroup> can optionally be defined; if so it should
+contain an arrayref of group names. See L<FS::radius_usergroup>.
+
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+ my $error;
+ warn "$me replacing $old with $new\n" if $DEBUG;
+
+ return "can't modify system account" if $old->_check_system;
+
+ return "Username in use"
+ if $old->username ne $new->username &&
+ qsearchs( 'svc_acct', { 'username' => $new->username,
+ 'domsvc' => $new->domsvc,
+ } );
+ {
+ #no warnings 'numeric'; #alas, a 5.006-ism
+ local($^W) = 0;
+ return "Can't change uid!" if $old->uid != $new->uid;
+ }
+
+ #change homdir when we change username
+ $new->setfield('dir', '') if $old->username ne $new->username;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ # redundant, but so $new->usergroup gets set
+ $error = $new->check;
+ return $error if $error;
+
+ $old->usergroup( [ $old->radius_groups ] );
+ warn "old groups: ". join(' ',@{$old->usergroup}). "\n" if $DEBUG;
+ warn "new groups: ". join(' ',@{$new->usergroup}). "\n" if $DEBUG;
+ if ( $new->usergroup ) {
+ #(sorta) false laziness with FS::part_export::sqlradius::_export_replace
+ my @newgroups = @{$new->usergroup};
+ foreach my $oldgroup ( @{$old->usergroup} ) {
+ if ( grep { $oldgroup eq $_ } @newgroups ) {
+ @newgroups = grep { $oldgroup ne $_ } @newgroups;
+ next;
+ }
+ my $radius_usergroup = qsearchs('radius_usergroup', {
+ svcnum => $old->svcnum,
+ groupname => $oldgroup,
+ } );
+ my $error = $radius_usergroup->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error deleting radius_usergroup $oldgroup: $error";
+ }
+ }
+
+ foreach my $newgroup ( @newgroups ) {
+ my $radius_usergroup = new FS::radius_usergroup ( {
+ svcnum => $new->svcnum,
+ groupname => $newgroup,
+ } );
+ my $error = $radius_usergroup->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "error adding radius_usergroup $newgroup: $error";
+ }
+ }
+
+ }
+
+ if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
+ $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
+ $error = $new->_check_duplicate;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $error = $new->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error if $error;
+ }
+
+ if ( $new->username ne $old->username ) {
+ #false laziness with sub insert (and cust_main)
+ my $queue = new FS::queue {
+ 'svcnum' => $new->svcnum,
+ 'job' => 'FS::svc_acct::append_fuzzyfiles'
+ };
+ $error = $queue->insert($new->username);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+}
+
+=item suspend
+
+Suspends this account by calling export-specific suspend hooks. If there is
+an error, returns the error, otherwise returns false.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=cut
+
+sub suspend {
+ my $self = shift;
+ return "can't suspend system account" if $self->_check_system;
+ $self->SUPER::suspend;
+}
+
+=item unsuspend
+
+Unsuspends this account by by calling export-specific suspend hooks. If there
+is an error, returns the error, otherwise returns false.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=cut
+
+sub unsuspend {
+ my $self = shift;
+ my %hash = $self->hash;
+ if ( $hash{_password} =~ /^\*SUSPENDED\* (.*)$/ ) {
+ $hash{_password} = $1;
+ my $new = new FS::svc_acct ( \%hash );
+ my $error = $new->replace($self);
+ return $error if $error;
+ }
+
+ $self->SUPER::unsuspend;
+}
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+If the B<auto_unset_catchall> configuration option is set, this method will
+automatically remove any references to the canceled service in the catchall
+field of svc_domain. This allows packages that contain both a svc_domain and
+its catchall svc_acct to be canceled in one step.
+
+=cut
+
+sub cancel {
+ # Only one thing to do at this level
+ my $self = shift;
+ foreach my $svc_domain (
+ qsearch( 'svc_domain', { catchall => $self->svcnum } ) ) {
+ if($conf->exists('auto_unset_catchall')) {
+ my %hash = $svc_domain->hash;
+ $hash{catchall} = '';
+ my $new = new FS::svc_domain ( \%hash );
+ my $error = $new->replace($svc_domain);
+ return $error if $error;
+ } else {
+ return "cannot unprovision svc_acct #".$self->svcnum.
+ " while assigned as catchall for svc_domain #".$svc_domain->svcnum;
+ }
+ }
+
+ $self->SUPER::cancel;
+}
+
+
+=item check
+
+Checks all fields to make sure this is a valid service. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my($recref) = $self->hashref;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ my $part_svc = $x;
+
+ if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
+ $self->usergroup(
+ [ split(',', $part_svc->part_svc_column('usergroup')->columnvalue) ] );
+ }
+
+ my $error = $self->ut_numbern('svcnum')
+ #|| $self->ut_number('domsvc')
+ || $self->ut_foreign_key('domsvc', 'svc_domain', 'svcnum' )
+ || $self->ut_textn('sec_phrase')
+ ;
+ return $error if $error;
+
+ my $ulen = $usernamemax || $self->dbdef_table->column('username')->length;
+ if ( $username_uppercase ) {
+ $recref->{username} =~ /^([a-z0-9_\-\.\&]{$usernamemin,$ulen})$/i
+ or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
+ $recref->{username} = $1;
+ } else {
+ $recref->{username} =~ /^([a-z0-9_\-\.\&]{$usernamemin,$ulen})$/
+ or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
+ $recref->{username} = $1;
+ }
+
+ if ( $username_letterfirst ) {
+ $recref->{username} =~ /^[a-z]/ or return gettext('illegal_username');
+ } elsif ( $username_letter ) {
+ $recref->{username} =~ /[a-z]/ or return gettext('illegal_username');
+ }
+ if ( $username_noperiod ) {
+ $recref->{username} =~ /\./ and return gettext('illegal_username');
+ }
+ if ( $username_nounderscore ) {
+ $recref->{username} =~ /_/ and return gettext('illegal_username');
+ }
+ if ( $username_nodash ) {
+ $recref->{username} =~ /\-/ and return gettext('illegal_username');
+ }
+ unless ( $username_ampersand ) {
+ $recref->{username} =~ /\&/ and return gettext('illegal_username');
+ }
+
+ $recref->{popnum} =~ /^(\d*)$/ or return "Illegal popnum: ".$recref->{popnum};
+ $recref->{popnum} = $1;
+ return "Unknown popnum" unless
+ ! $recref->{popnum} ||
+ qsearchs('svc_acct_pop',{'popnum'=> $recref->{popnum} } );
+
+ unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+
+ $recref->{uid} =~ /^(\d*)$/ or return "Illegal uid";
+ $recref->{uid} = $1 eq '' ? $self->unique('uid') : $1;
+
+ $recref->{gid} =~ /^(\d*)$/ or return "Illegal gid";
+ $recref->{gid} = $1 eq '' ? $recref->{uid} : $1;
+ #not all systems use gid=uid
+ #you can set a fixed gid in part_svc
+
+ return "Only root can have uid 0"
+ if $recref->{uid} == 0
+ && $recref->{username} ne 'root'
+ && $recref->{username} ne 'toor';
+
+
+ $recref->{dir} =~ /^([\/\w\-\.\&]*)$/
+ or return "Illegal directory: ". $recref->{dir};
+ $recref->{dir} = $1;
+ return "Illegal directory"
+ if $recref->{dir} =~ /(^|\/)\.+(\/|$)/; #no .. component
+ return "Illegal directory"
+ if $recref->{dir} =~ /\&/ && ! $username_ampersand;
+ unless ( $recref->{dir} ) {
+ $recref->{dir} = $dir_prefix . '/';
+ if ( $dirhash > 0 ) {
+ for my $h ( 1 .. $dirhash ) {
+ $recref->{dir} .= substr($recref->{username}, $h-1, 1). '/';
+ }
+ } elsif ( $dirhash < 0 ) {
+ for my $h ( reverse $dirhash .. -1 ) {
+ $recref->{dir} .= substr($recref->{username}, $h, 1). '/';
+ }
+ }
+ $recref->{dir} .= $recref->{username};
+ ;
+ }
+
+ unless ( $recref->{username} eq 'sync' ) {
+ if ( grep $_ eq $recref->{shell}, @shells ) {
+ $recref->{shell} = (grep $_ eq $recref->{shell}, @shells)[0];
+ } else {
+ return "Illegal shell \`". $self->shell. "\'; ".
+ $conf->dir. "/shells contains: @shells";
+ }
+ } else {
+ $recref->{shell} = '/bin/sync';
+ }
+
+ } else {
+ $recref->{gid} ne '' ?
+ return "Can't have gid without uid" : ( $recref->{gid}='' );
+ $recref->{dir} ne '' ?
+ return "Can't have directory without uid" : ( $recref->{dir}='' );
+ $recref->{shell} ne '' ?
+ return "Can't have shell without uid" : ( $recref->{shell}='' );
+ }
+
+ # $error = $self->ut_textn('finger');
+ # return $error if $error;
+ if ( $self->getfield('finger') eq '' ) {
+ my $cust_pkg = $self->svcnum
+ ? $self->cust_svc->cust_pkg
+ : qsearchs('cust_pkg', { 'pkgnum' => $self->getfield('pkgnum') } );
+ if ( $cust_pkg ) {
+ my $cust_main = $cust_pkg->cust_main;
+ $self->setfield('finger', $cust_main->first.' '.$cust_main->get('last') );
+ }
+ }
+ $self->getfield('finger') =~
+ /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\'\"\,\.\?\/\*\<\>]*)$/
+ or return "Illegal finger: ". $self->getfield('finger');
+ $self->setfield('finger', $1);
+
+ $recref->{quota} =~ /^(\w*)$/ or return "Illegal quota";
+ $recref->{quota} = $1;
+
+ unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) {
+ if ( $recref->{slipip} eq '' ) {
+ $recref->{slipip} = '';
+ } elsif ( $recref->{slipip} eq '0e0' ) {
+ $recref->{slipip} = '0e0';
+ } else {
+ $recref->{slipip} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/
+ or return "Illegal slipip: ". $self->slipip;
+ $recref->{slipip} = $1;
+ }
+
+ }
+
+ #arbitrary RADIUS stuff; allow ut_textn for now
+ foreach ( grep /^radius_/, fields('svc_acct') ) {
+ $self->ut_textn($_);
+ }
+
+ #generate a password if it is blank
+ $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) )
+ unless ( $recref->{_password} );
+
+ #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) {
+ if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{$passwordmin,$passwordmax})$/ ) {
+ $recref->{_password} = $1.$3;
+ #uncomment this to encrypt password immediately upon entry, or run
+ #bin/crypt_pw in cron to give new users a window during which their
+ #password is available to techs, for faxing, etc. (also be aware of
+ #radius issues!)
+ #$recref->{password} = $1.
+ # crypt($3,$saltset[int(rand(64))].$saltset[int(rand(64))]
+ #;
+ } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([\w\.\/\$\;\+]{13,60})$/ ) {
+ $recref->{_password} = $1.$3;
+ } elsif ( $recref->{_password} eq '*' ) {
+ $recref->{_password} = '*';
+ } elsif ( $recref->{_password} eq '!' ) {
+ $recref->{_password} = '!';
+ } elsif ( $recref->{_password} eq '!!' ) {
+ $recref->{_password} = '!!';
+ } else {
+ #return "Illegal password";
+ return gettext('illegal_password'). " $passwordmin-$passwordmax ".
+ FS::Msgcat::_gettext('illegal_password_characters').
+ ": ". $recref->{_password};
+ }
+
+ $self->SUPER::check;
+}
+
+=item _check_system
+
+Internal function to check the username against the list of system usernames
+from the I<system_usernames> configuration value. Returns true if the username
+is listed on the system username list.
+
+=cut
+
+sub _check_system {
+ my $self = shift;
+ scalar( grep { $self->username eq $_ || $self->email eq $_ }
+ $conf->config('system_usernames')
+ );
+}
+
+=item _check_duplicate
+
+Internal function to check for duplicates usernames, username@domain pairs and
+uids.
+
+If the I<global_unique-username> configuration value is set to B<username> or
+B<username@domain>, enforces global username or username@domain uniqueness.
+
+In all cases, check for duplicate uids and usernames or username@domain pairs
+per export and with identical I<svcpart> values.
+
+=cut
+
+sub _check_duplicate {
+ my $self = shift;
+
+ #this is Pg-specific. what to do for mysql etc?
+ # ( mysql LOCK TABLES certainly isn't equivalent or useful here :/ )
+ warn "$me locking svc_acct table for duplicate search" if $DEBUG;
+ dbh->do("LOCK TABLE svc_acct IN SHARE ROW EXCLUSIVE MODE")
+ or die dbh->errstr;
+ warn "$me acquired svc_acct table lock for duplicate search" if $DEBUG;
+
+ my $svcpart = $self->svcpart;
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ unless ( $part_svc ) {
+ return 'unknown svcpart '. $self->svcpart;
+ }
+
+ my $global_unique = $conf->config('global_unique-username');
+
+ my @dup_user = grep { $svcpart != $_->svcpart }
+ qsearch( 'svc_acct', { 'username' => $self->username } );
+ return gettext('username_in_use')
+ if $global_unique eq 'username' && @dup_user;
+
+ my @dup_userdomain = grep { $svcpart != $_->svcpart }
+ qsearch( 'svc_acct', { 'username' => $self->username,
+ 'domsvc' => $self->domsvc } );
+ return gettext('username_in_use')
+ if $global_unique eq 'username@domain' && @dup_userdomain;
+
+ my @dup_uid;
+ if ( $part_svc->part_svc_column('uid')->columnflag ne 'F'
+ && $self->username !~ /^(toor|(hyla)?fax)$/ ) {
+ @dup_uid = grep { $svcpart != $_->svcpart }
+ qsearch( 'svc_acct', { 'uid' => $self->uid } );
+ } else {
+ @dup_uid = ();
+ }
+
+ if ( @dup_user || @dup_userdomain || @dup_uid ) {
+ my $exports = FS::part_export::export_info('svc_acct');
+ my %conflict_user_svcpart;
+ my %conflict_userdomain_svcpart = ( $self->svcpart => 'SELF', );
+
+ foreach my $part_export ( $part_svc->part_export ) {
+
+ #this will catch to the same exact export
+ my @svcparts = map { $_->svcpart } $part_export->export_svc;
+
+ #this will catch to exports w/same exporthost+type ???
+ #my @other_part_export = qsearch('part_export', {
+ # 'machine' => $part_export->machine,
+ # 'exporttype' => $part_export->exporttype,
+ #} );
+ #foreach my $other_part_export ( @other_part_export ) {
+ # push @svcparts, map { $_->svcpart }
+ # qsearch('export_svc', { 'exportnum' => $part_export->exportnum });
+ #}
+
+ #my $nodomain = $exports->{$part_export->exporttype}{'nodomain'};
+ #silly kludge to avoid uninitialized value errors
+ my $nodomain = exists( $exports->{$part_export->exporttype}{'nodomain'} )
+ ? $exports->{$part_export->exporttype}{'nodomain'}
+ : '';
+ if ( $nodomain =~ /^Y/i ) {
+ $conflict_user_svcpart{$_} = $part_export->exportnum
+ foreach @svcparts;
+ } else {
+ $conflict_userdomain_svcpart{$_} = $part_export->exportnum
+ foreach @svcparts;
+ }
+ }
+
+ foreach my $dup_user ( @dup_user ) {
+ my $dup_svcpart = $dup_user->cust_svc->svcpart;
+ if ( exists($conflict_user_svcpart{$dup_svcpart}) ) {
+ return "duplicate username: conflicts with svcnum ". $dup_user->svcnum.
+ " via exportnum ". $conflict_user_svcpart{$dup_svcpart};
+ }
+ }
+
+ foreach my $dup_userdomain ( @dup_userdomain ) {
+ my $dup_svcpart = $dup_userdomain->cust_svc->svcpart;
+ if ( exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
+ return "duplicate username\@domain: conflicts with svcnum ".
+ $dup_userdomain->svcnum. " via exportnum ".
+ $conflict_userdomain_svcpart{$dup_svcpart};
+ }
+ }
+
+ foreach my $dup_uid ( @dup_uid ) {
+ my $dup_svcpart = $dup_uid->cust_svc->svcpart;
+ if ( exists($conflict_user_svcpart{$dup_svcpart})
+ || exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
+ return "duplicate uid: conflicts with svcnum". $dup_uid->svcnum.
+ "via exportnum ". $conflict_user_svcpart{$dup_svcpart}
+ || $conflict_userdomain_svcpart{$dup_svcpart};
+ }
+ }
+
+ }
+
+ return '';
+
+}
+
+=item radius
+
+Depriciated, use radius_reply instead.
+
+=cut
+
+sub radius {
+ carp "FS::svc_acct::radius depriciated, use radius_reply";
+ $_[0]->radius_reply;
+}
+
+=item radius_reply
+
+Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
+reply attributes of this record.
+
+Note that this is now the preferred method for reading RADIUS attributes -
+accessing the columns directly is discouraged, as the column names are
+expected to change in the future.
+
+=cut
+
+sub radius_reply {
+ my $self = shift;
+ my %reply =
+ map {
+ /^(radius_(.*))$/;
+ my($column, $attrib) = ($1, $2);
+ #$attrib =~ s/_/\-/g;
+ ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
+ } grep { /^radius_/ && $self->getfield($_) } fields( $self->table );
+ if ( $self->slipip && $self->slipip ne '0e0' ) {
+ $reply{$radius_ip} = $self->slipip;
+ }
+ %reply;
+}
+
+=item radius_check
+
+Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
+check attributes of this record.
+
+Note that this is now the preferred method for reading RADIUS attributes -
+accessing the columns directly is discouraged, as the column names are
+expected to change in the future.
+
+=cut
+
+sub radius_check {
+ my $self = shift;
+ my $password = $self->_password;
+ my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password';
+ ( $pw_attrib => $password,
+ map {
+ /^(rc_(.*))$/;
+ my($column, $attrib) = ($1, $2);
+ #$attrib =~ s/_/\-/g;
+ ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) );
+ } grep { /^rc_/ && $self->getfield($_) } fields( $self->table )
+ );
+}
+
+=item domain
+
+Returns the domain associated with this account.
+
+=cut
+
+sub domain {
+ my $self = shift;
+ die "svc_acct.domsvc is null for svcnum ". $self->svcnum unless $self->domsvc;
+ my $svc_domain = $self->svc_domain
+ or die "no svc_domain.svcnum for svc_acct.domsvc ". $self->domsvc;
+ $svc_domain->domain;
+}
+
+=item svc_domain
+
+Returns the FS::svc_domain record for this account's domain (see
+L<FS::svc_domain>).
+
+=cut
+
+sub svc_domain {
+ my $self = shift;
+ $self->{'_domsvc'}
+ ? $self->{'_domsvc'}
+ : qsearchs( 'svc_domain', { 'svcnum' => $self->domsvc } );
+}
+
+=item cust_svc
+
+Returns the FS::cust_svc record for this account (see L<FS::cust_svc>).
+
+=cut
+
+sub cust_svc {
+ my $self = shift;
+ qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item email
+
+Returns an email address associated with the account.
+
+=cut
+
+sub email {
+ my $self = shift;
+ $self->username. '@'. $self->domain;
+}
+
+=item acct_snarf
+
+Returns an array of FS::acct_snarf records associated with the account.
+If the acct_snarf table does not exist or there are no associated records,
+an empty list is returned
+
+=cut
+
+sub acct_snarf {
+ my $self = shift;
+ return () unless dbdef->table('acct_snarf');
+ eval "use FS::acct_snarf;";
+ die $@ if $@;
+ qsearch('acct_snarf', { 'svcnum' => $self->svcnum } );
+}
+
+=item seconds_since TIMESTAMP
+
+Returns the number of seconds this account has been online since TIMESTAMP,
+according to the session monitor (see L<FS::Session>).
+
+TIMESTAMP is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+#note: POD here, implementation in FS::cust_svc
+sub seconds_since {
+ my $self = shift;
+ $self->cust_svc->seconds_since(@_);
+}
+
+=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+Returns the numbers of seconds this account has been online between
+TIMESTAMP_START (inclusive) and TIMESTAMP_END (exclusive), according to an
+external SQL radacct table, specified via sqlradius export. Sessions which
+started in the specified range but are still open are counted from session
+start to the end of the range (unless they are over 1 day old, in which case
+they are presumed missing their stop record and not counted). Also, sessions
+which end in the range but started earlier are counted from the start of the
+range to session end. Finally, sessions which start before the range but end
+after are counted for the entire range.
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+#note: POD here, implementation in FS::cust_svc
+sub seconds_since_sqlradacct {
+ my $self = shift;
+ $self->cust_svc->seconds_since_sqlradacct(@_);
+}
+
+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
+in this package for sessions ending between TIMESTAMP_START (inclusive) and
+TIMESTAMP_END (exclusive).
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+#note: POD here, implementation in FS::cust_svc
+sub attribute_since_sqlradacct {
+ my $self = shift;
+ $self->cust_svc->attribute_since_sqlradacct(@_);
+}
+
+=item get_session_history_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+Returns an array of hash references of this customers login history for the
+given time range. (document this better)
+
+=cut
+
+sub get_session_history_sqlradacct {
+ my $self = shift;
+ $self->cust_svc->get_session_history_sqlradacct(@_);
+}
+
+=item radius_groups
+
+Returns all RADIUS groups for this account (see L<FS::radius_usergroup>).
+
+=cut
+
+sub radius_groups {
+ my $self = shift;
+ if ( $self->usergroup ) {
+ #when provisioning records, export callback runs in svc_Common.pm before
+ #radius_usergroup records can be inserted...
+ @{$self->usergroup};
+ } else {
+ map { $_->groupname }
+ qsearch('radius_usergroup', { 'svcnum' => $self->svcnum } );
+ }
+}
+
+=item clone_suspended
+
+Constructor used by FS::part_export::_export_suspend fallback. Document
+better.
+
+=cut
+
+sub clone_suspended {
+ my $self = shift;
+ my %hash = $self->hash;
+ $hash{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+ new FS::svc_acct \%hash;
+}
+
+=item clone_kludge_unsuspend
+
+Constructor used by FS::part_export::_export_unsuspend fallback. Document
+better.
+
+=cut
+
+sub clone_kludge_unsuspend {
+ my $self = shift;
+ my %hash = $self->hash;
+ $hash{_password} = '';
+ new FS::svc_acct \%hash;
+}
+
+=item check_password
+
+Checks the supplied password against the (possibly encrypted) password in the
+database. Returns true for a sucessful authentication, false for no match.
+
+Currently supported encryptions are: classic DES crypt() and MD5
+
+=cut
+
+sub check_password {
+ my($self, $check_password) = @_;
+
+ #remove old-style SUSPENDED kludge, they should be allowed to login to
+ #self-service and pay up
+ ( my $password = $self->_password ) =~ s/^\*SUSPENDED\* //;
+
+ #eventually should check a "password-encoding" field
+ if ( $password =~ /^(\*|!!?)$/ ) { #no self-service login
+ return 0;
+ } elsif ( length($password) < 13 ) { #plaintext
+ $check_password eq $password;
+ } elsif ( length($password) == 13 ) { #traditional DES crypt
+ crypt($check_password, $password) eq $password;
+ } elsif ( $password =~ /^\$1\$/ ) { #MD5 crypt
+ unix_md5_crypt($check_password, $password) eq $password;
+ } elsif ( $password =~ /^\$2a?\$/ ) { #Blowfish
+ warn "Can't check password: Blowfish encryption not yet supported, svcnum".
+ $self->svcnum. "\n";
+ 0;
+ } else {
+ warn "Can't check password: Unrecognized encryption for svcnum ".
+ $self->svcnum. "\n";
+ 0;
+ }
+
+}
+
+=item crypt_password
+
+Returns an encrypted password, either by passing through an encrypted password
+in the database or by encrypting a plaintext password from the database.
+
+=cut
+
+sub crypt_password {
+ my $self = shift;
+ #false laziness w/shellcommands.pm
+ #eventually should check a "password-encoding" field
+ if ( length($self->_password) == 13
+ || $self->_password =~ /^\$(1|2a?)\$/ ) {
+ $self->_password;
+ } else {
+ crypt(
+ $self->_password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ }
+}
+
+=item virtual_maildir
+
+Returns $domain/maildirs/$username/
+
+=cut
+
+sub virtual_maildir {
+ my $self = shift;
+ $self->domain. '/maildirs/'. $self->username. '/';
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item send_email
+
+This is the FS::svc_acct job-queue-able version. It still uses
+FS::Misc::send_email under-the-hood.
+
+=cut
+
+sub send_email {
+ my %opt = @_;
+
+ eval "use FS::Misc qw(send_email)";
+ die $@ if $@;
+
+ $opt{mimetype} ||= 'text/plain';
+ $opt{mimetype} .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
+
+ my $error = send_email(
+ 'from' => $opt{from},
+ 'to' => $opt{to},
+ 'subject' => $opt{subject},
+ 'content-type' => $opt{mimetype},
+ 'body' => [ map "$_\n", split("\n", $opt{body}) ],
+ );
+ die $error if $error;
+}
+
+=item check_and_rebuild_fuzzyfiles
+
+=cut
+
+sub check_and_rebuild_fuzzyfiles {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ -e "$dir/svc_acct.username"
+ or &rebuild_fuzzyfiles;
+}
+
+=item rebuild_fuzzyfiles
+
+=cut
+
+sub rebuild_fuzzyfiles {
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ #username
+
+ open(USERNAMELOCK,">>$dir/svc_acct.username")
+ or die "can't open $dir/svc_acct.username: $!";
+ flock(USERNAMELOCK,LOCK_EX)
+ or die "can't lock $dir/svc_acct.username: $!";
+
+ my @all_username = map $_->getfield('username'), qsearch('svc_acct', {});
+
+ open (USERNAMECACHE,">$dir/svc_acct.username.tmp")
+ or die "can't open $dir/svc_acct.username.tmp: $!";
+ print USERNAMECACHE join("\n", @all_username), "\n";
+ close USERNAMECACHE or die "can't close $dir/svc_acct.username.tmp: $!";
+
+ rename "$dir/svc_acct.username.tmp", "$dir/svc_acct.username";
+ close USERNAMELOCK;
+
+}
+
+=item all_username
+
+=cut
+
+sub all_username {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ open(USERNAMECACHE,"<$dir/svc_acct.username")
+ or die "can't open $dir/svc_acct.username: $!";
+ my @array = map { chomp; $_; } <USERNAMECACHE>;
+ close USERNAMECACHE;
+ \@array;
+}
+
+=item append_fuzzyfiles USERNAME
+
+=cut
+
+sub append_fuzzyfiles {
+ my $username = shift;
+
+ &check_and_rebuild_fuzzyfiles;
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ open(USERNAME,">>$dir/svc_acct.username")
+ or die "can't open $dir/svc_acct.username: $!";
+ flock(USERNAME,LOCK_EX)
+ or die "can't lock $dir/svc_acct.username: $!";
+
+ print USERNAME "$username\n";
+
+ flock(USERNAME,LOCK_UN)
+ or die "can't unlock $dir/svc_acct.username: $!";
+ close USERNAME;
+
+ 1;
+}
+
+
+
+=item radius_usergroup_selector GROUPS_ARRAYREF [ SELECTNAME ]
+
+=cut
+
+sub radius_usergroup_selector {
+ my $sel_groups = shift;
+ my %sel_groups = map { $_=>1 } @$sel_groups;
+
+ my $selectname = shift || 'radius_usergroup';
+
+ my $dbh = dbh;
+ my $sth = $dbh->prepare(
+ 'SELECT DISTINCT(groupname) FROM radius_usergroup ORDER BY groupname'
+ ) or die $dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+ my @all_groups = map { $_->[0] } @{$sth->fetchall_arrayref};
+
+ my $html = <<END;
+ <SCRIPT>
+ function ${selectname}_doadd(object) {
+ var myvalue = object.${selectname}_add.value;
+ var optionName = new Option(myvalue,myvalue,false,true);
+ var length = object.$selectname.length;
+ object.$selectname.options[length] = optionName;
+ object.${selectname}_add.value = "";
+ }
+ </SCRIPT>
+ <SELECT MULTIPLE NAME="$selectname">
+END
+
+ foreach my $group ( @all_groups ) {
+ $html .= '<OPTION';
+ if ( $sel_groups{$group} ) {
+ $html .= ' SELECTED';
+ $sel_groups{$group} = 0;
+ }
+ $html .= ">$group</OPTION>\n";
+ }
+ foreach my $group ( grep { $sel_groups{$_} } keys %sel_groups ) {
+ $html .= "<OPTION SELECTED>$group</OPTION>\n";
+ };
+ $html .= '</SELECT>';
+
+ $html .= qq!<BR><INPUT TYPE="text" NAME="${selectname}_add">!.
+ qq!<INPUT TYPE="button" VALUE="Add new group" onClick="${selectname}_doadd(this.form)">!;
+
+ $html;
+}
+
+=back
+
+=head1 BUGS
+
+The $recref stuff in sub check should be cleaned up.
+
+The suspend, unsuspend and cancel methods update the database, but not the
+current object. This is probably a bug as it's unexpected and
+counterintuitive.
+
+radius_usergroup_selector? putting web ui components in here? they should
+probably live somewhere else...
+
+insertion of RADIUS group stuff in insert could be done with child_objects now
+(would probably clean up export of them too)
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, edit/part_svc.cgi from an installed web interface,
+export.html from the base documentation, L<FS::Record>, L<FS::Conf>,
+L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>, L<FS::queue>,
+L<freeside-queued>), L<FS::svc_acct_pop>,
+schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_acct_pop.pm b/FS/FS/svc_acct_pop.pm
new file mode 100644
index 0000000..f98f91a
--- /dev/null
+++ b/FS/FS/svc_acct_pop.pm
@@ -0,0 +1,210 @@
+package FS::svc_acct_pop;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK @svc_acct_pop %svc_acct_pop );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw( FS::Record Exporter );
+@EXPORT_OK = qw( popselector );
+
+=head1 NAME
+
+FS::svc_acct_pop - Object methods for svc_acct_pop records
+
+=head1 SYNOPSIS
+
+ use FS::svc_acct_pop;
+
+ $record = new FS::svc_acct_pop \%hash;
+ $record = new FS::svc_acct_pop { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $html = FS::svc_acct_pop::popselector( $popnum, $state );
+
+=head1 DESCRIPTION
+
+An FS::svc_acct object represents an point of presence. FS::svc_acct_pop
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item popnum - primary key (assigned automatically for new accounts)
+
+=item city
+
+=item state
+
+=item ac - area code
+
+=item exch - exchange
+
+=item loc - rest of number
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new point of presence (if only it were that easy!). To add the
+point of presence to the database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_acct_pop'; }
+
+=item insert
+
+Adds this point of presence to the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item delete
+
+Removes this point of presence from the database.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid point of presence. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ $self->ut_numbern('popnum')
+ or $self->ut_text('city')
+ or $self->ut_text('state')
+ or $self->ut_number('ac')
+ or $self->ut_number('exch')
+ or $self->ut_numbern('loc')
+ or $self->SUPER::check
+ ;
+
+}
+
+=item text
+
+Returns:
+
+"$city, $state ($ac)/$exch"
+
+=cut
+
+sub text {
+ my $self = shift;
+ $self->city. ', '. $self->state.
+ ' ('. $self->ac. ')/'. $self->exch. '-'. $self->loc;
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item popselector [ POPNUM [ STATE ] ]
+
+=cut
+
+#horrible false laziness with signup.cgi (pull special-case for 0 & 1
+# pop code out from signup.cgi??)
+sub popselector {
+ my( $popnum, $state ) = @_;
+
+ unless ( @svc_acct_pop ) { #cache pop list
+ @svc_acct_pop = qsearch('svc_acct_pop', {} );
+ %svc_acct_pop = ();
+ push @{$svc_acct_pop{$_->state}}, $_ foreach @svc_acct_pop;
+ }
+
+ my $text = <<END;
+ <SCRIPT>
+ function opt(what,href,text) {
+ var optionName = new Option(text, href, false, false)
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function popstate_changed(what) {
+ state = what.options[what.selectedIndex].text;
+ what.form.popnum.options.length = 0
+ what.form.popnum.options[0] = new Option("", "", false, true);
+END
+
+ foreach my $popstate ( sort { $a cmp $b } keys %svc_acct_pop ) {
+ $text .= "\nif ( state == \"$popstate\" ) {\n";
+
+ foreach my $pop ( @{$svc_acct_pop{$popstate}}) {
+ my $o_popnum = $pop->popnum;
+ my $poptext = $pop->text;
+ $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n"
+ }
+ $text .= "}\n";
+ }
+
+ $text .= "}\n</SCRIPT>\n";
+
+ $text .=
+ qq!<SELECT NAME="popstate" SIZE=1 onChange="popstate_changed(this)">!.
+ qq!<OPTION> !;
+ $text .= "<OPTION>$_" foreach sort { $a cmp $b } keys %svc_acct_pop;
+ $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD><TD>';
+
+ $text .= qq!<SELECT NAME="popnum" SIZE=1><OPTION> !;
+ my @initial_select;
+ if ( scalar(@svc_acct_pop) > 100 ) {
+ @initial_select = qsearchs( 'svc_acct_pop', { 'popnum' => $popnum } );
+ } else {
+ @initial_select = @svc_acct_pop;
+ }
+ foreach my $pop ( @initial_select ) {
+ $text .= qq!<OPTION VALUE="!. $pop->popnum. '"'.
+ ( ( $popnum && $pop->popnum == $popnum ) ? ' SELECTED' : '' ). ">".
+ $pop->text;
+ }
+ $text .= '</SELECT>';
+
+ $text;
+
+}
+
+=back
+
+=head1 VERSION
+
+$Id: svc_acct_pop.pm,v 1.10 2003-08-05 00:20:47 khoff Exp $
+
+=head1 BUGS
+
+It should be renamed to part_pop.
+
+popselector? putting web ui components in here? they should probably live
+somewhere else...
+
+popselector: pull special-case for 0 & 1 pop code out from signup.cgi
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::svc_acct>, L<FS::part_pop_local>, schema.html from the
+base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
new file mode 100755
index 0000000..aaac891
--- /dev/null
+++ b/FS/FS/svc_broadband.pm
@@ -0,0 +1,243 @@
+package FS::svc_broadband;
+
+use strict;
+use vars qw(@ISA $conf);
+use FS::Record qw( qsearchs qsearch dbh );
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::addr_block;
+use NetAddr::IP;
+
+@ISA = qw( FS::svc_Common );
+
+$FS::UID::callback{'FS::svc_broadband'} = sub {
+ $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::svc_broadband - Object methods for svc_broadband records
+
+=head1 SYNOPSIS
+
+ use FS::svc_broadband;
+
+ $record = new FS::svc_broadband \%hash;
+ $record = new FS::svc_broadband { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_broadband object represents a 'broadband' Internet connection, such
+as a DSL, cable modem, or fixed wireless link. These services are assumed to
+have the following properties:
+
+FS::svc_broadband inherits from FS::svc_Common. The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=item blocknum - see FS::addr_block
+
+=item
+speed_up - maximum upload speed, in bits per second. If set to zero, upload
+speed will be unlimited. Exports that do traffic shaping should handle this
+correctly, and not blindly set the upload speed to zero and kill the customer's
+connection.
+
+=item
+speed_down - maximum download speed, as above
+
+=item ip_addr - the customer's IP address. If the customer needs more than one
+IP address, set this to the address of the customer's router. As a result, the
+customer's router will have the same address for both its internal and external
+interfaces thus saving address space. This has been found to work on most NAT
+routers available.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new svc_broadband. To add the record to the database, see
+"insert".
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'svc_broadband'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see FS::cust_svc) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+# Standard FS::svc_Common::insert
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# Standard FS::svc_Common::delete
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# Standard FS::svc_Common::replace
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
+
+=item check
+
+Checks all fields to make sure this is a valid broadband service. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $x = $self->setfixed;
+
+ return $x unless ref($x);
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
+ || $self->ut_number('speed_up')
+ || $self->ut_number('speed_down')
+ || $self->ut_ipn('ip_addr')
+ ;
+ return $error if $error;
+
+ if($self->speed_up < 0) { return 'speed_up must be positive'; }
+ if($self->speed_down < 0) { return 'speed_down must be positive'; }
+
+ if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
+ my $next_addr = $self->addr_block->next_free_addr;
+ if ($next_addr) {
+ $self->ip_addr($next_addr->addr);
+ } else {
+ return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
+ }
+ }
+
+ # This should catch errors in the ip_addr. If it doesn't,
+ # they'll almost certainly not map into the block anyway.
+ my $self_addr = $self->NetAddr; #netmask is /32
+ return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
+
+ my $block_addr = $self->addr_block->NetAddr;
+ unless ($block_addr->contains($self_addr)) {
+ return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
+ }
+
+ my $router = $self->addr_block->router
+ or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
+ if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
+ } # do nothing
+ else {
+ return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
+ }
+
+ $self->SUPER::check;
+}
+
+=item NetAddr
+
+Returns a NetAddr::IP object containing the IP address of this service. The netmask
+is /32.
+
+=cut
+
+sub NetAddr {
+ my $self = shift;
+ return new NetAddr::IP ($self->ip_addr);
+}
+
+=item addr_block
+
+Returns the FS::addr_block record (i.e. the address block) for this broadband service.
+
+=cut
+
+sub addr_block {
+ my $self = shift;
+
+ return qsearchs('addr_block', { blocknum => $self->blocknum });
+}
+
+=back
+
+=item allowed_routers
+
+Returns a list of allowed FS::router objects.
+
+=cut
+
+sub allowed_routers {
+ my $self = shift;
+
+ return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
+}
+
+=head1 BUGS
+
+The business with sb_field has been 'fixed', in a manner of speaking.
+
+=head1 SEE ALSO
+
+FS::svc_Common, FS::Record, FS::addr_block,
+FS::part_svc, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
new file mode 100644
index 0000000..2642146
--- /dev/null
+++ b/FS/FS/svc_domain.pm
@@ -0,0 +1,445 @@
+package FS::svc_domain;
+
+use strict;
+use vars qw( @ISA $whois_hack $conf
+ @defaultrecords $soadefaultttl $soaemail $soaexpire $soamachine
+ $soarefresh $soaretry
+);
+use Carp;
+use Date::Format;
+#use Net::Whois::Raw;
+use FS::Record qw(fields qsearch qsearchs dbh);
+use FS::Conf;
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::cust_pkg;
+use FS::cust_main;
+use FS::domain_record;
+use FS::queue;
+
+@ISA = qw( FS::svc_Common );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::domain'} = sub {
+ $conf = new FS::Conf;
+
+ @defaultrecords = $conf->config('defaultrecords');
+ $soadefaultttl = $conf->config('soadefaultttl');
+ $soaemail = $conf->config('soaemail');
+ $soaexpire = $conf->config('soaexpire');
+ $soamachine = $conf->config('soamachine');
+ $soarefresh = $conf->config('soarefresh');
+ $soaretry = $conf->config('soaretry');
+
+};
+
+=head1 NAME
+
+FS::svc_domain - Object methods for svc_domain records
+
+=head1 SYNOPSIS
+
+ use FS::svc_domain;
+
+ $record = new FS::svc_domain \%hash;
+ $record = new FS::svc_domain { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_domain object represents a domain. FS::svc_domain inherits from
+FS::svc_Common. The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatically for new accounts)
+
+=item domain
+
+=item catchall - optional svcnum of an svc_acct record, designating an email catchall account.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new domain. To add the domain to the database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_domain'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this domain to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields I<pkgnum> and I<svcpart> (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+The additional field I<action> should be set to I<N> for new domains or I<M>
+for transfers.
+
+A registration or transfer email will be submitted unless
+$FS::svc_domain::whois_hack is true.
+
+The additional field I<email> can be used to manually set the admin contact
+email address on this email. Otherwise, the svc_acct records for this package
+(see L<FS::cust_pkg>) are searched. If there is exactly one svc_acct record
+in the same package, it is automatically used. Otherwise an error is returned.
+
+If any I<soamachine> configuration file exists, an SOA record is added to
+the domain_record table (see <FS::domain_record>).
+
+If any records are defined in the I<defaultrecords> configuration file,
+appropriate records are added to the domain_record table (see
+L<FS::domain_record>).
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ return $error if $error;
+
+ return "Domain in use (here)"
+ if qsearchs( 'svc_domain', { 'domain' => $self->domain } );
+
+ my $whois = $self->whois;
+ if ( $self->action eq "N" && ! $whois_hack && $whois ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Domain in use (see whois)";
+ }
+ if ( $self->action eq "M" && ! $whois ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Domain not found (see whois)";
+ }
+
+ $error = $self->SUPER::insert(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $self->submit_internic unless $whois_hack;
+
+ if ( $soamachine ) {
+ my $soa = new FS::domain_record {
+ 'svcnum' => $self->svcnum,
+ 'reczone' => '@',
+ 'recaf' => 'IN',
+ 'rectype' => 'SOA',
+ 'recdata' => "$soamachine $soaemail ( ". time2str("%Y%m%d", time). "00 ".
+ "$soarefresh $soaretry $soaexpire $soadefaultttl )"
+ };
+ $error = $soa->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "couldn't insert SOA record for new domain: $error";
+ }
+
+ foreach my $record ( @defaultrecords ) {
+ my($zone,$af,$type,$data) = split(/\s+/,$record,4);
+ my $domain_record = new FS::domain_record {
+ 'svcnum' => $self->svcnum,
+ 'reczone' => $zone,
+ 'recaf' => $af,
+ 'rectype' => $type,
+ 'recdata' => $data,
+ };
+ my $error = $domain_record->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "couldn't insert record for new domain: $error";
+ }
+ }
+
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ ''; #no error
+}
+
+=item delete
+
+Deletes this domain from the database. If there is an error, returns the
+error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ return "Can't delete a domain which has accounts!"
+ if qsearch( 'svc_acct', { 'domsvc' => $self->svcnum } );
+
+ #return "Can't delete a domain with (domain_record) zone entries!"
+ # if qsearch('domain_record', { 'svcnum' => $self->svcnum } );
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $domain_record ( reverse $self->domain_record ) {
+ my $error = $domain_record->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ return "Can't change domain - reorder."
+ if $old->getfield('domain') ne $new->getfield('domain');
+
+ # Better to do it here than to force the caller to remember that svc_domain is weird.
+ $new->setfield(action => 'M');
+ my $error = $new->SUPER::replace($old);
+ return $error if $error;
+}
+
+=item suspend
+
+Just returns false (no error) for now.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Just returns false (no error) for now.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Just returns false (no error) for now.
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid domain. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ #my $part_svc = $x;
+
+ my $error = $self->ut_numbern('svcnum')
+ || $self->ut_numbern('catchall')
+ ;
+ return $error if $error;
+
+ #hmm
+ my $pkgnum;
+ if ( $self->svcnum ) {
+ my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
+ $pkgnum = $cust_svc->pkgnum;
+ } else {
+ $pkgnum = $self->pkgnum;
+ }
+
+ my($recref) = $self->hashref;
+
+ unless ( $whois_hack ) {
+ unless ( $self->email ) { #find out an email address
+ my @svc_acct;
+ foreach ( qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ) ) {
+ my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $_->svcnum } );
+ push @svc_acct, $svc_acct if $svc_acct;
+ }
+
+ if ( scalar(@svc_acct) == 0 ) {
+ return "Must order an account in package ". $pkgnum. " first";
+ } elsif ( scalar(@svc_acct) > 1 ) {
+ return "More than one account in package ". $pkgnum. ": specify admin contact email";
+ } else {
+ $self->email($svc_acct[0]->email );
+ }
+ }
+ }
+
+ #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) {
+ if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu)$/ ) {
+ $recref->{domain} = "$1.$2";
+ # hmmmmmmmm.
+ } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)$/ ) {
+ $recref->{domain} = $1;
+ } else {
+ return "Illegal domain ". $recref->{domain}.
+ " (or unknown registry - try \$whois_hack)";
+ }
+
+ $recref->{action} =~ /^(M|N)$/
+ or return "Illegal action: ". $recref->{action};
+ $recref->{action} = $1;
+
+ if ( $recref->{catchall} ne '' ) {
+ my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $recref->{catchall} } );
+ return "Unknown catchall" unless $svc_acct;
+ }
+
+ $self->ut_textn('purpose')
+ or $self->SUPER::check;
+
+}
+
+=item domain_record
+
+=cut
+
+sub domain_record {
+ my $self = shift;
+
+ my %order = (
+ SOA => 1,
+ NS => 2,
+ MX => 3,
+ CNAME => 4,
+ A => 5,
+ );
+
+ sort { $order{$a->rectype} <=> $order{$b->rectype} }
+ qsearch('domain_record', { svcnum => $self->svcnum } );
+
+}
+
+sub catchall_svc_acct {
+ my $self = shift;
+ if ( $self->catchall ) {
+ qsearchs( 'svc_acct', { 'svcnum' => $self->catchall } );
+ } else {
+ '';
+ }
+}
+
+=item whois
+
+# Returns the Net::Whois::Domain object (see L<Net::Whois>) for this domain, or
+# undef if the domain is not found in whois.
+
+(If $FS::svc_domain::whois_hack is true, returns that in all cases instead.)
+
+=cut
+
+sub whois {
+ #$whois_hack or new Net::Whois::Domain $_[0]->domain;
+ $whois_hack or die "whois_hack not set...\n";
+}
+
+=item _whois
+
+Depriciated.
+
+=cut
+
+sub _whois {
+ die "_whois depriciated";
+}
+
+=item submit_internic
+
+Submits a registration email for this domain.
+
+=cut
+
+sub submit_internic {
+ #my $self = shift;
+ carp "submit_internic depreciated";
+}
+
+=back
+
+=head1 BUGS
+
+Delete doesn't send a registration template.
+
+All registries should be supported.
+
+Should change action to a real field.
+
+The $recref stuff in sub check should be cleaned up.
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>,
+L<FS::part_svc>, L<FS::cust_pkg>, L<Net::Whois>, schema.html from the base
+documentation, config.html from the base documentation.
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/svc_external.pm b/FS/FS/svc_external.pm
new file mode 100644
index 0000000..79eec97
--- /dev/null
+++ b/FS/FS/svc_external.pm
@@ -0,0 +1,180 @@
+package FS::svc_external;
+
+use strict;
+use vars qw(@ISA); # $conf
+use FS::UID;
+#use FS::Record qw( qsearch qsearchs dbh);
+use FS::svc_Common;
+
+@ISA = qw( FS::svc_Common );
+
+#FS::UID::install_callback( sub {
+# $conf = new FS::Conf;
+#};
+
+=head1 NAME
+
+FS::svc_external - Object methods for svc_external records
+
+=head1 SYNOPSIS
+
+ use FS::svc_external;
+
+ $record = new FS::svc_external \%hash;
+ $record = new FS::svc_external { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_external object represents a externally tracked service.
+FS::svc_external inherits from FS::svc_Common. The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=item id - unique number of external record
+
+=item title - for invoice line items
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new external service. To add the external service to the database,
+see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'svc_external'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this external service to the database. If there is an error, returns the
+error, otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+#sub insert {
+# my $self = shift;
+# my $error;
+#
+# $error = $self->SUPER::insert(@_);
+# return $error if $error;
+#
+# '';
+#}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+#sub delete {
+# my $self = shift;
+# my $error;
+#
+# $error = $self->SUPER::delete;
+# return $error if $error;
+#
+# '';
+#}
+
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+#sub replace {
+# my ( $new, $old ) = ( shift, shift );
+# my $error;
+#
+# $error = $new->SUPER::replace($old);
+# return $error if $error;
+#
+# '';
+#}
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid external service. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ my $part_svc = $x;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_numbern('id')
+ || $self->ut_textn('title')
+ ;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
+L<FS::cust_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_forward.pm b/FS/FS/svc_forward.pm
new file mode 100644
index 0000000..b8d55fe
--- /dev/null
+++ b/FS/FS/svc_forward.pm
@@ -0,0 +1,306 @@
+package FS::svc_forward;
+
+use strict;
+use vars qw( @ISA );
+use FS::Conf;
+use FS::Record qw( fields qsearch qsearchs dbh );
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::svc_domain;
+
+@ISA = qw( FS::svc_Common );
+
+=head1 NAME
+
+FS::svc_forward - Object methods for svc_forward records
+
+=head1 SYNOPSIS
+
+ use FS::svc_forward;
+
+ $record = new FS::svc_forward \%hash;
+ $record = new FS::svc_forward { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_forward object represents a mail forwarding alias. FS::svc_forward
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatcially for new accounts)
+
+=item srcsvc - svcnum of the source of the forward (see L<FS::svc_acct>)
+
+=item src - literal source (username or full email address)
+
+=item dstsvc - svcnum of the destination of the forward (see L<FS::svc_acct>)
+
+=item dst - literal destination (username or full email address)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new mail forwarding alias. To add the mail forwarding alias to the
+database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_forward'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this mail forwarding alias to the database. If there is an error, returns
+the error, otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ $error = $self->check;
+ return $error if $error;
+
+ $error = $self->SUPER::insert(@_);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ ''; #no error
+
+}
+
+=item delete
+
+Deletes this mail forwarding alias from the database. If there is an error,
+returns the error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+=cut
+
+sub delete {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::Autocommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+
+ if ( $new->srcsvc != $old->srcsvc
+ && ( $new->dstsvc != $old->dstsvc
+ || ! $new->dstsvc && $new->dst ne $old->dst
+ )
+ ) {
+ return "Can't change both source and destination of a mail forward!"
+ }
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $new->SUPER::replace($old);
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item suspend
+
+Just returns false (no error) for now.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Just returns false (no error) for now.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Just returns false (no error) for now.
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid mail forwarding alias. If there
+is an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ #my $part_svc = $x;
+
+ my $error = $self->ut_numbern('svcnum')
+ || $self->ut_numbern('srcsvc')
+ || $self->ut_numbern('dstsvc')
+ ;
+ return $error if $error;
+
+ return "Both srcsvc and src were defined; only one can be specified"
+ if $self->srcsvc && $self->src;
+
+ return "one of srcsvc or src is required"
+ unless $self->srcsvc || $self->src;
+
+ return "Unknown srcsvc: ". $self->srcsvc
+ unless ! $self->srcsvc || $self->srcsvc_acct;
+
+ return "Both dstsvc and dst were defined; only one can be specified"
+ if $self->dstsvc && $self->dst;
+
+ return "one of dstsvc or dst is required"
+ unless $self->dstsvc || $self->dst;
+
+ return "Unknown dstsvc: ". $self->dstsvc
+ unless ! $self->dstsvc || $self->dstsvc_acct;
+ #return "Unknown dstsvc"
+ # unless qsearchs('svc_acct', { 'svcnum' => $self->dstsvc } )
+ # || ! $self->dstsvc;
+
+ if ( $self->src ) {
+ $self->src =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)?$/
+ or return "Illegal src: ". $self->dst;
+ $self->src("$1$2");
+ } else {
+ $self->src('');
+ }
+
+ if ( $self->dst ) {
+ $self->dst =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)?$/
+ or return "Illegal dst: ". $self->dst;
+ $self->dst("$1$2");
+ } else {
+ $self->dst('');
+ }
+
+ $self->SUPER::check;
+}
+
+=item srcsvc_acct
+
+Returns the FS::svc_acct object referenced by the srcsvc column, or false for
+literally specified forwards.
+
+=cut
+
+sub srcsvc_acct {
+ my $self = shift;
+ qsearchs('svc_acct', { 'svcnum' => $self->srcsvc } );
+}
+
+=item dstsvc_acct
+
+Returns the FS::svc_acct object referenced by the srcsvc column, or false for
+literally specified forwards.
+
+=cut
+
+sub dstsvc_acct {
+ my $self = shift;
+ qsearchs('svc_acct', { 'svcnum' => $self->dstsvc } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::Conf>, L<FS::cust_svc>, L<FS::part_svc>, L<FS::cust_pkg>,
+L<FS::svc_acct>, L<FS::svc_domain>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm
new file mode 100644
index 0000000..6c276a1
--- /dev/null
+++ b/FS/FS/svc_www.pm
@@ -0,0 +1,284 @@
+package FS::svc_www;
+
+use strict;
+use vars qw(@ISA $conf $apacheip);
+#use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearchs dbh );
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::domain_record;
+use FS::svc_acct;
+use FS::svc_domain;
+
+@ISA = qw( FS::svc_Common );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::svc_www'} = sub {
+ $conf = new FS::Conf;
+ $apacheip = $conf->config('apacheip');
+};
+
+=head1 NAME
+
+FS::svc_www - Object methods for svc_www records
+
+=head1 SYNOPSIS
+
+ use FS::svc_www;
+
+ $record = new FS::svc_www \%hash;
+ $record = new FS::svc_www { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_www object represents an web virtual host. FS::svc_www inherits
+from FS::svc_Common. The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=item recnum - DNS `A' record corresponding to this web virtual host. (see L<FS::domain_record>)
+
+=item usersvc - account (see L<FS::svc_acct>) corresponding to this web virtual host.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new web virtual host. To add the record to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'svc_www'; }
+
+=item insert [ , OPTION => VALUE ... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ my $error = $self->check;
+ return $error if $error;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ #if ( $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/ ) {
+ if ( $self->recnum =~ /^([\w\-]+|\@)\.(\d+)$/ ) {
+ my( $reczone, $domain_svcnum ) = ( $1, $2 );
+ unless ( $apacheip ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "Configuration option apacheip not set; can't autocreate A record";
+ #"for $reczone". $svc_domain->domain;
+ }
+ my $domain_record = new FS::domain_record {
+ 'svcnum' => $domain_svcnum,
+ 'reczone' => $reczone,
+ 'recaf' => 'IN',
+ 'rectype' => 'A',
+ 'recdata' => $apacheip,
+ };
+ $error = $domain_record->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ $self->recnum($domain_record->recnum);
+ }
+
+ $error = $self->SUPER::insert(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ my $error;
+
+ $error = $self->SUPER::delete;
+ return $error if $error;
+
+ '';
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+ my $error;
+
+ $error = $new->SUPER::replace($old);
+ return $error if $error;
+
+ '';
+}
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid web virtual host. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ #my $part_svc = $x;
+
+ my $error =
+ $self->ut_numbern('svcnum')
+# || $self->ut_number('recnum')
+ || $self->ut_number('usersvc')
+ ;
+ return $error if $error;
+
+ if ( $self->recnum =~ /^(\d+)$/ ) {
+
+ $self->recnum($1);
+ return "Unknown recnum: ". $self->recnum
+ unless qsearchs('domain_record', { 'recnum' => $self->recnum } );
+
+ } elsif ( $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/ ) {
+
+ my( $reczone, $domain ) = ( $1, $2 );
+
+ my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $domain } )
+ or return "unknown domain $domain (recnum $1.$2)";
+
+ my $domain_record = qsearchs( 'domain_record', {
+ 'reczone' => $reczone,
+ 'svcnum' => $svc_domain->svcnum,
+ });
+
+ if ( $domain_record ) {
+ $self->recnum($domain_record->recnum);
+ } else {
+ #insert will create it
+ #$self->recnum("$reczone.$domain");
+ $self->recnum("$reczone.". $svc_domain->svcnum);
+ }
+
+ } else {
+ return "Illegal recnum: ". $self->recnum;
+ }
+
+ return "Unknown usersvc (svc_acct.svcnum): ". $self->usersvc
+ unless qsearchs('svc_acct', { 'svcnum' => $self->usersvc } );
+
+ $self->SUPER::check;
+
+}
+
+=item domain_record
+
+Returns the FS::domain_record record for this web virtual host's zone (see
+L<FS::domain_record>).
+
+=cut
+
+sub domain_record {
+ my $self = shift;
+ qsearchs('domain_record', { 'recnum' => $self->recnum } );
+}
+
+=item svc_acct
+
+Returns the FS::svc_acct record for this web virtual host's owner (see
+L<FS::svc_acct>).
+
+=cut
+
+sub svc_acct {
+ my $self = shift;
+ qsearchs('svc_acct', { 'svcnum' => $self->usersvc } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::domain_record>, L<FS::cust_svc>,
+L<FS::part_svc>, L<FS::cust_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/type_pkgs.pm b/FS/FS/type_pkgs.pm
new file mode 100644
index 0000000..5b3b11c
--- /dev/null
+++ b/FS/FS/type_pkgs.pm
@@ -0,0 +1,126 @@
+package FS::type_pkgs;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs );
+use FS::agent_type;
+use FS::part_pkg;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::type_pkgs - Object methods for type_pkgs records
+
+=head1 SYNOPSIS
+
+ use FS::type_pkgs;
+
+ $record = new FS::type_pkgs \%hash;
+ $record = new FS::type_pkgs { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::type_pkgs record links an agent type (see L<FS::agent_type>) to a
+billing item definition (see L<FS::part_pkg>). FS::type_pkgs inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item typenum - Agent type, see L<FS::agent_type>
+
+=item pkgpart - Billing item definition, see L<FS::part_pkg>
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'type_pkgs'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database. If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is an error,
+returns the error, otherwise returns false. Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_number('typenum')
+ || $self->ut_number('pkgpart')
+ ;
+ return $error if $error;
+
+ return "Unknown typenum"
+ unless qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+
+ return "Unknown pkgpart"
+ unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+
+ $self->SUPER::check;
+}
+
+=item part_pkg
+
+Returns the FS::part_pkg object associated with this record.
+
+=cut
+
+sub part_pkg {
+ my $self = shift;
+ qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=cut
+
+=back
+
+=head1 VERSION
+
+$Id: type_pkgs.pm,v 1.3 2003-08-05 00:20:48 khoff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::agent_type>, L<FS::part_pkgs>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
new file mode 100644
index 0000000..4e78e72
--- /dev/null
+++ b/FS/MANIFEST
@@ -0,0 +1,212 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+README
+bin/freeside-addoutsource
+bin/freeside-addoutsourceuser
+bin/freeside-adduser
+bin/freeside-apply-credits
+bin/freeside-bill
+bin/freeside-count-active-customers
+bin/freeside-daily
+bin/freeside-deloutsource
+bin/freeside-deloutsourceuser
+bin/freeside-deluser
+bin/freeside-email
+bin/freeside-expiration-alerter
+bin/freeside-queued
+bin/freeside-radgroup
+bin/freeside-reexport
+bin/freeside-selfservice-server
+bin/freeside-setinvoice
+bin/freeside-setup
+bin/freeside-sqlradius-radacctd
+bin/freeside-sqlradius-reset
+bin/freeside-sqlradius-seconds
+FS.pm
+FS/CGI.pm
+FS/InitHandler.pm
+FS/ClientAPI.pm
+FS/ClientAPI/passwd.pm
+FS/ClientAPI/MyAccount.pm
+FS/Conf.pm
+FS/ConfItem.pm
+FS/Misc.pm
+FS/Record.pm
+FS/Report.pm
+FS/Report/Table.pm
+FS/Report/Table/Monthly.pm
+FS/SearchCache.pm
+FS/UI/Base.pm
+FS/UI/CGI.pm
+FS/UI/Gtk.pm
+FS/UI/agent.pm
+FS/UID.pm
+FS/Msgcat.pm
+FS/acct_snarf.pm
+FS/agent.pm
+FS/agent_type.pm
+FS/cust_bill.pm
+FS/cust_bill_pkg.pm
+FS/cust_bill_pkg_detail.pm
+FS/cust_credit.pm
+FS/cust_credit_bill.pm
+FS/cust_main.pm
+FS/cust_main_county.pm
+FS/cust_main_invoice.pm
+FS/cust_pay.pm
+FS/cust_bill_event.pm
+FS/cust_bill_pay.pm
+FS/cust_pay_batch.pm
+FS/cust_pay_refund.pm
+FS/cust_pkg.pm
+FS/cust_refund.pm
+FS/cust_credit_refund.pm
+FS/cust_svc.pm
+FS/part_bill_event.pm
+FS/export_svc.pm
+FS/part_export.pm
+FS/part_export_option.pm
+FS/part_export/acct_sql.pm
+FS/part_export/apache.pm
+FS/part_export/bind.pm
+FS/part_export/bind_slave.pm
+FS/part_export/bsdshell.pm
+FS/part_export/communigate_pro.pm
+FS/part_export/communigate_pro_singledomain.pm
+FS/part_export/cp.pm
+FS/part_export/cyrus.pm
+FS/part_export/domain_shellcommands.pm
+FS/part_export/forward_shellcommands.pm
+FS/part_export/http.pm
+FS/part_export/infostreet.pm
+FS/part_export/ldap.pm
+FS/part_export/null.pm
+FS/part_export/shellcommands.pm
+FS/part_export/shellcommands_withdomain.pm
+FS/part_export/sqlmail.pm
+FS/part_export/sqlradius.pm
+FS/part_export/sysvshell.pm
+FS/part_export/textradius.pm
+FS/part_export/vpopmail.pm
+FS/part_export/www_shellcommands.pm
+FS/part_pkg.pm
+FS/part_pop_local.pm
+FS/part_referral.pm
+FS/part_svc.pm
+FS/part_svc_column.pm
+FS/part_svc_router.pm
+FS/part_virtual_field.pm
+FS/pkg_svc.pm
+FS/svc_Common.pm
+FS/svc_acct.pm
+FS/svc_acct_pop.pm
+FS/svc_broadband.pm
+FS/svc_domain.pm
+FS/svc_external.pm
+FS/router.pm
+FS/type_pkgs.pm
+FS/nas.pm
+FS/port.pm
+FS/session.pm
+FS/domain_record.pm
+FS/prepay_credit.pm
+FS/svc_www.pm
+FS/svc_forward.pm
+FS/raddb.pm
+FS/radius_usergroup.pm
+FS/queue.pm
+FS/queue_arg.pm
+FS/queue_depend.pm
+FS/msgcat.pm
+FS/cust_tax_exempt.pm
+t/agent.t
+t/agent_type.t
+t/CGI.t
+t/InitHandler.t
+t/ClientAPI.t
+t/Conf.t
+t/ConfItem.t
+t/Misc.t
+t/Record.t
+t/Report.t
+t/Report-Table.t
+t/Report-Table-Monthly.t
+t/UID.t
+t/Msgcat.t
+t/SearchCache.t
+t/cust_bill.t
+t/cust_bill_event.t
+t/cust_bill_pay.t
+t/cust_bill_pkg.t
+t/cust_bill_pkg_detail.t
+t/cust_credit.t
+t/cust_credit_bill.t
+t/cust_credit_refund.t
+t/cust_main.t
+t/cust_main_county.t
+t/cust_main_invoice.t
+t/cust_pay.t
+t/cust_pay_batch.t
+t/cust_pay_refund.t
+t/cust_pkg.t
+t/cust_refund.t
+t/cust_svc.t
+t/cust_tax_exempt.t
+t/domain_record.t
+t/nas.t
+t/part_bill_event.t
+t/export_svc.t
+t/part_export.t
+t/part_export_option.t
+t/part_export-acct_sql.t
+t/part_export-apache.t
+t/part_export-bind.t
+t/part_export-bind_slave.t
+t/part_export-bsdshell.t
+t/part_export-communigate_pro.t
+t/part_export-communigate_pro_singledomain.t
+t/part_export-cp.t
+t/part_export-cyrus.t
+t/part_export-domain_shellcommands.t
+t/part_export-forward_shellcommands.t
+t/part_export-http.t
+t/part_export-infostreet.t
+t/part_export-ldap.t
+t/part_export-null.t
+t/part_export-passwdfile.t
+t/part_export-postfix.t
+t/part_export-router.t
+t/part_export-shellcommands.t
+t/part_export-shellcommands_withdomain.t
+t/part_export-sqlmail.t
+t/part_export-sqlradius.t
+t/part_export-sysvshell.t
+t/part_export-textradius.t
+t/part_export-vpopmail.t
+t/part_export-www_shellcommands.t
+t/part_pkg.t
+t/part_pop_local.t
+t/part_referral.t
+t/part_svc.t
+t/part_svc_column.t
+t/pkg_svc.t
+t/port.t
+t/prepay_credit.t
+t/radius_usergroup.t
+t/session.t
+t/svc_acct.t
+t/svc_acct_pop.t
+t/svc_broadband.t
+t/svc_Common.t
+t/svc_domain.t
+t/svc_external.t
+t/svc_forward.t
+t/svc_www.t
+t/type_pkgs.t
+t/queue.t
+t/queue_arg.t
+t/queue_depend.t
+t/msgcat.t
+t/raddb.t
diff --git a/FS/MANIFEST.SKIP b/FS/MANIFEST.SKIP
new file mode 100644
index 0000000..ae335e7
--- /dev/null
+++ b/FS/MANIFEST.SKIP
@@ -0,0 +1 @@
+CVS/
diff --git a/FS/Makefile.PL b/FS/Makefile.PL
new file mode 100644
index 0000000..1647f8e
--- /dev/null
+++ b/FS/Makefile.PL
@@ -0,0 +1,10 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'FS',
+ 'VERSION_FROM' => 'FS.pm', # finds $VERSION
+ 'EXE_FILES' => [ glob 'bin/*' ],
+ 'INSTALLSCRIPT' => '/usr/local/bin',
+ 'INSTALLSITEBIN' => '/usr/local/bin',
+);
diff --git a/FS/README b/FS/README
new file mode 100644
index 0000000..d4c35ac
--- /dev/null
+++ b/FS/README
@@ -0,0 +1,6 @@
+This is the Perl module section of Freeside.
+
+perl Makefile.PL
+make
+make test
+make install
diff --git a/FS/bin/freeside-addoutsource b/FS/bin/freeside-addoutsource
new file mode 100644
index 0000000..5cec17f
--- /dev/null
+++ b/FS/bin/freeside-addoutsource
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+domain=$1
+
+createdb $domain && \
+\
+mkdir /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \
+\
+chown freeside /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \
+\
+cp /home/ivan/freeside/conf/[a-z]* /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \
+\
+touch /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \
+\
+chown freeside /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \
+\
+chmod 600 /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \
+\
+echo -e "DBI:Pg:host=localhost;dbname=$domain\nfreeside\n" >/usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \
+\
+mkdir /usr/local/etc/freeside/counters.DBI:Pg:host=localhost\;dbname=$domain && \
+mkdir /usr/local/etc/freeside/cache.DBI:Pg:host=localhost\;dbname=$domain && \
+mkdir /usr/local/etc/freeside/export.DBI:Pg:host=localhost\;dbname=$domain
+
diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser
new file mode 100644
index 0000000..abb515b
--- /dev/null
+++ b/FS/bin/freeside-addoutsourceuser
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+username=$1
+domain=$2
+password=$3
+
+freeside-adduser -h /usr/local/etc/freeside/htpasswd \
+ -s conf.DBI:Pg:host=localhost\;dbname=$domain/secrets \
+ -b \
+ $username $password 2>/dev/null
+
+[ -e /usr/local/etc/freeside/dbdef.DBI:Pg:host=localhost\;dbname=$domain ] \
+ || ( freeside-setup -s $username 2>/dev/null; \
+ /home/ivan/freeside/bin/populate-msgcat $username 2>/dev/null )
+
diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser
new file mode 100644
index 0000000..c3ee05b
--- /dev/null
+++ b/FS/bin/freeside-adduser
@@ -0,0 +1,63 @@
+#!/usr/bin/perl -w
+#
+# $Id: freeside-adduser,v 1.8 2002-09-27 05:36:29 ivan Exp $
+
+use strict;
+use vars qw($opt_h $opt_b $opt_c $opt_s);
+use Fcntl qw(:flock);
+use Getopt::Std;
+
+my $FREESIDE_CONF = "/usr/local/etc/freeside";
+
+getopts("bch:s:");
+die &usage if $opt_c && ! $opt_h;
+my $user = shift or die &usage;
+
+if ( $opt_h ) {
+ my @args = ( 'htpasswd' );
+ push @args, '-b' if $opt_b;
+ push @args, '-c' if $opt_c;
+ push @args, $opt_h, $user;
+ push @args, shift if $opt_b;
+ system(@args) == 0 or die "htpasswd failed: $?";
+}
+
+my $secretfile = $opt_s || 'secrets';
+
+open(MAPSECRETS,">>$FREESIDE_CONF/mapsecrets")
+ and flock(MAPSECRETS,LOCK_EX)
+ or die "can't open $FREESIDE_CONF/mapsecrets: $!";
+print MAPSECRETS "$user $secretfile\n";
+close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!";
+
+sub usage {
+ die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] [ -b ] ] [ -s secretfile ] username"
+}
+
+=head1 NAME
+
+freeside-adduser - Command line interface to add (freeside) users.
+
+=head1 SYNOPSIS
+
+ freeside-adduser [ -h htpasswd_file [ -c ] ] [ -s secretfile ] username
+
+=head1 DESCRIPTION
+
+Adds a user to the Freeside billing system. This is for adding users (internal
+sales/tech folks) to the web interface, not for adding customer accounts.
+
+ -h: Also call htpasswd for this user with the given filename
+
+ -c: Passed to htpasswd(1)
+
+ -s: Specify an alternate secret file
+
+ -b: same as htpasswd(1), probably insecure, not recommended
+
+=head1 SEE ALSO
+
+L<htpasswd>(1), base Freeside documentation
+
+=cut
+
diff --git a/FS/bin/freeside-apply-credits b/FS/bin/freeside-apply-credits
new file mode 100755
index 0000000..ea6a7bd
--- /dev/null
+++ b/FS/bin/freeside-apply-credits
@@ -0,0 +1,21 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( $user $cust_main @customers );
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_main;
+
+$user = shift or die &usage;
+&adminsuidsetup( $user );
+
+my @customers = qsearch('cust_main', {} );
+die "No customers" unless (scalar(@customers) > 0);
+
+foreach $cust_main (@customers) {
+ print "Applying credits for customer #". $cust_main->custnum;
+ $cust_main->apply_credits;
+}
+
+
+
diff --git a/FS/bin/freeside-bill b/FS/bin/freeside-bill
new file mode 100755
index 0000000..49ad4a7
--- /dev/null
+++ b/FS/bin/freeside-bill
@@ -0,0 +1,128 @@
+#!/usr/bin/perl -w
+# don't take any world-facing input
+#!/usr/bin/perl -Tw
+
+use strict;
+use Fcntl qw(:flock);
+use Date::Parse;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_main;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw($opt_a $opt_c $opt_d $opt_p);
+getopts("acd:p");
+my $user = shift or die &usage;
+
+adminsuidsetup $user;
+
+my %bill_only = map { $_ => 1 } (
+ @ARGV ? @ARGV : ( map $_->custnum, qsearch('cust_main', {} ) )
+);
+
+#we're at now now (and later).
+my($time)= $opt_d ? str2time($opt_d) : $^T;
+
+# find packages w/ bill < time && cancel != '', and create corresponding
+# customer objects
+
+my($cust_main,%saw);
+foreach $cust_main (
+ map {
+ unless ( exists $saw{ $_->custnum } && defined $saw{ $_->custnum} ) {
+ $saw{ $_->custnum } = 0; # to avoid 'use of uninitialized value' errors
+ }
+ if (
+ ( $opt_a || ( ( $_->getfield('bill') || 0 ) <= $time ) )
+ && $bill_only{ $_->custnum }
+ && !$saw{ $_->custnum }++
+ ) {
+ qsearchs('cust_main',{'custnum'=> $_->custnum } );
+ } else {
+ ();
+ }
+ } ( qsearch('cust_pkg', { 'cancel' => '' }),
+ qsearch('cust_pkg', { 'cancel' => 0 }),
+ )
+) {
+
+ # and bill them
+
+ print "Billing customer #" . $cust_main->getfield('custnum') . "\n";
+
+ my($error);
+
+ $error=$cust_main->bill('time'=>$time);
+ warn "Error billing, customer #" . $cust_main->getfield('custnum') .
+ ":" . $error if $error;
+
+ if ($opt_p) {
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+ }
+
+ if ($opt_c) {
+ $error=$cust_main->collect( 'invoice_time' => $time);
+ warn "Error collecting from customer #" . $cust_main->custnum. ":$error"
+ if $error;
+
+ #sleep 1;
+ }
+
+}
+
+# subroutines
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ # Date::Parse
+ $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-bill [ -c [ -p ] ] [ -d 'date' ] user [ custnum custnum ... ]\n";
+}
+
+=head1 NAME
+
+freeside-bill - Command line (crontab, script) interface to customer billing.
+
+=head1 SYNOPSIS
+
+ freeside-bill [ -c [ -p ] [ -a ] ] [ -d 'date' ] user [ custnum custnum ... ]
+
+=head1 DESCRIPTION
+
+This script is deprecated in 1.4.0. You should use freeside-daily instead.
+
+Bills customers. Searches for customers who are due for billing and calls
+the bill and collect methods of a cust_main object. See L<FS::cust_main>.
+
+ -c: Turn on collecting (you probably want this).
+
+ -p: Apply unapplied payments and credits before collecting (you probably want
+ this too)
+
+ -a: Call collect even if there isn't a new invoice (probably a bad idea for
+ daily use)
+
+ -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with,
+ but be careful.
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+custnum: if one or more customer numbers are specified, only bills those
+customers. Otherwise, bills all customers.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-daily>, L<FS::cust_main>, config.html from the base documentation
+
+=cut
+
diff --git a/FS/bin/freeside-count-active-customers b/FS/bin/freeside-count-active-customers
new file mode 100755
index 0000000..759085a
--- /dev/null
+++ b/FS/bin/freeside-count-active-customers
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+domain=$1
+
+echo "\t
+select count(*) from cust_main where
+ 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0
+ )
+ )
+ OR 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ );
+" | psql -U freeside -q $domain | head -1
+
diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily
new file mode 100755
index 0000000..99d95d5
--- /dev/null
+++ b/FS/bin/freeside-daily
@@ -0,0 +1,151 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Fcntl qw(:flock);
+use Date::Parse;
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup driver_name dbh datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::Conf;
+use FS::cust_main;
+
+&untaint_argv; #what it sounds like (eww)
+use vars qw($opt_d $opt_v $opt_p $opt_s $opt_y);
+getopts("p:d:vsy:");
+my $user = shift or die &usage;
+
+adminsuidsetup $user;
+
+$FS::cust_main::DEBUG = 1 if $opt_v;
+
+my %search;
+$search{'payby'} = $opt_p if $opt_p;
+
+my @cust_main = @ARGV
+ ? map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV
+ : qsearch('cust_main', \%search )
+;
+
+#we're at now now (and later).
+my($time)= $opt_d ? str2time($opt_d) : $^T;
+$time += $opt_y * 86400 if $opt_y;
+
+my($cust_main,%saw);
+foreach $cust_main ( @cust_main ) {
+
+ # $^T not $time because -d is for pre-printing invoices
+ foreach my $cust_pkg (
+ grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs
+ ) {
+ my $error = $cust_pkg->cancel;
+ warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ".
+ $cust_main->custnum. ": $error"
+ if $error;
+ }
+
+ my $error = $cust_main->bill( 'time' => $time,
+ 'resetup' => $opt_s, );
+ warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error;
+
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ $error = $cust_main->collect( 'invoice_time' => $time );
+ warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error;
+
+}
+
+if ( driver_name eq 'Pg' ) {
+ dbh->{AutoCommit} = 1; #so we can vacuum
+ foreach my $statement ( 'vacuum', 'vacuum analyze' ) {
+ my $sth = dbh->prepare($statement) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ }
+}
+
+my $conf = new FS::Conf;
+my $dest = $conf->config('dump-scpdest');
+if ( $dest ) {
+ datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc;
+ my $database = $1;
+ eval "use Net::SCP qw(scp);";
+ if ( driver_name eq 'Pg' ) {
+ system("pg_dump $database >/var/tmp/$database.sql")
+ } else {
+ die "database dumps not yet supported for ". driver_name;
+ }
+ if ( $conf->config('dump-pgpid') ) {
+ eval 'use GnuPG';
+ my $gpg = new GnuPG;
+ $gpg->encrypt( plaintext => "/var/tmp/$database.sql",
+ output => "/var/tmp/$database.gpg",
+ recipient => $conf->config('dump-pgpid'),
+ );
+ scp("/var/tmp/$database.gpg", $dest);
+ unlink "/var/tmp/$database.gpg" or die $!;
+ } else {
+ scp("/var/tmp/$database.sql", $dest);
+ }
+ unlink "/var/tmp/$database.sql" or die $!;
+}
+
+# subroutines
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ # Date::Parse
+ $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-daily [ -d 'date' ] user [ custnum custnum ... ]\n";
+}
+
+=head1 NAME
+
+freeside-daily - Run daily billing and invoice collection events.
+
+=head1 SYNOPSIS
+
+ freeside-daily [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -s ] [ -v ] user [ custnum custnum ... ]
+
+=head1 DESCRIPTION
+
+Bills customers and runs invoice collection events. Should be run from
+crontab daily.
+
+This script replaces freeside-bill from 1.3.1.
+
+Bills customers. Searches for customers who are due for billing and calls
+the bill and collect methods of a cust_main object. See L<FS::cust_main>.
+
+ -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with,
+ but be careful.
+
+ -y: In addition to -d, which specifies an absolute date, the -y switch
+ specifies an offset, in days. For example, "-y 15" would increment the
+ "pretend date" 15 days from whatever was specified by the -d switch
+ (or now, if no -d switch was given).
+
+ -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
+
+ -s: re-charge setup fees
+
+ -v: enable debugging
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+custnum: if one or more customer numbers are specified, only bills those
+customers. Otherwise, bills all customers.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, config.html from the base documentation
+
+=cut
+
diff --git a/FS/bin/freeside-deloutsource b/FS/bin/freeside-deloutsource
new file mode 100644
index 0000000..5618535
--- /dev/null
+++ b/FS/bin/freeside-deloutsource
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+domain=$1
+
+dropdb $domain && \
+rm -rf /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf /usr/local/etc/freeside/counters.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf /usr/local/etc/freeside/cache.DBI:Pg:host=localhost\;dbname=$domain && \
+rm -rf /usr/local/etc/freeside/export.DBI:Pg:host=localhost\;dbname=$domain && \
+rm /usr/local/etc/freeside/dbdef.DBI:Pg:host=localhost\;dbname=$domain
+
diff --git a/FS/bin/freeside-deloutsourceuser b/FS/bin/freeside-deloutsourceuser
new file mode 100644
index 0000000..96871e5
--- /dev/null
+++ b/FS/bin/freeside-deloutsourceuser
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+username=$1
+
+freeside-deluser -h /usr/local/etc/freeside/htpasswd $username 2>/dev/null
+
diff --git a/FS/bin/freeside-deluser b/FS/bin/freeside-deluser
new file mode 100644
index 0000000..57d6ce1
--- /dev/null
+++ b/FS/bin/freeside-deluser
@@ -0,0 +1,64 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_h);
+use Fcntl qw(:flock);
+use Getopt::Std;
+
+my $FREESIDE_CONF = "/usr/local/etc/freeside";
+
+getopts("h:");
+my $user = shift or die &usage;
+
+if ( $opt_h ) {
+ open(HTPASSWD,"<$opt_h")
+ and flock(HTPASSWD,LOCK_EX)
+ or die "can't open $opt_h: $!";
+ open(HTPASSWD_TMP,">$opt_h.tmp") or die "can't open $opt_h.tmp: $!";
+ while (<HTPASSWD>) {
+ print HTPASSWD_TMP $_ unless /^$user:/;
+ }
+ close HTPASSWD_TMP;
+ rename "$opt_h.tmp", "$opt_h" or die $!;
+ flock(HTPASSWD,LOCK_UN);
+ close HTPASSWD;
+}
+
+open(MAPSECRETS,"<$FREESIDE_CONF/mapsecrets")
+ and flock(MAPSECRETS,LOCK_EX)
+ or die "can't open $FREESIDE_CONF/mapsecrets: $!";
+open(MAPSECRETS_TMP,">>$FREESIDE_CONF/mapsecrets.tmp")
+ or die "can't open $FREESIDE_CONF/mapsecrets.tmp: $!";
+while (<MAPSECRETS>) {
+ print MAPSECRETS_TMP $_ unless /^$user\s/;
+}
+close MAPSECRETS_TMP;
+rename "$FREESIDE_CONF/mapsecrets.tmp", "$FREESIDE_CONF/mapsecrets" or die $!;
+flock(MAPSECRETS,LOCK_UN);
+close MAPSECRETS;
+
+sub usage {
+ die "Usage:\n\n freeside-deluser [ -h htpasswd_file ] username"
+}
+
+=head1 NAME
+
+freeside-deluser - Command line interface to add (freeside) users.
+
+=head1 SYNOPSIS
+
+ freeside-deluser [ -h htpasswd_file ] username
+
+=head1 DESCRIPTION
+
+Adds a user to the Freeside billing system. This is for adding users (internal
+sales/tech folks) to the web interface, not for adding customer accounts.
+
+ -h: Also delete from the given htpasswd filename
+
+=head1 SEE ALSO
+
+L<freeside-adduser>, L<htpasswd>(1), base Freeside documentation
+
+=cut
+
diff --git a/FS/bin/freeside-email b/FS/bin/freeside-email
new file mode 100755
index 0000000..400dc2a
--- /dev/null
+++ b/FS/bin/freeside-email
@@ -0,0 +1,59 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw(qsearch);
+use FS::svc_acct;
+
+&untaint_argv; #what it sounds like (eww)
+my $user = shift or die &usage;
+
+adminsuidsetup $user;
+
+my $conf = new FS::Conf;
+
+my @svc_acct = qsearch('svc_acct', {});
+my @emails = map $_->email, @svc_acct;
+
+print join("\n", @emails), "\n";
+
+# subroutines
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ # Date::Parse
+ $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-email user\n";
+}
+
+=head1 NAME
+
+freeside-email - Prints email addresses of all users on STDOUT
+
+=head1 SYNOPSIS
+
+ freeside-email user
+
+=head1 DESCRIPTION
+
+Prints the email addresses of all customers on STDOUT, separated by newlines.
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+=head1 VERSION
+
+$Id: freeside-email,v 1.2 2002-09-18 22:50:44 ivan Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-expiration-alerter b/FS/bin/freeside-expiration-alerter
new file mode 100755
index 0000000..691fd3a
--- /dev/null
+++ b/FS/bin/freeside-expiration-alerter
@@ -0,0 +1,226 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use Date::Format;
+use Time::Local;
+use Text::Template;
+use Getopt::Std;
+use Net::SMTP;
+use Mail::Header;
+use Mail::Internet;
+use FS::Conf;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_main;
+
+use vars qw($smtpmachine @body);
+
+#hush, perl!
+$FS::alerter::_template::first = "";
+$FS::alerter::_template::last = "";
+$FS::alerter::_template::company = "";
+$FS::alerter::_template::payby = "";
+$FS::alerter::_template::expdate = "";
+
+# Set the mail program and other variables
+my $mail_sender = "billing\@mydomain.tld"; # or invoice_from if available
+my $failure_recipient = "postmaster"; # or invoice_from if available
+my $warning_time = 30 * 24 * 60 * 60;
+my $urgent_time = 15 * 24 * 60 * 60;
+my $panic_time = 5 * 24 * 60 * 60;
+my $window_time = 24 * 60 * 60;
+
+&untaint_argv; #what it sounds like (eww)
+
+#we're at now now (and later).
+my($_date)= $^T;
+
+# Get the current month
+my ($sec,$min,$hour,$mday,$mon,$year) =
+ (localtime($_date) )[0,1,2,3,4,5];
+$mon++;
+
+# Login to the database
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# Get the needed configuration files
+my $conf = new FS::Conf;
+$smtpmachine = $conf->config('smtpmachine');
+$mail_sender = $conf->config('invoice_from')
+ if $conf->exists('invoice_from');
+$failure_recipient = $conf->config('invoice_from')
+ if $conf->exists('invoice_from');
+
+
+my(@customers)=qsearch('cust_main',{});
+if (scalar(@customers) == 0)
+{
+ exit 1;
+}
+
+# Prepare for sending email
+
+$ENV{MAILADDRESS} = $mail_sender;
+my $header = new Mail::Header ( [
+ "From: Account Processor",
+ "To: $failure_recipient",
+ "Sender: $mail_sender",
+ "Reply-To: $mail_sender",
+ "Subject: Unnotified Billing Arrangement Expirations",
+] );
+
+my @alerter_template = $conf->config('alerter_template')
+ or die "cannot load config file alerter_template";
+
+my $alerter = new Text::Template (TYPE => 'ARRAY', SOURCE => [ map "$_\n", @alerter_template ])
+ or die "can't create new Text::Template object: Text::Template::ERROR";
+$alerter->compile() or die "can't compile template: Text::Template::ERROR";
+
+# Now I can start looping
+foreach my $customer (@customers)
+{
+ my $paydate = $customer->getfield('paydate');
+ next if $paydate =~ /^\s*$/; #skip empty expiration dates
+
+ my $custnum = $customer->getfield('custnum');
+ my $first = $customer->getfield('first');
+ my $last = $customer->getfield('last');
+ my $company = $customer->getfield('company');
+ my $payby = $customer->getfield('payby');
+ my $payinfo = $customer->getfield('payinfo');
+ my $daytime = $customer->getfield('daytime');
+ my $night = $customer->getfield('night');
+
+ my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+
+ my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+ #credit cards expire at the end of the month/year of their exp date
+ if ($payby eq 'CARD' || $payby eq 'DCRD') {
+ ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+ $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+ $expire_time--;
+ }
+
+ if (($expire_time < $_date + $warning_time &&
+ $expire_time > $_date + $warning_time - $window_time) ||
+ ($expire_time < $_date + $urgent_time &&
+ $expire_time > $_date + $urgent_time - $window_time) ||
+ ($expire_time < $_date + $panic_time &&
+ $expire_time > $_date + $panic_time - $window_time)) {
+
+
+
+ my @packages = $customer->ncancelled_pkgs;
+ if (scalar(@packages) != 0) {
+ my @invoicing_list = $customer->invoicing_list;
+ if ( grep { $_ ne 'POST' } @invoicing_list ) {
+ my $header = new Mail::Header ( [
+ "From: $mail_sender",
+ "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
+ "Sender: $mail_sender",
+ "Reply-To: $mail_sender",
+ "Date: ". time2str("%a, %d %b %Y %X %z", time),
+ "Subject: Billing Arrangement Expiration",
+ ] );
+ $FS::alerter::_template::first = $first;
+ $FS::alerter::_template::last = $last;
+ $FS::alerter::_template::company = $company;
+ if ($payby eq 'CARD' || $payby eq 'DCRD') {
+ $FS::alerter::_template::payby = "credit card (" .
+ substr($payinfo, 0, 2) . "xxxxxxxxxx" .
+ substr($payinfo, -4) . ")";
+ }elsif ($payby eq 'COMP') {
+ $FS::alerter::_template::payby = "complimentary account";
+ }else{
+ $FS::alerter::_template::payby = "current method";
+ }
+ $FS::alerter::_template::expdate = $expire_time;
+
+ my $message = new Mail::Internet (
+ 'Header' => $header,
+ 'Body' => [ $alerter->fill_in( PACKAGE => 'FS::alerter::_template' ) ],
+ );
+ $!=0;
+ $message->smtpsend( Host => $smtpmachine )
+ or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+ or die "Can't send expiration email: $!";
+
+ } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
+ push @body, sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
+ $custnum,
+ $first . " " . $last . " " . $company,
+ $payby,
+ $paydate,
+ $daytime,
+ $night);
+ }
+ }
+ }
+}
+
+# Now I need to send EMAIL
+if (scalar(@body)) {
+ my $message = new Mail::Internet (
+ 'Header' => $header,
+ 'Body' => [ (@body) ],
+ );
+ $!=0;
+ $message->smtpsend( Host => $smtpmachine )
+ or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+ or die "can't send alerter failure email to $failure_recipient".
+ " via server $smtpmachine with SMTP: $!";
+}
+
+# subroutines
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ $ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal argument \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-expiration-alerter user\n";
+}
+
+=head1 NAME
+
+freeside-expiration-alerter - Emails notifications of credit card expirations.
+
+=head1 SYNOPSIS
+
+ freeside-expiration-alerter user
+
+=head1 DESCRIPTION
+
+Emails customers notice that their credit card or other billing arrangement
+is about to expire. Usually run as a cron job.
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+=head1 VERSION
+
+$Id: freeside-expiration-alerter,v 1.5 2003-04-21 20:53:57 ivan Exp $
+
+=head1 BUGS
+
+Yes..... Use at your own risk. No guarantees or warrantees of any
+kind apply to this program. Parts of this program are hacked from
+other GNU licensed software created mainly by Ivan Kohler.
+
+This is released under the GNU Public License. See www.gnu.org
+for more information regarding this license.
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, config.html from the base documentation
+
+=head1 AUTHOR
+
+Jeff Finucane <jeff@cmh.net>
+
+=cut
+
+
diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued
new file mode 100644
index 0000000..e14ddad
--- /dev/null
+++ b/FS/bin/freeside-queued
@@ -0,0 +1,310 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $log_file $sigterm $sigint $kids $max_kids %kids );
+use subs qw( _die _logmsg );
+use Fcntl qw(:flock);
+use POSIX qw(:sys_wait_h setsid);
+use Date::Format;
+use IO::File;
+use FS::UID qw(adminsuidsetup forksuidsetup driver_name dbh myconnect);
+use FS::Record qw(qsearch qsearchs);
+use FS::queue;
+use FS::queue_depend;
+
+# no autoloading just yet
+use FS::cust_main;
+use FS::svc_acct;
+use Net::SSH 0.07;
+use FS::part_export;
+
+$max_kids = '10'; #guess it should be a config file...
+$kids = 0;
+
+my $user = shift or die &usage;
+
+#my $pid_file = "/var/run/freeside-queued.$user.pid";
+my $pid_file = "/var/run/freeside-queued.pid";
+
+&daemonize1;
+
+#sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
+#$SIG{CHLD} = \&REAPER;
+
+$sigterm = 0;
+$sigint = 0;
+$SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; };
+$SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; };
+
+my $freeside_gid = scalar(getgrnam('freeside'))
+ or die "can't setgid to freeside group\n";
+$) = $freeside_gid;
+$( = $freeside_gid;
+#if freebsd can't setuid(), presumably it can't setgid() either. grr fleabsd
+($(,$)) = ($),$();
+$) = $freeside_gid;
+
+$> = $FS::UID::freeside_uid;
+$< = $FS::UID::freeside_uid;
+#freebsd is sofa king broken, won't setuid()
+($<,$>) = ($>,$<);
+$> = $FS::UID::freeside_uid;
+
+$ENV{HOME} = (getpwuid($>))[7]; #for ssh
+
+$@ = 'not connected';
+while ( $@ ) {
+ eval { adminsuidsetup $user; };
+ if ( $@ ) {
+ warn $@;
+ warn "sleeping for reconnect...\n";
+ sleep 5;
+ }
+}
+
+$log_file = "/usr/local/etc/freeside/queuelog.". $FS::UID::datasrc;
+
+&daemonize2;
+
+$SIG{__DIE__} = \&_die;
+$SIG{__WARN__} = \&_logmsg;
+
+warn "freeside-queued starting\n";
+
+my $warnkids=0;
+while (1) {
+
+ &reap_kids;
+ #prevent runaway forking
+ if ( $kids >= $max_kids ) {
+ warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
+ &reap_kids;
+ sleep 1; #waiting for signals is cheap
+ next;
+ }
+ $warnkids=0;
+
+ unless ( dbh && dbh->ping ) {
+ warn "WARNING: connection to database lost, reconnecting...\n";
+
+ eval { myconnect; };
+
+ unless ( !$@ && dbh && dbh->ping ) {
+ warn "WARNING: still no connection to database, sleeping for retry...\n";
+ sleep 10;
+ next;
+ } else {
+ warn "WARNING: reconnected to database\n";
+ }
+ }
+
+ #my($job, $ljob);
+ #{
+ # my $oldAutoCommit = $FS::UID::AutoCommit;
+ # local $FS::UID::AutoCommit = 0;
+ $FS::UID::AutoCommit = 0;
+
+ #assuming mysql 4.1 w/subqueries now
+ #my $nodepend = driver_name eq 'mysql'
+ # ? ''
+ # : 'AND 0 = ( SELECT COUNT(*) FROM queue_depend'.
+ # ' WHERE queue_depend.jobnum = queue.jobnum ) ';
+ my $nodepend = 'AND 0 = ( SELECT COUNT(*) FROM queue_depend'.
+ ' WHERE queue_depend.jobnum = queue.jobnum ) ';
+
+ my $job = qsearchs(
+ 'queue',
+ { 'status' => 'new' },
+ '',
+ driver_name eq 'mysql'
+ ? "$nodepend ORDER BY jobnum LIMIT 1 FOR UPDATE"
+ : "$nodepend ORDER BY jobnum FOR UPDATE LIMIT 1"
+ ) or do {
+ # if $oldAutoCommit {
+ dbh->commit or do {
+ warn "WARNING: database error, closing connection: ". dbh->errstr;
+ undef $FS::UID::dbh;
+ next;
+ };
+ # }
+ sleep 5; #connecting to db is expensive
+ next;
+ };
+
+ #assuming mysql 4.1 w/subqueries now
+ #if ( driver_name eq 'mysql'
+ # && qsearch('queue_depend', { 'jobnum' => $job->jobnum } ) ) {
+ # dbh->commit or die dbh->errstr; #if $oldAutoCommit;
+ # sleep 5; #would be better if mysql could do everything in query above
+ # next;
+ #}
+
+ my %hash = $job->hash;
+ $hash{'status'} = 'locked';
+ my $ljob = new FS::queue ( \%hash );
+ my $error = $ljob->replace($job);
+ if ( $error ) {
+ warn "WARNING: database error locking job, closing connection: ".
+ dbh->errstr;
+ undef $FS::UID::dbh;
+ next;
+ }
+
+ # if $oldAutoCommit {
+ dbh->commit or do {
+ warn "WARNING: database error, closing connection: ". dbh->errstr;
+ undef $FS::UID::dbh;
+ next;
+ };
+ # }
+
+ $FS::UID::AutoCommit = 1;
+ #}
+
+ my @args = $ljob->args;
+
+ defined( my $pid = fork ) or do {
+ warn "WARNING: can't fork: $!\n";
+ my %hash = $job->hash;
+ $hash{'status'} = 'failed';
+ $hash{'statustext'} = "[freeside-queued] can't fork: $!";
+ my $ljob = new FS::queue ( \%hash );
+ my $error = $ljob->replace($job);
+ die $error if $error;
+ next; #don't increment the kid counter
+ };
+
+ if ( $pid ) {
+ $kids++;
+ $kids{$pid} = 1;
+ } else { #kid time
+
+ #get new db handle
+ $FS::UID::dbh->{InactiveDestroy} = 1;
+
+ forksuidsetup($user);
+
+ #auto-use export classes...
+ if ( $ljob->job =~ /(FS::part_export::\w+)::/ ) {
+ my $class = $1;
+ eval "use $class;";
+ if ( $@ ) {
+ warn "job use $class failed";
+ my %hash = $ljob->hash;
+ $hash{'status'} = 'failed';
+ $hash{'statustext'} = $@;
+ my $fjob = new FS::queue( \%hash );
+ my $error = $fjob->replace($ljob);
+ die $error if $error;
+ exit; #end-of-kid
+ };
+ }
+
+ my $eval = "&". $ljob->job. '(@args);';
+ warn "running $eval";
+ eval $eval; #throw away return value? suppose so
+ if ( $@ ) {
+ warn "job $eval failed";
+ my %hash = $ljob->hash;
+ $hash{'status'} = 'failed';
+ $hash{'statustext'} = $@;
+ my $fjob = new FS::queue( \%hash );
+ my $error = $fjob->replace($ljob);
+ die $error if $error;
+ } else {
+ $ljob->delete;
+ }
+
+ exit;
+ #end-of-kid
+ }
+
+} continue {
+ if ( $sigterm ) {
+ warn "received TERM signal; exiting\n";
+ exit;
+ }
+ if ( $sigint ) {
+ warn "received INT signal; exiting\n";
+ exit;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-queued user\n";
+}
+
+sub _die {
+ my $msg = shift;
+ unlink $pid_file if -e $pid_file;
+ _logmsg($msg);
+}
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
+
+sub daemonize1 {
+
+ chdir "/" or die "Can't chdir to /: $!";
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ defined(my $pid = fork) or die "Can't fork: $!";
+ if ( $pid ) {
+ print "freeside-queued started with pid $pid\n"; #logging to $log_file\n";
+ exit unless $pid_file;
+ my $pidfh = new IO::File ">$pid_file" or exit;
+ print $pidfh "$pid\n";
+ exit;
+ }
+ #open STDOUT, '>/dev/null'
+ # or die "Can't write to /dev/null: $!";
+ #setsid or die "Can't start a new session: $!";
+ #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+
+}
+
+sub daemonize2 {
+ open STDOUT, '>/dev/null'
+ or die "Can't write to /dev/null: $!";
+ setsid or die "Can't start a new session: $!";
+ open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+}
+
+sub reap_kids {
+ foreach my $pid ( keys %kids ) {
+ my $kid = waitpid($pid, WNOHANG);
+ if ( $kid > 0 ) {
+ $kids--;
+ delete $kids{$kid};
+ }
+ }
+}
+
+=head1 NAME
+
+freeside-queued - Job queue daemon
+
+=head1 SYNOPSIS
+
+ freeside-queued user
+
+=head1 DESCRIPTION
+
+Job queue daemon. Should be running at all times.
+
+user: from the mapsecrets file - see config.html from the base documentation
+
+=head1 VERSION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-radgroup b/FS/bin/freeside-radgroup
new file mode 100644
index 0000000..ed85626
--- /dev/null
+++ b/FS/bin/freeside-radgroup
@@ -0,0 +1,76 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_svc;
+use FS::svc_acct;
+
+&untaint_argv; #what it sounds like (eww)
+
+my($user, $action, $groupname, $svcpart) = @ARGV;
+
+adminsuidsetup $user;
+
+my @svc_acct = map { $_->svc_x } qsearch('cust_svc', { svcpart => $svcpart } );
+
+if ( lc($action) eq 'add' ) {
+ foreach my $svc_acct ( @svc_acct ) {
+ my @groups = $svc_acct->radius_groups;
+ next if grep { $_ eq $groupname } @groups;
+ push @groups, $groupname;
+ my %hash = $svc_acct->hash;
+ $hash{usergroup} = \@groups;
+ my $new = new FS::svc_acct \%hash;
+ my $error = $new->replace($svc_acct);
+ die $error if $error;
+ }
+} else {
+ die &usage;
+}
+
+# subroutines
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-radgroup user action groupname svcpart\n";
+}
+
+=head1 NAME
+
+freeside-radgroup - Command line utility to manipulate radius groups
+
+=head1 SYNOPSIS
+
+ freeside-addgroup user action groupname svcpart
+
+=head1 DESCRIPTION
+
+ B<user> is a freeside user as added with freeside-adduser.
+
+ B<command> is the action to take. Available actions are: I<add>
+
+ B<groupname> is the group to add (or remove, etc.)
+
+ B<svcpart> specifies which accounts will be updated.
+
+=head1 EXAMPLES
+
+freeside-radgroup freesideuser add groupname 3
+
+Adds I<groupname> to all accounts with service definition 3.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-adduser>, L<FS::svc_acct>, L<FS::part_svc>
+
+=cut
+
diff --git a/FS/bin/freeside-reexport b/FS/bin/freeside-reexport
new file mode 100644
index 0000000..54af9dd
--- /dev/null
+++ b/FS/bin/freeside-reexport
@@ -0,0 +1,71 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_s $opt_u $opt_p);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::svc_acct;
+use FS::cust_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $export_x = shift or die &usage;
+my @part_export;
+if ( $export_x =~ /^(\d+)$/ ) {
+ @part_export = qsearchs('part_export', { exportnum=>$1 } )
+ or die "exportnum $export_x not found\n";
+} else {
+ @part_export = qsearch('part_export', { exporttype=>$export_x } )
+ or die "no exports of type $export_x found\n";
+}
+
+getopts('s:u:p:');
+
+my @svc_x = ();
+if ( $opt_s ) {
+ my $cust_svc = qsearchs('cust_svc', { svcnum=>$opt_s } )
+ or die "svcnum $opt_s not found\n";
+ push @svc_x, $cust_svc->svc_x;
+} elsif ( $opt_u ) {
+ my $svc_x = qsearchs('svc_acct', { username=>$opt_u } )
+ or die "username $opt_u not found\n";
+ push @svc_x, $svc_x;
+} elsif ( $opt_p ) {
+ push @svc_x, map { $_->svc_x } qsearch('cust_svc', { svcpart=>$opt_p } );
+ die "no services with svcpart $opt_p found\n" unless @svc_x;
+}
+
+foreach my $part_export ( @part_export ) {
+ foreach my $svc_x ( @svc_x ) {
+ my $error = $part_export->export_insert($svc_x);
+ die $error if $error;
+ }
+}
+
+
+sub usage {
+ die "Usage:\n\n freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ]\n";
+}
+
+=head1 NAME
+
+freeside-reexport - Command line tool to re-trigger export jobs for existing services
+
+=head1 SYNOPSIS
+
+ freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ]
+
+=head1 DESCRIPTION
+
+ Re-queues the export job for the specified exportnum or exporttype(s) and
+ specified service (selected by svcnum or username).
+
+=head1 SEE ALSO
+
+L<freeside-sqlradius-reset>, L<FS::part_export>
+
+=cut
+
diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server
new file mode 100644
index 0000000..c045893
--- /dev/null
+++ b/FS/bin/freeside-selfservice-server
@@ -0,0 +1,295 @@
+#!/usr/bin/perl -w
+#
+# freeside-selfservice-server
+
+# alas, much false laziness with freeside-queued and fs_signup_server. at
+# least it is slated to replace fs_{signup,passwd,mailadmin}_server
+# should probably generalize the version in here, or better yet use
+# Proc::Daemon or somesuch
+
+use strict;
+use vars qw( $Debug %kids $kids $max_kids $shutdown $log_file $ssh_pid
+ $keepalives );
+use subs qw( lock_write unlock_write );
+use Fcntl qw(:flock);
+use POSIX qw(:sys_wait_h setsid);
+use IO::Handle;
+use IO::Select;
+use IO::File;
+use Storable 2.09 qw(nstore_fd fd_retrieve);
+use Net::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup forksuidsetup);
+use FS::ClientAPI;
+
+use FS::Conf;
+use FS::cust_bill;
+use FS::cust_pkg;
+
+$Debug = 1; # 2 will turn on more logging
+ # 3 will log packet contents, including passwords
+
+$shutdown = 0;
+$max_kids = '10'; #?
+$keepalives = 0; #let clientd turn it on, so we don't barf on old ones
+$kids = 0;
+
+my $user = shift or die &usage;
+my $machine = shift or die &usage;
+my $tag = scalar(@ARGV) ? shift : '';
+
+# $FS::UID::datasrc not posible
+my $pid_file = "/var/run/freeside-selfservice-server.$user.$machine.pid";
+
+my $lock_file = "/usr/local/etc/freeside/selfservice.$machine.writelock";
+
+&init($user);
+
+my $conf = new FS::Conf;
+
+my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name?
+
+my $warnkids=0;
+while (1) {
+ my($writer,$reader,$error) = (new IO::Handle, new IO::Handle, new IO::Handle);
+ warn "connecting to $machine\n" if $Debug;
+
+ $ssh_pid = sshopen2($machine,$reader,$writer,$clientd,$tag);
+
+# nstore_fd(\*writer, {'hi'=>'there'});
+
+ warn "entering main loop\n" if $Debug;
+ my $undisp = 0;
+ my $keepalive_count = 0;
+ my $s = IO::Select->new( $reader );
+ while (1) {
+
+ &reap_kids;
+
+ warn "waiting for packet from client\n" if $Debug && !$undisp;
+ $undisp = 1;
+ my @handles = $s->can_read(5);
+ unless ( @handles ) {
+ &shutdown if $shutdown;
+ if ( $keepalives && $keepalive_count++ > 10 ) {
+ $keepalive_count = 0;
+ lock_write;
+ nstore_fd( { _token => '_keepalive' }, $writer );
+ unlock_write;
+ }
+ next;
+ }
+
+ $undisp = 0;
+
+ warn "receiving packet from client\n" if $Debug;
+
+ my $packet = eval { fd_retrieve($reader); };
+ if ( $@ ) {
+ warn "Storable error receiving packet from client".
+ " (assuming lost connection): $@\n"
+ if $Debug;
+ if ( $ssh_pid ) {
+ warn "sending TERM signal to ssh process $ssh_pid\n" if $Debug;
+ kill 'TERM', $ssh_pid;
+ $ssh_pid = 0;
+ }
+ last;
+ }
+ warn "packet received\n".
+ join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+ if $Debug > 2;
+
+ if ( $packet->{_packet} eq '_enable_keepalive' ) {
+ warn "enabling keep alives\n" if $Debug;
+ $keepalives=1;
+ next;
+ }
+
+ #prevent runaway forking
+ my $warnkids = 0;
+ while ( $kids >= $max_kids ) {
+ warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
+ &reap_kids;
+ sleep 1;
+ }
+
+ warn "forking child\n" if $Debug;
+ defined( my $pid = fork ) or die "can't fork: $!";
+ if ( $pid ) {
+ $kids++;
+ $kids{$pid} = 1;
+ warn "child $pid spawned\n" if $Debug;
+ } else { #kid time
+
+ ##get new db handle
+ #$FS::UID::dbh->{InactiveDestroy} = 1;
+ #forksuidsetup($user);
+
+ #get db handle
+ adminsuidsetup($user);
+
+ my $type = $packet->{_packet};
+ warn "calling $type handler\n" if $Debug;
+ my $rv = eval { FS::ClientAPI->dispatch($type, $packet); };
+ if ( $@ ) {
+ warn my $error = "WARNING: error dispatching $type: $@";
+ $rv = { _error => $error };
+ }
+ $rv->{_token} = $packet->{_token}; #identifier
+
+ open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+ lock_write;
+ warn "sending response\n" if $Debug;
+ nstore_fd($rv, $writer) or die "FATAL: can't send response: $!";
+ $writer->flush or die "FATAL: can't flush: $!";
+ unlock_write;
+
+ warn "child exiting\n" if $Debug;
+ exit; #end-of-kid
+ }
+
+ }
+
+ &shutdown if $shutdown;
+ warn "connection lost, reconnecting\n" if $Debug;
+ sleep 3;
+
+}
+
+###
+# utility subroutines
+###
+
+sub reap_kids {
+ #warn "reaping kids\n";
+ foreach my $pid ( keys %kids ) {
+ my $kid = waitpid($pid, WNOHANG);
+ if ( $kid > 0 ) {
+ $kids--;
+ delete $kids{$kid};
+ }
+ }
+ #warn "done reaping\n";
+}
+
+sub init {
+ my $user = shift;
+
+ chdir "/" or die "Can't chdir to /: $!";
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ defined(my $pid = fork) or die "Can't fork: $!";
+ if ( $pid ) {
+ print "freeside-selfservice-server to $machine started with pid $pid\n"; #logging to $log_file
+ exit unless $pid_file;
+ my $pidfh = new IO::File ">$pid_file" or exit;
+ print $pidfh "$pid\n";
+ exit;
+ }
+
+# sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
+# #sub REAPER { my $pid = wait; $kids--; $SIG{CHLD} = \&REAPER; }
+# $SIG{CHLD} = \&REAPER;
+
+ $shutdown = 0;
+ $SIG{HUP} = sub { warn "SIGHUP received; shutting down\n"; $shutdown++; };
+ $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $shutdown++; };
+ $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $shutdown++; };
+ $SIG{QUIT} = sub { warn "SIGQUIT received; shutting down\n"; $shutdown++; };
+ $SIG{PIPE} = sub { warn "SIGPIPE received; shutting down\n"; $shutdown++; };
+
+ #false laziness w/freeside-queued
+ my $freeside_gid = scalar(getgrnam('freeside'))
+ or die "can't setgid to freeside group\n";
+
+ open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+ chown $FS::UID::freeside_uid, $freeside_gid, $lock_file;
+
+ $) = $freeside_gid;
+ $( = $freeside_gid;
+ #if freebsd can't setuid(), presumably it can't setgid() either. grr fleabsd
+ ($(,$)) = ($),$();
+ $) = $freeside_gid;
+
+ $> = $FS::UID::freeside_uid;
+ $< = $FS::UID::freeside_uid;
+ #freebsd is sofa king broken, won't setuid()
+ ($<,$>) = ($>,$<);
+ $> = $FS::UID::freeside_uid;
+ #eslaf
+
+ $ENV{HOME} = (getpwuid($>))[7]; #for ssh
+ adminsuidsetup $user;
+
+ #$log_file = "/usr/local/etc/freeside/selfservice.". $FS::UID::datasrc; #MACHINE NAME
+ $log_file = "/usr/local/etc/freeside/selfservice.$machine.log";
+
+ open STDOUT, '>/dev/null'
+ or die "Can't write to /dev/null: $!";
+ setsid or die "Can't start a new session: $!";
+ open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+
+ $SIG{__DIE__} = \&_die;
+ $SIG{__WARN__} = \&_logmsg;
+
+ warn "freeside-selfservice-server starting\n";
+
+}
+
+sub shutdown {
+ &reap_kids;
+ my $wait = 12; #wait up to 1 minute
+ while ( $kids > 0 && $wait-- ) {
+ warn "waiting for $kids children to terminate";
+ sleep 5;
+ &reap_kids;
+ }
+ warn "abandoning $kids children" if $kids;
+ kill 'TERM', $ssh_pid if $ssh_pid;
+ die "exiting";
+}
+
+sub _die {
+ my $msg = shift;
+ unlink $pid_file if -e $pid_file;
+ _logmsg($msg);
+}
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ _do_logmsg( "[server] [". scalar(localtime). "] [$$] $msg\n" );
+}
+
+sub _do_logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "$msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
+
+sub lock_write {
+ warn "locking $lock_file mutex for write to write stream\n" if $Debug > 1;
+
+ #broken on freebsd?
+ #flock($writer, LOCK_EX) or die "FATAL: can't lock write stream: $!";
+
+ flock(LOCKFILE, LOCK_EX) or die "FATAL: can't lock $lock_file: $!";
+
+}
+
+sub unlock_write {
+ warn "unlocking $lock_file mutex\n" if $Debug > 1;
+
+ #broken on freebsd?
+ #flock($writer, LOCK_UN) or die "WARNING: can't release write lock: $!";
+
+ flock(LOCKFILE, LOCK_UN) or die "FATAL: can't unlock $lock_file: $!";
+
+}
+
+sub usage {
+ die "Usage:\n\n freeside-selfservice-server user machine\n";
+}
+
diff --git a/FS/bin/freeside-setinvoice b/FS/bin/freeside-setinvoice
new file mode 100644
index 0000000..708e2fa
--- /dev/null
+++ b/FS/bin/freeside-setinvoice
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs);
+use FS::cust_main;
+use FS::svc_acct;
+
+&untaint_argv; #what it sounds like (eww)
+my $user = shift or die &usage;
+
+adminsuidsetup $user;
+
+foreach my $cust_main (
+ grep { ! scalar($_->invoicing_list) }
+ qsearch( 'cust_main', {} )
+) {
+ my @dest;
+ my @cust_pkg = $cust_main->ncancelled_pkgs;
+ foreach my $cust_pkg ( @cust_pkg ) {
+ foreach my $cust_svc ( $cust_pkg->cust_svc ) {
+ my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $cust_svc->svcnum } );
+ push @dest, $svc_acct->svcnum if $svc_acct;
+ }
+ }
+ push @dest, 'POST' unless @dest;
+ $cust_main->invoicing_list(\@dest);
+}
+
+sub untaint_argv {
+ foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+ $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
+ $ARGV[$_]=$1;
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-setinvoice user\n";
+}
+
+
diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup
new file mode 100755
index 0000000..5de71a9
--- /dev/null
+++ b/FS/bin/freeside-setup
@@ -0,0 +1,1158 @@
+#!/usr/bin/perl -Tw
+
+#to delay loading dbdef until we're ready
+BEGIN { $FS::Record::setup_hack = 1; }
+
+use strict;
+use vars qw($opt_s);
+use Getopt::Std;
+use Locale::Country;
+use Locale::SubCountry;
+use DBI;
+use DBIx::DBSchema 0.21;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets);
+use FS::Record;
+use FS::cust_main_county;
+use FS::raddb;
+use FS::part_bill_event;
+
+die "Not running uid freeside!" unless checkeuid();
+
+my %attrib2db =
+ map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
+
+getopts("s");
+my $user = shift or die &usage;
+getsecrets($user);
+
+#needs to match FS::Record
+my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc;
+
+###
+
+#print "\nEnter the maximum username length: ";
+#my($username_len)=&getvalue;
+my $username_len = 32; #usernamemax config file
+
+#print "\n\n", <<END, ":";
+#Freeside tracks the RADIUS User-Name, check attribute Password and
+#reply attribute Framed-IP-Address for each user. You can specify additional
+#check and reply attributes (or you can add them later with the
+#fs-radius-add-check and fs-radius-add-reply programs).
+#
+#First enter any additional RADIUS check attributes you need to track for each
+#user, separated by whitespace.
+#END
+#my @check_attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
+# split(" ",&getvalue);
+#
+#print "\n\n", <<END, ":";
+#Now enter any additional reply attributes you need to track for each user,
+#separated by whitespace.
+#END
+#my @attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
+# split(" ",&getvalue);
+#
+#print "\n\n", <<END, ":";
+#Do you wish to enable the tracking of a second, separate shipping/service
+#address?
+#END
+#my $ship = &_yesno;
+#
+#sub getvalue {
+# my($x)=scalar(<STDIN>);
+# chop $x;
+# $x;
+#}
+#
+#sub _yesno {
+# print " [y/N]:";
+# my $x = scalar(<STDIN>);
+# $x =~ /^y/i;
+#}
+
+my @check_attributes = (); #add later
+my @attributes = (); #add later
+my $ship = $opt_s;
+
+###
+
+my($char_d) = 80; #default maxlength for text fields
+
+#my(@date_type) = ( 'timestamp', '', '' );
+my(@date_type) = ( 'int', 'NULL', '' );
+my(@perl_type) = ( 'text', 'NULL', '' );
+my @money_type = ( 'decimal', '', '10,2' );
+
+###
+# create a dbdef object from the old data structure
+###
+
+my(%tables)=&tables_hash_hack;
+
+#turn it into objects
+my($dbdef) = new DBIx::DBSchema ( map {
+ my(@columns);
+ while (@{$tables{$_}{'columns'}}) {
+ my($name,$type,$null,$length)=splice @{$tables{$_}{'columns'}}, 0, 4;
+ push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length );
+ }
+ DBIx::DBSchema::Table->new(
+ $_,
+ $tables{$_}{'primary_key'},
+ DBIx::DBSchema::ColGroup::Unique->new($tables{$_}{'unique'}),
+ DBIx::DBSchema::ColGroup::Index->new($tables{$_}{'index'}),
+ @columns,
+ );
+} (keys %tables) );
+
+my $cust_main = $dbdef->table('cust_main');
+unless ($ship) { #remove ship_ from cust_main
+ $cust_main->delcolumn($_) foreach ( grep /^ship_/, $cust_main->columns );
+} else { #add indices
+ push @{$cust_main->index->lol_ref},
+ map { [ "ship_$_" ] } qw( last company daytime night fax );
+}
+
+#add radius attributes to svc_acct
+
+my($svc_acct)=$dbdef->table('svc_acct');
+
+my($attribute);
+foreach $attribute (@attributes) {
+ $svc_acct->addcolumn ( new DBIx::DBSchema::Column (
+ 'radius_'. $attribute,
+ 'varchar',
+ 'NULL',
+ $char_d,
+ ));
+}
+
+foreach $attribute (@check_attributes) {
+ $svc_acct->addcolumn( new DBIx::DBSchema::Column (
+ 'rc_'. $attribute,
+ 'varchar',
+ 'NULL',
+ $char_d,
+ ));
+}
+
+#create history tables (false laziness w/create-history-tables)
+foreach my $table ( grep { ! /^h_/ } $dbdef->tables ) {
+ my $tableobj = $dbdef->table($table)
+ or die "unknown table $table";
+
+ die "unique->lol_ref undefined for $table"
+ unless defined $tableobj->unique->lol_ref;
+ die "index->lol_ref undefined for $table"
+ unless defined $tableobj->index->lol_ref;
+
+ my $h_tableobj = DBIx::DBSchema::Table->new( {
+ name => "h_$table",
+ primary_key => 'historynum',
+ unique => DBIx::DBSchema::ColGroup::Unique->new( [] ),
+ 'index' => DBIx::DBSchema::ColGroup::Index->new( [
+ @{$tableobj->unique->lol_ref},
+ @{$tableobj->index->lol_ref}
+ ] ),
+ columns => [
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'historynum',
+ 'type' => 'serial',
+ 'null' => 'NOT NULL',
+ 'length' => '',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_date',
+ 'type' => 'int',
+ 'null' => 'NULL',
+ 'length' => '',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_user',
+ 'type' => 'varchar',
+ 'null' => 'NOT NULL',
+ 'length' => '80',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_action',
+ 'type' => 'varchar',
+ 'null' => 'NOT NULL',
+ 'length' => '80',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ map {
+ my $column = $tableobj->column($_);
+
+ #clone so as to not disturb the original
+ $column = DBIx::DBSchema::Column->new( {
+ map { $_ => $column->$_() }
+ qw( name type null length default local )
+ } );
+
+ $column->type('int')
+ if $column->type eq 'serial';
+ #$column->default('')
+ # if $column->default =~ /^nextval\(/i;
+ #( my $local = $column->local ) =~ s/AUTO_INCREMENT//i;
+ #$column->local($local);
+ $column;
+ } $tableobj->columns
+ ],
+ } );
+ $dbdef->addtable($h_tableobj);
+}
+
+#important
+$dbdef->save($dbdef_file);
+&FS::Record::reload_dbdef($dbdef_file);
+
+###
+# create 'em
+###
+
+my($dbh)=adminsuidsetup $user;
+
+#create tables
+$|=1;
+
+foreach my $statement ( $dbdef->sql($dbh) ) {
+ $dbh->do( $statement )
+ or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement";
+}
+
+#cust_main_county
+foreach my $country ( sort map uc($_), all_country_codes ) {
+
+ my $subcountry = eval { new Locale::SubCountry($country) };
+ my @states = $subcountry ? $subcountry->all_codes : undef;
+
+ if ( !scalar(@states) || ( scalar(@states) == 1 && !defined($states[0]) ) ) {
+
+ my $cust_main_county = new FS::cust_main_county({
+ 'tax' => 0,
+ 'country' => $country,
+ });
+ my $error = $cust_main_county->insert;
+ die $error if $error;
+
+ } else {
+
+ if ( $states[0] =~ /^(\d+|\w)$/ ) {
+ @states = map $subcountry->full_name($_), @states
+ }
+
+ foreach my $state ( @states ) {
+
+ my $cust_main_county = new FS::cust_main_county({
+ 'state' => $state,
+ 'tax' => 0,
+ 'country' => $country,
+ });
+ my $error = $cust_main_county->insert;
+ die $error if $error;
+
+ }
+
+ }
+}
+
+#billing events
+foreach my $aref (
+ [ 'COMP', 'Comp invoice', '$cust_bill->comp();', 30, 'comp' ],
+ [ 'CARD', 'Batch card', '$cust_bill->batch_card();', 40, 'batch-card' ],
+ [ 'BILL', 'Send invoice', '$cust_bill->send();', 50, 'send' ],
+ [ 'DCRD', 'Send invoice', '$cust_bill->send();', 50, 'send' ],
+ [ 'DCHK', 'Send invoice', '$cust_bill->send();', 50, 'send' ],
+) {
+
+ my $part_bill_event = new FS::part_bill_event({
+ 'payby' => $aref->[0],
+ 'event' => $aref->[1],
+ 'eventcode' => $aref->[2],
+ 'seconds' => 0,
+ 'weight' => $aref->[3],
+ 'plan' => $aref->[4],
+ });
+ my($error);
+ $error=$part_bill_event->insert;
+ die $error if $error;
+
+}
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+#print "Freeside database initialized sucessfully\n";
+
+sub usage {
+ die "Usage:\n freeside-setup [ -s ] user\n";
+}
+
+###
+# Now it becomes an object. much better.
+###
+sub tables_hash_hack {
+
+ #note that s/(date|change)/_$1/; to avoid keyword conflict.
+ #put a kludge in FS::Record to catch this or? (pry need some date-handling
+ #stuff anyway also)
+
+ my(%tables)=( #yech.}
+
+ 'agent' => {
+ 'columns' => [
+ 'agentnum', 'serial', '', '',
+ 'agent', 'varchar', '', $char_d,
+ 'typenum', 'int', '', '',
+ 'freq', 'int', 'NULL', '',
+ 'prog', @perl_type,
+ 'disabled', 'char', 'NULL', 1,
+ 'username', 'varchar', 'NULL', $char_d,
+ '_password','varchar', 'NULL', $char_d,
+ ],
+ 'primary_key' => 'agentnum',
+ 'unique' => [],
+ 'index' => [ ['typenum'], ['disabled'] ],
+ },
+
+ 'agent_type' => {
+ 'columns' => [
+ 'typenum', 'serial', '', '',
+ 'atype', 'varchar', '', $char_d,
+ ],
+ 'primary_key' => 'typenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'type_pkgs' => {
+ 'columns' => [
+ 'typenum', 'int', '', '',
+ 'pkgpart', 'int', '', '',
+ ],
+ 'primary_key' => '',
+ 'unique' => [ ['typenum', 'pkgpart'] ],
+ 'index' => [ ['typenum'] ],
+ },
+
+ 'cust_bill' => {
+ 'columns' => [
+ 'invnum', 'serial', '', '',
+ 'custnum', 'int', '', '',
+ '_date', @date_type,
+ 'charged', @money_type,
+ 'printed', 'int', '', '',
+ 'closed', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'invnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'] ],
+ },
+
+ 'cust_bill_event' => {
+ 'columns' => [
+ 'eventnum', 'serial', '', '',
+ 'invnum', 'int', '', '',
+ 'eventpart', 'int', '', '',
+ '_date', @date_type,
+ 'status', 'varchar', '', $char_d,
+ 'statustext', 'text', 'NULL', '',
+ ],
+ 'primary_key' => 'eventnum',
+ #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
+ 'unique' => [],
+ 'index' => [ ['invnum'], ['status'] ],
+ },
+
+ 'part_bill_event' => {
+ 'columns' => [
+ 'eventpart', 'serial', '', '',
+ 'payby', 'char', '', 4,
+ 'event', 'varchar', '', $char_d,
+ 'eventcode', @perl_type,
+ 'seconds', 'int', 'NULL', '',
+ 'weight', 'int', '', '',
+ 'plan', 'varchar', 'NULL', $char_d,
+ 'plandata', 'text', 'NULL', '',
+ 'disabled', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'eventpart',
+ 'unique' => [],
+ 'index' => [ ['payby'], ['disabled'], ],
+ },
+
+ 'cust_bill_pkg' => {
+ 'columns' => [
+ 'pkgnum', 'int', '', '',
+ 'invnum', 'int', '', '',
+ 'setup', @money_type,
+ 'recur', @money_type,
+ 'sdate', @date_type,
+ 'edate', @date_type,
+ 'itemdesc', 'varchar', 'NULL', $char_d,
+ ],
+ 'primary_key' => '',
+ 'unique' => [],
+ 'index' => [ ['invnum'] ],
+ },
+
+ 'cust_bill_pkg_detail' => {
+ 'columns' => [
+ 'detailnum', 'serial', '', '',
+ 'pkgnum', 'int', '', '',
+ 'invnum', 'int', '', '',
+ 'detail', 'varchar', '', $char_d,
+ ],
+ 'primary_key' => 'detailnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum', 'invnum' ] ],
+ },
+
+ 'cust_credit' => {
+ 'columns' => [
+ 'crednum', 'serial', '', '',
+ 'custnum', 'int', '', '',
+ '_date', @date_type,
+ 'amount', @money_type,
+ 'otaker', 'varchar', '', 32,
+ 'reason', 'text', 'NULL', '',
+ 'closed', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'crednum',
+ 'unique' => [],
+ 'index' => [ ['custnum'] ],
+ },
+
+ 'cust_credit_bill' => {
+ 'columns' => [
+ 'creditbillnum', 'serial', '', '',
+ 'crednum', 'int', '', '',
+ 'invnum', 'int', '', '',
+ '_date', @date_type,
+ 'amount', @money_type,
+ ],
+ 'primary_key' => 'creditbillnum',
+ 'unique' => [],
+ 'index' => [ ['crednum'], ['invnum'] ],
+ },
+
+ 'cust_main' => {
+ 'columns' => [
+ 'custnum', 'serial', '', '',
+ 'agentnum', 'int', '', '',
+# 'titlenum', 'int', 'NULL', '',
+ 'last', 'varchar', '', $char_d,
+# 'middle', 'varchar', 'NULL', $char_d,
+ 'first', 'varchar', '', $char_d,
+ 'ss', 'varchar', 'NULL', 11,
+ 'company', 'varchar', 'NULL', $char_d,
+ 'address1', 'varchar', '', $char_d,
+ 'address2', 'varchar', 'NULL', $char_d,
+ 'city', 'varchar', '', $char_d,
+ 'county', 'varchar', 'NULL', $char_d,
+ 'state', 'varchar', 'NULL', $char_d,
+ 'zip', 'varchar', '', 10,
+ 'country', 'char', '', 2,
+ 'daytime', 'varchar', 'NULL', 20,
+ 'night', 'varchar', 'NULL', 20,
+ 'fax', 'varchar', 'NULL', 12,
+ 'ship_last', 'varchar', 'NULL', $char_d,
+# 'ship_middle', 'varchar', 'NULL', $char_d,
+ 'ship_first', 'varchar', 'NULL', $char_d,
+ 'ship_company', 'varchar', 'NULL', $char_d,
+ 'ship_address1', 'varchar', 'NULL', $char_d,
+ 'ship_address2', 'varchar', 'NULL', $char_d,
+ 'ship_city', 'varchar', 'NULL', $char_d,
+ 'ship_county', 'varchar', 'NULL', $char_d,
+ 'ship_state', 'varchar', 'NULL', $char_d,
+ 'ship_zip', 'varchar', 'NULL', 10,
+ 'ship_country', 'char', 'NULL', 2,
+ 'ship_daytime', 'varchar', 'NULL', 20,
+ 'ship_night', 'varchar', 'NULL', 20,
+ 'ship_fax', 'varchar', 'NULL', 12,
+ 'payby', 'char', '', 4,
+ 'payinfo', 'varchar', 'NULL', $char_d,
+ 'paycvv', 'varchar', 'NULL', 4,
+ #'paydate', @date_type,
+ 'paydate', 'varchar', 'NULL', 10,
+ 'payname', 'varchar', 'NULL', $char_d,
+ 'tax', 'char', 'NULL', 1,
+ 'otaker', 'varchar', '', 32,
+ 'refnum', 'int', '', '',
+ 'referral_custnum', 'int', 'NULL', '',
+ 'comments', 'text', 'NULL', '',
+ ],
+ 'primary_key' => 'custnum',
+ 'unique' => [],
+ #'index' => [ ['last'], ['company'] ],
+ 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ],
+ [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ],
+ ],
+ },
+
+ 'cust_main_invoice' => {
+ 'columns' => [
+ 'destnum', 'serial', '', '',
+ 'custnum', 'int', '', '',
+ 'dest', 'varchar', '', $char_d,
+ ],
+ 'primary_key' => 'destnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ],
+ },
+
+ 'cust_main_county' => { #county+state+country are checked off the
+ #cust_main_county for validation and to provide
+ # a tax rate.
+ 'columns' => [
+ 'taxnum', 'serial', '', '',
+ 'state', 'varchar', 'NULL', $char_d,
+ 'county', 'varchar', 'NULL', $char_d,
+ 'country', 'char', '', 2,
+ 'taxclass', 'varchar', 'NULL', $char_d,
+ 'exempt_amount', @money_type,
+ 'tax', 'real', '', '', #tax %
+ 'taxname', 'varchar', 'NULL', $char_d,
+ 'setuptax', 'char', 'NULL', 1, # Y = setup tax exempt
+ 'recurtax', 'char', 'NULL', 1, # Y = recur tax exempt
+ ],
+ 'primary_key' => 'taxnum',
+ 'unique' => [],
+ # 'unique' => [ ['taxnum'], ['state', 'county'] ],
+ 'index' => [],
+ },
+
+ 'cust_pay' => {
+ 'columns' => [
+ 'paynum', 'serial', '', '',
+ #now cust_bill_pay #'invnum', 'int', '', '',
+ 'custnum', 'int', '', '',
+ 'paid', @money_type,
+ '_date', @date_type,
+ 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index into
+ # payment type table.
+ 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above
+ 'paybatch', 'varchar', 'NULL', $char_d, #for auditing purposes.
+ 'closed', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'paynum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ] ],
+ },
+
+ 'cust_pay_void' => {
+ 'columns' => [
+ 'paynum', 'int', '', '',
+ 'custnum', 'int', '', '',
+ 'paid', @money_type,
+ '_date', @date_type,
+ 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index into
+ # payment type table.
+ 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above
+ 'paybatch', 'varchar', 'NULL', $char_d, #for auditing purposes.
+ 'closed', 'char', 'NULL', 1,
+ 'void_date', @date_type,
+ 'reason', 'varchar', 'NULL', $char_d,
+ 'otaker', 'varchar', '', 32,
+ ],
+ 'primary_key' => 'paynum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ] ],
+ },
+
+ 'cust_bill_pay' => {
+ 'columns' => [
+ 'billpaynum', 'serial', '', '',
+ 'invnum', 'int', '', '',
+ 'paynum', 'int', '', '',
+ 'amount', @money_type,
+ '_date', @date_type
+ ],
+ 'primary_key' => 'billpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+ },
+
+ 'cust_pay_batch' => { #what's this used for again? list of customers
+ #in current CARD batch? (necessarily CARD?)
+ 'columns' => [
+ 'paybatchnum', 'serial', '', '',
+ 'invnum', 'int', '', '',
+ 'custnum', 'int', '', '',
+ 'last', 'varchar', '', $char_d,
+ 'first', 'varchar', '', $char_d,
+ 'address1', 'varchar', '', $char_d,
+ 'address2', 'varchar', 'NULL', $char_d,
+ 'city', 'varchar', '', $char_d,
+ 'state', 'varchar', 'NULL', $char_d,
+ 'zip', 'varchar', '', 10,
+ 'country', 'char', '', 2,
+# 'trancode', 'int', '', '',
+ 'cardnum', 'varchar', '', 16,
+ #'exp', @date_type,
+ 'exp', 'varchar', '', 11,
+ 'payname', 'varchar', 'NULL', $char_d,
+ 'amount', @money_type,
+ ],
+ 'primary_key' => 'paybatchnum',
+ 'unique' => [],
+ 'index' => [ ['invnum'], ['custnum'] ],
+ },
+
+ 'cust_pkg' => {
+ 'columns' => [
+ 'pkgnum', 'serial', '', '',
+ 'custnum', 'int', '', '',
+ 'pkgpart', 'int', '', '',
+ 'otaker', 'varchar', '', 32,
+ 'setup', @date_type,
+ 'bill', @date_type,
+ 'last_bill', @date_type,
+ 'susp', @date_type,
+ 'cancel', @date_type,
+ 'expire', @date_type,
+ 'manual_flag', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'pkgnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'] ],
+ },
+
+ 'cust_refund' => {
+ 'columns' => [
+ 'refundnum', 'serial', '', '',
+ #now cust_credit_refund #'crednum', 'int', '', '',
+ 'custnum', 'int', '', '',
+ '_date', @date_type,
+ 'refund', @money_type,
+ 'otaker', 'varchar', '', 32,
+ 'reason', 'varchar', '', $char_d,
+ 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index
+ # into payment type table.
+ 'payinfo', 'varchar', 'NULL', $char_d, #see cust_main above
+ 'paybatch', 'varchar', 'NULL', $char_d,
+ 'closed', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'refundnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cust_credit_refund' => {
+ 'columns' => [
+ 'creditrefundnum', 'serial', '', '',
+ 'crednum', 'int', '', '',
+ 'refundnum', 'int', '', '',
+ 'amount', @money_type,
+ '_date', @date_type
+ ],
+ 'primary_key' => 'creditrefundnum',
+ 'unique' => [],
+ 'index' => [ [ 'crednum', 'refundnum' ] ],
+ },
+
+
+ 'cust_svc' => {
+ 'columns' => [
+ 'svcnum', 'serial', '', '',
+ 'pkgnum', 'int', 'NULL', '',
+ 'svcpart', 'int', '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ],
+ },
+
+ 'part_pkg' => {
+ 'columns' => [
+ 'pkgpart', 'serial', '', '',
+ 'pkg', 'varchar', '', $char_d,
+ 'comment', 'varchar', '', $char_d,
+ 'setup', @perl_type,
+ 'freq', 'varchar', '', $char_d, #billing frequency
+ 'recur', @perl_type,
+ 'setuptax', 'char', 'NULL', 1,
+ 'recurtax', 'char', 'NULL', 1,
+ 'plan', 'varchar', 'NULL', $char_d,
+ 'plandata', 'text', 'NULL', '',
+ 'disabled', 'char', 'NULL', 1,
+ 'taxclass', 'varchar', 'NULL', $char_d,
+ ],
+ 'primary_key' => 'pkgpart',
+ 'unique' => [],
+ 'index' => [ [ 'disabled' ], ],
+ },
+
+# 'part_title' => {
+# 'columns' => [
+# 'titlenum', 'int', '', '',
+# 'title', 'varchar', '', $char_d,
+# ],
+# 'primary_key' => 'titlenum',
+# 'unique' => [ [] ],
+# 'index' => [ [] ],
+# },
+
+ 'pkg_svc' => {
+ 'columns' => [
+ 'pkgpart', 'int', '', '',
+ 'svcpart', 'int', '', '',
+ 'quantity', 'int', '', '',
+ 'primary_svc','char', 'NULL', 1,
+ ],
+ 'primary_key' => '',
+ 'unique' => [ ['pkgpart', 'svcpart'] ],
+ 'index' => [ ['pkgpart'] ],
+ },
+
+ 'part_referral' => {
+ 'columns' => [
+ 'refnum', 'serial', '', '',
+ 'referral', 'varchar', '', $char_d,
+ 'disabled', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'refnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ },
+
+ 'part_svc' => {
+ 'columns' => [
+ 'svcpart', 'serial', '', '',
+ 'svc', 'varchar', '', $char_d,
+ 'svcdb', 'varchar', '', $char_d,
+ 'disabled', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'svcpart',
+ 'unique' => [],
+ 'index' => [ [ 'disabled' ] ],
+ },
+
+ 'part_svc_column' => {
+ 'columns' => [
+ 'columnnum', 'serial', '', '',
+ 'svcpart', 'int', '', '',
+ 'columnname', 'varchar', '', 64,
+ 'columnvalue', 'varchar', 'NULL', $char_d,
+ 'columnflag', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'columnnum',
+ 'unique' => [ [ 'svcpart', 'columnname' ] ],
+ 'index' => [ [ 'svcpart' ] ],
+ },
+
+ #(this should be renamed to part_pop)
+ 'svc_acct_pop' => {
+ 'columns' => [
+ 'popnum', 'serial', '', '',
+ 'city', 'varchar', '', $char_d,
+ 'state', 'varchar', '', $char_d,
+ 'ac', 'char', '', 3,
+ 'exch', 'char', '', 3,
+ 'loc', 'char', 'NULL', 4, #NULL for legacy purposes
+ ],
+ 'primary_key' => 'popnum',
+ 'unique' => [],
+ 'index' => [ [ 'state' ] ],
+ },
+
+ 'part_pop_local' => {
+ 'columns' => [
+ 'localnum', 'serial', '', '',
+ 'popnum', 'int', '', '',
+ 'city', 'varchar', 'NULL', $char_d,
+ 'state', 'char', 'NULL', 2,
+ 'npa', 'char', '', 3,
+ 'nxx', 'char', '', 3,
+ ],
+ 'primary_key' => 'localnum',
+ 'unique' => [],
+ 'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
+ },
+
+ 'svc_acct' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'username', 'varchar', '', $username_len, #unique (& remove dup code)
+ '_password', 'varchar', '', 72, #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60)
+ 'sec_phrase', 'varchar', 'NULL', $char_d,
+ 'popnum', 'int', 'NULL', '',
+ 'uid', 'int', 'NULL', '',
+ 'gid', 'int', 'NULL', '',
+ 'finger', 'varchar', 'NULL', $char_d,
+ 'dir', 'varchar', 'NULL', $char_d,
+ 'shell', 'varchar', 'NULL', $char_d,
+ 'quota', 'varchar', 'NULL', $char_d,
+ 'slipip', 'varchar', 'NULL', 15, #four TINYINTs, bah.
+ 'seconds', 'int', 'NULL', '', #uhhhh
+ 'domsvc', 'int', '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ #'unique' => [ [ 'username', 'domsvc' ] ],
+ 'unique' => [],
+ 'index' => [ ['username'], ['domsvc'] ],
+ },
+
+ #'svc_charge' => {
+ # 'columns' => [
+ # 'svcnum', 'int', '', '',
+ # 'amount', @money_type,
+ # ],
+ # 'primary_key' => 'svcnum',
+ # 'unique' => [ [] ],
+ # 'index' => [ [] ],
+ #},
+
+ 'svc_domain' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'domain', 'varchar', '', $char_d,
+ 'catchall', 'int', 'NULL', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ ['domain'] ],
+ 'index' => [],
+ },
+
+ 'domain_record' => {
+ 'columns' => [
+ 'recnum', 'serial', '', '',
+ 'svcnum', 'int', '', '',
+ #'reczone', 'varchar', '', $char_d,
+ 'reczone', 'varchar', '', 255,
+ 'recaf', 'char', '', 2,
+ 'rectype', 'varchar', '', 5,
+ #'recdata', 'varchar', '', $char_d,
+ 'recdata', 'varchar', '', 255,
+ ],
+ 'primary_key' => 'recnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'] ],
+ },
+
+ 'svc_forward' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'srcsvc', 'int', 'NULL', '',
+ 'src', 'varchar', 'NULL', 255,
+ 'dstsvc', 'int', 'NULL', '',
+ 'dst', 'varchar', 'NULL', 255,
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['srcsvc'], ['dstsvc'] ],
+ },
+
+ 'svc_www' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'recnum', 'int', '', '',
+ 'usersvc', 'int', '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ #'svc_wo' => {
+ # 'columns' => [
+ # 'svcnum', 'int', '', '',
+ # 'svcnum', 'int', '', '',
+ # 'svcnum', 'int', '', '',
+ # 'worker', 'varchar', '', $char_d,
+ # '_date', @date_type,
+ # ],
+ # 'primary_key' => 'svcnum',
+ # 'unique' => [ [] ],
+ # 'index' => [ [] ],
+ #},
+
+ 'prepay_credit' => {
+ 'columns' => [
+ 'prepaynum', 'serial', '', '',
+ 'identifier', 'varchar', '', $char_d,
+ 'amount', @money_type,
+ 'seconds', 'int', 'NULL', '',
+ ],
+ 'primary_key' => 'prepaynum',
+ 'unique' => [ ['identifier'] ],
+ 'index' => [],
+ },
+
+ 'port' => {
+ 'columns' => [
+ 'portnum', 'serial', '', '',
+ 'ip', 'varchar', 'NULL', 15,
+ 'nasport', 'int', 'NULL', '',
+ 'nasnum', 'int', '', '',
+ ],
+ 'primary_key' => 'portnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'nas' => {
+ 'columns' => [
+ 'nasnum', 'serial', '', '',
+ 'nas', 'varchar', '', $char_d,
+ 'nasip', 'varchar', '', 15,
+ 'nasfqdn', 'varchar', '', $char_d,
+ 'last', 'int', '', '',
+ ],
+ 'primary_key' => 'nasnum',
+ 'unique' => [ [ 'nas' ], [ 'nasip' ] ],
+ 'index' => [ [ 'last' ] ],
+ },
+
+ 'session' => {
+ 'columns' => [
+ 'sessionnum', 'serial', '', '',
+ 'portnum', 'int', '', '',
+ 'svcnum', 'int', '', '',
+ 'login', @date_type,
+ 'logout', @date_type,
+ ],
+ 'primary_key' => 'sessionnum',
+ 'unique' => [],
+ 'index' => [ [ 'portnum' ] ],
+ },
+
+ 'queue' => {
+ 'columns' => [
+ 'jobnum', 'serial', '', '',
+ 'job', 'text', '', '',
+ '_date', 'int', '', '',
+ 'status', 'varchar', '', $char_d,
+ 'statustext', 'text', 'NULL', '',
+ 'svcnum', 'int', 'NULL', '',
+ ],
+ 'primary_key' => 'jobnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ], [ 'status' ] ],
+ },
+
+ 'queue_arg' => {
+ 'columns' => [
+ 'argnum', 'serial', '', '',
+ 'jobnum', 'int', '', '',
+ 'arg', 'text', 'NULL', '',
+ ],
+ 'primary_key' => 'argnum',
+ 'unique' => [],
+ 'index' => [ [ 'jobnum' ] ],
+ },
+
+ 'queue_depend' => {
+ 'columns' => [
+ 'dependnum', 'serial', '', '',
+ 'jobnum', 'int', '', '',
+ 'depend_jobnum', 'int', '', '',
+ ],
+ 'primary_key' => 'dependnum',
+ 'unique' => [],
+ 'index' => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
+ },
+
+ 'export_svc' => {
+ 'columns' => [
+ 'exportsvcnum' => 'serial', '', '',
+ 'exportnum' => 'int', '', '',
+ 'svcpart' => 'int', '', '',
+ ],
+ 'primary_key' => 'exportsvcnum',
+ 'unique' => [ [ 'exportnum', 'svcpart' ] ],
+ 'index' => [ [ 'exportnum' ], [ 'svcpart' ] ],
+ },
+
+ 'part_export' => {
+ 'columns' => [
+ 'exportnum', 'serial', '', '',
+ #'svcpart', 'int', '', '',
+ 'machine', 'varchar', '', $char_d,
+ 'exporttype', 'varchar', '', $char_d,
+ 'nodomain', 'char', 'NULL', 1,
+ ],
+ 'primary_key' => 'exportnum',
+ 'unique' => [],
+ 'index' => [ [ 'machine' ], [ 'exporttype' ] ],
+ },
+
+ 'part_export_option' => {
+ 'columns' => [
+ 'optionnum', 'serial', '', '',
+ 'exportnum', 'int', '', '',
+ 'optionname', 'varchar', '', $char_d,
+ 'optionvalue', 'text', 'NULL', '',
+ ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'exportnum' ], [ 'optionname' ] ],
+ },
+
+ 'radius_usergroup' => {
+ 'columns' => [
+ 'usergroupnum', 'serial', '', '',
+ 'svcnum', 'int', '', '',
+ 'groupname', 'varchar', '', $char_d,
+ ],
+ 'primary_key' => 'usergroupnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ], [ 'groupname' ] ],
+ },
+
+ 'msgcat' => {
+ 'columns' => [
+ 'msgnum', 'serial', '', '',
+ 'msgcode', 'varchar', '', $char_d,
+ 'locale', 'varchar', '', 16,
+ 'msg', 'text', '', '',
+ ],
+ 'primary_key' => 'msgnum',
+ 'unique' => [ [ 'msgcode', 'locale' ] ],
+ 'index' => [],
+ },
+
+ 'cust_tax_exempt' => {
+ 'columns' => [
+ 'exemptnum', 'serial', '', '',
+ 'custnum', 'int', '', '',
+ 'taxnum', 'int', '', '',
+ 'year', 'int', '', '',
+ 'month', 'int', '', '',
+ 'amount', @money_type,
+ ],
+ 'primary_key' => 'exemptnum',
+ 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
+ 'index' => [],
+ },
+
+ 'router' => {
+ 'columns' => [
+ 'routernum', 'serial', '', '',
+ 'routername', 'varchar', '', $char_d,
+ 'svcnum', 'int', 'NULL', '',
+ ],
+ 'primary_key' => 'routernum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'part_svc_router' => {
+ 'columns' => [
+ 'svcpart', 'int', '', '',
+ 'routernum', 'int', '', '',
+ ],
+ 'primary_key' => '',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'addr_block' => {
+ 'columns' => [
+ 'blocknum', 'serial', '', '',
+ 'routernum', 'int', '', '',
+ 'ip_gateway', 'varchar', '', 15,
+ 'ip_netmask', 'int', '', '',
+ ],
+ 'primary_key' => 'blocknum',
+ 'unique' => [ [ 'blocknum', 'routernum' ] ],
+ 'index' => [],
+ },
+
+ 'svc_broadband' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'blocknum', 'int', '', '',
+ 'speed_up', 'int', '', '',
+ 'speed_down', 'int', '', '',
+ 'ip_addr', 'varchar', '', 15,
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'part_virtual_field' => {
+ 'columns' => [
+ 'vfieldpart', 'int', '', '',
+ 'dbtable', 'varchar', '', 32,
+ 'name', 'varchar', '', 32,
+ 'check_block', 'text', 'NULL', '',
+ 'length', 'int', 'NULL', '',
+ 'list_source', 'text', 'NULL', '',
+ 'label', 'varchar', 'NULL', 80,
+ ],
+ 'primary_key' => 'vfieldpart',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'virtual_field' => {
+ 'columns' => [
+ 'recnum', 'int', '', '',
+ 'vfieldpart', 'int', '', '',
+ 'value', 'varchar', '', 128,
+ ],
+ 'primary_key' => '',
+ 'unique' => [ [ 'vfieldpart', 'recnum' ] ],
+ 'index' => [],
+ },
+
+ 'acct_snarf' => {
+ 'columns' => [
+ 'snarfnum', 'int', '', '',
+ 'svcnum', 'int', '', '',
+ 'machine', 'varchar', '', 255,
+ 'protocol', 'varchar', '', $char_d,
+ 'username', 'varchar', '', $char_d,
+ '_password', 'varchar', '', $char_d,
+ ],
+ 'primary_key' => 'snarfnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ] ],
+ },
+
+ 'svc_external' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '',
+ 'id', 'int', 'NULL', '',
+ 'title', 'varchar', 'NULL', $char_d,
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'cust_pay_refund' => {
+ 'columns' => [
+ 'payrefundnum', 'serial', '', '',
+ 'paynum', 'int', '', '',
+ 'refundnum', 'int', '', '',
+ '_date', @date_type,
+ 'amount', @money_type,
+ ],
+ 'primary_key' => 'payrefundnum',
+ 'unique' => [],
+ 'index' => [ ['paynum'], ['refundnum'] ],
+ },
+
+
+
+ );
+
+ %tables;
+
+}
+
diff --git a/FS/bin/freeside-sqlradius-radacctd b/FS/bin/freeside-sqlradius-radacctd
new file mode 100644
index 0000000..4e8d57c
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-radacctd
@@ -0,0 +1,180 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( $log_file $sigterm $sigint );
+use subs qw( _die _logmsg );
+use Fcntl qw(:flock);
+use POSIX qw(setsid);
+use Date::Format;
+use IO::File;
+use FS::UID qw(adminsuidsetup);
+#use FS::Record qw(qsearch qsearchs);
+#use FS::part_export;
+#use FS::svc_acct;
+#use FS::cust_svc;
+
+#lots of false laziness w/freeside-queued
+
+my $user = shift or die &usage;
+
+#my $pid_file = "/var/run/freeside-sqlradius-radacctd.$user.pid";
+my $pid_file = "/var/run/freeside-sqlradius-radacctd.pid";
+
+&daemonize1;
+
+#sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
+#$SIG{CHLD} = \&REAPER;
+
+$sigterm = 0;
+$sigint = 0;
+$SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; };
+$SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; };
+
+my $freeside_gid = scalar(getgrnam('freeside'))
+ or die "can't setgid to freeside group\n";
+$) = $freeside_gid;
+$( = $freeside_gid;
+#if freebsd can't setuid(), presumably it can't setgid() either. grr fleabsd
+($(,$)) = ($),$();
+$) = $freeside_gid;
+
+$> = $FS::UID::freeside_uid;
+$< = $FS::UID::freeside_uid;
+#freebsd is sofa king broken, won't setuid()
+($<,$>) = ($>,$<);
+$> = $FS::UID::freeside_uid;
+
+#$ENV{HOME} = (getpwuid($>))[7]; #for ssh
+adminsuidsetup $user;
+
+$log_file= "/usr/local/etc/freeside/sqlradius-radacctd-log.". $FS::UID::datasrc;
+
+&daemonize2;
+
+$SIG{__DIE__} = \&_die;
+$SIG{__WARN__} = \&_logmsg;
+
+warn "freeside-sqlradius-radacctd starting\n";
+
+#eslaf
+
+#my $machine = shift or die &usage; #would need to be up higher for real
+my @exports = qsearch('part_export', { 'exporttype' => 'sqlradius' } );
+
+while (1) {
+
+ my %seen = ();
+ foreach my $export ( @exports ) {
+ next if $seen{$export->option('datasrc')}++;
+ my $dbh = DBI->connect(
+ map { $export->option($_) } qw( datasrc username password )
+ ) or do {
+ warn "can't connect to ". $export->option('datasrc'). ": ". $DBI::errstr;
+ next;
+ }
+
+ # find old radacct position
+ #$lastid = 0;
+
+ # get new radacct records
+ my $sth = $dbh->prepare('SELECT * FROM radacct WHERE radacctid > ?') or do {
+ warn "can't select in radacct table from ". $export->option('datasrc').
+ ": ". $dbh->errstr;
+ next;
+ };
+
+ while ( my $radacct = $sth->fetchrow_arrayref({}) ) {
+
+ my $session = new FS::session {
+ portnum =>
+ svcnum =>
+ login =>
+ #logout =>
+ };
+
+ }
+
+ # look for updated radacct records & replace them
+
+ }
+
+ sleep 5;
+
+}
+
+#more false laziness w/freeside-queued
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-radacctd user\n";
+}
+
+sub _die {
+ my $msg = shift;
+ unlink $pid_file if -e $pid_file;
+ _logmsg($msg);
+}
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
+
+sub daemonize1 {
+
+ chdir "/" or die "Can't chdir to /: $!";
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ defined(my $pid = fork) or die "Can't fork: $!";
+ if ( $pid ) {
+ print "freeside-sqlradius-radacctd started with pid $pid\n";
+ #logging to $log_file\n";
+ exit unless $pid_file;
+ my $pidfh = new IO::File ">$pid_file" or exit;
+ print $pidfh "$pid\n";
+ exit;
+ }
+ #open STDOUT, '>/dev/null'
+ # or die "Can't write to /dev/null: $!";
+ #setsid or die "Can't start a new session: $!";
+ #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+
+}
+
+sub daemonize2 {
+ open STDOUT, '>/dev/null'
+ or die "Can't write to /dev/null: $!";
+ setsid or die "Can't start a new session: $!";
+ open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+}
+
+
+#eslaf
+
+=head1 NAME
+
+freeside-sqlradius-radacctd - Real-time radacct import daemon
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-radacctd username
+
+=head1 DESCRIPTION
+
+Imports records from an SQL radacct table in real-time into the session
+monitor.
+
+This enables per-minute or per-hour charges as well as the
+"View active NAS ports" function.
+
+B<username> is a username added by freeside-adduser.
+
+=head1 SEE ALSO
+
+session.html from the base documentation.
+
+=cut
+
diff --git a/FS/bin/freeside-sqlradius-reset b/FS/bin/freeside-sqlradius-reset
new file mode 100755
index 0000000..11cbe9e
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-reset
@@ -0,0 +1,85 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::svc_acct;
+use FS::cust_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#my $machine = shift or die &usage;
+
+my @exports = ();
+if ( @ARGV ) {
+ foreach my $exportnum ( @ARGV ) {
+ foreach my $exporttype (qw( sqlradius sqlradius_withdomain )) {
+ push @exports, qsearch('part_export', { exportnum => $exportnum,
+ exporttype => $exporttype, } );
+ }
+ }
+ } else {
+ @exports = qsearch('part_export', { exporttype=>'sqlradius' } );
+ push @exports, qsearch('part_export', { exporttype=>'sqlradius_withdomain' } );
+}
+
+foreach my $export ( @exports ) {
+ my $icradius_dbh = DBI->connect(
+ map { $export->option($_) } qw( datasrc username password )
+ ) or die $DBI::errstr;
+ for my $table (qw( radcheck radreply usergroup )) {
+ my $sth = $icradius_dbh->prepare("DELETE FROM $table");
+ $sth->execute or die "Can't reset $table table: ". $sth->errstr;
+ }
+ $icradius_dbh->disconnect;
+}
+
+foreach my $export ( @exports ) {
+
+ #my @svcparts = map { $_->svcpart } $export->export_svc;
+
+ my @svc_acct =
+ map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
+ map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ $export->export_svc;
+
+ foreach my $svc_acct ( @svc_acct ) {
+
+ #false laziness with FS::svc_acct::insert (like it matters)
+ my $error = $export->export_insert($svc_acct);
+ die $error if $error;
+
+ }
+}
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-reset user [ exportnum, ... ]\n";
+}
+
+=head1 NAME
+
+freeside-sqlradius-reset - Command line interface to reset and recreate RADIUS SQL tables
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-reset username [ EXPORTNUM, ... ]
+
+=head1 DESCRIPTION
+
+Deletes the radcheck, radreply and usergroup tables and repopulates them from
+the Freeside database, for the specified exports, or, if no exports are
+specified, for all sqlradius and sqlradius_withdomain exports.
+
+B<username> is a username added by freeside-adduser.
+
+=head1 SEE ALSO
+
+L<freeside-reexport>, L<FS::part_export>, L<FS::part_export::sqlradius>
+
+=cut
+
+
+
diff --git a/FS/bin/freeside-sqlradius-seconds b/FS/bin/freeside-sqlradius-seconds
new file mode 100644
index 0000000..1c978fa
--- /dev/null
+++ b/FS/bin/freeside-sqlradius-seconds
@@ -0,0 +1,58 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use Date::Parse;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::svc_acct;
+
+my $fs_user = shift or die &usage;
+adminsuidsetup( $fs_user );
+
+my $target_user = shift or die &usage;
+my $start = shift or die &usage;
+$start = str2time($start);
+my $stop = scalar(@ARGV) ? str2time(shift) : time;
+
+my $svc_acct = qsearchs( 'svc_acct', { 'username' => $target_user } );
+die "username $target_user not found\n" unless $svc_acct;
+
+print $svc_acct->seconds_since_sqlradacct( $start, $stop ). "\n";
+
+sub usage {
+ die "Usage:\n\n freeside-sqlradius-seconds freeside_username target_username start_date stop_date\n";
+}
+
+
+=head1 NAME
+
+freeside-sqlradius-seconds - Real-time radacct import daemon
+
+=head1 SYNOPSIS
+
+ freeside-sqlradius-seconds freeside_username target_username start_date [ stop_date ]
+
+=head1 DESCRIPTION
+
+Returns the number of seconds the specified username has been online between
+start_date (inclusive) and stop_date (exclusive).
+See L<FS::svc_acct/seconds_since_sqlradacct>
+
+B<freeside_username> is a username added by freeside-adduser.
+B<target_username> is the username of the user account to query.
+B<start_date> and B<stop_date> are in any format Date::Parse is happy with.
+B<stop_date> defaults to now if not specified.
+
+=head1 BUGS
+
+Selection of the account in question is rather simplistic in that
+B<target_username> doesn't necessarily identify a unique account (and wouldn't
+even if a domain was specified), and no sqlradius export is checked for.
+
+=head1 SEE ALSO
+
+L<FS::svc_acct/seconds_since_sqlradacct>
+
+=cut
+
+1;
diff --git a/FS/t/CGI.t b/FS/t/CGI.t
new file mode 100644
index 0000000..1b4e238
--- /dev/null
+++ b/FS/t/CGI.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::CGI;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/ClientAPI.t b/FS/t/ClientAPI.t
new file mode 100644
index 0000000..973d8da
--- /dev/null
+++ b/FS/t/ClientAPI.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::ClientAPI;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Conf.t b/FS/t/Conf.t
new file mode 100644
index 0000000..a9f7653
--- /dev/null
+++ b/FS/t/Conf.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Conf;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/ConfItem.t b/FS/t/ConfItem.t
new file mode 100644
index 0000000..c7932d7
--- /dev/null
+++ b/FS/t/ConfItem.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::ConfItem;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/InitHandler.t b/FS/t/InitHandler.t
new file mode 100644
index 0000000..0ce60c8
--- /dev/null
+++ b/FS/t/InitHandler.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::InitHandler;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Misc.t b/FS/t/Misc.t
new file mode 100644
index 0000000..cc7751a
--- /dev/null
+++ b/FS/t/Misc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Misc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Msgcat.t b/FS/t/Msgcat.t
new file mode 100644
index 0000000..29e71b3
--- /dev/null
+++ b/FS/t/Msgcat.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Msgcat;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Record.t b/FS/t/Record.t
new file mode 100644
index 0000000..00de1ed
--- /dev/null
+++ b/FS/t/Record.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Record;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Report-Table-Monthly.t b/FS/t/Report-Table-Monthly.t
new file mode 100644
index 0000000..6ff365d
--- /dev/null
+++ b/FS/t/Report-Table-Monthly.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Report::Table::Monthly;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Report-Table.t b/FS/t/Report-Table.t
new file mode 100644
index 0000000..866d498
--- /dev/null
+++ b/FS/t/Report-Table.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Report::Table;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Report.t b/FS/t/Report.t
new file mode 100644
index 0000000..76d6ea4
--- /dev/null
+++ b/FS/t/Report.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Report;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/SearchCache.t b/FS/t/SearchCache.t
new file mode 100644
index 0000000..3c26f35
--- /dev/null
+++ b/FS/t/SearchCache.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::SearchCache;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/UID.t b/FS/t/UID.t
new file mode 100644
index 0000000..9f7da4e
--- /dev/null
+++ b/FS/t/UID.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::UID;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/acct_snarf.t b/FS/t/acct_snarf.t
new file mode 100644
index 0000000..642760f
--- /dev/null
+++ b/FS/t/acct_snarf.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::acct_snarf;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/agent.t b/FS/t/agent.t
new file mode 100644
index 0000000..769cce2
--- /dev/null
+++ b/FS/t/agent.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::agent;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/agent_type.t b/FS/t/agent_type.t
new file mode 100644
index 0000000..99c66a1
--- /dev/null
+++ b/FS/t/agent_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::agent_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill.t b/FS/t/cust_bill.t
new file mode 100644
index 0000000..b43f08e
--- /dev/null
+++ b/FS/t/cust_bill.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_event.t b/FS/t/cust_bill_event.t
new file mode 100644
index 0000000..0e2ca3e
--- /dev/null
+++ b/FS/t/cust_bill_event.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_event;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pay.t b/FS/t/cust_bill_pay.t
new file mode 100644
index 0000000..001eed0
--- /dev/null
+++ b/FS/t/cust_bill_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pkg.t b/FS/t/cust_bill_pkg.t
new file mode 100644
index 0000000..0e45bdb
--- /dev/null
+++ b/FS/t/cust_bill_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_bill_pkg_detail.t b/FS/t/cust_bill_pkg_detail.t
new file mode 100644
index 0000000..ea6e3d1
--- /dev/null
+++ b/FS/t/cust_bill_pkg_detail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_detail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_credit.t b/FS/t/cust_credit.t
new file mode 100644
index 0000000..cddf75c
--- /dev/null
+++ b/FS/t/cust_credit.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_credit;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_credit_bill.t b/FS/t/cust_credit_bill.t
new file mode 100644
index 0000000..0ef54c3
--- /dev/null
+++ b/FS/t/cust_credit_bill.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_credit_bill;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_credit_refund.t b/FS/t/cust_credit_refund.t
new file mode 100644
index 0000000..6b2b599
--- /dev/null
+++ b/FS/t/cust_credit_refund.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_credit_refund;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_main.t b/FS/t/cust_main.t
new file mode 100644
index 0000000..b0ffbdb
--- /dev/null
+++ b/FS/t/cust_main.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_main_county.t b/FS/t/cust_main_county.t
new file mode 100644
index 0000000..dd61199
--- /dev/null
+++ b/FS/t/cust_main_county.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_county;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_main_invoice.t b/FS/t/cust_main_invoice.t
new file mode 100644
index 0000000..9661620
--- /dev/null
+++ b/FS/t/cust_main_invoice.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_invoice;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pay.t b/FS/t/cust_pay.t
new file mode 100644
index 0000000..f6d0b75
--- /dev/null
+++ b/FS/t/cust_pay.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pay;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pay_batch.t b/FS/t/cust_pay_batch.t
new file mode 100644
index 0000000..02b572c
--- /dev/null
+++ b/FS/t/cust_pay_batch.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pay_batch;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pay_refund.t b/FS/t/cust_pay_refund.t
new file mode 100644
index 0000000..85d6c23
--- /dev/null
+++ b/FS/t/cust_pay_refund.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pay_refund;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pay_void.t b/FS/t/cust_pay_void.t
new file mode 100644
index 0000000..dca9bec
--- /dev/null
+++ b/FS/t/cust_pay_void.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pay_void;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_pkg.t b/FS/t/cust_pkg.t
new file mode 100644
index 0000000..c6a6860
--- /dev/null
+++ b/FS/t/cust_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_refund.t b/FS/t/cust_refund.t
new file mode 100644
index 0000000..91583da
--- /dev/null
+++ b/FS/t/cust_refund.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_refund;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_svc.t b/FS/t/cust_svc.t
new file mode 100644
index 0000000..267d731
--- /dev/null
+++ b/FS/t/cust_svc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_tax_exempt.pm b/FS/t/cust_tax_exempt.pm
new file mode 100644
index 0000000..8af13e3
--- /dev/null
+++ b/FS/t/cust_tax_exempt.pm
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_tax_exempt;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_tax_exempt.t b/FS/t/cust_tax_exempt.t
new file mode 100644
index 0000000..8af13e3
--- /dev/null
+++ b/FS/t/cust_tax_exempt.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_tax_exempt;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/domain_record.t b/FS/t/domain_record.t
new file mode 100644
index 0000000..794518c
--- /dev/null
+++ b/FS/t/domain_record.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::domain_record;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/export_svc.t b/FS/t/export_svc.t
new file mode 100644
index 0000000..773c5de
--- /dev/null
+++ b/FS/t/export_svc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::export_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/msgcat.t b/FS/t/msgcat.t
new file mode 100644
index 0000000..c38c639
--- /dev/null
+++ b/FS/t/msgcat.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::msgcat;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/nas.t b/FS/t/nas.t
new file mode 100644
index 0000000..6f8ae36
--- /dev/null
+++ b/FS/t/nas.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::nas;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_bill_event.t b/FS/t/part_bill_event.t
new file mode 100644
index 0000000..5626a9f
--- /dev/null
+++ b/FS/t/part_bill_event.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_bill_event;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-acct_sql.t b/FS/t/part_export-acct_sql.t
new file mode 100644
index 0000000..9eed472
--- /dev/null
+++ b/FS/t/part_export-acct_sql.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::acct_sql;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-apache.t b/FS/t/part_export-apache.t
new file mode 100644
index 0000000..b999508
--- /dev/null
+++ b/FS/t/part_export-apache.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::apache;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-bind.t b/FS/t/part_export-bind.t
new file mode 100644
index 0000000..d0c96be
--- /dev/null
+++ b/FS/t/part_export-bind.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::bind;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-bind_slave.t b/FS/t/part_export-bind_slave.t
new file mode 100644
index 0000000..c6a0386
--- /dev/null
+++ b/FS/t/part_export-bind_slave.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::bind_slave;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-bsdshell.t b/FS/t/part_export-bsdshell.t
new file mode 100644
index 0000000..eaf417a
--- /dev/null
+++ b/FS/t/part_export-bsdshell.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::bsdshell;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-communigate_pro.t b/FS/t/part_export-communigate_pro.t
new file mode 100644
index 0000000..88b8b64
--- /dev/null
+++ b/FS/t/part_export-communigate_pro.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::communigate_pro;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-communigate_pro_singledomain.t b/FS/t/part_export-communigate_pro_singledomain.t
new file mode 100644
index 0000000..6f8a64e
--- /dev/null
+++ b/FS/t/part_export-communigate_pro_singledomain.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::communigate_pro_singledomain;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-cp.t b/FS/t/part_export-cp.t
new file mode 100644
index 0000000..bbefa6c
--- /dev/null
+++ b/FS/t/part_export-cp.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::cp;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-cyrus.t b/FS/t/part_export-cyrus.t
new file mode 100644
index 0000000..e0b3f35
--- /dev/null
+++ b/FS/t/part_export-cyrus.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::cyrus;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-domain_shellcommands.t b/FS/t/part_export-domain_shellcommands.t
new file mode 100644
index 0000000..a2a44fb
--- /dev/null
+++ b/FS/t/part_export-domain_shellcommands.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::domain_shellcommands;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-forward_shellcommands.t b/FS/t/part_export-forward_shellcommands.t
new file mode 100644
index 0000000..78ca68d
--- /dev/null
+++ b/FS/t/part_export-forward_shellcommands.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::forward_shellcommands;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-http.t b/FS/t/part_export-http.t
new file mode 100644
index 0000000..ea41b93
--- /dev/null
+++ b/FS/t/part_export-http.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::http;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-infostreet.t b/FS/t/part_export-infostreet.t
new file mode 100644
index 0000000..1b33418
--- /dev/null
+++ b/FS/t/part_export-infostreet.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::infostreet;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-ldap.t b/FS/t/part_export-ldap.t
new file mode 100644
index 0000000..826c341
--- /dev/null
+++ b/FS/t/part_export-ldap.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::ldap;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-null.t b/FS/t/part_export-null.t
new file mode 100644
index 0000000..055cdce
--- /dev/null
+++ b/FS/t/part_export-null.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::null;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-passwdfile.t b/FS/t/part_export-passwdfile.t
new file mode 100644
index 0000000..0f18f30
--- /dev/null
+++ b/FS/t/part_export-passwdfile.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::passwdfile;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-postfix.t b/FS/t/part_export-postfix.t
new file mode 100644
index 0000000..9518caa
--- /dev/null
+++ b/FS/t/part_export-postfix.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::postfix;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-router.t b/FS/t/part_export-router.t
new file mode 100644
index 0000000..54e4b63
--- /dev/null
+++ b/FS/t/part_export-router.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::router;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-shellcommands.t b/FS/t/part_export-shellcommands.t
new file mode 100644
index 0000000..7bb47d3
--- /dev/null
+++ b/FS/t/part_export-shellcommands.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::shellcommands;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-shellcommands_withdomain.t b/FS/t/part_export-shellcommands_withdomain.t
new file mode 100644
index 0000000..c0bd1bb
--- /dev/null
+++ b/FS/t/part_export-shellcommands_withdomain.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::shellcommands_withdomain;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-sqlmail.t b/FS/t/part_export-sqlmail.t
new file mode 100644
index 0000000..b048a75
--- /dev/null
+++ b/FS/t/part_export-sqlmail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::sqlmail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-sqlradius.t b/FS/t/part_export-sqlradius.t
new file mode 100644
index 0000000..5fb23a5
--- /dev/null
+++ b/FS/t/part_export-sqlradius.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::sqlradius;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-sqlradius_withdomain.t b/FS/t/part_export-sqlradius_withdomain.t
new file mode 100644
index 0000000..504bf67
--- /dev/null
+++ b/FS/t/part_export-sqlradius_withdomain.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::sqlradius_withdomain;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-sysvshell.t b/FS/t/part_export-sysvshell.t
new file mode 100644
index 0000000..7fc24ac
--- /dev/null
+++ b/FS/t/part_export-sysvshell.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::sysvshell;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-textradius.t b/FS/t/part_export-textradius.t
new file mode 100644
index 0000000..d8a48a0
--- /dev/null
+++ b/FS/t/part_export-textradius.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::textradius;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-vpopmail.t b/FS/t/part_export-vpopmail.t
new file mode 100644
index 0000000..2e37114
--- /dev/null
+++ b/FS/t/part_export-vpopmail.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::vpopmail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export-www_shellcommands.t b/FS/t/part_export-www_shellcommands.t
new file mode 100644
index 0000000..2ea79cf
--- /dev/null
+++ b/FS/t/part_export-www_shellcommands.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export::www_shellcommands;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export.t b/FS/t/part_export.t
new file mode 100644
index 0000000..26b3987
--- /dev/null
+++ b/FS/t/part_export.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_export_option.t b/FS/t/part_export_option.t
new file mode 100644
index 0000000..13200c2
--- /dev/null
+++ b/FS/t/part_export_option.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_export_option;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pkg.t b/FS/t/part_pkg.t
new file mode 100644
index 0000000..fd96073
--- /dev/null
+++ b/FS/t/part_pkg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pkg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_pop_local.t b/FS/t/part_pop_local.t
new file mode 100644
index 0000000..4e4ad17
--- /dev/null
+++ b/FS/t/part_pop_local.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_pop_local;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_referral.t b/FS/t/part_referral.t
new file mode 100644
index 0000000..d20b979
--- /dev/null
+++ b/FS/t/part_referral.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_referral;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_svc.t b/FS/t/part_svc.t
new file mode 100644
index 0000000..bdb2a7a
--- /dev/null
+++ b/FS/t/part_svc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/part_svc_column.t b/FS/t/part_svc_column.t
new file mode 100644
index 0000000..467025c
--- /dev/null
+++ b/FS/t/part_svc_column.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_svc_column;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/pkg_svc.t b/FS/t/pkg_svc.t
new file mode 100644
index 0000000..77d3429
--- /dev/null
+++ b/FS/t/pkg_svc.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::pkg_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/port.t b/FS/t/port.t
new file mode 100644
index 0000000..46377aa
--- /dev/null
+++ b/FS/t/port.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::port;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/prepay_credit.t b/FS/t/prepay_credit.t
new file mode 100644
index 0000000..e7626bd
--- /dev/null
+++ b/FS/t/prepay_credit.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::prepay_credit;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/queue.t b/FS/t/queue.t
new file mode 100644
index 0000000..43e3373
--- /dev/null
+++ b/FS/t/queue.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::queue;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/queue_arg.t b/FS/t/queue_arg.t
new file mode 100644
index 0000000..cf3f91d
--- /dev/null
+++ b/FS/t/queue_arg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::queue_arg;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/queue_depend.t b/FS/t/queue_depend.t
new file mode 100644
index 0000000..8eaa2cd
--- /dev/null
+++ b/FS/t/queue_depend.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::queue_depend;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/raddb.t b/FS/t/raddb.t
new file mode 100644
index 0000000..ac28d07
--- /dev/null
+++ b/FS/t/raddb.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::raddb;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/radius_usergroup.t b/FS/t/radius_usergroup.t
new file mode 100644
index 0000000..325742c
--- /dev/null
+++ b/FS/t/radius_usergroup.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::radius_usergroup;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/session.t b/FS/t/session.t
new file mode 100644
index 0000000..c4b714e
--- /dev/null
+++ b/FS/t/session.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::session;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_Common.t b/FS/t/svc_Common.t
new file mode 100644
index 0000000..ed49e1e
--- /dev/null
+++ b/FS/t/svc_Common.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_Common;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_acct.t b/FS/t/svc_acct.t
new file mode 100644
index 0000000..9ca78c9
--- /dev/null
+++ b/FS/t/svc_acct.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_acct;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_acct_pop.t b/FS/t/svc_acct_pop.t
new file mode 100644
index 0000000..e612c40
--- /dev/null
+++ b/FS/t/svc_acct_pop.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_acct_pop;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_broadband.t b/FS/t/svc_broadband.t
new file mode 100644
index 0000000..02dc112
--- /dev/null
+++ b/FS/t/svc_broadband.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_broadband;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_domain.t b/FS/t/svc_domain.t
new file mode 100644
index 0000000..4d91898
--- /dev/null
+++ b/FS/t/svc_domain.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_domain;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_external.t b/FS/t/svc_external.t
new file mode 100644
index 0000000..20a6767
--- /dev/null
+++ b/FS/t/svc_external.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_external;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_forward.t b/FS/t/svc_forward.t
new file mode 100644
index 0000000..d653d34
--- /dev/null
+++ b/FS/t/svc_forward.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_forward;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_www.t b/FS/t/svc_www.t
new file mode 100644
index 0000000..eb4e83f
--- /dev/null
+++ b/FS/t/svc_www.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_www;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/type_pkgs.t b/FS/t/type_pkgs.t
new file mode 100644
index 0000000..9840180
--- /dev/null
+++ b/FS/t/type_pkgs.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::type_pkgs;
+$loaded=1;
+print "ok 1\n";
diff --git a/GPL b/GPL
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/GPL
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..4b9b085
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1 @@
+See httemplate/docs/index.html
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..be53406
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,297 @@
+#!/usr/bin/make
+
+#solaris and perhaps other very weirdass /bin/sh
+#SHELL="/bin/ksh"
+
+DATASOURCE = DBI:Pg:dbname=freeside
+#DATASOURCE=DBI:mysql:freeside
+
+DB_USER = freeside
+DB_PASSWORD=
+
+#TEMPLATE = asp
+TEMPLATE = mason
+
+ASP_GLOBAL = /usr/local/etc/freeside/asp-global
+MASON_HANDLER = /usr/local/etc/freeside/handler.pl
+MASONDATA = /usr/local/etc/freeside/masondata
+
+#deb
+FREESIDE_DOCUMENT_ROOT = /var/www/freeside
+#redhat, fedora, mandrake
+#FREESIDE_DOCUMENT_ROOT = /var/www/html/freeside
+#freebsd
+#FREESIDE_DOCUMENT_ROOT = /usr/local/www/data/freeside
+#openbsd
+#FREESIDE_DOCUMENT_ROOT = /var/www/htdocs/freeside
+#suse
+#FREESIDE_DOCUMENT_ROOT = /srv/www/htdocs/freeside
+#apache
+#FREESIDE_DOCUMENT_ROOT = /usr/local/apache/htdocs/freeside
+
+#deb, redhat, fedora, mandrake, suse, others?
+INIT_FILE = /etc/init.d/freeside
+#freebsd
+#INIT_FILE = /usr/local/etc/rc.d/011.freeside.sh
+
+#deb, suse
+HTTPD_RESTART = /etc/init.d/apache restart
+#redhat, fedora, mandrake
+#HTTPD_RESTART = /etc/init.d/httpd restart
+#freebsd
+#HTTPD_RESTART = /usr/local/etc/rc.d/apache.sh stop; sleep 10; /usr/local/etc/rc.d/apache.sh start
+#openbsd
+#HTTPD_RESTART = kill -TERM `cat /var/www/logs/httpd.pid`; sleep 10; /usr/sbin/httpd -u -DSSL
+#apache
+#HTTPD_RESTART = /usr/local/apache/bin/apachectl stop; sleep 10; /usr/local/apache/bin/apachectl startssl
+
+FREESIDE_RESTART = ${INIT_FILE} restart
+
+#deb, redhat, fedora, mandrake, suse, others?
+INSTALLGROUP = root
+#freebsd, openbsd
+#INSTALLGROUP = wheel
+
+#edit the stuff below to have the daemons start
+
+QUEUED_USER=fs_queue
+
+#eventually this shouldn't be needed
+FREESIDE_PATH = `pwd`
+
+SELFSERVICE_USER = fs_selfservice
+#never run on the same machine in production!!!
+SELFSERVICE_MACHINES = localhost
+# SELFSERVICE_MACHINES = www.example.com
+# SELFSERVICE_MACHINES = web1.example.com web2.example.com
+
+#user with sudo access on SELFSERVICE_MACHINES for automated self-service
+#installation.
+SELFSERVICE_INSTALL_USER = ivan
+SELFSERVICE_INSTALL_USERADD = /usr/sbin/useradd
+#SELFSERVICE_INSTALL_USERADD = "/usr/sbin/pw useradd"
+
+RT_ENABLED = 0
+#RT_ENABLED = 1
+RT_DOMAIN = example.com
+RT_TIMEZONE = US/Pacific;
+#RT_TIMEZONE = US/Eastern;
+
+#---
+
+#not changable yet
+FREESIDE_CONF = /usr/local/etc/freeside
+#rt/config.layout.in
+RT_PATH = /opt/rt3
+
+VERSION=1.5.0pre5
+TAG=freeside_1_5_0pre5
+
+help:
+ @echo "supported targets: aspdocs masondocs alldocs docs install-docs"
+ @echo " htmlman"
+ @echo " perl-modules install-perl-modules"
+ @echo " install deploy"
+ @echo " create-database"
+ @echo " configure-rt create-rt"
+ @echo " clean"
+
+aspdocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* httemplate/*/*/*/*/*
+ rm -rf aspdocs
+ cp -pr httemplate aspdocs
+ perl -p -i -e "\
+ s/%%%VERSION%%%/${VERSION}/g;\
+ " aspdocs/index.html
+ touch aspdocs
+
+
+masondocs: htmlman httemplate/* httemplate/*/* httemplate/*/*/* httemplate/*/*/*/* httemplate/*/*/*/*/*
+ rm -rf masondocs
+ cp -pr httemplate masondocs
+ ( cd masondocs; \
+ ../bin/masonize; \
+ )
+ perl -p -i -e "\
+ s/%%%VERSION%%%/${VERSION}/g;\
+ " masondocs/index.html
+ touch masondocs
+
+alldocs: aspdocs masondocs
+
+docs:
+ make ${TEMPLATE}docs
+
+htmlman:
+ [ -e ./httemplate/docs/man ] || mkdir httemplate/docs/man
+ [ -e ./httemplate/docs/man/bin ] || mkdir httemplate/docs/man/bin
+ [ -e ./httemplate/docs/man/FS ] || mkdir httemplate/docs/man/FS
+ [ -e ./httemplate/docs/man/FS/UI ] || mkdir httemplate/docs/man/FS/UI
+ [ -e ./httemplate/docs/man/FS/part_export ] || mkdir httemplate/docs/man/FS/part_export
+ chmod a+rx bin/pod2x
+ [ -e DONT_REBUILD_DOCS ] || bin/pod2x
+
+forcehtmlman:
+ [ -e ./httemplate/docs/man ] || mkdir httemplate/docs/man
+ [ -e ./httemplate/docs/man/bin ] || mkdir httemplate/docs/man/bin
+ [ -e ./httemplate/docs/man/FS ] || mkdir httemplate/docs/man/FS
+ [ -e ./httemplate/docs/man/FS/UI ] || mkdir httemplate/docs/man/FS/UI
+ [ -e ./httemplate/docs/man/FS/part_export ] || mkdir httemplate/docs/man/FS/part_export
+ bin/pod2x
+
+install-docs: docs
+ [ -e ${FREESIDE_DOCUMENT_ROOT} ] && mv ${FREESIDE_DOCUMENT_ROOT} ${FREESIDE_DOCUMENT_ROOT}.`date +%Y%m%d%H%M%S` || true
+ cp -r ${TEMPLATE}docs ${FREESIDE_DOCUMENT_ROOT}
+ [ "${TEMPLATE}" = "asp" -a ! -e ${ASP_GLOBAL} ] && mkdir ${ASP_GLOBAL} || true
+ [ "${TEMPLATE}" = "asp" ] && chown -R freeside ${ASP_GLOBAL} || true
+ [ "${TEMPLATE}" = "asp" ] && cp htetc/global.asa ${ASP_GLOBAL} || true
+ [ "${TEMPLATE}" = "asp" ] && \
+ perl -p -i -e "\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+ " ${ASP_GLOBAL}/global.asa || true
+ [ "${TEMPLATE}" = "mason" ] && cp htetc/handler.pl ${MASON_HANDLER} || true
+ [ "${TEMPLATE}" = "mason" ] && \
+ perl -p -i -e "\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+ s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \
+ " ${MASON_HANDLER} || true
+ [ "${TEMPLATE}" = "mason" -a ! -e ${MASONDATA} ] && mkdir ${MASONDATA} || true
+ [ "${TEMPLATE}" = "mason" ] && chown -R freeside ${MASONDATA} || true
+
+perl-modules:
+ cd FS; \
+ [ -e Makefile ] || perl Makefile.PL; \
+ make
+
+install-perl-modules: perl-modules
+ cd FS; \
+ make install UNINST=1
+
+install-init:
+ #[ -e ${INIT_FILE} ] || install -o root -g ${INSTALLGROUP} -m 711 init.d/freeside-init ${INIT_FILE}
+ install -o root -g ${INSTALLGROUP} -m 711 init.d/freeside-init ${INIT_FILE}
+ perl -p -i -e "\
+ s/%%%QUEUED_USER%%%/${QUEUED_USER}/g;\
+ s'%%%FREESIDE_PATH%%%'${FREESIDE_PATH}'g;\
+ s/%%%SELFSERVICE_USER%%%/${SELFSERVICE_USER}/g;\
+ s/%%%SELFSERVICE_MACHINES%%%/${SELFSERVICE_MACHINES}/g;\
+ " ${INIT_FILE}
+
+install-selfservice:
+ [ -e ~freeside/.ssh/id_dsa.pub ] || su -c 'ssh-keygen -t dsa' - freeside
+ for MACHINE in ${SELFSERVICE_MACHINES}; do \
+ scp -r fs_selfservice/FS-SelfService ${SELFSERVICE_INSTALL_USER}@$$MACHINE:. ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "cd FS-SelfService; perl Makefile.PL && make" ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "cd FS-SelfService; sudo make install" ;\
+ scp ~freeside/.ssh/id_dsa.pub ${SELFSERVICE_INSTALL_USER}@$$MACHINE:. ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "sudo ${SELFSERVICE_INSTALL_USERADD} freeside; sudo install -D -o freeside -m 600 ./id_dsa.pub ~freeside/.ssh/authorized_keys" ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "sudo install -o freeside -d /usr/local/freeside" ;\
+ done
+
+update-selfservice:
+ for MACHINE in ${SELFSERVICE_MACHINES}; do \
+ RSYNC_RSH=ssh rsync -rlptz fs_selfservice/FS-SelfService/ ${SELFSERVICE_INSTALL_USER}@$$MACHINE:FS-SelfService ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "cd FS-SelfService; perl Makefile.PL && make" ;\
+ ssh ${SELFSERVICE_INSTALL_USER}@$$MACHINE "cd FS-SelfService; sudo make install" ;\
+ done
+
+install: install-perl-modules install-docs install-init install-rt
+
+deploy: install
+ ${HTTPD_RESTART}
+ ${FREESIDE_RESTART}
+
+create-database:
+ perl -e 'use DBIx::DataSource qw( create_database ); create_database( "${DATASOURCE}", "${DB_USER}", "${DB_PASSWORD}" ) or die $$DBIx::DataSource::errstr;'
+
+create-config: install-perl-modules
+ [ -e ${FREESIDE_CONF} ] && mv ${FREESIDE_CONF} ${FREESIDE_CONF}.`date +%Y%m%d%H%M%S` || true
+ install -d -o freeside ${FREESIDE_CONF}
+
+ touch ${FREESIDE_CONF}/secrets
+ chown freeside ${FREESIDE_CONF}/secrets
+ chmod 600 ${FREESIDE_CONF}/secrets
+
+ echo -e "${DATASOURCE}\n${DB_USER}\n${DB_PASSWORD}" >${FREESIDE_CONF}/secrets
+ chmod 600 ${FREESIDE_CONF}/secrets
+ chown freeside ${FREESIDE_CONF}/secrets
+
+ mkdir "${FREESIDE_CONF}/conf.${DATASOURCE}"
+ rm -rf conf/registries #old dirs just won't go away
+ #cp conf/[a-z]* "${FREESIDE_CONF}/conf.${DATASOURCE}"
+ cp `ls -d conf/[a-z]* | grep -v CVS` "${FREESIDE_CONF}/conf.${DATASOURCE}"
+ chown -R freeside "${FREESIDE_CONF}/conf.${DATASOURCE}"
+
+ mkdir "${FREESIDE_CONF}/counters.${DATASOURCE}"
+ chown freeside "${FREESIDE_CONF}/counters.${DATASOURCE}"
+
+ mkdir "${FREESIDE_CONF}/cache.${DATASOURCE}"
+ chown freeside "${FREESIDE_CONF}/cache.${DATASOURCE}"
+
+ mkdir "${FREESIDE_CONF}/export.${DATASOURCE}"
+ chown freeside "${FREESIDE_CONF}/export.${DATASOURCE}"
+
+configure-rt:
+ cd rt; \
+ cp config.layout.in config.layout; \
+ perl -p -i -e "\
+ s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g;\
+ s'%%%MASONDATA%%%'${MASONDATA}'g;\
+ " config.layout; \
+ ./configure --enable-layout=Freeside\
+ --with-db-type=Pg \
+ --with-db-dba=${DB_USER} \
+ --with-db-database=freeside \
+ --with-db-rt-user=${DB_USER} \
+ --with-db-rt-pass=${DB_PASSWORD} \
+ --with-web-user=freeside \
+ --with-web-group=freeside \
+ --with-rt-group=freeside
+
+create-rt: configure-rt
+ cd rt; make install
+ echo -e "${DB_PASSWORD}\n\\d sessions"\
+ | psql -U ${DB_USER} -W freeside 2>&1\
+ | grep '^Did not find'\
+ && rt/sbin/rt-setup-database --dba '${DB_USER}' \
+ --dba-password '${DB_PASSWORD}' \
+ --action schema \
+ || true
+ rt/sbin/rt-setup-database --action insert_initial \
+ && rt/sbin/rt-setup-database --action insert --datafile ${RT_PATH}/etc/initialdata \
+ || true
+ perl -p -i -e "\
+ s'%%%RT_DOMAIN%%%'${RT_DOMAIN}'g;\
+ s'%%%RT_TIMEZONE%%%'${RT_TIMEZONE}'g;\
+ " ${RT_PATH}/etc/RT_SiteConfig.pm
+
+install-rt:
+ [ ${RT_ENABLED} -eq 1 ] && ( cd rt; make install ) || true
+
+clean:
+ rm -rf aspdocs masondocs
+ cd FS; \
+ make clean
+
+#these are probably only useful if you're me...
+
+upload-docs: forcehtmlman
+ ssh pouncequick.420.am rm -rf /var/www/www.sisd.com/freeside/devdocs
+ scp -pr httemplate/docs pouncequick.420.am:/var/www/www.sisd.com/freeside/devdocs
+
+release: upload-docs
+ cd /home/ivan/freeside
+ #cvs tag ${TAG}
+ cvs tag -F ${TAG}
+
+ #cd /home/ivan
+ cvs export -r ${TAG} -d freeside-${VERSION} freeside
+ tar czvf freeside-${VERSION}.tar.gz freeside-${VERSION}
+
+ scp freeside-${VERSION}.tar.gz ivan@pouncequick.420.am:/var/www/sisd.420.am/freeside/
+ mv freeside-${VERSION} freeside-${VERSION}.tar.gz ..
+
+update-webdemo:
+ ssh ivan@pouncequick.420.am '( cd freeside; cvs update -d -P )'
+ #ssh root@pouncequick.420.am '( cd /home/ivan/freeside; make clean; make deploy )'
+ ssh root@pouncequick.420.am '( cd /home/ivan/freeside; make deploy )'
+
diff --git a/README b/README
new file mode 100644
index 0000000..1030b38
--- /dev/null
+++ b/README
@@ -0,0 +1,43 @@
+Freeside
+
+Copyright (C) 2000,2001,2002,2003 Ivan Kohler
+Copyright (C) 1999 Silicon Interactive Software Design
+All rights reserved
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of:
+
+ a) the GNU General Public License as published by the Free
+ Software Foundation; either version 2, or (at your option) any
+ later version
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with this program, in the file `GPL'; if not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite
+ 330, Boston, MA 02111-1307, USA.
+
+Freeside is a billing and administration package for Internet Service
+Providers.
+
+The Freeside home page is at `http://www.sisd.com/freeside'.
+
+The documentation is in `httemplate/docs'.
+
+A mailing list for users is available. Send a blank message to
+<ivan-freeside-subscribe@sisd.com> to subscribe.
+
+A mailing list for developers is available. It is intended to be lower volume
+and higher SNR than the users list. Send a blank message to
+<ivan-freeside-devel-subscribe@sisd.com> to subscribe.
+
+Commercial support is available from Ivan Kohler <ivan@sisd.com>. Requests for
+free support sent to me directly will be ignored. Please subscribe to the the
+user mailing list to request free support!
+
+Ivan Kohler <ivan-freeside_readme@420.am>
+
diff --git a/README.1.5.0pre6 b/README.1.5.0pre6
new file mode 100644
index 0000000..7e46264
--- /dev/null
+++ b/README.1.5.0pre6
@@ -0,0 +1,37 @@
+CREATE TABLE cust_pay_refund (
+ payrefundnum serial NOT NULL,
+ paynum int NOT NULL,
+ refundnum int NOT NULL,
+ _date int NOT NULL,
+ amount decimal(10,2) NOT NULL,
+ PRIMARY KEY (payrefundnum)
+);
+CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum);
+CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum);
+
+CREATE TABLE cust_pay_void (
+ paynum int NOT NULL,
+ custnum int NOT NULL,
+ paid decimal(10,2) NOT NULL,
+ _date int,
+ payby char(4) NOT NULL,
+ payinfo varchar(80),
+ paybatch varchar(80),
+ closed char(1),
+ void_date int,
+ reason varchar(80),
+ otaker varchar(32) NOT NULL,
+ PRIMARY KEY (paynum)
+);
+CREATE INDEX cust_pay_void1 ON cust_pay_void(custnum);
+
+alter table svc_external alter column id drop not null;
+alter table h_svc_external alter column id drop not null;
+
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' );
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' );
+
+dbdef-create username
+create-history-tables username cust_pay_refund cust_pay_void
+dbdef-create username
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..4c582c9
--- /dev/null
+++ b/TODO
@@ -0,0 +1,9 @@
+$Id: TODO,v 1.68 2002-02-16 18:14:23 ivan Exp $
+
+The TODO list / bug-tracking is now kept in a database. See
+http://pouncequick.420.am/rt/
+
+If you are interested in helping with any of these, please join the
+*development* mailing list (send a blank message to
+ivan-freeside-devel-subscribe@sisd.com) to avoid duplication of effort.
+
diff --git a/bin/apache.export b/bin/apache.export
new file mode 100755
index 0000000..47863a9
--- /dev/null
+++ b/bin/apache.export
@@ -0,0 +1,67 @@
+#!/usr/bin/perl -w
+
+use strict;
+#use File::Path;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::cust_svc;
+use FS::svc_www;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#needs the export number in there somewhere too...?
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/apache";
+mkdir $spooldir, 0700 unless -d $spooldir;
+
+my @exports = qsearch('part_export', { 'exporttype' => 'apache' } );
+
+my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+# dry_run => 1,
+});
+
+foreach my $export ( @exports ) {
+
+ my $machine = $export->machine;
+ my $file = "$spooldir/$machine.conf";
+
+ open(HTTPD_CONF,">$file") or die "can't open $file: $!";
+
+ my $template = $export->option('template');
+
+ my @svc_www = $export->svc_x;
+
+ foreach my $svc_www ( @svc_www ) {
+ use vars qw($zone $username);
+ $zone = $svc_www->domain_record->zone;
+ $username = $svc_www->svc_acct->username;
+ print HTTPD_CONF eval(qq("$template")). "\n\n";
+ }
+
+ my $user = $export->option('user');
+ my $httpd_conf = $export->option('httpd_conf');
+
+ $rsync->exec( {
+ src => $file,
+ dest => "$user\@$machine:$httpd_conf",
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+ # warn $rsync->out;
+
+ my $restart = $export->option('restart') || 'apachectl graceful';
+
+ ssh("root\@$machine", $restart);
+
+}
+
+close HTTPD_CONF;
+
+# -----
+
+sub usage {
+ die "Usage:\n apache.export user\n";
+}
+
diff --git a/bin/artera.import b/bin/artera.import
new file mode 100644
index 0000000..716ddda
--- /dev/null
+++ b/bin/artera.import
@@ -0,0 +1,75 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use Text::CSV_XS;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::svc_external;
+use FS::svc_domain;
+use FS::svc_acct;
+
+$FS::svc_Common::noexport_hack = 1;
+
+my $svcpart = 30;
+
+my $user = shift
+ or die 'Usage:\n\n artera.import user <artera_active_orders.csv';
+adminsuidsetup $user;
+
+##
+
+my $csv = new Text::CSV_XS;
+
+my $header = scalar(<>);
+
+my( $num, $linked ) = ( 0, 0 );
+
+while (<>) {
+ my $status = $csv->parse($_)
+ or die $csv->error_input;
+ my($serial, $keycode, $name, $ordernum, $email) = $csv->fields();
+ #warn join(" - ", $serial, $keycode, $name, $ordernum, $email ). "\n";
+
+ $email =~ /^([^@]+)\@([^@]+)$/
+ or die $email;
+ my($username, $domain) = ( $1, $2 );
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } );
+ my $cust_svc = '';
+ if ( $svc_domain ) {
+ my $svc_acct = qsearchs('svc_acct', {
+ 'username' => $username,
+ 'domsvc' => $svc_domain->svcnum,
+ } );
+ $cust_svc = $svc_acct->cust_svc
+ if $svc_acct;
+ #} else {
+ # warn "can't find domain $domain\n";
+ }
+
+ my $exist = qsearchs('svc_external', { 'id' => $serial } );
+ next if $exist;
+
+ my $svc_external = new FS::svc_external {
+ 'svcpart' => $svcpart,
+ 'pkgnum' => ( $cust_svc ? $cust_svc->pkgnum : '' ),
+ 'id' => $serial,
+ 'title' => $keycode,
+ };
+ #my $error = $svc_external->check;
+ my $error = $svc_external->insert;
+ if ( $cust_svc && $error =~ /^Already/ ) {
+ warn $error;
+ $svc_external->pkgnum('');
+ $error = $svc_external->insert;
+ }
+ warn $error if $error;
+
+ $num++;
+ $linked++ if $cust_svc;
+ #print "$num imported, $linked linked\n";
+
+}
+
+print "$num imported, $linked linked\n";
+
diff --git a/bin/bind.export b/bin/bind.export
new file mode 100755
index 0000000..d0b9379
--- /dev/null
+++ b/bin/bind.export
@@ -0,0 +1,191 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Path;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/bind";
+mkdir $spooldir, 0700 unless -d $spooldir;
+
+my @exports = qsearch('part_export', { 'exporttype' => 'bind' } );
+my @sexports = qsearch('part_export', { 'exporttype' => 'bind_slave' } );
+
+my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+# dry_run => 1,
+});
+
+foreach my $export ( @exports ) {
+
+ my $machine = $export->machine;
+ my $prefix = "$spooldir/$machine";
+
+ my $bind_rel = $export->option('bind_release');
+ my $ndc_cmd = $export->option('reload')
+ || ( ($bind_rel eq 'BIND9') ? 'rndc' : 'ndc' );
+ my $minttl = $export->option('bind9_minttl');
+
+ #prevent old domain files from piling up
+ #rmtree "$prefix" or die "can't rmtree $prefix.db: $!";
+
+ mkdir $prefix, 0700 unless -d $prefix;
+
+ open(NAMED_CONF,">$prefix/named.conf")
+ or die "can't open $prefix/named.conf: $!";
+
+ open(CONF_HEADER,"<$prefix/named.conf.HEADER")
+ or die "can't open $prefix/named.conf.HEADER: $!";
+ while (<CONF_HEADER>) { print NAMED_CONF $_; }
+ close CONF_HEADER;
+
+ my $zonepath = $export->option('zonepath');
+ $zonepath =~ s/\/$//;
+
+ my @svc_domain = $export->svc_x;
+
+ foreach my $svc_domain ( @svc_domain ) {
+ my $domain = $svc_domain->domain;
+ my @masters = qsearch('domain_record', {
+ 'svcnum' => $svc_domain->svcnum,
+ 'rectype' => '_mstr',
+ } );
+ if ( @masters ) {
+ my $masters = join('; ', map { $_->recdata } @masters );
+
+ print NAMED_CONF <<END;
+zone "$domain" {
+ type slave;
+ file "db.$domain";
+ masters { $masters; };
+};
+
+END
+
+ } else {
+
+ print NAMED_CONF <<END;
+zone "$domain" {
+ type master;
+ file "$zonepath/db.$domain";
+};
+
+END
+
+ open (DB_MASTER,">$prefix/db.$domain")
+ or die "can't open $prefix/db.$domain: $!";
+
+ if ($bind_rel eq 'BIND9') {
+ print DB_MASTER "\$TTL $minttl\n\$ORIGIN $domain.\n";
+ }
+
+ my @domain_records =
+ qsearch('domain_record', { 'svcnum' => $svc_domain->svcnum } );
+ foreach my $domain_record (
+ sort { $b->rectype cmp $a->rectype } @domain_records
+ ) {
+ #if ( $domain_record->rectype eq 'SOA' ) {
+ # print DB_MASTER join("\t", $domain_record-> reczone
+ #} else {
+ print DB_MASTER join("\t",
+ map { $domain_record->getfield($_) }
+ qw( reczone recaf rectype recdata )
+ ), "\n";
+ #}
+ }
+
+ close DB_MASTER;
+
+ }
+
+ }
+
+ $rsync->exec( {
+ src => "$prefix/",
+ recursive => 1,
+ dest => "root\@$machine:$zonepath/",
+ exclude => [qw( *.import named.conf.HEADER named.conf )],
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+ # warn $rsync->out;
+
+ $rsync->exec( {
+ src => "$prefix/named.conf",
+ dest => "root\@$machine:". $export->option('named_conf'),
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+# warn $rsync->out;
+
+ ssh("root\@$machine", "$ndc_cmd reload");
+
+}
+
+close NAMED_CONF;
+
+foreach my $sexport ( @sexports ) { #false laziness with above
+
+ my $machine = $sexport->machine;
+ my $prefix = "$spooldir/$machine";
+
+ my $bind_rel = $sexport->option('bind_release');
+ my $ndc_cmd = ($bind_rel eq 'BIND9') ? 'rndc' : 'ndc';
+
+ #prevent old domain files from piling up
+ #rmtree "$prefix" or die "can't rmtree $prefix.db: $!";
+
+ mkdir $prefix, 0700 unless -d $prefix;
+
+ open(NAMED_CONF,">$prefix/named.conf")
+ or die "can't open $prefix/named.conf: $!";
+
+ open(CONF_HEADER,"<$prefix/named.conf.HEADER")
+ or die "can't open $prefix/named.conf.HEADER: $!";
+ while (<CONF_HEADER>) { print NAMED_CONF $_; }
+ close CONF_HEADER;
+
+ my $masters = $sexport->option('master');
+
+ #false laziness with freeside-sqlradius-reset
+ my @svc_domain =
+ map { qsearchs('svc_domain', { 'svcnum' => $_->svcnum } ) }
+ map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+ $sexport->export_svc;
+
+ foreach my $svc_domain ( @svc_domain ) {
+ my $domain = $svc_domain->domain;
+ print NAMED_CONF <<END;
+zone "$domain" {
+ type slave;
+ file "db.$domain";
+ masters { $masters; };
+};
+
+END
+
+ }
+
+ $rsync->exec( {
+ src => "$prefix/named.conf",
+ dest => "root\@$machine:". $sexport->option('named_conf'),
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+# warn $rsync->out;
+
+ ssh("root\@$machine", "$ndc_cmd reload");
+
+}
+close NAMED_CONF;
+
+# -----
+
+sub usage {
+ die "Usage:\n bind.export user\n";
+}
+
diff --git a/bin/bind.import b/bin/bind.import
new file mode 100755
index 0000000..41313fb
--- /dev/null
+++ b/bin/bind.import
@@ -0,0 +1,214 @@
+#!/usr/bin/perl -w
+#
+# -s: import slave zones as master. useful if you need to recreate your
+# primary nameserver from a secondary
+# -c chroot_dir: import data from chrooted bind (corrects the path for
+# downloading zone files
+#
+# need to manually put header in
+# /usr/local/etc/freeside/export.<datasrc./bind/<machine>/named.conf.HEADER
+
+use strict;
+use vars qw( %d_part_svc );
+use Getopt::Std;
+use Term::Query qw(query);
+#use BIND::Conf_Parser;
+#use DNS::ZoneParse 0.81;
+
+#use Net::SCP qw(iscp);
+use Net::SCP qw(scp);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch); #qsearchs);
+#use FS::svc_acct_sm;
+use FS::svc_domain;
+use FS::domain_record;
+#use FS::svc_acct;
+#use FS::part_svc;
+
+use vars qw($opt_s $opt_c);
+getopts("sc:");
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::domain_record::noserial_hack = 1;
+
+use vars qw($spooldir);
+$spooldir = "/usr/local/etc/freeside/export.". datasrc. "/bind";
+mkdir $spooldir unless -d $spooldir;
+
+%d_part_svc =
+ map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_domain'});
+
+print "\n\n",
+ ( join "\n", map "$_: ".$d_part_svc{$_}->svc, sort keys %d_part_svc ),
+ "\n\n";
+use vars qw($domain_svcpart);
+$^W=0; #Term::Query isn't -w-safe
+$domain_svcpart =
+ query "Enter part number for domains: ", 'irk', [ keys %d_part_svc ];
+$^W=1;
+
+print "\n\n", <<END;
+Enter the location and name of your primary named.conf file, for example
+"ns.isp.com:/var/named/named.conf"
+END
+my($named_conf)=&getvalue(":");
+
+use vars qw($named_machine $prefix);
+$named_machine = (split(/:/, $named_conf))[0];
+$prefix = "$spooldir/$named_machine";
+mkdir $prefix unless -d $prefix;
+
+#iscp("root\@$named_conf","$prefix/named.conf.import");
+scp("root\@$named_conf","$prefix/named.conf.import");
+
+
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+##
+
+$FS::svc_domain::whois_hack=1;
+
+my $p = Parser->new;
+$p->parse_file("$prefix/named.conf.import");
+
+print "\nBIND import completed.\n";
+
+##
+
+sub usage {
+ die "Usage:\n\n bind.import user\n";
+}
+
+########
+BEGIN {
+
+ package Parser;
+ use BIND::Conf_Parser;
+ use vars qw(@ISA $named_dir);
+ @ISA = qw(BIND::Conf_Parser);
+
+ sub handle_option {
+ my($self, $option, $argument) = @_;
+ return unless $option eq "directory";
+ $named_dir = $argument;
+ }
+
+ sub handle_zone {
+ my($self, $name, $class, $type, $options) = @_;
+ return unless $class eq 'in';
+ return if grep { $name eq $_ } (qw(
+ . localhost 127.in-addr.arpa 0.in-addr.arpa 255.in-addr.arpa
+ 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa
+ 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.int
+ ));
+
+ use FS::Record qw(qsearchs);
+ use FS::svc_domain;
+
+ my $domain =
+ qsearchs('svc_domain', { 'domain' => $name } )
+ || new FS::svc_domain( {
+ svcpart => $main::domain_svcpart,
+ domain => $name,
+ action => 'N',
+ } );
+ unless ( $domain->svcnum ) {
+ my $error = $domain->insert;
+ die $error if $error;
+ }
+
+ if ( $type eq 'slave' && !$main::opt_s ) {
+
+ #use Data::Dumper;
+ #print Dumper($options);
+ #exit;
+
+ foreach my $master ( @{ $options->{masters} } ) {
+ my $domain_record = new FS::domain_record( {
+ 'svcnum' => $domain->svcnum,
+ 'reczone' => '@',
+ 'recaf' => 'IN',
+ 'rectype' => '_mstr',
+ 'recdata' => $master,
+ } );
+ my $error = $domain_record->insert;
+ die $error if $error;
+ }
+
+ } elsif ( $type eq 'master' || ( $type eq 'slave' && $main::opt_s ) ) {
+
+ my $file = $options->{file};
+
+ use File::Basename;
+ my $basefile = basename($file);
+ my $sourcefile = $file;
+ $sourcefile = "$named_dir/$sourcefile" unless $file =~ /^\//;
+ $sourcefile = "$main::opt_c/$sourcefile" if $main::opt_c;
+
+ use Net::SCP qw(iscp scp);
+ scp("root\@$main::named_machine:$sourcefile",
+ "$main::prefix/$basefile.import");
+
+ use DNS::ZoneParse 0.84;
+ my $zone = DNS::ZoneParse->new("$main::prefix/$basefile.import");
+
+ my $dump = $zone->dump;
+
+ #use Data::Dumper;
+ #print "$name: ". Dumper($dump);
+ #exit;
+
+ foreach my $rectype ( keys %$dump ) {
+ if ( $rectype =~ /^SOA$/i ) {
+ my $rec = $dump->{$rectype};
+ my $domain_record = new FS::domain_record( {
+ 'svcnum' => $domain->svcnum,
+ 'reczone' => $rec->{origin},
+ 'recaf' => 'IN',
+ 'rectype' => $rectype,
+ 'recdata' =>
+ $rec->{primary}. ' '. $rec->{email}. ' ( '.
+ join(' ', map $rec->{$_},
+ qw( serial refresh retry expire minimumTTL ) ).
+ ' )',
+ } );
+ my $error = $domain_record->insert;
+ die $error if $error;
+ } else {
+ #die $dump->{$rectype};
+ foreach my $rec ( @{ $dump->{$rectype} } ) {
+ my $domain_record = new FS::domain_record( {
+ 'svcnum' => $domain->svcnum,
+ 'reczone' => $rec->{name},
+ 'recaf' => $rec->{class},
+ 'rectype' => $rectype,
+ 'recdata' => ( $rectype =~ /^MX$/i
+ ? $rec->{priority}. ' '. $rec->{host}
+ : $rec->{host} ),
+ } );
+ my $error = $domain_record->insert;
+ die $error if $error;
+ }
+ }
+ }
+
+ #} else {
+ # die "unrecognized type $type\n";
+ }
+
+ }
+
+}
+#########
+
diff --git a/bin/bsdshell.export b/bin/bsdshell.export
new file mode 100755
index 0000000..6e0d103
--- /dev/null
+++ b/bin/bsdshell.export
@@ -0,0 +1,114 @@
+#!/usr/bin/perl -w
+
+# bsdshell export
+
+use strict;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::cust_svc;
+use FS::svc_acct;
+
+my @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc;
+#my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/shell";
+
+my @bsd_exports = qsearch('part_export', { 'exporttype' => 'bsdshell' } );
+
+my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+# dry_run => 1,
+});
+
+foreach my $export ( @bsd_exports ) {
+ my $machine = $export->machine;
+ my $prefix = "$spooldir/$machine";
+ mkdir $prefix, 0700 unless -d $prefix;
+
+ #LOCKING!!!
+
+ ( open(MASTER,">$prefix/master.passwd")
+ #!!! and flock(MASTER,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $prefix/master.passwd: $!";
+ ( open(PASSWD,">$prefix/passwd")
+ #!!! and flock(PASSWD,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $prefix/passwd: $!";
+
+ chmod 0644, "$prefix/passwd";
+ chmod 0600, "$prefix/master.passwd";
+
+ my @svc_acct = $export->svc_x;
+
+ next unless @svc_acct;
+
+ foreach my $svc_acct ( sort { $a->uid <=> $b->uid } @svc_acct ) {
+
+ my $password = $svc_acct->_password;
+ my $cpassword;
+ #if ( ( length($password) <= 8 )
+ if ( ( length($password) <= 12 )
+ && ( $password ne '*' )
+ && ( $password ne '!!' )
+ && ( $password ne '' )
+ ) {
+ $cpassword=crypt($password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ # MD5 !!!!
+ } else {
+ $cpassword=$password;
+ }
+
+ ###
+ # FORMAT OF THE PASSWD FILE HERE
+ print PASSWD join(":",
+ $svc_acct->username,
+ 'x', # "##". $username,
+ $svc_acct->uid,
+ $svc_acct->gid,
+ $svc_acct->finger,
+ $svc_acct->dir,
+ $svc_acct->shell,
+ ), "\n";
+
+ ###
+ # FORMAT OF FreeBSD MASTER PASSWD FILE HERE
+ print MASTER join(":",
+ $svc_acct->username, # User name
+ $cpassword, # Encrypted password
+ $svc_acct->uid, # User ID
+ $svc_acct->gid, # Group ID
+ "", # Login Class
+ "0", # Password Change Time
+ "0", # Password Expiration Time
+ $svc_acct->finger, # Users name
+ $svc_acct->dir, # Users home directory
+ $svc_acct->shell, # shell
+ ), "\n" ;
+
+ }
+
+ #!!! flock(MASTER,LOCK_UN);
+ #!!! flock(PASSWD,LOCK_UN);
+ close MASTER;
+ close PASSWD;
+
+ $rsync->exec( {
+ src => "$prefix/passwd",
+ dest => "root\@$machine:/etc/passwd"
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+
+ $rsync->exec( {
+ src => "$prefix/master.passwd",
+ dest => "root\@$machine:/etc/master.passwd.new"
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+ ssh("root\@$machine", "pwd_mkdb /etc/master.passwd.new");
+
+ # UNLOCK!!
+}
diff --git a/bin/create-fetchmailrc b/bin/create-fetchmailrc
new file mode 100644
index 0000000..11bde0c
--- /dev/null
+++ b/bin/create-fetchmailrc
@@ -0,0 +1,47 @@
+#!/usr/bin/perl -w
+# this quick hack helps you generate/maintain .fetchmailrc files from
+# FS::acct_snarf data. it is run from a shellcommands export as:
+# create-fetchmailrc $username $dir $snarf_machine1 $snarf_username1 $snarf__password1 $snarf_machine2 $snarf_username2 $snarf__password2 ...
+
+use strict;
+use POSIX qw( setuid setgid );
+
+my $header = <<END;
+# Configuration created by create-fetchmailrc
+set postmaster "postmaster"
+set bouncemail
+set no spambounce
+set properties ""
+set daemon 240
+END
+
+my $username = shift @ARGV or die "no username specified\n";
+my $homedir = shift @ARGV or die "no homedir specified\n";
+my $filename = "$homedir/.fetchmailrc";
+
+my $gid = scalar(getgrnam($username)) or die "can't find $username's gid\n";
+my $uid = scalar(getpwnam($username)) or die "can't find $username's uid\n";
+
+exit unless $ARGV[0];
+
+open(FETCHMAILRC, ">$filename") or die "can't open $filename: $!\n";
+chown $uid, $gid, $filename or die "can't chown $uid.$gid $filename: $!\n";
+chmod 0600, $filename or die "can't chmod 600 $filename: $!\n";
+print FETCHMAILRC $header;
+
+while ($ARGV[0]) {
+ my( $s_machine, $s_username, $s_password ) = splice( @ARGV, 0, 3 );
+ print FETCHMAILRC <<END;
+poll $s_machine
+ user '$s_username' there with password '$s_password' is '$username' here
+END
+}
+
+close FETCHMAILRC;
+
+setgid($gid) or die "can't setgid $gid\n";
+setuid($uid) or die "can't setuid $uid\n";
+$ENV{HOME} = $homedir;
+
+system(qq(fetchmail -a -K --antispam "550,451" -d 180 -f $filename));
+
diff --git a/bin/create-history-tables b/bin/create-history-tables
new file mode 100755
index 0000000..39248bf
--- /dev/null
+++ b/bin/create-history-tables
@@ -0,0 +1,93 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use DBI;
+use DBIx::DBSchema 0.21;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(dbdef);
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my $schema = dbdef();
+
+#false laziness w/fs-setup
+my @tables = scalar(@ARGV)
+ ? @ARGV
+ : grep { ! /^(h|pg)_/ } $schema->tables;
+foreach my $table ( @tables ) {
+ next if grep { /^h_$table/ } $schema->tables;
+ warn "creating history table for $table\n";
+ my $tableobj = $schema->table($table)
+ or die "unknown table $table (did you run dbdef-create?)\n";
+ my $h_tableobj = DBIx::DBSchema::Table->new( {
+ name => "h_$table",
+ primary_key => 'historynum',
+ unique => DBIx::DBSchema::ColGroup::Unique->new( [] ),
+ 'index' => DBIx::DBSchema::ColGroup::Index->new( [
+ @{$tableobj->unique->lol_ref},
+ @{$tableobj->index->lol_ref}
+ ] ),
+ columns => [
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'historynum',
+ 'type' => 'serial',
+ 'null' => 'NOT NULL',
+ 'length' => '',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_date',
+ 'type' => 'int',
+ 'null' => 'NULL',
+ 'length' => '',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_user',
+ 'type' => 'varchar',
+ 'null' => 'NOT NULL',
+ 'length' => '80',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ DBIx::DBSchema::Column->new( {
+ 'name' => 'history_action',
+ 'type' => 'varchar',
+ 'null' => 'NOT NULL',
+ 'length' => '80',
+ 'default' => '',
+ 'local' => '',
+ } ),
+ map {
+ my $column = $tableobj->column($_);
+ $column->type('int')
+ if $column->type eq 'serial';
+ $column->default('')
+ if $column->default =~ /^nextval\(/i;
+ ( my $local = $column->local ) =~ s/AUTO_INCREMENT//i;
+ $column->local($local);
+ $column;
+ } $tableobj->columns
+ ],
+ } );
+ foreach my $statement ( $h_tableobj->sql_create_table($dbh) ) {
+ $dbh->do( $statement )
+ or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement";
+ }
+
+}
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+sub usage {
+ die "Usage:\n create-history-tables user [ table table ... ] \n";
+}
+
diff --git a/bin/dbdef-create b/bin/dbdef-create
new file mode 100755
index 0000000..a449d67
--- /dev/null
+++ b/bin/dbdef-create
@@ -0,0 +1,24 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use DBI;
+use DBIx::DBSchema 0.22;
+use FS::UID qw(adminsuidsetup datasrc driver_name);
+
+my $user = shift or die &usage;
+
+my($dbh)=adminsuidsetup $user;
+
+#needs to match FS::Record
+my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc;
+
+my $dbdef = new_native DBIx::DBSchema $dbh;
+
+#print $dbdef->pretty_print;
+
+#important
+$dbdef->save($dbdef_file);
+
+sub usage {
+ die "Usage:\n dbdef-create user\n";
+}
diff --git a/bin/fix-sequences b/bin/fix-sequences
new file mode 100755
index 0000000..2ff89d3
--- /dev/null
+++ b/bin/fix-sequences
@@ -0,0 +1,69 @@
+#!/usr/bin/perl -Tw
+
+# run dbdef-create first!
+
+use strict;
+use DBI;
+use DBIx::DBSchema 0.21;
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+use FS::UID qw(adminsuidsetup driver_name);
+use FS::Record qw(dbdef);
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my $schema = dbdef();
+
+#false laziness w/fs-setup
+my @tables = scalar(@ARGV)
+ ? @ARGV
+ : grep { ! /^h_/ } $schema->tables;
+foreach my $table ( @tables ) {
+ my $tableobj = $schema->table($table)
+ or die "unknown table $table (did you run dbdef-create?)\n";
+
+ my $primary_key = $tableobj->primary_key;
+ next unless $primary_key;
+
+ my $col = $tableobj->column($primary_key);
+
+
+ next unless uc($col->type) eq 'SERIAL'
+ || ( driver_name eq 'Pg'
+ && defined($col->default)
+ && $col->default =~ /^nextval\(/i
+ )
+ || ( driver_name eq 'mysql'
+ && defined($col->local)
+ && $col->local =~ /AUTO_INCREMENT/i
+ );
+
+ my $seq = "${table}_${primary_key}_seq";
+ if ( driver_name eq 'Pg'
+ && defined($col->default)
+ && $col->default =~ /^nextval\('"(public\.)?(\w+_seq)"'::text\)$/
+ ) {
+ $seq = $2;
+ }
+
+ warn "fixing sequence for $table\n";
+
+
+ my $sql = "SELECT setval( '$seq',
+ ( SELECT max($primary_key) FROM $table ) );";
+
+ #warn $col->default. " $seq\n$sql\n";
+ $dbh->do( $sql ) or die $dbh->errstr;
+
+}
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+sub usage {
+ die "Usage:\n fix-sequences user [ table table ... ] \n";
+}
+
diff --git a/bin/freeside-init b/bin/freeside-init
new file mode 100755
index 0000000..fe12931
--- /dev/null
+++ b/bin/freeside-init
@@ -0,0 +1,60 @@
+#! /bin/sh
+#
+# start the freeside job queue daemon
+
+#PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/local/bin/freeside-queued
+NAME=freeside-queued
+DESC="freeside job queue daemon"
+USER="ivan"
+
+test -f $DAEMON || exit 0
+
+set -e
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+# start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid -b -m\
+# --exec $DAEMON
+ $DAEMON $USER &
+ echo "$NAME."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON
+ echo "$NAME."
+ rm /var/run/$NAME.pid
+ ;;
+ #reload)
+ #
+ # If the daemon can reload its config files on the fly
+ # for example by sending it SIGHUP, do it here.
+ #
+ # If the daemon responds to changes in its config file
+ # directly anyway, make this a do-nothing entry.
+ #
+ # echo "Reloading $DESC configuration files."
+ # start-stop-daemon --stop --signal 1 --quiet --pidfile \
+ # /var/run/$NAME.pid --exec $DAEMON
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented, move the "force-reload"
+ # option to the "reload" entry above. If not, "force-reload" is
+ # just the same as "restart".
+ #
+ $0 stop
+ sleep 1
+ $0 start
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $N {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/bin/freeside-session-kill b/bin/freeside-session-kill
new file mode 100755
index 0000000..d5fd703
--- /dev/null
+++ b/bin/freeside-session-kill
@@ -0,0 +1,103 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($conf);
+use Fcntl qw(:flock);
+use FS::UID qw(adminsuidsetup datasrc dbh);
+use FS::Record qw(dbdef qsearch fields);
+use FS::session;
+use FS::svc_acct;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $sessionlock = "/usr/local/etc/freeside/session-kill.lock.". datasrc;
+
+open(LOCK,"+>>$sessionlock") or die "Can't open $sessionlock: $!";
+select(LOCK); $|=1; select(STDOUT);
+unless ( flock(LOCK,LOCK_EX|LOCK_NB) ) {
+ seek(LOCK,0,0);
+ my($pid)=<LOCK>;
+ chop($pid);
+ #no reason to start loct of blocking processes
+ die "Is another session kill process running under pid $pid?\n";
+}
+seek(LOCK,0,0);
+print LOCK $$,"\n";
+
+$FS::UID::AutoCommit = 0;
+
+my $now = time;
+
+#uhhhhh
+
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table; #down this path lies madness
+use DBIx::DBSchema::Column;
+
+my $dbdef = dbdef or die;
+#warn $dbdef;
+#warn $dbdef->{'tables'};
+#warn keys %{$dbdef->{'tables'}};
+my $session_table = $dbdef->table('session') or die;
+my $svc_acct_table = $dbdef->table('svc_acct') or die;
+
+my $session_svc_acct = new DBIx::DBSchema::Table ( 'session,svc_acct', '', '', '',
+ map( DBIx::DBSchema::Column->new( "session.$_",
+ $session_table->column($_)->type,
+ $session_table->column($_)->null,
+ $session_table->column($_)->length,
+ ), $session_table->columns() ),
+ map( DBIx::DBSchema::Column->new( "svc_acct.$_",
+ $svc_acct_table->column($_)->type,
+ $svc_acct_table->column($_)->null,
+ $svc_acct_table->column($_)->length,
+ ), $svc_acct_table->columns ),
+# map("svc_acct.$_", $svc_acct_table->columns),
+);
+
+$dbdef->addtable($session_svc_acct); #madness, i tell you
+
+$FS::Record::DEBUG = 1;
+my @session = qsearch('session,svc_acct', {}, '', ' WHERE '. join(' AND ',
+ 'svc_acct.svcnum = session.svcnum',
+ '( session.logout IS NULL OR session.logout = 0 )',
+ "( $now - session.login ) >= svc_acct.seconds"
+). " FOR UPDATE" );
+
+my $dbh = dbh;
+
+foreach my $join ( @session ) {
+
+ my $session = new FS::session ( {
+ map { $_ => $join->{'Hash'}{"session.$_"} } fields('session')
+ } ); #see no evil
+
+ my $svc_acct = new FS::svc_acct ( {
+ map { $_ => $join->{'Hash'}{"svc_acct.$_"} } fields('svc_acct')
+ } );
+
+ #false laziness w/ fs_session_server
+ my $nsession = new FS::session ( { $session->hash } );
+ my $error = $nsession->replace($session);
+ if ( $error ) {
+ $dbh->rollback;
+ die $error;
+ }
+ my $time = $nsession->logout - $nsession->login;
+ my $new_svc_acct = new FS::svc_acct ( { $svc_acct->hash } );
+ my $seconds = $new_svc_acct->seconds;
+ $seconds -= $time;
+ $seconds = 0 if $seconds < 0;
+ $new_svc_acct->seconds( $seconds );
+ $error = $new_svc_acct->replace( $svc_acct );
+ warn "can't debit time from ". $svc_acct->username. ": $error\n"; #don't want to rollback, though
+ #ssenizal eslaf
+
+}
+
+$dbh->commit or die $dbh->errstr;
+
+sub usage {
+ die "Usage:\n\n freeside-session-kill user\n";
+}
diff --git a/bin/freeside.import b/bin/freeside.import
new file mode 100644
index 0000000..fdfcc08
--- /dev/null
+++ b/bin/freeside.import
@@ -0,0 +1,146 @@
+#!/usr/bin/perl -w
+
+use strict;
+use DBI;
+
+my $s_datasrc = 'DBI:mysql:host=ns1.enetonline.net;port=3307;user=ivan;dbname=freeside';
+my $s_dbuser = 'ivan';
+my $s_dbpass = '';
+
+my $d_datasrc = 'DBI:Pg:dbname=freeside';
+my $d_dbuser = 'freeside';
+my $d_dbpass = '';
+
+#my @tables = qw(
+#addr_block
+#agent
+#agent_type
+#cust_bill
+#cust_bill_event
+#cust_bill_pay
+#cust_bill_pkg
+#cust_bill_pkg_detail
+#cust_credit
+#cust_credit_bill
+#cust_credit_refund
+#cust_main
+#cust_main_county
+#cust_main_invoice
+#cust_pay
+#cust_pay_batch
+#cust_pkg
+#cust_refund
+#cust_svc
+#cust_tax_exempt
+#domain_record
+#export_svc
+#h_addr_block
+#h_agent
+#h_agent_type
+#h_cust_bill
+#h_cust_bill_event
+#h_cust_bill_pay
+#h_cust_bill_pkg
+#h_cust_bill_pkg_detail
+#h_cust_credit
+#h_cust_credit_bill
+#h_cust_credit_refund
+#h_cust_main
+#h_cust_main_county
+#h_cust_main_invoice
+#h_cust_pay
+#h_cust_pay_batch
+#h_cust_pkg
+#h_cust_refund
+#h_cust_svc
+#h_cust_tax_exempt
+#h_domain_record
+#h_export_svc
+#h_msgcat
+#h_nas
+#h_part_bill_event
+#h_part_export
+#h_part_export_option
+#h_part_pkg
+#h_part_pop_local
+#h_part_referral
+#h_part_svc
+#h_part_svc_column
+#h_part_svc_router
+#h_pkg_svc
+#h_port
+#h_prepay_credit
+#h_queue
+#h_queue_arg
+#h_queue_depend
+#h_radius_usergroup
+#h_router
+#h_router_field
+#h_sb_field
+#h_session
+#h_svc_acct
+#h_svc_acct_pop
+#h_svc_broadband
+#h_svc_domain
+#h_svc_forward
+#h_svc_www
+#h_type_pkgs
+#msgcat
+#nas
+#part_bill_event
+#part_export
+#part_export_option
+#part_pkg
+
+my @tables = qw(
+part_pop_local
+part_referral
+part_router_field
+part_sb_field
+part_svc
+part_svc_column
+part_svc_router
+pkg_svc
+port
+prepay_credit
+queue
+queue_arg
+queue_depend
+radius_usergroup
+router
+router_field
+sb_field
+session
+svc_acct
+svc_acct_pop
+svc_broadband
+svc_domain
+svc_forward
+svc_www
+type_pkgs
+);
+
+my $s_dbh = DBI->connect($s_datasrc, $s_dbuser, $s_dbpass) or die $DBI::errstr;
+my $d_dbh = DBI->connect($d_datasrc, $d_dbuser, $d_dbpass) or die $DBI::errstr;
+
+foreach my $table ( @tables ) {
+ $d_dbh->do("delete from $table");
+
+ my $s_sth = $s_dbh->prepare("select * from $table");
+ $s_sth->execute or die $s_sth->errstr;
+
+ my $row;
+ while ( $row = $s_sth->fetchrow_arrayref ) {
+ my $d_sth = $d_dbh->prepare(
+ "insert into $table ( ".
+ join(', ', @{$s_sth->{NAME}} ).
+ ' ) VALUES ( '.
+ join(', ', map { '?' } @{$s_sth->{NAME}} ).
+ ' )'
+ ) or die $d_dbh->errstr;
+
+ $d_sth->execute(@$row) or die $d_sth->errstr;
+
+ }
+}
+
diff --git a/bin/fs-migrate-part_svc b/bin/fs-migrate-part_svc
new file mode 100755
index 0000000..b0f3ac5
--- /dev/null
+++ b/bin/fs-migrate-part_svc
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch fields);
+use FS::part_svc;
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+foreach my $part_svc ( qsearch('part_svc', {} ) ) {
+ foreach my $field (
+ grep { defined($part_svc->getfield($part_svc->svcdb.'__'.$_.'_flag') ) }
+ fields($part_svc->svcdb)
+ ) {
+ my $flag = $part_svc->getfield($part_svc->svcdb.'__'.$field.'_flag');
+ if ( uc($flag) =~ /^([DF])$/ ) {
+ my $part_svc_column = new FS::part_svc_column {
+ 'svcpart' => $part_svc->svcpart,
+ 'columnname' => $field,
+ 'columnflag' => $1,
+ 'columnvalue' => $part_svc->getfield($part_svc->svcdb.'__'.$field),
+ };
+ my $error = $part_svc_column->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ die $error;
+ }
+ }
+ }
+}
+
+$dbh->commit or die $dbh->errstr;
+
+sub usage {
+ die "Usage:\n fs-migrate-part_svc user\n";
+}
+
diff --git a/bin/fs-migrate-payref b/bin/fs-migrate-payref
new file mode 100755
index 0000000..1584197
--- /dev/null
+++ b/bin/fs-migrate-payref
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_pay;
+use FS::cust_refund;
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+# apply payments to invoices
+
+foreach my $cust_pay ( qsearch('cust_pay', {} ) ) {
+ my $error = $cust_pay->upgrade_replace;
+ warn $error if $error;
+}
+
+# apply refunds to credits
+
+foreach my $cust_refund ( qsearch('cust_refund') ) {
+ my $error = $cust_refund->upgrade_replace;
+ warn $error if $error;
+}
+
+# ? apply credits to invoices
+
+sub usage {
+ die "Usage:\n fs-migrate-payref user\n";
+}
+
diff --git a/bin/fs-migrate-svc_acct_sm b/bin/fs-migrate-svc_acct_sm
new file mode 100755
index 0000000..e34b235
--- /dev/null
+++ b/bin/fs-migrate-svc_acct_sm
@@ -0,0 +1,229 @@
+#!/usr/bin/perl -Tw
+#
+# $Id: fs-migrate-svc_acct_sm,v 1.4 2002-06-21 09:13:16 ivan Exp $
+#
+# jeff@cmh.net 01-Jul-20
+
+#to delay loading dbdef until we're ready
+#BEGIN { $FS::Record::setup_hack = 1; }
+
+use strict;
+use Term::Query qw(query);
+#use DBI;
+#use DBIx::DBSchema;
+#use DBIx::DBSchema::Table;
+#use DBIx::DBSchema::Column;
+#use DBIx::DBSchema::ColGroup::Unique;
+#use DBIx::DBSchema::ColGroup::Index;
+use FS::Conf;
+use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_domain;
+use FS::svc_forward;
+use vars qw( $conf $old_default_domain %part_domain_svc %part_acct_svc %part_forward_svc $svc_acct $svc_acct_sm $error);
+
+die "Not running uid freeside!" unless checkeuid();
+
+my $user = shift or die &usage;
+getsecrets($user);
+
+$conf = new FS::Conf;
+$old_default_domain = $conf->config('domain');
+
+#needs to match FS::Record
+#my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc;
+
+###
+# This section would be the appropriate place to manipulate
+# the schema & tables.
+###
+
+## we need to add the domsvc to svc_acct
+## we must add a svc_forward record....
+## I am thinking that the fields svcnum (int), destsvc (int), and
+## dest (varchar (80)) are appropriate, with destsvc/dest an either/or
+## much in the spirit of cust_main_invoice
+
+###
+# massage the data
+###
+
+my($dbh)=adminsuidsetup $user;
+
+$|=1;
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+%part_domain_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_domain'});
+%part_acct_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+%part_forward_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_forward'});
+
+die "No services with svcdb svc_domain!\n" unless %part_domain_svc;
+die "No services with svcdb svc_acct!\n" unless %part_acct_svc;
+die "No services with svcdb svc_forward!\n" unless %part_forward_svc;
+
+my($svc_domain) = qsearchs('svc_domain', { 'domain' => $old_default_domain });
+if (! $svc_domain || $svc_domain->domain != $old_default_domain) {
+ print <<EOF;
+
+Your database currently does not contain a svc_domain record for the
+domain $old_default_domain. Would you like me to add one for you?
+EOF
+
+ my($response)=scalar(<STDIN>);
+ chop $response;
+ if ($response =~ /^[yY]/) {
+ print "\n\n", &menu_domain_svc, "\n", <<END;
+I need to create new domain accounts. Which service shall I use for that?
+END
+ my($domain_svcpart)=&getdomainpart;
+
+ $svc_domain = new FS::svc_domain {
+ 'domain' => $old_default_domain,
+ 'svcpart' => $domain_svcpart,
+ 'action' => 'M',
+ };
+# $error=$svc_domain->insert && die "Error adding domain $old_default_domain: $error";
+ $error=$svc_domain->insert;
+ die "Error adding domain $old_default_domain: $error" if $error;
+ }else{
+ print <<EOF;
+
+ This program cannot function properly until a svc_domain record matching
+your conf_dir/domain file exists.
+EOF
+
+ exit 1;
+ }
+}
+
+print "\n\n", &menu_acct_svc, "\n", <<END;
+I may need to create some new pop accounts and set up forwarding to them
+for some users. Which service shall I use for that?
+END
+my($pop_svcpart)=&getacctpart;
+
+print "\n\n", &menu_forward_svc, "\n", <<END;
+I may need to create some new forwarding for some users. Which service
+shall I use for that?
+END
+my($forward_svcpart)=&getforwardpart;
+
+sub menu_domain_svc {
+ ( join "\n", map "$_: ".$part_domain_svc{$_}->svc, sort keys %part_domain_svc ). "\n";
+}
+sub menu_acct_svc {
+ ( join "\n", map "$_: ".$part_acct_svc{$_}->svc, sort keys %part_acct_svc ). "\n";
+}
+sub menu_forward_svc {
+ ( join "\n", map "$_: ".$part_forward_svc{$_}->svc, sort keys %part_forward_svc ). "\n";
+}
+sub getdomainpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_domain_svc ];
+ $^W=1;
+ $return;
+}
+sub getacctpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_acct_svc ];
+ $^W=1;
+ $return;
+}
+sub getforwardpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_forward_svc ];
+ $^W=1;
+ $return;
+}
+
+
+#migrate data
+
+my(@svc_accts) = qsearch('svc_acct', {});
+foreach $svc_acct (@svc_accts) {
+ my(@svc_acct_sms) = qsearch('svc_acct_sm', {
+ domuid => $svc_acct->getfield('uid'),
+ }
+ );
+
+ # Ok.. we've got the svc_acct record, and an array of svc_acct_sm's
+ # What do we do from here?
+
+ # The intuitive:
+ # plop the svc_acct into the 'default domain'
+ # and then represent the svc_acct_sm's with svc_forwards
+ # they can be gussied up manually, later
+ #
+ # Perhaps better:
+ # when no svc_acct_sm exists, place svc_acct in 'default domain'
+ # when one svc_acct_sm exists, place svc_acct in corresponding
+ # domain & possibly create a svc_forward in 'default domain'
+ # when multiple svc_acct_sm's exists (in different domains) we'd
+ # better use the 'intuitive' approach.
+ #
+ # Specific way:
+ # as 'perhaps better,' but we may be able to guess which domain
+ # is correct by comparing the svcnum of domains to the username
+ # of the svc_acct
+ #
+
+ # The intuitive way:
+
+ my $def_acct = new FS::svc_acct ( { $svc_acct->hash } );
+ $def_acct->setfield('domsvc' => $svc_domain->getfield('svcnum'));
+ $error = $def_acct->replace($svc_acct);
+ die "Error replacing svc_acct for " . $def_acct->username . " : $error" if $error;
+
+ foreach $svc_acct_sm (@svc_acct_sms) {
+
+ my($domrec)=qsearchs('svc_domain', {
+ svcnum => $svc_acct_sm->getfield('domsvc'),
+ }) || die "svc_acct_sm references invalid domsvc $svc_acct_sm->getfield('domsvc')\n";
+
+ if ($svc_acct_sm->getfield('domuser') =~ /^\*$/) {
+
+ my($newdom) = new FS::svc_domain ( { $domrec->hash } );
+ $newdom->setfield('catchall', $svc_acct->svcnum);
+ $newdom->setfield('action', "M");
+ $error = $newdom->replace($domrec);
+ die "Error replacing svc_domain for (anything)@" . $domrec->domain . " : $error" if $error;
+
+ } else {
+
+ my($newacct) = new FS::svc_acct {
+ 'svcpart' => $pop_svcpart,
+ 'username' => $svc_acct_sm->getfield('domuser'),
+ 'domsvc' => $svc_acct_sm->getfield('domsvc'),
+ 'dir' => '/dev/null',
+ };
+ $error = $newacct->insert;
+ die "Error adding svc_acct for " . $newacct->username . " : $error" if $error;
+
+ my($newforward) = new FS::svc_forward {
+ 'svcpart' => $forward_svcpart,
+ 'srcsvc' => $newacct->getfield('svcnum'),
+ 'dstsvc' => $def_acct->getfield('svcnum'),
+ };
+ $error = $newforward->insert;
+ die "Error adding svc_forward for " . $newacct->username ." : $error" if $error;
+ }
+
+ $error = $svc_acct_sm->delete;
+ die "Error deleting svc_acct_sm for " . $svc_acct_sm->domuser ." : $error" if $error;
+
+ };
+
+};
+
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+print "svc_acct_sm records sucessfully migrated\n";
+
+sub usage {
+ die "Usage:\n fs-migrate-svc_acct_sm user\n";
+}
+
diff --git a/bin/fs-radius-add-check b/bin/fs-radius-add-check
new file mode 100755
index 0000000..4e4769e
--- /dev/null
+++ b/bin/fs-radius-add-check
@@ -0,0 +1,68 @@
+#!/usr/bin/perl -Tw
+
+# quick'n'dirty hack of fs-setup to add radius attributes
+
+use strict;
+use DBI;
+use FS::UID qw(adminsuidsetup checkeuid getsecrets);
+use FS::raddb;
+
+die "Not running uid freeside!" unless checkeuid();
+
+my %attrib2db =
+ map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
+
+my $user = shift or die &usage;
+getsecrets($user);
+
+my $dbh = adminsuidsetup $user;
+
+###
+
+print "\n\n", <<END, ":";
+Enter the additional RADIUS check attributes you need to track for
+each user, separated by whitespace.
+END
+my @attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
+ split(" ",&getvalue);
+
+sub getvalue {
+ my($x)=scalar(<STDIN>);
+ chop $x;
+ $x;
+}
+
+###
+
+my($char_d) = 80; #default maxlength for text fields
+
+###
+
+foreach my $attribute ( @attributes ) {
+
+ my $statement =
+ "ALTER TABLE svc_acct ADD COLUMN rc_$attribute varchar($char_d) NULL";
+ my $sth = $dbh->prepare( $statement )
+ or warn "Error preparing $statement: ". $dbh->errstr;
+ my $rc = $sth->execute
+ or warn "Error executing $statement: ". $sth->errstr;
+
+ $statement =
+ "ALTER TABLE h_svc_acct ADD COLUMN rc_$attribute varchar($char_d) NULL";
+ $sth = $dbh->prepare( $statement )
+ or warn "Error preparing $statement: ". $dbh->errstr;
+ $rc = $sth->execute
+ or warn "Error executing $statement: ". $sth->errstr;
+
+}
+
+$dbh->commit or die $dbh->errstr;
+
+$dbh->disconnect or die $dbh->errstr;
+
+print "\n\n", "Now you must run dbdef-create.\n\n";
+
+sub usage {
+ die "Usage:\n fs-radius-add-check user\n";
+}
+
diff --git a/bin/fs-radius-add-reply b/bin/fs-radius-add-reply
new file mode 100755
index 0000000..3de0137
--- /dev/null
+++ b/bin/fs-radius-add-reply
@@ -0,0 +1,69 @@
+#!/usr/bin/perl -Tw
+
+# quick'n'dirty hack of fs-setup to add radius attributes
+
+use strict;
+use DBI;
+use FS::UID qw(adminsuidsetup checkeuid getsecrets);
+use FS::raddb;
+
+die "Not running uid freeside!" unless checkeuid();
+
+my %attrib2db =
+ map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib;
+
+my $user = shift or die &usage;
+getsecrets($user);
+
+my $dbh = adminsuidsetup $user;
+
+###
+
+print "\n\n", <<END, ":";
+Enter the additional RADIUS reply attributes you need to track for
+each user, separated by whitespace.
+END
+my @attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; }
+ split(" ",&getvalue);
+
+sub getvalue {
+ my($x)=scalar(<STDIN>);
+ chop $x;
+ $x;
+}
+
+###
+
+my($char_d) = 80; #default maxlength for text fields
+
+###
+
+foreach my $attribute ( @attributes ) {
+
+ my $statement =
+ "ALTER TABLE svc_acct ADD COLUMN radius_$attribute varchar($char_d) NULL";
+ my $sth = $dbh->prepare( $statement )
+ or warn "Error preparing $statement: ". $dbh->errstr;
+ my $rc = $sth->execute
+ or warn "Error executing $statement: ". $sth->errstr;
+
+ $statement =
+ "ALTER TABLE h_svc_acct ADD COLUMN radius_$attribute varchar($char_d) NULL";
+ $sth = $dbh->prepare( $statement )
+ or warn "Error preparing $statement: ". $dbh->errstr;
+ $rc = $sth->execute
+ or warn "Error executing $statement: ". $sth->errstr;
+
+}
+
+$dbh->commit or die $dbh->errstr;
+
+$dbh->disconnect or die $dbh->errstr;
+
+print "\n\n", "Now you must run dbdef-create.\n\n";
+
+sub usage {
+ die "Usage:\n fs-radius-add-reply user\n";
+}
+
+
diff --git a/bin/generate-prepay b/bin/generate-prepay
new file mode 100755
index 0000000..cb4ba7f
--- /dev/null
+++ b/bin/generate-prepay
@@ -0,0 +1,35 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::prepay_credit;
+
+require 5.004; #srand(time|$$);
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user );
+
+my $amount = shift or die &usage;
+
+my $seconds = shift or die &usage;
+
+my $num_digits = shift or die &usage;
+
+my $num_entries = shift or die &usage;
+
+for ( 1 .. $num_entries ) {
+ my $identifier = join( '', map int(rand(10)), ( 1 .. $num_digits ) );
+ my $prepay_credit = new FS::prepay_credit {
+ 'identifier' => $identifier,
+ 'amount' => $amount,
+ 'seconds' => $seconds,
+ };
+ my $error = $prepay_credit->insert;
+ die $error if $error;
+ print "$identifier\n";
+}
+
+sub usage {
+ die "Usage:\n\n generate-prepay user amount seconds num_digits num_entries";
+}
+
diff --git a/bin/generate-raddb b/bin/generate-raddb
new file mode 100755
index 0000000..f946b05
--- /dev/null
+++ b/bin/generate-raddb
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+# usage: generate-raddb radius-server/raddb/dictionary* >raddb.pm
+# i.e.: generate-raddb ~/src/freeradius-0.2/raddb/dictionary* >FS/raddb.pm
+
+print <<END;
+package FS::raddb;
+use vars qw(%attrib);
+
+%attrib = (
+END
+
+while (<>) {
+ next if /^(#|\s*$|\$INCLUDE\s+)/;
+ next if /^(VALUE|VENDOR|BEGIN\-VENDOR|END\-VENDOR)\s+/;
+ /^(ATTRIBUTE|ATTRIB_NMC)\s+([\w\-\/]+)\s+/ or die $_;
+ $attrib = $2;
+ $dbname = lc($2);
+ $dbname =~ s/[\-\/]/_/g;
+ $dbname = substr($dbname,0,24);
+ while ( exists $hash{$dbname} ) {
+ #warn $dbname;
+ $dbname =~ s/(.)$//;
+ my $w = $1;
+ $w =~ tr/_a-z0-9/a-z0-9_/;
+ $dbname = "$dbname$w";
+ }
+ $hash{$dbname} = $attrib;
+ #print "$2\n";
+}
+
+foreach ( keys %hash ) {
+# print "$_\n" if length($_)>24;
+# print substr($_,0,24),"\n" if length($_)>24;
+# $max = length($_) if length($_)>$max;
+# have to fudge things since everything >24 is *not* unique
+
+ #print " '". substr($_,0,24). "' => '$hash{$_}',\n";
+ print " '$_' => '$hash{$_}',\n";
+}
+
+print <<END;
+);
+
+1;
+END
+
diff --git a/bin/generate-tests b/bin/generate-tests
new file mode 100755
index 0000000..73fd29e
--- /dev/null
+++ b/bin/generate-tests
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+@files = glob('FS/*.pm');
+foreach (@files) {
+# warn $_;
+ chomp;
+ s/^FS\///;
+ $f=$_;
+ $f=~s/pm$/t/;
+ $m=$_;
+ $m=~s/\.pm$//;
+ open(TEST,">t/$f");
+ print "t/$f\n";
+ print TEST
+ 'BEGIN { $| = 1; print "1..1\n" }'. "\n".
+ 'END {print "not ok 1\n" unless $loaded;}'. "\n".
+ "use FS::$m;\n".
+ '$loaded=1;'. "\n".
+ 'print "ok 1\n";'. "\n"
+ ;
+ close TEST;
+}
diff --git a/bin/ispman.ldap.import b/bin/ispman.ldap.import
new file mode 100755
index 0000000..7495f47
--- /dev/null
+++ b/bin/ispman.ldap.import
@@ -0,0 +1,114 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Net::LDAP::LDIF;
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::svc_domain;
+use FS::svc_acct;
+
+my $user = shift or die;
+adminsuidsetup($user);
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+my $domain_svcpart = 1;
+my $account_svcpart = 2;
+my $mailbox_svcpart = 3;
+my $fedweeknet_svcpart = 4;
+
+#my $ldif =
+# Net::LDAP::LDIF->new( "ispman-06-23-04.ldif", "r", onerror => 'undef' );
+my $ldif =
+ Net::LDAP::LDIF->new( "ispman-06-23-04.ldif", "r", onerror => 'warn' );
+
+#my %objectclass;
+
+my $acct = 0;
+my $imported = 0;
+
+my $entry;
+while ( $entry = $ldif->read_entry ) {
+ #warn "$entry\n";
+ my %attributes = map { $_ => [ $entry->get_value( $_ ) ] } $entry->attributes;
+
+ my $objectclass = join('/', @{$attributes{'objectclass'}} );
+
+ next unless $objectclass eq 'posixAccount/ispmanDomainUser/radiusprofile';
+
+ foreach my $attr ( keys %attributes ) {
+ print join( " => ", substr($attr.' 'x30,0,30), @{$attributes{ $attr }} ), "\n";
+ #if ( $attr eq 'objectclass' ) {
+ # $objectclass{ join('/', @{$attributes{$attr}} ) }++;
+ #}
+ }
+ print "\n";
+
+ $acct++;
+
+ my $email = $attributes{'maillocaladdress'}->[0];
+ $email =~ /^(\w+)\@([\w\.\-]+)$/ or die $email;
+ die "$1 ne ". $attributes{'ispmanuserid'}->[0]. "\n"
+ unless lc($1) eq $attributes{'ispmanuserid'}->[0];
+ my $username = lc($1);
+ my $domain = lc($2);
+
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ || new FS::svc_domain { 'svcpart' => $domain_svcpart,
+ 'domain' => $domain,
+ 'action' => 'N',
+ };
+
+ unless ( $svc_domain->svcnum ) {
+ my $error = $svc_domain->insert;
+ if ( $error ) {
+ die "inserting domain: $error\n";
+ }
+ }
+
+ ( my $password = $attributes{'userpassword'}->[0] ) =~ s/^\{crypt\}//;
+
+ # pick svcpart
+ my $svcpart = $account_svcpart;
+ if ( $domain eq 'fedweeknet.com' ) {
+ $svcpart = $fedweeknet_svcpart;
+ } elsif ( $attributes{'dialupaccess'}->[0] =~ /(false|no)/i ) {
+ $svcpart = $mailbox_svcpart;
+ }
+
+ my $dir = $attributes{'homedirectory'}->[0];
+ $dir =~ s/\s+//g;
+ $dir =~ s/\@/_/;
+
+ my $svc_acct = new FS::svc_acct {
+ 'svcpart' => $svcpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'finger' => $attributes{'cn'}->[0],
+ 'domsvc' => $svc_domain->svcnum,
+ 'shell' => $attributes{'loginshell'}->[0],
+ 'uid' => $attributes{'uidnumber'}->[0],
+ 'gid' => $attributes{'gidnumber'}->[0],
+ 'dir' => $dir,
+ 'quota' => $attributes{'mailquota'}->[0],
+ };
+ my $error = $svc_acct->insert;
+ #my $error = $svc_acct->check;
+
+ if ( $error ) {
+ warn "$error\n";
+ } else {
+ $imported++;
+ }
+
+}
+
+print "$imported of $acct imported\n";
+
+#print "\n\n";
+
+#foreach ( sort { $objectclass{$b} <=> $objectclass{$a} } keys %objectclass ) {
+# print "$objectclass{$_}: $_\n";
+#}
diff --git a/bin/masonize b/bin/masonize
new file mode 100755
index 0000000..169ba71
--- /dev/null
+++ b/bin/masonize
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+
+foreach $file ( split(/\n/, `find . -depth -print`) ) {
+ next unless $file =~ /(cgi|html)$/;
+ open(F,$file) or die "can't open $file for reading: $!";
+ @file = <F>;
+ #print "$file ". scalar(@file). "\n";
+ close $file;
+ $newline = ''; #avoid prepending extraneous newlines
+ $all = join('',@file);
+
+ $w = '';
+
+ $mode = 'html';
+ while ( length($all) ) {
+
+ if ( $mode eq 'html' ) {
+
+ if ( $all =~ /^(.+?)(<%=?.*)$/s && $1 !~ /<%/s ) {
+ $w .= $1;
+ $all = $2;
+ next;
+ } elsif ( $all =~ /^<%=(.*)$/s ) {
+ $w .= '<%';
+ $all = $1;
+ $mode = 'perlv';
+ #die;
+ next;
+ } elsif ( $all =~ /^<%(.*)$/s ) {
+ $w .= $newline; $newline = "\n";
+ $all = $1;
+ $mode = 'perlc';
+
+ #avoid newline prepend fix from borking indented first <%
+ $w =~ s/\n\s+\z/\n/;
+ $w .= "\n" if $w =~ /.+\z/;
+
+ next;
+ } elsif ( $all !~ /<%/s ) {
+ $w .= $all;
+ last;
+ } else {
+ warn length($all); die;
+ }
+ die;
+
+ } elsif ( $mode eq 'perlv' ) {
+
+ if ( $all =~ /^(.*?%>)(.*)$/s ) {
+ $w .= $1;
+ $all=$2;
+ $mode = 'html';
+ next;
+ }
+ die 'unterminated <%= ???';
+
+ } elsif ( $mode eq 'perlc' ) {
+
+ if ( $all =~ /^([^\n]*?)%>(.*)$/s ) {
+ $w .= "%$1\n";
+ $all=$2;
+ $mode='html';
+ next;
+ }
+ if ( $all =~ /^([^\n]*)\n(.*)$/s ) {
+ $w .= "%$1\n";
+ $all=$2;
+ next;
+ }
+
+ } else { die };
+
+ }
+
+ system("chmod u+w $file");
+ select W; $| = 1; select STDOUT;
+ open(W,">$file") or die "can't open $file for writing: $!";
+ print W $w;
+ close W;
+}
diff --git a/bin/passwd.import b/bin/passwd.import
new file mode 100755
index 0000000..093f8ba
--- /dev/null
+++ b/bin/passwd.import
@@ -0,0 +1,120 @@
+#!/usr/bin/perl -Tw
+# $Id: passwd.import,v 1.8 2003-06-12 14:08:00 ivan Exp $
+
+use strict;
+use vars qw(%part_svc);
+use Date::Parse;
+use Term::Query qw(query);
+use Net::SCP qw(iscp);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+my($spooldir)="/usr/local/etc/freeside/export.". datasrc;
+
+#$FS::svc_acct::nossh_hack = 1;
+$FS::svc_Common::noexport_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number to import.
+END
+my($shell_svcpart)=&getpart;
+
+print "\n\n", <<END;
+Enter the location and name of your _user_ passwd file, for example
+"mail.isp.com:/etc/passwd" or "nis.isp.com:/etc/global/passwd"
+END
+my($loc_passwd)=&getvalue(":");
+iscp("root\@$loc_passwd", "$spooldir/passwd.import");
+
+print "\n\n", <<END;
+Enter the location and name of your _user_ shadow file, for example
+"mail.isp.com:/etc/shadow" or "bsd.isp.com:/etc/master.passwd"
+END
+my($loc_shadow)=&getvalue(":");
+iscp("root\@$loc_shadow", "$spooldir/shadow.import");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+open(PASSWD,"<$spooldir/passwd.import");
+open(SHADOW,"<$spooldir/shadow.import");
+
+my(%password);
+while (<SHADOW>) {
+ chop;
+ my($username,$password)=split(/:/);
+ #$password =~ s/^\!$/\*/;
+ #$password =~ s/\!+/\*SUSPENDED\* /;
+ $password{$username}=$password;
+}
+
+while (<PASSWD>) {
+ chop;
+ my($username,$x,$uid,$gid,$finger,$dir,$shell) = split(/:/);
+ my $password = $password{$username};
+
+ my $svcpart = $shell_svcpart;
+
+ #if ( qsearchs('svc_acct', { 'username' => $username } ) ) {
+ # warn "warning: $username already exists; skipping\n";
+ # next;
+ #}
+
+ my($svc_acct) = new FS::svc_acct ({
+ 'svcpart' => $svcpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'uid' => $uid,
+ 'gid' => $gid,
+ 'finger' => $finger,
+ 'dir' => $dir,
+ 'shell' => $shell,
+ #%{$allparam{$username}},
+ });
+ my($error);
+ $error=$svc_acct->insert;
+ if ( $error ) {
+ if ( $error =~ /duplicate/i ) {
+ warn "$username: $error";
+ } else {
+ die "$username: $error";
+ }
+ }
+
+}
+
+sub usage {
+ die "Usage:\n\n passwd.import user\n";
+}
+
diff --git a/bin/pod2x b/bin/pod2x
new file mode 100755
index 0000000..46ccc77
--- /dev/null
+++ b/bin/pod2x
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+#use Pod::Text;
+#$Pod::Text::termcap=1;
+
+my $site_perl = "./FS";
+#my $catman = "./catman";
+#my $catman = "./htdocs/docs/man";
+#my $html = "./htdocs/docs/man";
+my $html = "./httemplate/docs/man";
+
+$|=1;
+
+die "Can't find $site_perl" unless -d $site_perl;
+#die "Can't find $catman" unless -d $catman;
+die "Can't find $html" unless -d $html;
+
+#make some useless links
+foreach my $file (
+ glob("$site_perl/bin/freeside-*"),
+) {
+ next if $file =~ /\.pod$/;
+ #symlink $file, "$file.pod"; # or die "link $file to $file.pod: $!";
+ system("cp $file $file.pod");
+}
+
+foreach my $file (
+ glob("$site_perl/*.pm"),
+ glob("$site_perl/*/*.pm"),
+ glob("$site_perl/*/*/*.pm"),
+ glob("$site_perl/bin/*.pod"),
+ glob("./fs_sesmon/FS-SessionClient/*.pm"),
+ glob("./fs_signup/FS-SignupClient/*.pm"),
+ glob("./fs_selfadmin/FS-MailAdminServer/*.pm"),
+) {
+ next if $file =~ /(^|\/)blib\//;
+ #$file =~ /\/([\w\-]+)\.pm$/ or die "oops file $file";
+ my $name;
+ if ( $file =~ /fs_\w+\/FS\-\w+\/(.*)\.pm$/ ) {
+ $name = "FS/$1";
+ } elsif ( $file =~ /$site_perl\/(.*)\.(pm|pod)$/ ) {
+ $name = $1;
+ } else {
+ die "oops file $file";
+ }
+ print "$name\n";
+ my $htmlroot = join('/', map '..',1..(scalar($file =~ tr/\///)-2)) || '.';
+# system "pod2text $file >$catman/$name.txt";
+ system "pod2html --podroot=$site_perl --podpath=./FS:./FS/UI:.:./bin --norecurse --htmlroot=$htmlroot $file >$html/$name.html";
+ #system "pod2html --podroot=$site_perl --htmlroot=$htmlroot $file >$html/$name.html";
+# system "pod2html $file >$html/$name.html";
+}
+
+#remove the useless links
+unlink glob("$site_perl/bin/*.pod");
+
diff --git a/bin/populate-msgcat b/bin/populate-msgcat
new file mode 100755
index 0000000..adac92d
--- /dev/null
+++ b/bin/populate-msgcat
@@ -0,0 +1,135 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::msgcat;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+foreach my $del_msgcat ( qsearch('msgcat', {}) ) {
+ my $error = $del_msgcat->delete;
+ die $error if $error;
+}
+
+my %messages = messages();
+
+foreach my $msgcode ( keys %messages ) {
+ foreach my $locale ( keys %{$messages{$msgcode}} ) {
+ my $msgcat = new FS::msgcat( {
+ 'msgcode' => $msgcode,
+ 'locale' => $locale,
+ 'msg' => $messages{$msgcode}{$locale},
+ });
+ my $error = $msgcat->insert;
+ die $error if $error;
+ }
+}
+
+#print "Message catalog initialized sucessfully\n";
+
+sub messages {
+
+ # 'msgcode' => {
+ # 'en_US' => 'Message',
+ # },
+
+ (
+
+ 'passwords_dont_match' => {
+ 'en_US' => "Passwords don't match",
+ },
+
+ 'invalid_card' => {
+ 'en_US' => 'Invalid credit card number',
+ },
+
+ 'unknown_card_type' => {
+ 'en_US' => 'Unknown card type',
+ },
+
+ 'not_a' => {
+ 'en_US' => 'Not a ',
+ },
+
+ 'empty_password' => {
+ 'en_US' => 'Empty password',
+ },
+
+ 'no_access_number_selected' => {
+ 'en_US' => 'No access number selected',
+ },
+
+ 'illegal_text' => {
+ 'en_US' => 'Illegal (text)',
+ #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field',
+ },
+
+ 'illegal_or_empty_text' => {
+ 'en_US' => 'Illegal or empty (text)',
+ #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field',
+ },
+
+ 'illegal_username' => {
+ 'en_US' => 'Illegal username',
+ },
+
+ 'illegal_password' => {
+ 'en_US' => 'Illegal password (',
+ },
+
+ 'illegal_password_characters' => {
+ 'en_US' => ' characters)',
+ },
+
+ 'username_in_use' => {
+ 'en_US' => 'Username in use',
+ },
+
+ 'illegal_email_invoice_address' => {
+ 'en_US' => 'Illegal email invoice address',
+ },
+
+ 'illegal_name' => {
+ 'en_US' => 'Illegal (name)',
+ #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field',
+ },
+
+ 'illegal_phone' => {
+ 'en_US' => 'Illegal (phone)',
+ #'en_US' => '',
+ },
+
+ 'illegal_zip' => {
+ 'en_US' => 'Illegal (zip)',
+ #'en_US' => '',
+ },
+
+ 'expired_card' => {
+ 'en_US' => 'Expired card',
+ },
+
+ 'daytime' => {
+ 'en_US' => 'Day Phone',
+ },
+
+ 'night' => {
+ 'en_US' => 'Night Phone',
+ },
+
+ 'svc_external-id' => {
+ 'en_US' => 'External ID',
+ },
+
+ 'svc_external-title' => {
+ 'en_US' => 'Title',
+ },
+
+ );
+}
+
+sub usage {
+ die "Usage:\n\n populate-msgcat user\n";
+}
+
diff --git a/bin/postfix.export b/bin/postfix.export
new file mode 100755
index 0000000..dbb08ce
--- /dev/null
+++ b/bin/postfix.export
@@ -0,0 +1,122 @@
+#!/usr/bin/perl -w
+
+use strict;
+#use File::Path;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch); # qsearchs);
+use FS::part_export;
+#use FS::cust_pkg;
+use FS::cust_svc;
+#use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/postfix";
+mkdir $spooldir, 0700 unless -d $spooldir;
+
+my @exports = qsearch('part_export', { 'exporttype' => 'postfix' } );
+
+my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+# dry_run => 1,
+});
+
+foreach my $export ( @exports ) {
+
+ my $machine = $export->machine;
+ my $prefix = "$spooldir/$machine";
+ mkdir $prefix, 0700 unless -d $prefix;
+
+ #construct %domain hash
+
+ my $mydomain = $export->option('mydomain');
+ my %domain;
+ foreach my $svc_forward ( $export->svc_x ) {
+
+ my( $username, $domain );
+ my $srcsvc_acct = $svc_forward->srcsvc_acct;
+ if ( $srcsvc_acct ) {
+ ( $username, $domain ) = ( $srcsvc_acct->username, $srcsvc_acct->domain );
+ } elsif ( $svc_forward->src =~ /([^@]*)\@([^@]+)$/ ) {
+ ( $username, $domain ) = ( $1, $2 );
+ } else {
+ die "bad svc_forward record? svcnum ". $svc_forward->svcnum. "\n";
+ }
+
+ my( $dusername, $ddomain );
+ my $dstsvc_acct = $svc_forward->dstsvc_acct;
+ if ( $dstsvc_acct ) {
+ $dusername = $dstsvc_acct->username;
+ $ddomain = $dstsvc_acct->domain;
+ } elsif ( $svc_forward->dst =~ /([^@]+)\@([^@]+)$/ ) {
+ ( $dusername, $ddomain ) = ( $1, $2 );
+ } else {
+ die "bad svc_forward record? svcnum ". $svc_forward->svcnum. "\n";
+ }
+ my $dest;
+ if ( $ddomain eq $mydomain ) {
+ $dest = $dusername;
+ } else {
+ $dest = "$dusername\@$ddomain";
+ }
+
+ push @{$domain{$domain}{$username}}, $dest;
+
+ }
+
+ #write aliases
+
+ my $aliases = delete $domain{$mydomain};
+ open(ALIASES, ">$prefix/aliases") or die "can't open $prefix/aliases: $!";
+ foreach my $alias ( keys %$aliases ) {
+ print ALIASES "$alias: ". join(',', @{ $aliases->{$alias} } ). "\n";
+ }
+ close ALIASES;
+
+ #write virtual
+
+ open(VIRTUAL, ">$prefix/virtual") or die "can't open $prefix/virtual: $!";
+ foreach my $domain ( keys %domain ) {
+ print VIRTUAL "$domain DOMAIN\n";
+ #foreach my $virtual ( sort { $a ne '' <=> $b ne '' } keys %{$domain{$domain}} ) {
+ foreach my $virtual ( sort { ( ($b ne '') <=> ($a ne '') ) || $a cmp $b } keys %{$domain{$domain}} ) {
+ print VIRTUAL "$virtual\@$domain ".
+ join(',', @{ $domain{$domain}{$virtual} } ). "\n";
+ }
+ print VIRTUAL "\n";
+ }
+ close VIRTUAL;
+
+ #rsync
+
+ my $user = $export->option('user');
+ $rsync->exec( {
+ src => "$prefix/aliases",
+ dest => "$user\@$machine:". $export->option('aliases'),
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+# warn $rsync->out;
+
+ ssh("$user\@$machine", $export->option('newaliases') || 'newaliases');
+# ssh("$user\@$machine", "postfix reload");
+
+ $rsync->exec( {
+ src => "$prefix/virtual",
+ dest => "$user\@$machine:". $export->option('virtual'),
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+# warn $rsync->out;
+ ssh("$user\@$machine", $export->option('postmap')
+ || 'postmap hash:/etc/postfix/virtual');
+ ssh("$user\@$machine", $export->option('reload') || 'postfix reload');
+
+}
+
+# -----
+
+sub usage {
+ die "Usage:\n postfix.export user\n";
+}
+
+
diff --git a/bin/postfix_courierimap.import b/bin/postfix_courierimap.import
new file mode 100755
index 0000000..12c138b
--- /dev/null
+++ b/bin/postfix_courierimap.import
@@ -0,0 +1,137 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw(%part_svc %domain_part_svc);
+#use Date::Parse;
+use DBI;
+use Term::Query qw(query);
+use FS::UID qw(adminsuidsetup); #datasrc
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number to import.
+END
+my $mailbox_svcpart = &getpart;
+
+%domain_part_svc = map { $_->svcpart, $_ }
+ qsearch('part_svc', { 'svcdb' => 'svc_domain'} );
+
+die "No services with svcdb svc_domain!\n" unless %domain_part_svc;
+
+print "\n\n", &menu_domain_svc, "\n", <<END;
+Enter part number for domains.
+END
+my $domain_svcpart = &getdomainpart;
+
+my $datasrc = &getvalue("\n\nEnter the DBI datasource:");
+my $db_user = &getvalue("\n\nEnter the database user:");
+my $db_pass = &getvalue("\n\nEnter the database password:");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub menu_domain_svc {
+ ( join "\n", map "$_: ".$domain_part_svc{$_}->svc, sort keys %domain_part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getdomainpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %domain_part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+my $dbh = DBI->connect( $datasrc, $db_user, $db_pass )
+ or die $DBI::errstr;
+
+my $sth = $dbh->prepare('SELECT username, password, crypt, name, domain FROM mailbox')
+ or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+
+my $row;
+while ( defined ( $row = $sth->fetchrow_arrayref ) ) {
+ my( $r_username, $password, $crypt, $finger, $r_domain ) = @$row;
+
+ my( $username, $domain );
+ if ( $r_username =~ /^([^@]+)\@([^@]+)$/ ) {
+ $username = $1;
+ $domain = $2;
+ } else {
+ $username = $r_username;
+ $domain = $r_domain;
+ }
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ || new FS::svc_domain {
+ 'domain' => $domain,
+ 'svcpart' => $domain_svcpart,
+ 'action' => 'N',
+ };
+ unless ( $svc_domain->svcnum ) {
+ my $error = $svc_domain->insert;
+ if ( $error ) {
+ die "can't insert domain $domain: $error\n";
+ }
+ }
+
+ $password = $crypt if $password eq '*CRYPTED*';
+
+ $finger =~ s/Outdoor Power.*$/Outdoor Power/;
+
+ my $svc_acct = new FS::svc_acct {
+ 'svcpart' => $mailbox_svcpart,
+ 'username' => $username,
+ 'domsvc' => $svc_domain->svcnum,
+ '_password' => $password,
+ 'finger' => $finger,
+ };
+
+ my $error = $svc_acct->insert;
+ #my $error = $svc_acct->check;
+ if ( $error ) {
+ if ( $error =~ /duplicate/i ) {
+ warn "$r_username / $r_domain: $error";
+ } else {
+ die "$r_username / $r_domain: $error";
+ }
+ }
+
+}
+
+sub usage {
+ die "Usage:\n\n postfix_courierimap.import user\n";
+}
+
+
diff --git a/bin/sendmail.import b/bin/sendmail.import
new file mode 100644
index 0000000..ef745fc
--- /dev/null
+++ b/bin/sendmail.import
@@ -0,0 +1,178 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Term::Query qw(query);
+use Net::SCP qw(iscp);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+##use FS::svc_acct_sm;
+#use FS::svc_domain;
+#use FS::domain_record;
+use FS::svc_acct;
+##use FS::part_svc;
+use FS::svc_forward;
+use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#$FS::svc_Common::noexport_hack = 1;
+#$FS::domain_record::noserial_hack = 1;
+
+use vars qw($defaultdomain);
+$defaultdomain = '295.ca';
+
+use vars qw(@svcpart $forward_svcpart);
+@svcpart = qw( 2 4 );
+$forward_svcpart = 7;
+
+use vars qw($spooldir);
+$spooldir = "/usr/local/etc/freeside/export.". datasrc. "/sendmail";
+mkdir($spooldir, 0755) unless -d $spooldir;
+
+print "\n\n", <<END;
+Enter the location and name of your Sendmail aliases file, for example
+"mail.isp.com:/etc/mail/aliases"
+END
+my($aliases)=&getvalue(":");
+
+use vars qw($aliases_machine $aliases_prefix);
+$aliases_machine = (split(/:/, $aliases))[0];
+$aliases_prefix = "$spooldir/$aliases_machine";
+mkdir($aliases_prefix, 0755) unless -d $aliases_prefix;
+
+#iscp("root\@$aliases","$aliases_prefix/aliases.import");
+iscp("ivan\@$aliases","$aliases_prefix/aliases.import");
+
+print "\n\n", <<END;
+Enter the location and name of your Sendmail virtusertable directory, for example
+"mail.isp.com:/etc/mail/virtusertable"
+END
+my($virtusertable)=&getvalue(":");
+
+use vars qw($virtusertable_machine $virtusertable_prefix);
+$virtusertable_machine = (split(/:/, $virtusertable))[0];
+$virtusertable_prefix = "$spooldir/$virtusertable_machine";
+mkdir($virtusertable_prefix, 0755) unless -d $virtusertable_prefix;
+mkdir("$virtusertable_prefix/virtusertable.import", 0755)
+ unless -d "$virtusertable_prefix/virtusertable.import";
+
+#iscp("root\@$virtusertable/*","$aliases_prefix/virtusertable.import/");
+iscp("ivan\@$virtusertable/*","$aliases_prefix/virtusertable.import/");
+
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+##
+
+foreach my $file (
+ "$aliases_prefix/aliases.import",
+ glob("$aliases_prefix/virtusertable.import/*"),
+) {
+
+ warn "importing $file\n";
+
+ open(FILE,"<$file") or die $!;
+ while (<FILE>) {
+ next if /^\s*#/ || /^\s*$/; #skip comments & blank lines
+
+ unless ( /^([\w\@\.\-]+)[:\s]\s*(.*\S)\s*$/ ) {
+ warn "Unparsable line: $_";
+ next;
+ }
+ my($rawusername, $rawdest) = ($1, $2);
+
+ my($username, $domain);
+ if ( $rawusername =~ /^([\w\-\.\&]*)\@([\w\.\-]+)$/ ) {
+ $username = $1;
+ $domain = $2;
+ } elsif ( $rawusername =~ /\@/ ) {
+ die "Unparsable username: $rawusername\n";
+ } else {
+ $username = $rawusername;
+ $domain = $defaultdomain;
+ }
+
+ #find svc_acct record or set $src
+ my($srcsvc, $src) = &svcnum_or_literal($username, $domain);
+
+ foreach my $dest ( split(/,/, $rawdest) ) {
+
+ my($dusername, $ddomain);
+ if ( $dest =~ /^([\w\-\.\&]+)\@([\w\.\-]+)$/ ) {
+ $dusername = $1;
+ $ddomain = $2;
+ } elsif ( $dest =~ /\@/ ) {
+ die "Unparsable username: $dest\n";
+ } else {
+ $dusername = $dest;
+ $ddomain = $defaultdomain;
+ }
+ my($dstsvc, $dst) = &svcnum_or_literal($dusername, $ddomain);
+
+ my $svc_forward = new FS::svc_forward ({
+ svcpart => $forward_svcpart,
+ srcsvc => $srcsvc,
+ src => $src,
+ dstsvc => $dstsvc,
+ dst => $dst,
+ });
+ my $error = $svc_forward->insert;
+ #my $error = $svc_forward->check;
+ if ( $error ) {
+ die "$rawusername: $rawdest: $error\n";
+ }
+ }
+
+
+ } #next entry
+
+} #next file
+
+##
+
+sub svcnum_or_literal {
+ my($username, $domain) = @_;
+
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } );
+ my $domsvc = $svc_domain ? $svc_domain->svcnum : '';
+
+ my @svc_acct = grep { my $svc_acct = $_;
+ grep { $svc_acct->cust_svc->svcpart == $_ } @svcpart
+ }
+ qsearch('svc_acct', {
+ 'username' => $username,
+ 'domsvc' => $domsvc,
+ });
+
+ if ( scalar(@svc_acct) > 1 ) {
+ die "multiple sources found for $username\@$domain !\n";
+ }
+
+ my( $svcnum, $literal ) = ('', '');
+ if ( @svc_acct ) {
+ my $svc_acct = $svc_acct[0];
+ $svcnum = $svc_acct->svcnum;
+ } else {
+ $literal = "$username\@$domain";
+ }
+
+ return( $svcnum, $literal );
+
+}
+
+sub usage {
+ die "Usage:\n\n sendmail.import user\n";
+}
+
+
+
+
+
diff --git a/bin/sequences.reset b/bin/sequences.reset
new file mode 100644
index 0000000..2dc1d3b
--- /dev/null
+++ b/bin/sequences.reset
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(dbdef dbh);
+
+my $user = shift;
+adminsuidsetup $user or die;
+
+foreach my $table ( dbdef->tables ) {
+ my $primary_key = dbdef->table($table)->primary_key;
+ next unless $primary_key;
+ #my $local = dbdef->table($table)->column($primary_key)->local;
+ ##next unless $default =~ /nextval/;
+ #print "$local\n";
+
+ my $statement = "select setval('${table}_${primary_key}_seq', ( select max($primary_key) from $table ) )";
+
+ print "$statement;\n";
+ next;
+
+ my $sth = dbh->prepare($statement) or do {
+ warn dbh->errstr. " preparing $statement\n";
+ next;
+ };
+ $sth->execute or do {
+ warn dbh->errstr. " executing $statement\n";
+ dbh->commit;
+ next;
+ }
+
+}
+
diff --git a/bin/shadow.reimport b/bin/shadow.reimport
new file mode 100755
index 0000000..7957011
--- /dev/null
+++ b/bin/shadow.reimport
@@ -0,0 +1,125 @@
+#!/usr/bin/perl -w
+#
+# -d: dry-run: make no changes
+# -r: replace: overwrite existing passwords (otherwise only "*" passwords will
+# be changed)
+# -b: blowfish replace: overwrite existing passwords only if they are
+# blowfish-encrypted
+
+use strict;
+use vars qw(%part_svc);
+use Getopt::Std;
+use Term::Query qw(query);
+use Net::SCP qw(iscp);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+
+use vars qw($opt_d $opt_r $opt_b);
+getopts("drb");
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+my($spooldir)="/usr/local/etc/freeside/export.". datasrc;
+
+#$FS::svc_acct::nossh_hack = 1;
+$FS::svc_Common::noexport_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number or part numbers to import.
+END
+my($shell_svcpart)=&getvalue;
+my @shell_svcpart = split(/[,\s]+/, $shell_svcpart);
+
+print "\n\n", <<END;
+Enter the location and name of your _user_ shadow file, for example
+"mail.isp.com:/etc/shadow" or "bsd.isp.com:/etc/master.passwd"
+END
+my($loc_shadow)=&getvalue(":");
+iscp("root\@$loc_shadow", "$spooldir/shadow.import");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+open(SHADOW,"<$spooldir/shadow.import");
+
+my($line, $updated);
+while (<SHADOW>) {
+ $line++;
+ chop;
+ my($username,$password)=split(/:/);
+
+# my @svc_acct = grep { $_->cust_svc->svcpart == $shell_svcpart }
+# qsearch('svc_acct', { 'username' => $username } );
+ my @svc_acct = grep {
+ my $svcpart = $_->cust_svc->svcpart;
+ grep { $_ == $svcpart } @shell_svcpart;
+ } qsearch('svc_acct', { 'username' => $username } );
+
+ next unless @svc_acct;
+
+ if ( scalar(@svc_acct) > 1 ) {
+ die "more than one $username found!\n";
+ next;
+ }
+
+ my $svc_acct = shift @svc_acct;
+
+ next unless $svc_acct->_password eq '*'
+ || $opt_r
+ || ( $opt_b && $svc_acct->_password =~ /^\$2a?\$/ );
+
+ next if $svc_acct->username eq 'root';
+
+ next if $password eq 'NP' || $password eq '*LK*';
+
+ next if $svc_acct->_password eq $password;
+ next if $svc_acct->_password =~ /^\*SUSPENDED\*/;
+
+ my $new_svc_acct = new FS::svc_acct( { $svc_acct->hash } );
+ $new_svc_acct->_password($password);
+ #warn "$username: ". $svc_acct->_password. " -> $password\n";
+ warn "changing password for $username\n";
+ unless ( $opt_d ) {
+ my $error = $new_svc_acct->replace($svc_acct);
+ die "$username: $error" if $error;
+ }
+
+ $updated++;
+
+}
+
+warn "$updated of $line passwords changed\n";
+
+sub usage {
+ die "Usage:\n\n shadow.reimport [ -d ] [ -r ] user\n";
+}
+
diff --git a/bin/sqlradius-norealm.reimport b/bin/sqlradius-norealm.reimport
new file mode 100755
index 0000000..b7d0166
--- /dev/null
+++ b/bin/sqlradius-norealm.reimport
@@ -0,0 +1,113 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw(%part_svc);
+#use Date::Parse;
+use DBI;
+use Term::Query qw(query);
+use FS::UID qw(adminsuidsetup); #datasrc
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+$FS::svc_Common::noexport_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number to import.
+END
+my $sqlradius_svcpart = &getpart;
+
+my $datasrc = &getvalue("\n\nEnter the DBI datasource:");
+my $db_user = &getvalue("\n\nEnter the database user:");
+my $db_pass = &getvalue("\n\nEnter the database password:");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+my $dbh = DBI->connect( $datasrc, $db_user, $db_pass )
+ or die $DBI::errstr;
+
+my $sth = $dbh->prepare('SELECT DISTINCT UserName FROM radcheck')
+ or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+
+my $row;
+while ( defined ( $row = $sth->fetchrow_arrayref ) ) {
+ my( $username ) = @$row;
+
+ my( $password, $group ) = ( '', '', '' );
+
+ my $rc_sth = $dbh->prepare(
+ 'SELECT Attribute, Value'.
+ ' FROM radcheck'.
+ ' WHERE UserName = ?'
+ ) or die $dbh->errstr;
+ $rc_sth->execute($username) or die $rc_sth->errstr;
+
+ foreach my $rc_row ( @{$rc_sth->fetchall_arrayref} ) {
+ my($attribute, $value) = @$rc_row;
+ if ( $attribute =~ /^((Crypt|User)-)?Password$/ ) {
+ $password = $value unless $password && !$1;
+ } else {
+ #handle other params!
+ }
+ }
+
+ my @svc_acct = grep { $_->cust_svc->svcpart == $sqlradius_svcpart }
+ qsearch('svc_acct', { 'username' => $username, } );
+
+ #print "$r_username / $realm: $password / $finger: ";
+ print "$username: $password: ";
+ if ( scalar(@svc_acct) == 0 ) {
+ print "not found\n";
+ next;
+ } elsif ( scalar(@svc_acct) > 1 ) {
+ print "multiple matches found?!?!\n";
+ next;
+ } else {
+ #print "correcting password and name\n";
+ print "correcting password\n";
+ }
+
+ my $svc_acct = $svc_acct[0];
+ #my $new = new FS::svc_acct { $svc_acct->hash, '_password' => $password, 'finger' => $finger };
+ my $new = new FS::svc_acct { $svc_acct->hash, '_password' => $password };
+ my $error = $new->replace($svc_acct);
+ #my $error = $new->check;
+ die "$username: $error" if $error;
+
+}
+
+sub usage {
+ die "Usage:\n\n sqlradius-norealm.reimport user\n";
+}
+
diff --git a/bin/sqlradius.import b/bin/sqlradius.import
new file mode 100644
index 0000000..e75f65b
--- /dev/null
+++ b/bin/sqlradius.import
@@ -0,0 +1,152 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw(%part_svc %domain_part_svc);
+#use Date::Parse;
+use DBI;
+use Term::Query qw(query);
+use FS::UID qw(adminsuidsetup); #datasrc
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number to import.
+END
+my $sqlradius_svcpart = &getpart;
+
+%domain_part_svc = map { $_->svcpart, $_ }
+ qsearch('part_svc', { 'svcdb' => 'svc_domain'} );
+
+die "No services with svcdb svc_domain!\n" unless %domain_part_svc;
+
+print "\n\n", &menu_domain_svc, "\n", <<END;
+Enter part number for domains.
+END
+my $domain_svcpart = &getdomainpart;
+
+my $datasrc = &getvalue("\n\nEnter the DBI datasource:");
+my $db_user = &getvalue("\n\nEnter the database user:");
+my $db_pass = &getvalue("\n\nEnter the database password:");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub menu_domain_svc {
+ ( join "\n", map "$_: ".$domain_part_svc{$_}->svc, sort keys %domain_part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getdomainpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %domain_part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+my $dbh = DBI->connect( $datasrc, $db_user, $db_pass )
+ or die $DBI::errstr;
+
+my $sth = $dbh->prepare('SELECT DISTINCT UserName, Realm FROM radcheck')
+ or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+
+my $row;
+while ( defined ( $row = $sth->fetchrow_arrayref ) ) {
+ my( $r_username, $realm ) = @$row;
+
+ my( $username, $domain );
+ if ( $r_username =~ /^([^@]+)\@([^@]+)$/ ) {
+ $username = $1;
+ $domain = $2;
+ } else {
+ $username = $r_username;
+ $domain = $realm;
+ }
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ || new FS::svc_domain {
+ 'domain' => $domain,
+ 'svcpart' => $domain_svcpart,
+ 'action' => 'N',
+ };
+ unless ( $svc_domain->svcnum ) {
+ my $error = $svc_domain->insert;
+ if ( $error ) {
+ die "can't insert domain $domain: $error\n";
+ }
+ }
+
+ my( $password, $finger, $group ) = ( '', '', '' );
+
+ my $rc_sth = $dbh->prepare(
+ 'SELECT Attribute, Value, Name, GroupName'.
+ ' FROM radcheck'.
+ ' WHERE UserName = ? and Realm = ?'
+ ) or die $dbh->errstr;
+ $rc_sth->execute($r_username, $realm) or die $rc_sth->errstr;
+
+ foreach my $rc_row ( @{$rc_sth->fetchall_arrayref} ) {
+ my($attribute, $value, $name, $groupname) = @$rc_row;
+ if ( $attribute =~ /^((User|Crypt)-)?Password$/ ) {
+ $password = $value;
+ $finger = $name;
+ $group = $groupname;
+ } else {
+ #handle other params!
+ }
+ }
+
+ my $svc_acct = new FS::svc_acct {
+ 'svcpart' => $sqlradius_svcpart,
+ 'username' => $username,
+ 'domsvc' => $svc_domain->svcnum,
+ '_password' => $password,
+ 'finger' => $finger,
+ };
+
+ my $error = $svc_acct->insert;
+ #my $error = $svc_acct->check;
+ if ( $error ) {
+ if ( $error =~ /duplicate/i ) {
+ warn "$r_username / $realm: $error";
+ } else {
+ die "$r_username / $realm: $error";
+ }
+ }
+
+}
+
+sub usage {
+ die "Usage:\n\n sqlradius.import user\n";
+}
+
diff --git a/bin/sqlradius.reimport b/bin/sqlradius.reimport
new file mode 100755
index 0000000..2218a3f
--- /dev/null
+++ b/bin/sqlradius.reimport
@@ -0,0 +1,160 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw(%part_svc %domain_part_svc);
+#use Date::Parse;
+use DBI;
+use Term::Query qw(query);
+use FS::UID qw(adminsuidsetup); #datasrc
+use FS::Record qw(qsearch qsearchs);
+use FS::svc_acct;
+use FS::part_svc;
+use FS::svc_domain;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+#push @FS::svc_acct::shells, qw(/bin/sync /sbin/shutdown /bin/halt /sbin/halt); #others?
+
+$FS::svc_Common::noexport_hack = 1;
+$FS::svc_domain::whois_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Enter part number to import.
+END
+my $sqlradius_svcpart = &getpart;
+
+%domain_part_svc = map { $_->svcpart, $_ }
+ qsearch('part_svc', { 'svcdb' => 'svc_domain'} );
+
+die "No services with svcdb svc_domain!\n" unless %domain_part_svc;
+
+print "\n\n", &menu_domain_svc, "\n", <<END;
+Enter part number for domains.
+END
+my $domain_svcpart = &getdomainpart;
+
+my $datasrc = &getvalue("\n\nEnter the DBI datasource:");
+my $db_user = &getvalue("\n\nEnter the database user:");
+my $db_pass = &getvalue("\n\nEnter the database password:");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub menu_domain_svc {
+ ( join "\n", map "$_: ".$domain_part_svc{$_}->svc, sort keys %domain_part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getdomainpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %domain_part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+my $dbh = DBI->connect( $datasrc, $db_user, $db_pass )
+ or die $DBI::errstr;
+
+my $sth = $dbh->prepare('SELECT DISTINCT UserName, Realm FROM radcheck')
+ or die $dbh->errstr;
+$sth->execute or die $sth->errstr;
+
+my $row;
+while ( defined ( $row = $sth->fetchrow_arrayref ) ) {
+ my( $r_username, $realm ) = @$row;
+
+ my( $username, $domain );
+ if ( $r_username =~ /^([^@]+)\@([^@]+)$/ ) {
+ $username = $1;
+ $domain = $2;
+ } else {
+ $username = $r_username;
+ $domain = $realm;
+ }
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
+ || new FS::svc_domain {
+ 'domain' => $domain,
+ 'svcpart' => $domain_svcpart,
+ 'action' => 'N',
+ };
+ unless ( $svc_domain->svcnum ) {
+ die "new domain? wtf";
+ my $error = $svc_domain->insert;
+ if ( $error ) {
+ die "can't insert domain $domain: $error\n";
+ }
+ }
+
+ #my( $password, $finger, $group ) = ( '', '', '' );
+ my( $password, $group ) = ( '', '', '' );
+
+ my $rc_sth = $dbh->prepare(
+ 'SELECT Attribute, Value, Name, GroupName'.
+ ' FROM radcheck'.
+ ' WHERE UserName = ? and Realm = ?'
+ ) or die $dbh->errstr;
+ $rc_sth->execute($r_username, $realm) or die $rc_sth->errstr;
+
+ foreach my $rc_row ( @{$rc_sth->fetchall_arrayref} ) {
+ my($attribute, $value, $name, $groupname) = @$rc_row;
+ if ( $attribute =~ /^((Crypt|User)-)?Password$/ ) {
+ $password = $value;
+ #$finger = $name;
+ $group = $groupname;
+ } else {
+ #handle other params!
+ }
+ }
+
+ my @svc_acct = grep { $_->cust_svc->svcpart == $sqlradius_svcpart }
+ qsearch('svc_acct', { 'username' => $username,
+ 'domsvc' => $svc_domain->svcnum, } );
+
+ #print "$r_username / $realm: $password / $finger: ";
+ print "$r_username / $realm: $password: ";
+ if ( scalar(@svc_acct) == 0 ) {
+ print "not found\n";
+ next;
+ } elsif ( scalar(@svc_acct) > 1 ) {
+ print "multiple matches found?!?!\n";
+ next;
+ } else {
+ #print "correcting password and name\n";
+ print "correcting password\n";
+ }
+
+ my $svc_acct = $svc_acct[0];
+ #my $new = new FS::svc_acct { $svc_acct->hash, '_password' => $password, 'finger' => $finger };
+ my $new = new FS::svc_acct { $svc_acct->hash, '_password' => $password };
+ my $error = $new->replace($svc_acct);
+ #my $error = $new->check;
+ die "$r_username / $realm: $error" if $error;
+
+}
+
+sub usage {
+ die "Usage:\n\n sqlradius.reimport user\n";
+}
+
diff --git a/bin/svc_acct.export b/bin/svc_acct.export
new file mode 100755
index 0000000..0bc370f
--- /dev/null
+++ b/bin/svc_acct.export
@@ -0,0 +1,641 @@
+#!/usr/bin/perl -w
+#
+# $Id: svc_acct.export,v 1.36 2002-05-16 14:28:35 ivan Exp $
+#
+# Create and export password, radius and vpopmail password files:
+# passwd, passwd.adjunct, shadow, acp_passwd, acp_userinfo, acp_dialup
+# users/assign, domains/vdomain/vpasswd
+# Also export sendmail and qmail config files.
+
+use strict;
+use vars qw($conf);
+use Fcntl qw(:flock);
+use File::Path;
+use IO::Handle;
+use FS::Conf;
+use Net::SSH qw(ssh);
+use Net::SCP qw(scp);
+use FS::UID qw(adminsuidsetup datasrc dbh);
+use FS::Record qw(qsearch qsearchs fields);
+use FS::svc_acct;
+use FS::svc_domain;
+use FS::svc_forward;
+
+my $ssh='ssh';
+my $rsync='rsync';
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+$conf = new FS::Conf;
+
+my $userpolicy = $conf->config('username_policy')
+ if $conf->exists('username_policy');
+
+my @shellmachines = $conf->config('shellmachines')
+ if $conf->exists('shellmachines');
+
+my @bsdshellmachines = $conf->config('bsdshellmachines')
+ if $conf->exists('bsdshellmachines');
+
+my @nismachines = $conf->config('nismachines')
+ if $conf->exists('nismachines');
+
+my @erpcdmachines = $conf->config('erpcdmachines')
+ if $conf->exists('erpcdmachines');
+
+my @radiusmachines = $conf->config('radiusmachines')
+ if $conf->exists('radiusmachines');
+
+my $textradiusprepend =
+ $conf->exists('textradiusprepend')
+ ? $conf->config('textradiusprepend')
+ : '';
+
+warn "using depriciated textradiusprepend file" if $textradiusprepend;
+
+
+my $radiusprepend =
+ $conf->exists('radiusprepend')
+ ? join("\n", $conf->config('radiusprepend'))
+ : '';
+
+my @vpopmailmachines = $conf->config('vpopmailmachines')
+ if $conf->exists('vpopmailmachines');
+my $vpopmailrestart = '';
+$vpopmailrestart = $conf->config('vpopmailrestart')
+ if $conf->exists('vpopmailrestart');
+
+my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachines[0]) if $vpopmailmachines[0];
+
+my($shellmachine, @qmailmachines);
+if ( $conf->exists('qmailmachines') ) {
+ $shellmachine = $conf->config('shellmachine');
+ @qmailmachines = $conf->config('qmailmachines');
+}
+
+my(@sendmailmachines, $sendmailconfigpath, $sendmailrestart);
+if ( $conf->exists('sendmailmachines') ) {
+ @sendmailmachines = $conf->config('sendmailmachines');
+ $sendmailconfigpath = $conf->config('sendmailconfigpath') || '/etc';
+ $sendmailrestart = $conf->config('sendmailrestart');
+}
+
+my $mydomain = $conf->config('domain') if $conf->exists('domain');
+
+
+
+
+my(@saltset)= ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+require 5.004; #srand(time|$$);
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc;
+my $spoollock = "/usr/local/etc/freeside/svc_acct.export.lock.". datasrc;
+
+open(EXPORT,"+>>$spoollock") or die "Can't open $spoollock: $!";
+select(EXPORT); $|=1; select(STDOUT);
+unless ( flock(EXPORT,LOCK_EX|LOCK_NB) ) {
+ seek(EXPORT,0,0);
+ my($pid)=<EXPORT>;
+ chop($pid);
+ #no reason to start lots of blocking processes
+ die "Is another export process running under pid $pid?\n";
+}
+seek(EXPORT,0,0);
+print EXPORT $$,"\n";
+
+my(@svc_domain)=qsearch('svc_domain',{});
+
+( open(MASTER,">$spooldir/master.passwd")
+ and flock(MASTER,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/.master.passwd: $!";
+( open(PASSWD,">$spooldir/passwd")
+ and flock(PASSWD,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/passwd: $!";
+( open(SHADOW,">$spooldir/shadow")
+ and flock(SHADOW,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/shadow: $!";
+( open(ACP_PASSWD,">$spooldir/acp_passwd")
+ and flock(ACP_PASSWD,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/acp_passwd: $!";
+( open(ACP_DIALUP,">$spooldir/acp_dialup")
+ and flock(ACP_DIALUP,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/acp_dialup: $!";
+( open(USERS,">$spooldir/users")
+ and flock(USERS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/users: $!";
+
+( open(ASSIGN,">$spooldir/assign")
+ and flock(ASSIGN,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/assign: $!";
+( open(RCPTHOSTS,">$spooldir/rcpthosts")
+ and flock(RCPTHOSTS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/rcpthosts: $!";
+( open(VPOPRCPTHOSTS,">$spooldir/vpoprcpthosts")
+ and flock(VPOPRCPTHOSTS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/rcpthosts: $!";
+( open(RECIPIENTMAP,">$spooldir/recipientmap")
+ and flock(RECIPIENTMAP,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/recipientmap: $!";
+( open(VIRTUALDOMAINS,">$spooldir/virtualdomains")
+ and flock(VIRTUALDOMAINS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtualdomains: $!";
+( open(VPOPVIRTUALDOMAINS,">$spooldir/vpopvirtualdomains")
+ and flock(VPOPVIRTUALDOMAINS,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtualdomains: $!";
+( open(VIRTUSERTABLE,">$spooldir/virtusertable")
+ and flock(VIRTUSERTABLE,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/virtusertable: $!";
+( open(SENDMAIL_CW,">$spooldir/sendmail.cw")
+ and flock(SENDMAIL_CW,LOCK_EX|LOCK_NB)
+) or die "Can't open $spooldir/sendmail.cw: $!";
+
+
+
+chmod 0644, "$spooldir/passwd",
+ "$spooldir/acp_dialup",
+ "$spooldir/assign",
+ "$spooldir/sendmail.cw",
+ "$spooldir/virtusertable",
+ "$spooldir/rcpthosts",
+ "$spooldir/vpoprcpthosts",
+ "$spooldir/recipientmap",
+ "$spooldir/virtualdomains",
+ "$spooldir/vpopvirtualdomains",
+
+;
+chmod 0600, "$spooldir/master.passwd",
+ "$spooldir/acp_passwd",
+ "$spooldir/shadow",
+ "$spooldir/users",
+;
+
+rmtree"$spooldir/domains", 0, 1;
+mkdir "$spooldir/domains", 0700;
+
+setpriority(0,0,10);
+
+print USERS "$radiusprepend\n";
+
+my %usernames; ## this hack helps keep the passwd files sane
+my @sendmail;
+
+my $svc_domain;
+foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) {
+
+ my($domain)=$svc_domain->domain;
+ print RCPTHOSTS "$domain\n.$domain\n";
+ print VPOPRCPTHOSTS "$domain\n";
+ print SENDMAIL_CW "$domain\n";
+
+ ###
+ # FORMAT OF THE ASSIGN/USERS FILE HERE
+ print ASSIGN join(":",
+ "+" . $domain . "-",
+ $domain,
+ $vpopuid,
+ $vpopgid,
+ $vpopdir . "/domains/" . $domain,
+ "-",
+ "",
+ "",
+ ), "\n" if $vpopmailmachines[0];
+
+ (mkdir "$spooldir/domains/" . $domain, 0700)
+ or die "Can't create $spooldir/domains/" . $domain .": $!";
+
+ ( open(QMAILDEFAULT,">$spooldir/domains/" . $domain . "/.qmail-default")
+ and flock(QMAILDEFAULT,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $spooldir/domains/" . $domain . "/.qmail-default: $!";
+
+ ( open(VPASSWD,">$spooldir/domains/" . $domain . "/vpasswd")
+ and flock(VPASSWD,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $spooldir/domains/" . $domain . "/vpasswd: $!";
+
+ my ($svc_acct);
+
+ if ($svc_domain->getfield('catchall')) {
+ $svc_acct = qsearchs('svc_acct', {'svcnum' => $svc_domain->catchall});
+ die "Cannot find catchall account for domain $domain\n" unless $svc_acct;
+
+ my $username = $svc_acct->username;
+ push @sendmail, "\@$domain\t$username\n";
+ print VIRTUALDOMAINS "$domain:$username-$domain\n",
+ ".$domain:$username-$domain\n",
+ ;
+
+ ###
+ # FORMAT OF THE .QMAIL-DEFAULT FILE HERE
+ print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" " . $svc_acct->email . "\n"
+ if $vpopmailmachines[0];
+
+ }else{
+ ###
+ # FORMAT OF THE .QMAIL-DEFAULT FILE HERE
+ print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" bounce-no-mailbox\n"
+ if $vpopmailmachines[0];
+ }
+
+ print VPOPVIRTUALDOMAINS "$domain:$domain\n";
+
+ foreach $svc_acct (qsearch('svc_acct', {'domsvc' => $svc_domain->svcnum})) {
+ my($password)=$svc_acct->getfield('_password');
+ my($cpassword,$rpassword);
+ #if ( ( length($password) <= 8 )
+ if ( ( length($password) <= 12 )
+ && ( $password ne '*' )
+ && ( $password ne '!!' )
+ && ( $password ne '' )
+ ) {
+ $cpassword=crypt($password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ $rpassword=$password;
+ } else {
+ $cpassword=$password;
+ $rpassword='UNIX';
+ }
+
+ my $username;
+
+ if ($mydomain && ($mydomain eq $svc_domain->domain)) {
+ $username=$svc_acct->username;
+ } elsif ($userpolicy =~ /^prepend domsvc$/) {
+ $username=$svc_acct->domsvc . $svc_acct->username;
+ } elsif ($userpolicy =~ /^append domsvc$/) {
+ $username=$svc_acct->username . $svc_acct->domsvc;
+ } elsif ($userpolicy =~ /^append domain$/) {
+ $username=$svc_acct->username . $svc_domain->domain;
+ } elsif ($userpolicy =~ /^append domain$/) {
+ $username=$svc_acct->username . $svc_domain->domain;
+ } elsif ($userpolicy =~ /^append \@domain$/) {
+ $username=$svc_acct->username . '@'. $svc_domain->domain;
+ } else {
+ die "Unknown policy in username_policy\n";
+ }
+
+ if ($svc_acct->dir ne '/dev/null' || $svc_acct->slipip ne '') {
+ if ($usernames{$username}++) {
+ die "Duplicate username detected: $username\n";
+ }
+ }
+
+ if ( $svc_acct->uid =~ /^(\d+)$/ ) {
+
+ die "Non-root user ". $svc_acct->username. " has 0 UID!"
+ if $svc_acct->uid == 0 && $svc_acct->username ne 'root';
+
+ if ( $svc_acct->dir ne "/dev/null") {
+
+ ###
+ # FORMAT OF FreeBSD MASTER PASSWD FILE HERE
+ print MASTER join(":",
+ $username, # User name
+ $cpassword, # Encrypted password
+ $svc_acct->uid, # User ID
+ $svc_acct->gid, # Group ID
+ "", # Login Class
+ "0", # Password Change Time
+ "0", # Password Expiration Time
+ $svc_acct->finger, # Users name
+ $svc_acct->dir, # Users home directory
+ $svc_acct->shell, # shell
+ ), "\n" ;
+
+
+ ###
+ # FORMAT OF THE PASSWD FILE HERE
+ print PASSWD join(":",
+ $username,
+ 'x', # "##". $username,
+ $svc_acct->uid,
+ $svc_acct->gid,
+ $svc_acct->finger,
+ $svc_acct->dir,
+ $svc_acct->shell,
+ ), "\n";
+
+ ###
+ # FORMAT OF THE SHADOW FILE HERE
+ print SHADOW join(":",
+ $username,
+ $cpassword,
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ), "\n";
+ }
+ }
+
+ ###
+ # FORMAT OF THE VPASSWD FILE HERE
+ print VPASSWD join(":",
+ $svc_acct->username,
+ $cpassword,
+ '1',
+ '0',
+ $svc_acct->username,
+ "$vpopdir/domains/" . $svc_domain->domain ."/" . $svc_acct->username,
+ 'NOQUOTA',
+ ), "\n";
+
+
+ if ( $svc_acct->slipip ne '' ) {
+
+ ###
+ # FORMAT OF THE ACP_* FILES HERE
+ print ACP_PASSWD join(":",
+ $username,
+ $cpassword,
+ "0",
+ "0",
+ "",
+ "",
+ "",
+ ), "\n";
+
+ my($ip)=$svc_acct->slipip;
+
+ unless ( $ip eq '0.0.0.0' || $svc_acct->slipip eq '0e0' ) {
+ print ACP_DIALUP $username, "\t*\t", $svc_acct->slipip, "\n";
+ }
+
+ my %radreply = $svc_acct->radius_reply;
+ my %radcheck = $svc_acct->radius_check;
+
+ my $radcheck = join ", ", map { qq($_ = "$radcheck{$_}") } keys %radcheck;
+ $radcheck .= ", " if $radcheck;
+
+ ###
+ # FORMAT OF THE USERS FILE HERE
+ print USERS
+ $username,
+ qq(\t${textradiusprepend}),
+ $radcheck,
+# qq(Password = "$rpassword"\n\t),
+ join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply;
+
+ #if ( $ip && $ip ne '0e0' ) {
+ # #print USERS qq(,\n\tFramed-Address = "$ip"\n\n);
+ # print USERS qq(,\n\tFramed-IP-Address = "$ip"\n\n);
+ #} else {
+ print USERS qq(\n\n);
+ #}
+
+ }
+
+ ###
+ # vpopmail directory structure creation
+
+ (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username, 0700)
+ or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . ": $!";
+ (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir", 0700)
+ or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir: $!";
+ (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/cur", 0700)
+ or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/cur: $!";
+ (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/new", 0700)
+ or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/new: $!";
+ (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/tmp", 0700)
+ or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/tmp: $!";
+
+ ( open(DOTQMAIL,">$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail")
+ and flock(DOTQMAIL,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail: $!";
+
+ my($svc_forward);
+ foreach $svc_forward (qsearch('svc_forward', {'srcsvc' => $svc_acct->svcnum})) {
+ my($destination);
+ if ($svc_forward->dstsvc) {
+ my $dst_acct = qsearchs('svc_acct', {'svcnum' => $svc_forward->dstsvc});
+ my $dst_domain = qsearchs('svc_domain', {'svcnum' => $dst_acct->domsvc});
+ $destination = $dst_acct->username . '@' . $dst_domain->domain;
+
+ if ($dst_domain->domain eq $mydomain) {
+ print VIRTUSERTABLE $svc_acct->username . "@" . $svc_domain->domain .
+ "\t" . $dst_acct->username . "\n";
+ print RECIPIENTMAP $svc_acct->username . "@" . $svc_domain->domain .
+ ":$destination\n";
+ }
+ } else {
+ $destination = $svc_forward->dst;
+ }
+
+ ###
+ # FORMAT OF .QMAIL FILES HERE
+ print DOTQMAIL "$destination\n";
+ }
+
+ flock(DOTQMAIL,LOCK_UN);
+ close DOTQMAIL;
+
+ }
+
+ flock(VPASSWD,LOCK_UN);
+ flock(QMAILDEFAULT,LOCK_UN);
+ close VPASSWD;
+ close QMAILDEFAULT;
+
+}
+
+###
+# FORMAT OF THE ASSIGN/USERS FILE FINAL LINE HERE
+print ASSIGN ".\n";
+
+print VIRTUSERTABLE @sendmail;
+
+flock(MASTER,LOCK_UN);
+flock(PASSWD,LOCK_UN);
+flock(SHADOW,LOCK_UN);
+flock(ACP_DIALUP,LOCK_UN);
+flock(ACP_PASSWD,LOCK_UN);
+flock(USERS,LOCK_UN);
+flock(ASSIGN,LOCK_UN);
+flock(SENDMAIL_CW,LOCK_UN);
+flock(VIRTUSERTABLE,LOCK_UN);
+flock(RCPTHOSTS,LOCK_UN);
+flock(VPOPRCPTHOSTS,LOCK_UN);
+flock(RECIPIENTMAP,LOCK_UN);
+flock(VPOPVIRTUALDOMAINS,LOCK_UN);
+
+close MASTER;
+close PASSWD;
+close SHADOW;
+close ACP_DIALUP;
+close ACP_PASSWD;
+close USERS;
+close ASSIGN;
+close SENDMAIL_CW;
+close VIRTUSERTABLE;
+close RCPTHOSTS;
+close VPOPRCPTHOSTS;
+close RECIPIENTMAP;
+close VPOPVIRTUALDOMAINS;
+
+###
+# export stuff
+#
+
+my($ashellmachine);
+foreach $ashellmachine (@shellmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/passwd","root\@$ashellmachine:/etc/passwd.new")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/shadow","root\@$ashellmachine:/etc/shadow.new")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$ashellmachine",
+ "( ".
+ "mv /etc/passwd.new /etc/passwd; ".
+ "mv /etc/shadow.new /etc/shadow; ".
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+my($bsdshellmachine);
+foreach $bsdshellmachine (@bsdshellmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/passwd","root\@$bsdshellmachine:/etc/passwd.new")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/master.passwd","root\@$bsdshellmachine:/etc/master.passwd.new")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$bsdshellmachine",
+ "( ".
+ "mv /etc/passwd.new /etc/passwd; ".
+ #"mv /etc/master.passwd.new /etc/master.passwd; ".
+ "pwd_mkdb /etc/master.passwd.new; ".
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+my($nismachine);
+foreach $nismachine (@nismachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/passwd","root\@$nismachine:/etc/global/passwd")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/shadow","root\@$nismachine:/etc/global/shadow")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$nismachine",
+ "( ".
+ "cd /var/yp; make; ".
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+my($erpcdmachine);
+foreach $erpcdmachine (@erpcdmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/acp_passwd","root\@$erpcdmachine:/usr/annex/acp_passwd")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/acp_dialup","root\@$erpcdmachine:/usr/annex/acp_dialup")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$erpcdmachine",
+ "( ".
+ "kill -USR1 \`cat /usr/annex/erpcd.pid\'".
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+my($radiusmachine);
+foreach $radiusmachine (@radiusmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/users","root\@$radiusmachine:/etc/raddb/users")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$radiusmachine",
+ "( ".
+ "builddbm".
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+#my @args = ("/bin/tar", "c", "--force-local", "-C", "$spooldir", "-f", "$spooldir/vpoptarball", "domains");
+
+#system {$args[0]} @args;
+
+my($vpopmailmachine);
+foreach $vpopmailmachine (@vpopmailmachines) {
+ my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachine);
+ my $scp = new Net::SCP;
+# $scp->scp("$spooldir/vpoptarball","root\@$machine:vpoptarball")
+# or die "scp error: ". $scp->{errstr};
+# ssh("root\@$machine",
+# "( ".
+# "rm -rf domains; ".
+# "tar xf vpoptarball; ".
+# "chown -R $vpopuid:$vpopgid domains; ".
+# "tar cf vpoptarball domains; ".
+# "cd $vpopdir; ".
+# "tar xf ~/vpoptarball; ".
+# " )"
+# )
+# == 0 or die "ssh error: $!";
+
+ chdir $spooldir;
+ my @args = ("$rsync", "-rlpt", "-e", "$ssh", "domains/", "vpopmail\@$machine:$vpopdir/domains/");
+
+ system {$args[0]} @args;
+
+ $scp->scp("$spooldir/assign","root\@$machine:/var/qmail/users/assign")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/vpopvirtualdomains","root\@$machine:/var/qmail/control/virtualdomains")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/vpoprcpthosts","root\@$machine:/var/qmail/control/rcpthosts")
+ or die "scp error: ". $scp->{errstr};
+
+ ssh("root\@$machine",
+ "( ".
+ $vpopmailrestart .
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+
+
+}
+
+my($sendmailmachine);
+foreach $sendmailmachine (@sendmailmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/sendmail.cw","root\@$sendmailmachine:$sendmailconfigpath/sendmail.cw.new")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/virtusertable","root\@$sendmailmachine:$sendmailconfigpath/virtusertable.new")
+ or die "scp error: ". $scp->{errstr};
+ ssh("root\@$sendmailmachine",
+ "( ".
+ "mv $sendmailconfigpath/sendmail.cw.new $sendmailconfigpath/sendmail.cw; ".
+ "mv $sendmailconfigpath/virtusertable.new $sendmailconfigpath/virtusertable; ".
+ $sendmailrestart.
+ " )"
+ )
+ == 0 or die "ssh error: $!";
+}
+
+my($qmailmachine);
+foreach $qmailmachine (@qmailmachines) {
+ my $scp = new Net::SCP;
+ $scp->scp("$spooldir/recipientmap","root\@$qmailmachine:/var/qmail/control/recipientmap")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/virtualdomains","root\@$qmailmachine:/var/qmail/control/virtualdomains")
+ or die "scp error: ". $scp->{errstr};
+ $scp->scp("$spooldir/rcpthosts","root\@$qmailmachine:/var/qmail/control/rcpthosts")
+ or die "scp error: ". $scp->{errstr};
+ #ssh("root\@$qmailmachine","/etc/init.d/qmail restart")
+ # == 0 or die "ssh error: $!";
+}
+
+unlink $spoollock;
+flock(EXPORT,LOCK_UN);
+close EXPORT;
+
+#
+
+sub usage {
+ die "Usage:\n\n svc_acct.export user\n";
+}
+
diff --git a/bin/svc_acct.import b/bin/svc_acct.import
new file mode 100755
index 0000000..eb94e1c
--- /dev/null
+++ b/bin/svc_acct.import
@@ -0,0 +1,238 @@
+#!/usr/bin/perl -Tw
+# $Id: svc_acct.import,v 1.17 2001-08-19 10:25:44 ivan Exp $
+
+use strict;
+use vars qw(%part_svc);
+use Date::Parse;
+use Term::Query qw(query);
+use Net::SCP qw(iscp);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch);
+use FS::svc_acct;
+use FS::part_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+push @FS::svc_acct::shells, qw(/bin/sync /sbin/shuddown /bin/halt); #others?
+
+my($spooldir)="/usr/local/etc/freeside/export.". datasrc;
+
+$FS::svc_acct::nossh_hack = 1;
+
+###
+
+%part_svc=map { $_->svcpart, $_ } qsearch('part_svc',{'svcdb'=>'svc_acct'});
+
+die "No services with svcdb svc_acct!\n" unless %part_svc;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Most accounts probably have entries in passwd and users (with Port-Limit
+nonexistant or 1).
+END
+my($ppp_svcpart)=&getpart;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Some accounts have entries in passwd and users, but with Port-Limit 2 (or
+more).
+END
+my($isdn_svcpart)=&getpart;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Some accounts might have entries in users only (Port-Limit 1)
+END
+my($oppp_svcpart)=&getpart;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Some accounts might have entries in users only (Port-Limit >= 2)
+END
+my($oisdn_svcpart)=&getpart;
+
+print "\n\n", &menu_svc, "\n", <<END;
+POP mail accounts have entries in passwd only, and have a particular shell.
+END
+my($pop_shell)=&getvalue("Enter that shell:");
+my($popmail_svcpart)=&getpart;
+
+print "\n\n", &menu_svc, "\n", <<END;
+Everything else in passwd is a shell account.
+END
+my($shell_svcpart)=&getpart;
+
+print "\n\n", <<END;
+Enter the location and name of your _user_ passwd file, for example
+"mail.isp.com:/etc/passwd" or "nis.isp.com:/etc/global/passwd"
+END
+my($loc_passwd)=&getvalue(":");
+iscp("root\@$loc_passwd", "$spooldir/passwd.import");
+
+print "\n\n", <<END;
+Enter the location and name of your _user_ shadow file, for example
+"mail.isp.com:/etc/shadow" or "bsd.isp.com:/etc/master.passwd"
+END
+my($loc_shadow)=&getvalue(":");
+iscp("root\@$loc_shadow", "$spooldir/shadow.import");
+
+print "\n\n", <<END;
+Enter the location and name of your radius "users" file, for example
+"radius.isp.com:/etc/raddb/users"
+END
+my($loc_users)=&getvalue(":");
+iscp("root\@$loc_users", "$spooldir/users.import");
+
+sub menu_svc {
+ ( join "\n", map "$_: ".$part_svc{$_}->svc, sort keys %part_svc ). "\n";
+}
+sub getpart {
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query "Enter part number:", 'irk', [ keys %part_svc ];
+ $^W=1;
+ $return;
+}
+sub getvalue {
+ my $prompt = shift;
+ $^W=0; # Term::Query isn't -w-safe
+ my $return = query $prompt, '';
+ $^W=1;
+ $return;
+}
+
+print "\n\n";
+
+###
+
+open(PASSWD,"<$spooldir/passwd.import");
+open(SHADOW,"<$spooldir/shadow.import");
+open(USERS,"<$spooldir/users.import");
+
+my(%upassword,%ip,%allparam);
+my(%param,$username);
+while (<USERS>) {
+ chop;
+ next if /^\s*$/;
+ next if /^\s*#/;
+ if ( /^\S/ ) {
+ /^(\w+)\s+(Auth-Type\s+=\s+Local,\s+)?Password\s+=\s+"([^"]+)"(,\s+Expiration\s+=\s+"([^"]*")\s*)?$/
+ or die "1Unexpected line in users.import: $_";
+ my($password,$expiration);
+ ($username,$password,$expiration)=(lc($1),$3,$5);
+ $password = '' if $password eq 'UNIX';
+ $upassword{$username}=$password;
+ undef %param;
+ } else {
+ die "2Unexpected line in users.import: $_";
+ }
+ while (<USERS>) {
+ chop;
+ if ( /^\s*$/ ) {
+ if ( defined $param{'radius_Framed_IP_Address'} ) {
+ $ip{$username} = $param{'radius_Framed_IP_Address'};
+ delete $param{'radius_Framed_IP_Address'};
+ } else {
+ $ip{$username} = '0e0';
+ }
+ $allparam{$username}={ %param };
+ last;
+ } elsif ( /^\s+([\w\-]+)\s=\s"?([\w\.\-\s]+)"?,?\s*$/ ) {
+ my($attribute,$value)=($1,$2);
+ $attribute =~ s/\-/_/g;
+ $param{'radius_'.$attribute}=$value;
+ } else {
+ die "3Unexpected line in users.import: $_";
+ }
+ }
+}
+#? incase there isn't a terminating blank line ?
+if ( defined $param{'radius_Framed_IP_Address'} ) {
+ $ip{$username} = $param{'radius_Framed_IP_Address'};
+ delete $param{'radius_Framed_IP_Address'};
+} else {
+ $ip{$username} = '0e0';
+}
+$allparam{$username}={ %param };
+
+my(%password);
+while (<SHADOW>) {
+ chop;
+ my($username,$password)=split(/:/);
+ #$password =~ s/^\!$/\*/;
+ #$password =~ s/\!+/\*SUSPENDED\* /;
+ $password{$username}=$password;
+}
+
+while (<PASSWD>) {
+ chop;
+ my($username,$x,$uid,$gid,$finger,$dir,$shell)=split(/:/);
+ my($password)=$upassword{$username} || $password{$username};
+
+ my($maxb)=${$allparam{$username}}{'radius_Port_Limit'};
+ my($svcpart);
+ if ( exists $upassword{$username} ) {
+ if ( $maxb >= 2 ) {
+ $svcpart = $isdn_svcpart
+ } elsif ( ! $maxb || $maxb == 1 ) {
+ $svcpart = $ppp_svcpart
+ } else {
+ die "Illegal Port-Limit in users ($username)!\n";
+ }
+ } elsif ( $shell eq $pop_shell ) {
+ $svcpart = $popmail_svcpart;
+ } else {
+ $svcpart = $shell_svcpart;
+ }
+
+ my($svc_acct) = new FS::svc_acct ({
+ 'svcpart' => $svcpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'uid' => $uid,
+ 'gid' => $gid,
+ 'finger' => $finger,
+ 'dir' => $dir,
+ 'shell' => $shell,
+ 'slipip' => $ip{$username},
+ %{$allparam{$username}},
+ });
+ my($error);
+ $error=$svc_acct->insert;
+ die $error if $error;
+
+ delete $allparam{$username};
+ delete $upassword{$username};
+}
+
+#my($username);
+foreach $username ( keys %upassword ) {
+ my($password)=$upassword{$username};
+
+ my($maxb)=${$allparam{$username}}{'radius_Port_Limit'} || 0;
+ my($svcpart);
+ if ( $maxb == 2 ) {
+ $svcpart = $oisdn_svcpart
+ } elsif ( ! $maxb || $maxb == 1 ) {
+ $svcpart = $oppp_svcpart
+ } else {
+ die "Illegal Port-Limit in users!\n";
+ }
+
+ my($svc_acct) = new FS::svc_acct ({
+ 'svcpart' => $svcpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'slipip' => $ip{$username},
+ %{$allparam{$username}},
+ });
+ my($error);
+ $error=$svc_acct->insert;
+ die $error, if $error;
+
+ delete $allparam{$username};
+ delete $upassword{$username};
+}
+
+#
+
+sub usage {
+ die "Usage:\n\n svc_acct.import user\n";
+}
+
diff --git a/bin/svc_domain.erase b/bin/svc_domain.erase
new file mode 100755
index 0000000..c023661
--- /dev/null
+++ b/bin/svc_domain.erase
@@ -0,0 +1,17 @@
+#!/usr/bin/perl -w
+#
+# $Id: svc_domain.erase,v 1.1 2002-04-20 11:57:35 ivan Exp $
+
+use strict;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+
+use FS::domain_record;
+use FS::svc_domain;
+
+adminsuidsetup(shift @ARGV) or die "Usage: svc_domain.erase user\n";
+
+foreach my $record ( qsearch('domain_record',{}), qsearch('svc_domain', {} ) ) {
+ my $error = $record->delete;
+ die $error if $error;
+}
diff --git a/bin/sysvshell.export b/bin/sysvshell.export
new file mode 100755
index 0000000..c13912c
--- /dev/null
+++ b/bin/sysvshell.export
@@ -0,0 +1,112 @@
+#!/usr/bin/perl -w
+
+# sysvshell export
+
+use strict;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::cust_svc;
+use FS::svc_acct;
+
+my @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc;
+#my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/shell";
+
+my @sysv_exports = qsearch('part_export', { 'exporttype' => 'sysvshell' } );
+
+my $rsync = File::Rsync->new({
+ rsh => 'ssh',
+# dry_run => 1,
+});
+
+foreach my $export ( @sysv_exports ) {
+ my $machine = $export->machine;
+ my $prefix = "$spooldir/$machine";
+ mkdir $prefix, 0700 unless -d $prefix;
+
+ #LOCKING!!!
+
+ ( open(SHADOW,">$prefix/shadow")
+ #!!! and flock(SHADOW,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $prefix/shadow: $!";
+ ( open(PASSWD,">$prefix/passwd")
+ #!!! and flock(PASSWD,LOCK_EX|LOCK_NB)
+ ) or die "Can't open $prefix/passwd: $!";
+
+ chmod 0644, "$prefix/passwd";
+ chmod 0600, "$prefix/shadow";
+
+ my @svc_acct = $export->svc_x;
+
+ next unless @svc_acct;
+
+ foreach my $svc_acct ( sort { $a->uid <=> $b->uid } @svc_acct ) {
+
+ my $password = $svc_acct->_password;
+ my $cpassword;
+ #if ( ( length($password) <= 8 )
+ if ( ( length($password) <= 12 )
+ && ( $password ne '*' )
+ && ( $password ne '!!' )
+ && ( $password ne '' )
+ ) {
+ $cpassword=crypt($password,
+ $saltset[int(rand(64))].$saltset[int(rand(64))]
+ );
+ # MD5 !!!!
+ } else {
+ $cpassword=$password;
+ }
+
+ ###
+ # FORMAT OF THE PASSWD FILE HERE
+ print PASSWD join(":",
+ $svc_acct->username,
+ 'x', # "##". $username,
+ $svc_acct->uid,
+ $svc_acct->gid,
+ $svc_acct->finger,
+ $svc_acct->dir,
+ $svc_acct->shell,
+ ), "\n";
+
+ ###
+ # FORMAT OF THE SHADOW FILE HERE
+ print SHADOW join(":",
+ $svc_acct->username,
+ $cpassword,
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ), "\n";
+
+ }
+
+ #!!! flock(SHADOW,LOCK_UN);
+ #!!! flock(PASSWD,LOCK_UN);
+ close SHADOW;
+ close PASSWD;
+
+ $rsync->exec( {
+ src => "$prefix/shadow",
+ dest => "root\@$machine:/etc/shadow"
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+
+ $rsync->exec( {
+ src => "$prefix/passwd",
+ dest => "root\@$machine:/etc/passwd"
+ } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+
+ # UNLOCK!!
+}
diff --git a/conf/agent_defaultpkg b/conf/agent_defaultpkg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conf/agent_defaultpkg
diff --git a/conf/alerter_template b/conf/alerter_template
new file mode 100644
index 0000000..5177e4e
--- /dev/null
+++ b/conf/alerter_template
@@ -0,0 +1,20 @@
+
+
+Ivan Kohler
+12345 Test Lane
+Truckee, CA 96161
+
+
+{ $first; } { $last; }:
+
+ We thank you for your continuing patronage. This notice is to remind you
+that your { $payby } used to pay SISD.COM for Internet
+service will expire on { use Date::Format; time2str("%x", $expdate); }. Please provide us with new billing
+information so that we may continue your service uninterrupted.
+
+Very Truly Yours,
+
+ SISD Service Team
+
+
+
diff --git a/conf/cust_pkg-change_svcpart b/conf/cust_pkg-change_svcpart
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conf/cust_pkg-change_svcpart
diff --git a/conf/declinetemplate b/conf/declinetemplate
new file mode 100644
index 0000000..14b8c60
--- /dev/null
+++ b/conf/declinetemplate
@@ -0,0 +1,10 @@
+Hi,
+
+Your credit card could not be processed for the following reason:
+ { $error }
+
+Please provide us with new billing information so that we may continue your
+service uninterrupted.
+
+Thanks.
+
diff --git a/conf/home b/conf/home
new file mode 100644
index 0000000..05280cb
--- /dev/null
+++ b/conf/home
@@ -0,0 +1 @@
+/home
diff --git a/conf/invoice_from b/conf/invoice_from
new file mode 100644
index 0000000..110ec8f
--- /dev/null
+++ b/conf/invoice_from
@@ -0,0 +1 @@
+ivan-unconfigured-freeside-installation@420.am
diff --git a/conf/invoice_latex b/conf/invoice_latex
new file mode 100644
index 0000000..195f8fb
--- /dev/null
+++ b/conf/invoice_latex
@@ -0,0 +1,155 @@
+%% file: Standard Multipage.tex
+%% Purpose: Multipage bill template for e-Bills
+%%
+%% Created by Mark Asplen-Taylor
+%% Asplen Management Ltd
+%% www.asplen.co.uk
+%%
+%% Modified for Freeside by Ivan Kohler
+%%
+%% Changes
+%% 0.1 4/12/00 Created
+%% 0.2 18/10/01 More fields added
+%% 1.0 16/11/01 RELEASED
+%% 1.2 16/10/02 Invoice number added
+%% 1.3 2/12/02 Logo graphic added
+%% 1.4 7/2/03 Multipage headers/footers added
+%% n/a 10/12/03 forked for Freeside; checked into CVS
+%%
+
+\documentclass[letterpaper]{article}
+
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}
+\usepackage{graphicx} % required for logo graphic
+
+\addtolength{\voffset}{-0.0in} % top margin to top of header
+\addtolength{\hoffset}{-0.60in} %left margin on page
+\addtolength{\topmargin}{-0.6in} % top margin to top of header
+\setlength{\headheight}{1in} % height of header
+\setlength{\headsep}{0.5in} % between header and text
+\addtolength{\textheight}{-0.4in} % height of main text
+
+\addtolength{\textheight}{-0.5in} % height of main text
+\setlength{\footskip}{0.5in} % bottom of footer from bottom of text
+
+\addtolength{\textwidth}{2.1in} % width of text
+\setlength{\oddsidemargin}{0in} % odd page left margin
+\setlength{\evensidemargin}{0in} % even page left margin
+
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{1pt}
+
+ % New command for address lines i.e. skip them if blank
+
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}
+\newcommand{\dollar}[1][]{\symbol{36}} % Inserts dollar symbol
+
+\pagestyle{fancy}
+
+%% Font options are:
+%% bch Bitsream Charter
+%% put Utopia
+%% phv Adobe Helvetica
+%% pnc New Century Schoolbook
+%% ptm Times
+%% pcr Courier
+
+\renewcommand{\familydefault}{phv}
+
+\begin{document}
+%
+%% Headers and footers defined for the first page
+\fancyfoot[CO,CE]{\small{
+\begin{tabular}{c}
+$footer
+\end{tabular}}}
+%
+%% The LH Heading comprising logo
+%% UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo
+\fancyhead[LO,LE]{
+\begin{tabular}{l}
+\includegraphics{/usr/local/etc/freeside/conf.DBI:Pg:dbname=freeside/logo.eps}
+\end{tabular}}
+%
+%% The Heading comprising isue date, customer ref & INVOICE name
+\fancyhead[RO,RE]{
+\begin{tabular}{rcl}
+Invoice date & & Invoice number \\
+\vspace{0.2cm}
+\textbf{$date} & & \textbf{$invnum} \\\hline
+\rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}}& \\
+\vspace{-0.2cm}
+ & & \\\hline
+\end{tabular}}
+%
+%% Header & footer changes for subsequent pages
+%
+\afterpage{ \fancyfoot[RO,RE]{\small{\thepage\ of \pageref{LastPage}}} }
+\afterpage{ \fancyfoot[CO,CE]{\small{$smallfooter}} }
+\afterpage{ \fancyhead[LO,LE]{\small{}} }
+\afterpage{ \fancyhead[RO,RE]{\small{
+\begin{tabular}{ll}
+Invoice date & Invoice number\\
+\textbf{$date} & \textbf{$invnum}\\
+\end{tabular}}} }
+%
+%
+\makebox{
+\begin{minipage}[t]{2.9in}
+\vspace{0.20in}
+\textbf{$payname}\\
+\addressline{$company}
+\addressline{$address1}
+\addressline{$address2}
+\addressline{$city, $state $zip}
+\addressline{$country}
+\end{minipage}}
+\hfill
+\makebox{
+\begin{minipage}[t]{2.5in}
+\begin{flushright}
+Terms: $terms\\
+$po_line\\
+\end{flushright}
+\end{minipage}}
+\vspace{0.5cm}
+%
+\section*{\textsc{Charges}}
+\begin{longtable}{|c|l|c|r|r|}
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[7.9cm][l]{\textbf{Description}} &
+\makebox[1.3cm][c]{\textbf{Quantity}} &
+\makebox[2.5cm][r]{\textbf{Unit Price}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endfirsthead
+\multicolumn{5}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\
+\hline
+\rule{0pt}{2.5ex}
+\makebox[1.4cm]{\textbf{Ref}} &
+\makebox[7.9cm][l]{\textbf{Description}} &
+\makebox[1.3cm][c]{\textbf{Quantity}} &
+\makebox[2.5cm][r]{\textbf{Unit Price}} &
+\makebox[2.5cm][r]{\textbf{Amount}} \\
+\hline
+\endhead
+\multicolumn{5}{r}{\rule{0pt}{2.5ex}/cont...}\\
+\endfoot
+%%TotalDetails
+ & \multicolumn{3}{l}{$total_item} & $total_amount\\
+%%EndTotalDetails
+\hline
+\endlastfoot
+%%Detail
+\rule{0pt}{2.5ex}$ref &
+\begin{tabular}{l}
+$description\tabularnewline
+\end{tabular}
+& $quantity & \dollar $amount & \dollar $amount\\\hline
+%%EndDetail
+\end{longtable}
+\vfill
+$notes
+\end{document}
diff --git a/conf/invoice_latexfooter b/conf/invoice_latexfooter
new file mode 100644
index 0000000..ee5d7e9
--- /dev/null
+++ b/conf/invoice_latexfooter
@@ -0,0 +1,5 @@
+Ivan Kohler\\
+12345 Test Lane\\
+Truckee, CA~~96161\\
+ivan@sisd.com~~~~+1 415 462 1624\\
+Freeside - open-source billing - http://www.sisd.com/freeside\\
diff --git a/conf/invoice_latexnotes b/conf/invoice_latexnotes
new file mode 100644
index 0000000..46af6d6
--- /dev/null
+++ b/conf/invoice_latexnotes
@@ -0,0 +1,8 @@
+%%
+%% Add any customer specific notes in here
+%%
+\section*{\textsc{Notes}}
+\begin{enumerate}
+\item Please make your check payable to \textbf{Ivan Kohler}.
+\item If you have any questions please email or telephone.
+\end{enumerate}
diff --git a/conf/invoice_latexsmallfooter b/conf/invoice_latexsmallfooter
new file mode 100644
index 0000000..527c356
--- /dev/null
+++ b/conf/invoice_latexsmallfooter
@@ -0,0 +1 @@
+Ivan Kohler~~~Freeside - open-source billing
diff --git a/conf/invoice_template b/conf/invoice_template
new file mode 100644
index 0000000..4b2c64b
--- /dev/null
+++ b/conf/invoice_template
@@ -0,0 +1,27 @@
+
+ Invoice
+ { substr("Page $page of $total_pages ", 0, 19); } { use Date::Format; time2str("%x", $date); } FS-{ $invnum; }
+
+
+Ivan Kohler
+12345 Test Lane
+Truckee, CA 96161
+
+
+{ $address[0]; }
+{ $address[1]; }
+{ $address[2]; }
+{ $address[3]; }
+{ $address[4]; }
+{ $address[5]; }
+
+{
+ join("\n",
+ map {
+ my ( $desc, $price ) = @{$_};
+ " ". substr( $desc. " "x65, 0, 65). " ". substr( $price. " "x11, 0, 11);
+ } invoice_lines(31)
+ );
+}
+
+ -=> Freeside - open-source billing for ISPs - http://www.sisd.com/freeside <=-
diff --git a/conf/locale b/conf/locale
new file mode 100644
index 0000000..7741b83
--- /dev/null
+++ b/conf/locale
@@ -0,0 +1 @@
+en_US
diff --git a/conf/logo.eps b/conf/logo.eps
new file mode 100644
index 0000000..8091b03
--- /dev/null
+++ b/conf/logo.eps
@@ -0,0 +1,13503 @@
+%!PS-Adobe-2.0 EPSF-2.0
+%%BoundingBox: 261 345 419 447
+%%HiResBoundingBox: 261.500000 345.500000 418.500000 446.500000
+%%Creator: xpdf/pdftops 3.00
+%%LanguageLevel: 2
+%%DocumentMedia: plain 612 792 0 () ()
+%%EndComments
+% EPSF created by ps2eps 1.54
+%%BeginProlog
+save
+countdictstack
+mark
+newpath
+/showpage {} def
+/setpagedevice {pop} def
+%%EndProlog
+%%Page 1 1
+/xpdf 75 dict def xpdf begin
+% PDF special state
+/pdfDictSize 15 def
+/pdfSetup {
+ 3 1 roll 2 array astore
+ /setpagedevice where {
+ pop 3 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ /Policies 1 dict dup begin /PageSize 3 def end def
+ { /Duplex true def } if
+ currentdict end setpagedevice
+ } {
+ pop pop
+ } ifelse
+} def
+/pdfStartPage {
+ pdfDictSize dict begin
+ /pdfFill [0] def
+ /pdfStroke [0] def
+ /pdfLastFill false def
+ /pdfLastStroke false def
+ /pdfTextMat [1 0 0 1 0 0] def
+ /pdfFontSize 0 def
+ /pdfCharSpacing 0 def
+ /pdfTextRender 0 def
+ /pdfTextRise 0 def
+ /pdfWordSpacing 0 def
+ /pdfHorizScaling 1 def
+ /pdfTextClipPath [] def
+} def
+/pdfEndPage { end } def
+% separation convention operators
+/findcmykcustomcolor where {
+ pop
+}{
+ /findcmykcustomcolor { 5 array astore } def
+} ifelse
+/setcustomcolor where {
+ pop
+}{
+ /setcustomcolor {
+ exch
+ [ exch /Separation exch dup 4 get exch /DeviceCMYK exch
+ 0 4 getinterval cvx
+ [ exch /dup load exch { mul exch dup } /forall load
+ /pop load dup ] cvx
+ ] setcolorspace setcolor
+ } def
+} ifelse
+/customcolorimage where {
+ pop
+}{
+ /customcolorimage {
+ gsave
+ [ exch /Separation exch dup 4 get exch /DeviceCMYK exch
+ 0 4 getinterval
+ [ exch /dup load exch { mul exch dup } /forall load
+ /pop load dup ] cvx
+ ] setcolorspace
+ 10 dict begin
+ /ImageType 1 def
+ /DataSource exch def
+ /ImageMatrix exch def
+ /BitsPerComponent exch def
+ /Height exch def
+ /Width exch def
+ /Decode [1 0] def
+ currentdict end
+ image
+ grestore
+ } def
+} ifelse
+% PDF color state
+/sCol {
+ pdfLastStroke not {
+ pdfStroke aload length
+ dup 1 eq {
+ pop setgray
+ }{
+ dup 3 eq {
+ pop setrgbcolor
+ }{
+ 4 eq {
+ setcmykcolor
+ }{
+ findcmykcustomcolor exch setcustomcolor
+ } ifelse
+ } ifelse
+ } ifelse
+ /pdfLastStroke true def /pdfLastFill false def
+ } if
+} def
+/fCol {
+ pdfLastFill not {
+ pdfFill aload length
+ dup 1 eq {
+ pop setgray
+ }{
+ dup 3 eq {
+ pop setrgbcolor
+ }{
+ 4 eq {
+ setcmykcolor
+ }{
+ findcmykcustomcolor exch setcustomcolor
+ } ifelse
+ } ifelse
+ } ifelse
+ /pdfLastFill true def /pdfLastStroke false def
+ } if
+} def
+% build a font
+/pdfMakeFont {
+ 4 3 roll findfont
+ 4 2 roll matrix scale makefont
+ dup length dict begin
+ { 1 index /FID ne { def } { pop pop } ifelse } forall
+ /Encoding exch def
+ currentdict
+ end
+ definefont pop
+} def
+/pdfMakeFont16 {
+ exch findfont
+ dup length dict begin
+ { 1 index /FID ne { def } { pop pop } ifelse } forall
+ /WMode exch def
+ currentdict
+ end
+ definefont pop
+} def
+/pdfMakeFont16L3 {
+ 1 index /CIDFont resourcestatus {
+ pop pop 1 index /CIDFont findresource /CIDFontType known
+ } {
+ false
+ } ifelse
+ {
+ 0 eq { /Identity-H } { /Identity-V } ifelse
+ exch 1 array astore composefont pop
+ } {
+ pdfMakeFont16
+ } ifelse
+} def
+% graphics state operators
+/q { gsave pdfDictSize dict begin } def
+/Q { end grestore } def
+/cm { concat } def
+/d { setdash } def
+/i { setflat } def
+/j { setlinejoin } def
+/J { setlinecap } def
+/M { setmiterlimit } def
+/w { setlinewidth } def
+% color operators
+/g { dup 1 array astore /pdfFill exch def setgray
+ /pdfLastFill true def /pdfLastStroke false def } def
+/G { dup 1 array astore /pdfStroke exch def setgray
+ /pdfLastStroke true def /pdfLastFill false def } def
+/rg { 3 copy 3 array astore /pdfFill exch def setrgbcolor
+ /pdfLastFill true def /pdfLastStroke false def } def
+/RG { 3 copy 3 array astore /pdfStroke exch def setrgbcolor
+ /pdfLastStroke true def /pdfLastFill false def } def
+/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor
+ /pdfLastFill true def /pdfLastStroke false def } def
+/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor
+ /pdfLastStroke true def /pdfLastFill false def } def
+/ck { 6 copy 6 array astore /pdfFill exch def
+ findcmykcustomcolor exch setcustomcolor
+ /pdfLastFill true def /pdfLastStroke false def } def
+/CK { 6 copy 6 array astore /pdfStroke exch def
+ findcmykcustomcolor exch setcustomcolor
+ /pdfLastStroke true def /pdfLastFill false def } def
+% path segment operators
+/m { moveto } def
+/l { lineto } def
+/c { curveto } def
+/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto
+ neg 0 rlineto closepath } def
+/h { closepath } def
+% path painting operators
+/S { sCol stroke } def
+/Sf { fCol stroke } def
+/f { fCol fill } def
+/f* { fCol eofill } def
+% clipping operators
+/W { clip newpath } def
+/W* { eoclip newpath } def
+% text state operators
+/Tc { /pdfCharSpacing exch def } def
+/Tf { dup /pdfFontSize exch def
+ dup pdfHorizScaling mul exch matrix scale
+ pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put
+ exch findfont exch makefont setfont } def
+/Tr { /pdfTextRender exch def } def
+/Ts { /pdfTextRise exch def } def
+/Tw { /pdfWordSpacing exch def } def
+/Tz { /pdfHorizScaling exch def } def
+% text positioning operators
+/Td { pdfTextMat transform moveto } def
+/Tm { /pdfTextMat exch def } def
+% text string operators
+/cshow where {
+ pop
+ /cshow2 {
+ dup {
+ pop pop
+ 1 string dup 0 3 index put 3 index exec
+ } exch cshow
+ pop pop
+ } def
+}{
+ /cshow2 {
+ currentfont /FontType get 0 eq {
+ 0 2 2 index length 1 sub {
+ 2 copy get exch 1 add 2 index exch get
+ 2 copy exch 256 mul add
+ 2 string dup 0 6 5 roll put dup 1 5 4 roll put
+ 3 index exec
+ } for
+ } {
+ dup {
+ 1 string dup 0 3 index put 3 index exec
+ } forall
+ } ifelse
+ pop pop
+ } def
+} ifelse
+/awcp {
+ exch {
+ false charpath
+ 5 index 5 index rmoveto
+ 6 index eq { 7 index 7 index rmoveto } if
+ } exch cshow2
+ 6 {pop} repeat
+} def
+/Tj {
+ fCol
+ 1 index stringwidth pdfTextMat idtransform pop
+ sub 1 index length dup 0 ne { div } { pop pop 0 } ifelse
+ pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32
+ 4 3 roll pdfCharSpacing pdfHorizScaling mul add 0
+ pdfTextMat dtransform
+ 6 5 roll Tj1
+} def
+/Tj16 {
+ fCol
+ 2 index stringwidth pdfTextMat idtransform pop
+ sub exch div
+ pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32
+ 4 3 roll pdfCharSpacing pdfHorizScaling mul add 0
+ pdfTextMat dtransform
+ 6 5 roll Tj1
+} def
+/Tj16V {
+ fCol
+ 2 index stringwidth pdfTextMat idtransform exch pop
+ sub exch div
+ 0 pdfWordSpacing pdfTextMat dtransform 32
+ 4 3 roll pdfCharSpacing add 0 exch
+ pdfTextMat dtransform
+ 6 5 roll Tj1
+} def
+/Tj1 {
+ 0 pdfTextRise pdfTextMat dtransform rmoveto
+ currentpoint 8 2 roll
+ pdfTextRender 1 and 0 eq {
+ 6 copy awidthshow
+ } if
+ pdfTextRender 3 and dup 1 eq exch 2 eq or {
+ 7 index 7 index moveto
+ 6 copy
+ currentfont /FontType get 3 eq { fCol } { sCol } ifelse
+ false awcp currentpoint stroke moveto
+ } if
+ pdfTextRender 4 and 0 ne {
+ 8 6 roll moveto
+ false awcp
+ /pdfTextClipPath [ pdfTextClipPath aload pop
+ {/moveto cvx}
+ {/lineto cvx}
+ {/curveto cvx}
+ {/closepath cvx}
+ pathforall ] def
+ currentpoint newpath moveto
+ } {
+ 8 {pop} repeat
+ } ifelse
+ 0 pdfTextRise neg pdfTextMat dtransform rmoveto
+} def
+/TJm { pdfFontSize 0.001 mul mul neg 0
+ pdfTextMat dtransform rmoveto } def
+/TJmV { pdfFontSize 0.001 mul mul neg 0 exch
+ pdfTextMat dtransform rmoveto } def
+/Tclip { pdfTextClipPath cvx exec clip newpath
+ /pdfTextClipPath [] def } def
+% Level 2 image operators
+/pdfImBuf 100 string def
+/pdfIm {
+ image
+ { currentfile pdfImBuf readline
+ not { pop exit } if
+ (%-EOD-) eq { exit } if } loop
+} def
+/pdfImSep {
+ findcmykcustomcolor exch
+ dup /Width get /pdfImBuf1 exch string def
+ dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def
+ /pdfImDecodeLow exch def
+ begin Width Height BitsPerComponent ImageMatrix DataSource end
+ /pdfImData exch def
+ { pdfImData pdfImBuf1 readstring pop
+ 0 1 2 index length 1 sub {
+ 1 index exch 2 copy get
+ pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi
+ 255 exch sub put
+ } for }
+ 6 5 roll customcolorimage
+ { currentfile pdfImBuf readline
+ not { pop exit } if
+ (%-EOD-) eq { exit } if } loop
+} def
+/pdfImM {
+ fCol imagemask
+ { currentfile pdfImBuf readline
+ not { pop exit } if
+ (%-EOD-) eq { exit } if } loop
+} def
+end
+xpdf begin
+/F2_0 /Helvetica 1 1
+[ /.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+ /.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+ /.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+ /.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef
+ /space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quotesingle
+ /parenleft/parenright/asterisk/plus/comma/hyphen/period/slash
+ /zero/one/two/three/four/five/six/seven
+ /eight/nine/colon/semicolon/less/equal/greater/question
+ /at/A/B/C/D/E/F/G
+ /H/I/J/K/L/M/N/O
+ /P/Q/R/S/T/U/V/W
+ /X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore
+ /grave/a/b/c/d/e/f/g
+ /h/i/j/k/l/m/n/o
+ /p/q/r/s/t/u/v/w
+ /x/y/z/braceleft/bar/braceright/asciitilde/bullet
+ /Euro/bullet/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl
+ /circumflex/perthousand/Scaron/guilsinglleft/OE/bullet/Zcaron/bullet
+ /bullet/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash
+ /tilde/trademark/scaron/guilsinglright/oe/bullet/zcaron/Ydieresis
+ /space/exclamdown/cent/sterling/currency/yen/brokenbar/section
+ /dieresis/copyright/ordfeminine/guillemotleft/logicalnot/hyphen/registered/macron
+ /degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered
+ /cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown
+ /Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla
+ /Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis
+ /Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply
+ /Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls
+ /agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla
+ /egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis
+ /eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide
+ /oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]
+pdfMakeFont
+612 792 false pdfSetup
+pdfStartPage
+26.1663 -1.02141e-14 translate
+0.9406 0.9406 scale
+[] 0 d
+1 i
+0 j
+0 J
+10 M
+1 w
+0 g
+0 G
+q
+[1 0 0 1 0 0] cm
+[1 0 0 1 0 0] Tm
+0 0 Td
+0 g
+328.715 366.945 10.4374 0.2006 re
+f*
+0 g
+324.902 367.146 18.0648 0.2005 re
+f*
+0 g
+322.292 367.346 23.2834 0.2006 re
+f*
+0 g
+320.285 367.547 27.2978 0.2005 re
+f*
+0 g
+318.278 367.747 31.3122 0.2006 re
+f*
+0 g
+316.672 367.948 34.323 0.2006 re
+f*
+0 g
+315.267 368.148 37.3338 0.2005 re
+f*
+0 g
+313.862 368.349 39.9433 0.2005 re
+f*
+0 g
+312.658 368.549 42.5525 0.2006 re
+f*
+0 g
+311.453 368.75 44.9612 0.2006 re
+f*
+0 g
+310.249 368.951 47.1691 0.2006 re
+f*
+0 g
+309.245 369.151 49.377 0.2005 re
+f*
+0 g
+308.242 369.352 50.5813 0.2005 re
+f*
+0 g
+307.238 369.552 49.377 0.2006 re
+f*
+0 g
+306.435 369.753 47.9719 0.2006 re
+f*
+0 g
+305.432 369.953 47.3698 0.2006 re
+f*
+0 g
+304.629 370.154 46.5669 0.2005 re
+f*
+0 g
+303.826 370.355 46.1654 0.2006 re
+f*
+0 g
+303.023 370.555 45.7641 0.2005 re
+f*
+1 g
+348.787 370.555 13.8496 0.2005 re
+f*
+0.498 0 0.482 rg
+362.637 370.555 2.2079 0.2005 re
+f*
+0 g
+302.22 370.756 45.3626 0.2006 re
+f*
+1 g
+347.583 370.756 13.8497 0.2006 re
+f*
+0.498 0 0.482 rg
+361.433 370.756 4.2151 0.2006 re
+f*
+0 g
+301.417 370.956 45.1618 0.2005 re
+f*
+1 g
+346.579 370.956 13.6489 0.2005 re
+f*
+0.498 0 0.482 rg
+360.228 370.956 6.2224 0.2005 re
+f*
+0 g
+300.615 371.157 45.1619 0.2006 re
+f*
+1 g
+345.776 371.157 13.4481 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 371.157 7.8281 0.2006 re
+f*
+0 g
+300.012 371.357 44.7605 0.2005 re
+f*
+1 g
+344.773 371.357 13.4481 0.2005 re
+f*
+0.498 0 0.482 rg
+358.221 371.357 9.6346 0.2005 re
+f*
+0 g
+299.209 371.558 44.7604 0.2006 re
+f*
+1 g
+343.97 371.558 13.2475 0.2006 re
+f*
+0.498 0 0.482 rg
+357.217 371.558 11.2403 0.2006 re
+f*
+0 g
+298.607 371.758 44.5597 0.2006 re
+f*
+1 g
+343.167 371.758 13.0467 0.2006 re
+f*
+0.498 0 0.482 rg
+356.214 371.758 13.0468 0.2006 re
+f*
+0 g
+298.005 371.959 44.5597 0.2005 re
+f*
+1 g
+342.565 371.959 12.8461 0.2005 re
+f*
+0.498 0 0.482 rg
+355.411 371.959 14.4518 0.2005 re
+f*
+0 g
+297.202 372.16 44.5597 0.2005 re
+f*
+1 g
+341.762 372.16 12.846 0.2005 re
+f*
+0.498 0 0.482 rg
+354.608 372.16 16.0576 0.2005 re
+f*
+0 g
+296.6 372.36 44.5597 0.2006 re
+f*
+1 g
+341.16 372.36 12.6454 0.2006 re
+f*
+0.498 0 0.482 rg
+353.805 372.36 17.4625 0.2006 re
+f*
+0 g
+295.998 372.561 44.359 0.2006 re
+f*
+1 g
+340.357 372.561 12.6453 0.2006 re
+f*
+0.498 0 0.482 rg
+353.002 372.561 18.8677 0.2006 re
+f*
+0 g
+295.396 372.761 44.359 0.2006 re
+f*
+1 g
+339.755 372.761 12.4446 0.2006 re
+f*
+0.498 0 0.482 rg
+352.2 372.761 20.2726 0.2006 re
+f*
+0 g
+294.794 372.962 44.359 0.2006 re
+f*
+1 g
+339.153 372.962 12.2439 0.2006 re
+f*
+0.498 0 0.482 rg
+351.397 372.962 21.6777 0.2006 re
+f*
+0 g
+294.192 373.162 44.359 0.2005 re
+f*
+1 g
+338.551 373.162 12.2439 0.2005 re
+f*
+0.498 0 0.482 rg
+350.794 373.162 22.882 0.2005 re
+f*
+0 g
+293.589 373.363 44.5597 0.2005 re
+f*
+1 g
+338.149 373.363 11.8424 0.2005 re
+f*
+0.498 0 0.482 rg
+349.991 373.363 24.2871 0.2005 re
+f*
+0 g
+292.987 373.563 44.5598 0.2006 re
+f*
+1 g
+337.547 373.563 11.8424 0.2006 re
+f*
+0.498 0 0.482 rg
+349.389 373.563 25.4914 0.2006 re
+f*
+0 g
+292.385 373.764 44.5597 0.2006 re
+f*
+1 g
+336.945 373.764 11.8425 0.2006 re
+f*
+0.498 0 0.482 rg
+348.787 373.764 26.6956 0.2006 re
+f*
+0 g
+291.783 373.965 44.7605 0.2005 re
+f*
+1 g
+336.543 373.965 11.6417 0.2005 re
+f*
+0.498 0 0.482 rg
+348.185 373.965 27.6993 0.2005 re
+f*
+0 g
+291.381 374.165 44.5597 0.2005 re
+f*
+1 g
+335.941 374.165 11.6417 0.2005 re
+f*
+0.498 0 0.482 rg
+347.583 374.165 28.9036 0.2005 re
+f*
+0 g
+290.779 374.366 44.7605 0.2006 re
+f*
+1 g
+335.54 374.366 11.4409 0.2006 re
+f*
+0.498 0 0.482 rg
+346.981 374.366 30.108 0.2006 re
+f*
+0 g
+290.378 374.566 44.5597 0.2006 re
+f*
+1 g
+334.938 374.566 11.441 0.2006 re
+f*
+0.498 0 0.482 rg
+346.379 374.566 31.1115 0.2006 re
+f*
+0 g
+289.776 374.767 44.7605 0.2005 re
+f*
+1 g
+334.536 374.767 11.4409 0.2005 re
+f*
+0.498 0 0.482 rg
+345.977 374.767 32.1152 0.2005 re
+f*
+0 g
+289.174 374.967 44.9611 0.2006 re
+f*
+1 g
+334.135 374.967 11.2403 0.2006 re
+f*
+0.498 0 0.482 rg
+345.375 374.967 33.1187 0.2006 re
+f*
+0 g
+288.772 375.168 44.9612 0.2005 re
+f*
+1 g
+333.733 375.168 11.0396 0.2005 re
+f*
+0.498 0 0.482 rg
+344.773 375.168 34.323 0.2005 re
+f*
+0 g
+288.371 375.368 44.9611 0.2006 re
+f*
+1 g
+333.332 375.368 11.0396 0.2006 re
+f*
+0.498 0 0.482 rg
+344.371 375.368 35.1259 0.2006 re
+f*
+0 g
+287.768 375.569 45.1619 0.2006 re
+f*
+1 g
+332.93 375.569 10.8389 0.2006 re
+f*
+0.498 0 0.482 rg
+343.769 375.569 36.3302 0.2006 re
+f*
+0 g
+287.367 375.77 45.1619 0.2006 re
+f*
+1 g
+332.529 375.77 10.8388 0.2006 re
+f*
+0.498 0 0.482 rg
+343.368 375.77 37.1331 0.2006 re
+f*
+0 g
+286.765 375.97 45.3626 0.2005 re
+f*
+1 g
+332.127 375.97 10.6382 0.2005 re
+f*
+0.498 0 0.482 rg
+342.766 375.97 38.1367 0.2005 re
+f*
+0 g
+286.363 376.171 45.3626 0.2005 re
+f*
+1 g
+331.726 376.171 10.6381 0.2005 re
+f*
+0.498 0 0.482 rg
+342.364 376.171 39.1403 0.2005 re
+f*
+0 g
+285.962 376.371 45.3625 0.2006 re
+f*
+1 g
+331.325 376.371 10.4375 0.2006 re
+f*
+0.498 0 0.482 rg
+341.762 376.371 40.1439 0.2006 re
+f*
+0 g
+285.561 376.572 45.3626 0.2006 re
+f*
+1 g
+330.923 376.572 10.4374 0.2006 re
+f*
+0.498 0 0.482 rg
+341.361 376.572 40.9468 0.2006 re
+f*
+0 g
+284.958 376.772 45.5633 0.2005 re
+f*
+1 g
+330.522 376.772 10.4374 0.2005 re
+f*
+0.498 0 0.482 rg
+340.959 376.772 41.7496 0.2005 re
+f*
+0 g
+284.557 376.973 45.7639 0.2006 re
+f*
+1 g
+330.321 376.973 10.2368 0.2006 re
+f*
+0.498 0 0.482 rg
+340.558 376.973 42.7532 0.2006 re
+f*
+0 g
+284.156 377.173 45.764 0.2005 re
+f*
+1 g
+329.92 377.173 10.2367 0.2005 re
+f*
+0.498 0 0.482 rg
+340.156 377.173 43.5561 0.2005 re
+f*
+0 g
+283.754 377.374 45.7641 0.2006 re
+f*
+1 g
+329.518 377.374 10.2367 0.2006 re
+f*
+0.498 0 0.482 rg
+339.755 377.374 44.3589 0.2006 re
+f*
+0 g
+283.353 377.575 45.9648 0.2006 re
+f*
+1 g
+329.317 377.575 10.0359 0.2006 re
+f*
+0.498 0 0.482 rg
+339.353 377.575 45.1619 0.2006 re
+f*
+0 g
+282.951 377.775 45.9647 0.2005 re
+f*
+1 g
+328.916 377.775 10.036 0.2005 re
+f*
+0.498 0 0.482 rg
+338.952 377.775 45.9647 0.2005 re
+f*
+0 g
+282.55 377.976 45.9647 0.2006 re
+f*
+1 g
+328.515 377.976 10.036 0.2006 re
+f*
+0.498 0 0.482 rg
+338.551 377.976 46.7676 0.2006 re
+f*
+0 g
+282.148 378.176 46.1655 0.2005 re
+f*
+1 g
+328.314 378.176 9.8352 0.2005 re
+f*
+0.498 0 0.482 rg
+338.149 378.176 47.5705 0.2005 re
+f*
+0 g
+281.747 378.377 46.1655 0.2006 re
+f*
+1 g
+327.912 378.377 9.8353 0.2006 re
+f*
+0.498 0 0.482 rg
+337.748 378.377 48.3733 0.2006 re
+f*
+0 g
+281.346 378.577 46.3662 0.2006 re
+f*
+1 g
+327.712 378.577 9.6346 0.2006 re
+f*
+0.498 0 0.482 rg
+337.346 378.577 49.1762 0.2006 re
+f*
+0 g
+280.944 378.778 46.3662 0.2005 re
+f*
+1 g
+327.31 378.778 9.6345 0.2005 re
+f*
+0.498 0 0.482 rg
+336.945 378.778 49.9792 0.2005 re
+f*
+0 g
+280.543 378.978 46.5668 0.2006 re
+f*
+1 g
+327.11 378.978 9.4339 0.2006 re
+f*
+0.498 0 0.482 rg
+336.543 378.978 50.7819 0.2006 re
+f*
+0 g
+280.141 379.179 46.7676 0.2005 re
+f*
+1 g
+326.909 379.179 9.4339 0.2005 re
+f*
+0.498 0 0.482 rg
+336.343 379.179 51.3841 0.2005 re
+f*
+0 g
+279.74 379.38 46.7677 0.2006 re
+f*
+1 g
+326.507 379.38 9.4338 0.2006 re
+f*
+0.498 0 0.482 rg
+335.941 379.38 52.187 0.2006 re
+f*
+0 g
+279.338 379.58 46.9684 0.2006 re
+f*
+1 g
+326.307 379.58 9.2331 0.2006 re
+f*
+0.498 0 0.482 rg
+335.54 379.58 52.9899 0.2006 re
+f*
+0 g
+278.937 379.781 46.9683 0.2005 re
+f*
+1 g
+325.905 379.781 9.2331 0.2005 re
+f*
+0.498 0 0.482 rg
+335.138 379.781 53.5921 0.2005 re
+f*
+0 g
+278.736 379.981 46.9684 0.2006 re
+f*
+1 g
+325.704 379.981 9.2331 0.2006 re
+f*
+0.498 0 0.482 rg
+334.938 379.981 54.1942 0.2006 re
+f*
+0 g
+278.335 380.182 47.1691 0.2005 re
+f*
+1 g
+325.504 380.182 9.0324 0.2005 re
+f*
+0.498 0 0.482 rg
+334.536 380.182 54.9971 0.2005 re
+f*
+0 g
+277.933 380.382 47.3698 0.2006 re
+f*
+1 g
+325.303 380.382 9.0323 0.2006 re
+f*
+0.498 0 0.482 rg
+334.335 380.382 55.5994 0.2006 re
+f*
+0 g
+277.532 380.583 47.3697 0.2005 re
+f*
+1 g
+324.902 380.583 9.0324 0.2005 re
+f*
+0.498 0 0.482 rg
+333.934 380.583 56.4022 0.2005 re
+f*
+0 g
+277.331 380.783 47.3698 0.2006 re
+f*
+1 g
+324.701 380.783 9.0324 0.2006 re
+f*
+0.498 0 0.482 rg
+333.733 380.783 56.8036 0.2006 re
+f*
+0 g
+287.367 380.984 37.1331 0.2006 re
+f*
+1 g
+324.5 380.984 8.8316 0.2006 re
+f*
+0.498 0 0.482 rg
+333.332 380.984 57.6066 0.2006 re
+f*
+0 g
+287.367 381.185 36.9324 0.2005 re
+f*
+1 g
+324.299 381.185 8.8316 0.2005 re
+f*
+0.498 0 0.482 rg
+333.131 381.185 58.2087 0.2005 re
+f*
+0 g
+287.367 381.385 36.5309 0.2006 re
+f*
+1 g
+323.898 381.385 8.8317 0.2006 re
+f*
+0.498 0 0.482 rg
+332.73 381.385 58.8108 0.2006 re
+f*
+0 g
+287.367 381.586 36.3302 0.2005 re
+f*
+1 g
+323.697 381.586 8.8317 0.2005 re
+f*
+0.498 0 0.482 rg
+332.529 381.586 59.413 0.2005 re
+f*
+0 g
+287.367 381.786 36.1295 0.2006 re
+f*
+1 g
+323.497 381.786 8.6309 0.2006 re
+f*
+0.498 0 0.482 rg
+332.127 381.786 60.2159 0.2006 re
+f*
+0 g
+287.367 381.987 35.9288 0.2006 re
+f*
+1 g
+323.296 381.987 8.6309 0.2006 re
+f*
+0.498 0 0.482 rg
+331.927 381.987 60.6173 0.2006 re
+f*
+0 g
+278.937 382.188 0.2007 0.2005 re
+f*
+1 g
+279.138 382.188 8.2295 0.2005 re
+f*
+0 g
+287.367 382.188 35.7281 0.2005 re
+f*
+1 g
+323.095 382.188 8.4302 0.2005 re
+f*
+0.498 0 0.482 rg
+331.525 382.188 61.4201 0.2005 re
+f*
+0 g
+278.937 382.388 43.9575 0.2006 re
+f*
+1 g
+322.894 382.388 8.4302 0.2006 re
+f*
+0.498 0 0.482 rg
+331.325 382.388 61.8216 0.2006 re
+f*
+0 g
+278.937 382.589 43.7569 0.2005 re
+f*
+1 g
+322.694 382.589 8.4301 0.2005 re
+f*
+0.498 0 0.482 rg
+331.124 382.589 62.4238 0.2005 re
+f*
+0 g
+278.937 382.789 43.5561 0.2006 re
+f*
+1 g
+322.493 382.789 8.2295 0.2006 re
+f*
+0.498 0 0.482 rg
+330.722 382.789 63.2266 0.2006 re
+f*
+0 g
+278.937 382.99 43.3554 0.2006 re
+f*
+1 g
+322.292 382.99 8.2295 0.2006 re
+f*
+0.498 0 0.482 rg
+330.522 382.99 63.628 0.2006 re
+f*
+0 g
+278.937 383.19 43.1547 0.2005 re
+f*
+1 g
+322.092 383.19 8.2294 0.2005 re
+f*
+0.498 0 0.482 rg
+330.321 383.19 64.2303 0.2005 re
+f*
+0 g
+278.937 383.391 42.9539 0.2006 re
+f*
+1 g
+321.891 383.391 8.2295 0.2006 re
+f*
+0.498 0 0.482 rg
+330.12 383.391 64.6317 0.2006 re
+f*
+0 g
+278.937 383.591 42.7533 0.2005 re
+f*
+1 g
+321.69 383.591 8.0287 0.2005 re
+f*
+0.498 0 0.482 rg
+329.719 383.591 65.4345 0.2005 re
+f*
+0 g
+278.937 383.792 42.5525 0.2006 re
+f*
+1 g
+321.489 383.792 8.0288 0.2006 re
+f*
+0.498 0 0.482 rg
+329.518 383.792 65.8359 0.2006 re
+f*
+0 g
+278.937 383.992 42.3518 0.2006 re
+f*
+1 g
+321.289 383.992 8.0288 0.2006 re
+f*
+0.498 0 0.482 rg
+329.317 383.992 66.4381 0.2006 re
+f*
+0 g
+278.937 384.193 42.1511 0.2005 re
+f*
+1 g
+321.088 384.193 8.0287 0.2005 re
+f*
+0.498 0 0.482 rg
+329.117 384.193 66.8396 0.2005 re
+f*
+0 g
+278.937 384.394 41.9503 0.2005 re
+f*
+1 g
+320.887 384.394 8.0288 0.2005 re
+f*
+0.498 0 0.482 rg
+328.916 384.394 67.241 0.2005 re
+f*
+0 g
+278.937 384.594 41.7497 0.2006 re
+f*
+1 g
+320.687 384.594 7.828 0.2006 re
+f*
+0.498 0 0.482 rg
+328.515 384.594 68.0439 0.2006 re
+f*
+0 g
+271.109 384.795 0.2008 0.2006 re
+f*
+1 g
+271.31 384.795 7.6273 0.2006 re
+f*
+0 g
+278.937 384.795 41.5489 0.2006 re
+f*
+1 g
+320.486 384.795 7.8281 0.2006 re
+f*
+0.498 0 0.482 rg
+328.314 384.795 68.4453 0.2006 re
+f*
+0 g
+270.707 384.995 0.6022 0.2006 re
+f*
+1 g
+271.31 384.995 7.6273 0.2006 re
+f*
+0 g
+278.937 384.995 41.3482 0.2006 re
+f*
+1 g
+320.285 384.995 7.828 0.2006 re
+f*
+0.498 0 0.482 rg
+328.113 384.995 69.0475 0.2006 re
+f*
+0 g
+270.507 385.196 0.8029 0.2005 re
+f*
+1 g
+271.31 385.196 7.6273 0.2005 re
+f*
+0 g
+278.937 385.196 41.1475 0.2005 re
+f*
+1 g
+320.084 385.196 7.828 0.2005 re
+f*
+0.498 0 0.482 rg
+327.912 385.196 69.4489 0.2005 re
+f*
+0 g
+270.306 385.396 1.0036 0.2006 re
+f*
+1 g
+271.31 385.396 7.6273 0.2006 re
+f*
+0 g
+278.937 385.396 40.9467 0.2006 re
+f*
+1 g
+319.884 385.396 7.8281 0.2006 re
+f*
+0.498 0 0.482 rg
+327.712 385.396 69.8504 0.2006 re
+f*
+0 g
+269.904 385.597 1.4051 0.2005 re
+f*
+1 g
+271.31 385.597 7.6273 0.2005 re
+f*
+0 g
+278.937 385.597 40.7461 0.2005 re
+f*
+1 g
+319.683 385.597 7.828 0.2005 re
+f*
+0.498 0 0.482 rg
+327.511 385.597 70.4525 0.2005 re
+f*
+0 g
+269.704 385.797 1.6058 0.2006 re
+f*
+1 g
+271.31 385.797 7.6273 0.2006 re
+f*
+0 g
+278.937 385.797 40.7461 0.2006 re
+f*
+1 g
+319.683 385.797 7.6273 0.2006 re
+f*
+0.498 0 0.482 rg
+327.31 385.797 70.8539 0.2006 re
+f*
+0 g
+269.503 385.998 1.8065 0.2005 re
+f*
+1 g
+271.31 385.998 7.6273 0.2005 re
+f*
+0 g
+278.937 385.998 40.5453 0.2005 re
+f*
+1 g
+319.482 385.998 7.6273 0.2005 re
+f*
+0.498 0 0.482 rg
+327.11 385.998 71.2554 0.2005 re
+f*
+0 g
+269.102 386.199 2.208 0.2006 re
+f*
+1 g
+271.31 386.199 7.6273 0.2006 re
+f*
+0 g
+278.937 386.199 40.3446 0.2006 re
+f*
+1 g
+319.281 386.199 7.6273 0.2006 re
+f*
+0.498 0 0.482 rg
+326.909 386.199 71.8576 0.2006 re
+f*
+0 g
+268.901 386.399 2.4087 0.2005 re
+f*
+1 g
+271.31 386.399 7.6273 0.2005 re
+f*
+0 g
+278.937 386.399 40.1438 0.2005 re
+f*
+1 g
+319.081 386.399 7.6274 0.2005 re
+f*
+0.498 0 0.482 rg
+326.708 386.399 72.259 0.2005 re
+f*
+0 g
+268.7 386.6 2.6094 0.2006 re
+f*
+1 g
+271.31 386.6 7.6273 0.2006 re
+f*
+0 g
+278.937 386.6 39.9431 0.2006 re
+f*
+1 g
+318.88 386.6 7.6274 0.2006 re
+f*
+0.498 0 0.482 rg
+326.507 386.6 72.6604 0.2006 re
+f*
+0 g
+268.299 386.8 3.0108 0.2006 re
+f*
+1 g
+271.31 386.8 7.6273 0.2006 re
+f*
+0 g
+278.937 386.8 39.9431 0.2006 re
+f*
+1 g
+318.88 386.8 7.4267 0.2006 re
+f*
+0.498 0 0.482 rg
+326.307 386.8 73.0618 0.2006 re
+f*
+0 g
+268.098 387.001 3.2115 0.2005 re
+f*
+1 g
+271.31 387.001 7.6273 0.2005 re
+f*
+0 g
+278.937 387.001 39.7425 0.2005 re
+f*
+1 g
+318.679 387.001 7.4265 0.2005 re
+f*
+0.498 0 0.482 rg
+326.106 387.001 73.6641 0.2005 re
+f*
+0 g
+267.897 387.201 3.4123 0.2005 re
+f*
+1 g
+271.31 387.201 7.6273 0.2005 re
+f*
+0 g
+278.937 387.201 39.5417 0.2005 re
+f*
+1 g
+318.479 387.201 7.4266 0.2005 re
+f*
+0.498 0 0.482 rg
+325.905 387.201 74.0655 0.2005 re
+f*
+0 g
+267.697 387.402 3.613 0.2006 re
+f*
+1 g
+271.31 387.402 7.6273 0.2006 re
+f*
+0 g
+278.937 387.402 39.341 0.2006 re
+f*
+1 g
+318.278 387.402 7.4266 0.2006 re
+f*
+0.498 0 0.482 rg
+325.704 387.402 74.4669 0.2006 re
+f*
+0 g
+267.295 387.603 4.0144 0.2006 re
+f*
+1 g
+271.31 387.603 7.6273 0.2006 re
+f*
+0 g
+278.937 387.603 39.341 0.2006 re
+f*
+1 g
+318.278 387.603 7.2259 0.2006 re
+f*
+0.498 0 0.482 rg
+325.504 387.603 74.8683 0.2006 re
+f*
+0 g
+267.094 387.803 4.2151 0.2006 re
+f*
+1 g
+271.31 387.803 7.6273 0.2006 re
+f*
+0 g
+278.937 387.803 39.1402 0.2006 re
+f*
+1 g
+318.077 387.803 7.226 0.2006 re
+f*
+0.498 0 0.482 rg
+325.303 387.803 75.4705 0.2006 re
+f*
+0 g
+266.894 388.004 4.4159 0.2006 re
+f*
+1 g
+271.31 388.004 7.6273 0.2006 re
+f*
+0 g
+278.937 388.004 38.9395 0.2006 re
+f*
+1 g
+317.876 388.004 7.226 0.2006 re
+f*
+0.498 0 0.482 rg
+325.102 388.004 75.8719 0.2006 re
+f*
+0 g
+266.693 388.204 4.6166 0.2005 re
+f*
+1 g
+271.31 388.204 7.6273 0.2005 re
+f*
+0 g
+278.937 388.204 38.7389 0.2005 re
+f*
+1 g
+317.676 388.204 7.2258 0.2005 re
+f*
+0.498 0 0.482 rg
+324.902 388.204 76.2734 0.2005 re
+f*
+0 g
+266.492 388.405 4.8173 0.2005 re
+f*
+1 g
+271.31 388.405 7.6273 0.2005 re
+f*
+0 g
+278.937 388.405 38.7389 0.2005 re
+f*
+1 g
+317.676 388.405 7.0251 0.2005 re
+f*
+0.498 0 0.482 rg
+324.701 388.405 76.6748 0.2005 re
+f*
+0 g
+266.292 388.605 5.018 0.2006 re
+f*
+1 g
+271.31 388.605 7.6273 0.2006 re
+f*
+0 g
+278.937 388.605 38.5381 0.2006 re
+f*
+1 g
+317.475 388.605 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+324.5 388.605 77.0762 0.2006 re
+f*
+0 g
+265.89 388.806 5.4195 0.2006 re
+f*
+1 g
+271.31 388.806 7.6273 0.2006 re
+f*
+0 g
+278.937 388.806 38.3374 0.2006 re
+f*
+1 g
+317.274 388.806 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+324.299 388.806 77.4777 0.2006 re
+f*
+0 g
+265.689 389.006 5.6202 0.2005 re
+f*
+1 g
+271.31 389.006 7.6273 0.2005 re
+f*
+0 g
+278.937 389.006 38.3374 0.2005 re
+f*
+1 g
+317.274 389.006 7.0252 0.2005 re
+f*
+0.498 0 0.482 rg
+324.299 389.006 77.8791 0.2005 re
+f*
+0 g
+265.489 389.207 5.8209 0.2005 re
+f*
+1 g
+271.31 389.207 7.6273 0.2005 re
+f*
+0 g
+278.937 389.207 38.1367 0.2005 re
+f*
+1 g
+317.074 389.207 7.0252 0.2005 re
+f*
+0.498 0 0.482 rg
+324.099 389.207 78.2805 0.2005 re
+f*
+0 g
+265.288 389.407 6.0216 0.2006 re
+f*
+1 g
+271.31 389.407 7.6273 0.2006 re
+f*
+0 g
+278.937 389.407 37.9359 0.2006 re
+f*
+1 g
+316.873 389.407 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+323.898 389.407 78.682 0.2006 re
+f*
+0 g
+265.087 389.608 6.2224 0.2006 re
+f*
+1 g
+271.31 389.608 7.6273 0.2006 re
+f*
+0 g
+278.937 389.608 37.9359 0.2006 re
+f*
+1 g
+316.873 389.608 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+323.697 389.608 79.0835 0.2006 re
+f*
+0 g
+264.886 389.809 6.4231 0.2005 re
+f*
+1 g
+271.31 389.809 7.6273 0.2005 re
+f*
+0 g
+278.937 389.809 37.7352 0.2005 re
+f*
+1 g
+316.672 389.809 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+323.497 389.809 79.4849 0.2005 re
+f*
+0 g
+264.686 390.009 6.6238 0.2006 re
+f*
+1 g
+271.31 390.009 7.6273 0.2006 re
+f*
+0 g
+278.937 390.009 37.5345 0.2006 re
+f*
+1 g
+316.471 390.009 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+323.497 390.009 79.6856 0.2006 re
+f*
+0 g
+264.485 390.21 6.8245 0.2005 re
+f*
+1 g
+271.31 390.21 7.6273 0.2005 re
+f*
+0 g
+278.937 390.21 37.5345 0.2005 re
+f*
+1 g
+316.471 390.21 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+323.296 390.21 80.087 0.2005 re
+f*
+0 g
+264.284 390.41 7.0252 0.2006 re
+f*
+1 g
+271.31 390.41 7.6273 0.2006 re
+f*
+0 g
+278.937 390.41 37.3338 0.2006 re
+f*
+1 g
+316.271 390.41 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+323.095 390.41 80.4884 0.2006 re
+f*
+0 g
+264.084 390.611 7.226 0.2006 re
+f*
+1 g
+271.31 390.611 7.6273 0.2006 re
+f*
+0 g
+278.937 390.611 37.1331 0.2006 re
+f*
+1 g
+316.07 390.611 6.8244 0.2006 re
+f*
+0.498 0 0.482 rg
+322.894 390.611 80.89 0.2006 re
+f*
+0 g
+263.883 390.811 7.4267 0.2006 re
+f*
+1 g
+271.31 390.811 7.6273 0.2006 re
+f*
+0 g
+278.937 390.811 37.1331 0.2006 re
+f*
+1 g
+316.07 390.811 6.8244 0.2006 re
+f*
+0.498 0 0.482 rg
+322.894 390.811 81.0907 0.2006 re
+f*
+0 g
+263.682 391.012 7.6274 0.2005 re
+f*
+1 g
+271.31 391.012 7.6273 0.2005 re
+f*
+0 g
+278.937 391.012 36.9323 0.2005 re
+f*
+1 g
+315.869 391.012 6.8246 0.2005 re
+f*
+0.498 0 0.482 rg
+322.694 391.012 81.492 0.2005 re
+f*
+0 g
+263.281 391.213 8.0288 0.2005 re
+f*
+1 g
+271.31 391.213 7.6273 0.2005 re
+f*
+0 g
+278.937 391.213 36.9323 0.2005 re
+f*
+1 g
+315.869 391.213 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+322.493 391.213 81.8935 0.2005 re
+f*
+0 g
+263.08 391.413 8.2296 0.2006 re
+f*
+1 g
+271.31 391.413 7.6273 0.2006 re
+f*
+0 g
+278.937 391.413 36.7317 0.2006 re
+f*
+1 g
+315.669 391.413 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+322.292 391.413 82.2949 0.2006 re
+f*
+0 g
+262.879 391.614 8.4303 0.2006 re
+f*
+1 g
+271.31 391.614 7.6273 0.2006 re
+f*
+0 g
+278.937 391.614 36.5309 0.2006 re
+f*
+1 g
+315.468 391.614 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+322.292 391.614 82.4957 0.2006 re
+f*
+0 g
+262.679 391.814 8.631 0.2005 re
+f*
+1 g
+271.31 391.814 7.6273 0.2005 re
+f*
+0 g
+278.937 391.814 36.5309 0.2005 re
+f*
+1 g
+315.468 391.814 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+322.092 391.814 82.8971 0.2005 re
+f*
+0 g
+262.478 392.015 8.8317 0.2006 re
+f*
+1 g
+271.31 392.015 7.6273 0.2006 re
+f*
+0 g
+278.937 392.015 36.3302 0.2006 re
+f*
+1 g
+315.267 392.015 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+321.891 392.015 83.2986 0.2006 re
+f*
+0 g
+262.277 392.215 9.0324 0.2005 re
+f*
+1 g
+271.31 392.215 7.6273 0.2005 re
+f*
+0 g
+278.937 392.215 36.3302 0.2005 re
+f*
+1 g
+315.267 392.215 6.6237 0.2005 re
+f*
+0.498 0 0.482 rg
+321.891 392.215 83.4993 0.2005 re
+f*
+0 g
+262.277 392.416 9.0324 0.2006 re
+f*
+1 g
+271.31 392.416 7.6273 0.2006 re
+f*
+0 g
+278.937 392.416 36.1295 0.2006 re
+f*
+1 g
+315.066 392.416 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+321.69 392.416 83.9007 0.2006 re
+f*
+0 g
+262.076 392.616 9.2332 0.2006 re
+f*
+1 g
+271.31 392.616 7.6273 0.2006 re
+f*
+0 g
+278.937 392.616 36.1295 0.2006 re
+f*
+1 g
+315.066 392.616 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+321.489 392.616 84.3022 0.2006 re
+f*
+0 g
+261.876 392.817 9.4339 0.2005 re
+f*
+1 g
+271.31 392.817 7.6273 0.2005 re
+f*
+0 g
+278.937 392.817 35.9287 0.2005 re
+f*
+1 g
+314.866 392.817 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+321.489 392.817 84.5029 0.2005 re
+f*
+0 g
+261.675 393.018 9.6346 0.2006 re
+f*
+1 g
+271.31 393.018 7.6273 0.2006 re
+f*
+0 g
+278.937 393.018 35.9287 0.2006 re
+f*
+1 g
+314.866 393.018 6.4231 0.2006 re
+f*
+0.498 0 0.482 rg
+321.289 393.018 84.9043 0.2006 re
+f*
+0 g
+261.474 393.218 9.8353 0.2005 re
+f*
+1 g
+271.31 393.218 7.6273 0.2005 re
+f*
+0 g
+278.937 393.218 35.7281 0.2005 re
+f*
+1 g
+314.665 393.218 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+321.088 393.218 85.3057 0.2005 re
+f*
+0 g
+261.274 393.419 10.036 0.2006 re
+f*
+1 g
+271.31 393.419 7.6273 0.2006 re
+f*
+0 g
+278.937 393.419 35.7281 0.2006 re
+f*
+1 g
+314.665 393.419 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+321.088 393.419 85.5064 0.2006 re
+f*
+0 g
+261.073 393.619 10.2368 0.2006 re
+f*
+1 g
+271.31 393.619 7.6273 0.2006 re
+f*
+0 g
+278.937 393.619 35.5273 0.2006 re
+f*
+1 g
+314.464 393.619 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+320.887 393.619 85.908 0.2006 re
+f*
+0 g
+260.872 393.82 10.4375 0.2005 re
+f*
+1 g
+271.31 393.82 7.6273 0.2005 re
+f*
+0 g
+278.937 393.82 35.5273 0.2005 re
+f*
+1 g
+314.464 393.82 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+320.887 393.82 86.1087 0.2005 re
+f*
+0 g
+260.671 394.02 10.6382 0.2006 re
+f*
+1 g
+271.31 394.02 7.6273 0.2006 re
+f*
+0 g
+278.937 394.02 35.3266 0.2006 re
+f*
+1 g
+314.263 394.02 6.4231 0.2006 re
+f*
+0.498 0 0.482 rg
+320.687 394.02 86.51 0.2006 re
+f*
+0 g
+260.471 394.221 10.8389 0.2005 re
+f*
+1 g
+271.31 394.221 7.6273 0.2005 re
+f*
+0 g
+278.937 394.221 35.3266 0.2005 re
+f*
+1 g
+314.263 394.221 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+320.486 394.221 86.9115 0.2005 re
+f*
+0 g
+260.27 394.421 11.0396 0.2006 re
+f*
+1 g
+271.31 394.421 7.6273 0.2006 re
+f*
+0 g
+278.937 394.421 35.1259 0.2006 re
+f*
+1 g
+314.063 394.421 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+320.486 394.421 86.9115 0.2006 re
+f*
+0 g
+260.27 394.622 11.0396 0.2006 re
+f*
+1 g
+271.31 394.622 7.6273 0.2006 re
+f*
+0 g
+278.937 394.622 35.1259 0.2006 re
+f*
+1 g
+314.063 394.622 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+320.285 394.622 87.3129 0.2006 re
+f*
+0 g
+260.069 394.823 11.2403 0.2005 re
+f*
+1 g
+271.31 394.823 7.6273 0.2005 re
+f*
+0 g
+278.937 394.823 34.9251 0.2005 re
+f*
+1 g
+313.862 394.823 6.4231 0.2005 re
+f*
+0.498 0 0.482 rg
+320.285 394.823 87.5137 0.2005 re
+f*
+0 g
+259.868 395.023 11.4411 0.2006 re
+f*
+1 g
+271.31 395.023 7.6273 0.2006 re
+f*
+0 g
+278.937 395.023 34.9251 0.2006 re
+f*
+1 g
+313.862 395.023 6.2224 0.2006 re
+f*
+0.498 0 0.482 rg
+320.084 395.023 87.9151 0.2006 re
+f*
+0 g
+259.668 395.224 11.6418 0.2005 re
+f*
+1 g
+271.31 395.224 7.6273 0.2005 re
+f*
+0 g
+278.937 395.224 34.7245 0.2005 re
+f*
+1 g
+313.661 395.224 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+320.084 395.224 88.1158 0.2005 re
+f*
+0 g
+259.467 395.424 11.8425 0.2006 re
+f*
+1 g
+271.31 395.424 7.6273 0.2006 re
+f*
+0 g
+278.937 395.424 34.7245 0.2006 re
+f*
+1 g
+313.661 395.424 6.2222 0.2006 re
+f*
+0.498 0 0.482 rg
+319.884 395.424 88.5173 0.2006 re
+f*
+0 g
+259.266 395.625 12.0432 0.2005 re
+f*
+1 g
+271.31 395.625 7.6273 0.2005 re
+f*
+0 g
+278.937 395.625 34.5237 0.2005 re
+f*
+1 g
+313.461 395.625 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+319.884 395.625 88.5173 0.2005 re
+f*
+0 g
+259.266 395.825 12.0432 0.2006 re
+f*
+1 g
+271.31 395.825 7.6273 0.2006 re
+f*
+0 g
+278.937 395.825 34.5237 0.2006 re
+f*
+1 g
+313.461 395.825 6.2224 0.2006 re
+f*
+0.498 0 0.482 rg
+319.683 395.825 88.9186 0.2006 re
+f*
+0 g
+259.066 396.026 12.2439 0.2006 re
+f*
+1 g
+271.31 396.026 7.6273 0.2006 re
+f*
+0 g
+278.937 396.026 34.5237 0.2006 re
+f*
+1 g
+313.461 396.026 6.2224 0.2006 re
+f*
+0.498 0 0.482 rg
+319.683 396.026 89.1194 0.2006 re
+f*
+0 g
+258.865 396.227 12.4447 0.2005 re
+f*
+1 g
+271.31 396.227 7.6273 0.2005 re
+f*
+0 g
+278.937 396.227 34.323 0.2005 re
+f*
+1 g
+313.26 396.227 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+319.482 396.227 89.5209 0.2005 re
+f*
+0 g
+258.664 396.427 12.6454 0.2006 re
+f*
+1 g
+271.31 396.427 7.6273 0.2006 re
+f*
+0 g
+278.937 396.427 34.323 0.2006 re
+f*
+1 g
+313.26 396.427 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+319.482 396.427 89.7216 0.2006 re
+f*
+0 g
+258.463 396.628 12.8461 0.2005 re
+f*
+1 g
+271.31 396.628 7.6273 0.2005 re
+f*
+0 g
+278.937 396.628 34.1223 0.2005 re
+f*
+1 g
+313.059 396.628 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+319.281 396.628 89.9223 0.2005 re
+f*
+0 g
+258.463 396.828 12.8461 0.2006 re
+f*
+1 g
+271.31 396.828 7.6273 0.2006 re
+f*
+0 g
+278.937 396.828 34.1223 0.2006 re
+f*
+1 g
+313.059 396.828 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+319.281 396.828 90.123 0.2006 re
+f*
+0 g
+258.263 397.029 13.0468 0.2006 re
+f*
+1 g
+271.31 397.029 7.6273 0.2006 re
+f*
+0 g
+278.937 397.029 34.1223 0.2006 re
+f*
+1 g
+313.059 397.029 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 397.029 90.5245 0.2006 re
+f*
+0 g
+258.062 397.229 13.2475 0.2005 re
+f*
+1 g
+271.31 397.229 7.6273 0.2005 re
+f*
+0 g
+278.937 397.229 33.9216 0.2005 re
+f*
+1 g
+312.858 397.229 6.2222 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 397.229 90.7253 0.2005 re
+f*
+0 g
+258.062 397.43 13.2475 0.2006 re
+f*
+1 g
+271.31 397.43 7.6273 0.2006 re
+f*
+0 g
+278.937 397.43 33.9216 0.2006 re
+f*
+1 g
+312.858 397.43 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 397.43 90.926 0.2006 re
+f*
+0 g
+257.861 397.63 13.4483 0.2005 re
+f*
+1 g
+271.31 397.63 7.6273 0.2005 re
+f*
+0 g
+278.937 397.63 33.7209 0.2005 re
+f*
+1 g
+312.658 397.63 6.2222 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 397.63 91.1267 0.2005 re
+f*
+0 g
+257.661 397.831 13.649 0.2006 re
+f*
+1 g
+271.31 397.831 7.6273 0.2006 re
+f*
+0 g
+278.937 397.831 33.7209 0.2006 re
+f*
+1 g
+312.658 397.831 6.2222 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 397.831 91.3274 0.2006 re
+f*
+0 g
+257.46 398.032 13.8497 0.2006 re
+f*
+1 g
+271.31 398.032 7.6273 0.2006 re
+f*
+0 g
+278.937 398.032 33.7209 0.2006 re
+f*
+1 g
+312.658 398.032 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+318.679 398.032 91.7287 0.2006 re
+f*
+0 g
+257.46 398.232 13.8497 0.2005 re
+f*
+1 g
+271.31 398.232 7.6273 0.2005 re
+f*
+0 g
+278.937 398.232 33.5201 0.2005 re
+f*
+1 g
+312.457 398.232 6.2224 0.2005 re
+f*
+0.498 0 0.482 rg
+318.679 398.232 91.7287 0.2005 re
+f*
+0 g
+257.259 398.433 14.0504 0.2006 re
+f*
+1 g
+271.31 398.433 7.6273 0.2006 re
+f*
+0 g
+278.937 398.433 33.5201 0.2006 re
+f*
+1 g
+312.457 398.433 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+318.479 398.433 92.1302 0.2006 re
+f*
+0 g
+257.058 398.633 14.2511 0.2005 re
+f*
+1 g
+271.31 398.633 7.6273 0.2005 re
+f*
+0 g
+278.937 398.633 33.5201 0.2005 re
+f*
+1 g
+312.457 398.633 6.0216 0.2005 re
+f*
+0.498 0 0.482 rg
+318.479 398.633 92.3309 0.2005 re
+f*
+0 g
+257.058 398.834 14.2511 0.2006 re
+f*
+1 g
+271.31 398.834 7.6273 0.2006 re
+f*
+0 g
+278.937 398.834 33.3194 0.2006 re
+f*
+1 g
+312.256 398.834 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+318.479 398.834 92.3309 0.2006 re
+f*
+0 g
+256.858 399.034 14.4519 0.2006 re
+f*
+1 g
+271.31 399.034 7.6273 0.2006 re
+f*
+0 g
+278.937 399.034 33.3194 0.2006 re
+f*
+1 g
+312.256 399.034 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+318.278 399.034 92.7324 0.2006 re
+f*
+0 g
+256.657 399.235 14.6526 0.2005 re
+f*
+1 g
+271.31 399.235 7.6273 0.2005 re
+f*
+0 g
+278.937 399.235 33.3194 0.2005 re
+f*
+1 g
+312.256 399.235 6.0216 0.2005 re
+f*
+0.498 0 0.482 rg
+318.278 399.235 92.9331 0.2005 re
+f*
+0 g
+256.657 399.435 14.6526 0.2005 re
+f*
+1 g
+271.31 399.435 7.6273 0.2005 re
+f*
+0 g
+278.937 399.435 33.1187 0.2005 re
+f*
+1 g
+312.056 399.435 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+318.278 399.435 92.9331 0.2005 re
+f*
+0 g
+256.456 399.636 14.8533 0.2006 re
+f*
+1 g
+271.31 399.636 7.6273 0.2006 re
+f*
+0 g
+278.937 399.636 33.1187 0.2006 re
+f*
+1 g
+312.056 399.636 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.077 399.636 93.3346 0.2006 re
+f*
+0 g
+256.256 399.837 15.054 0.2006 re
+f*
+1 g
+271.31 399.837 7.6273 0.2006 re
+f*
+0 g
+278.937 399.837 33.1187 0.2006 re
+f*
+1 g
+312.056 399.837 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.077 399.837 93.5353 0.2006 re
+f*
+0 g
+256.256 400.037 15.054 0.2006 re
+f*
+1 g
+271.31 400.037 7.6273 0.2006 re
+f*
+0 g
+278.937 400.037 32.918 0.2006 re
+f*
+1 g
+311.855 400.037 6.2222 0.2006 re
+f*
+0.498 0 0.482 rg
+318.077 400.037 93.5353 0.2006 re
+f*
+0 g
+256.055 400.238 15.2547 0.2005 re
+f*
+1 g
+271.31 400.238 7.6273 0.2005 re
+f*
+0 g
+278.937 400.238 32.918 0.2005 re
+f*
+1 g
+311.855 400.238 6.0215 0.2005 re
+f*
+0.498 0 0.482 rg
+317.876 400.238 93.9367 0.2005 re
+f*
+0 g
+255.854 400.438 15.4555 0.2006 re
+f*
+1 g
+271.31 400.438 7.6273 0.2006 re
+f*
+0 g
+278.937 400.438 32.918 0.2006 re
+f*
+1 g
+311.855 400.438 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+317.876 400.438 93.9367 0.2006 re
+f*
+0 g
+255.854 400.639 15.4555 0.2005 re
+f*
+1 g
+271.31 400.639 7.6273 0.2005 re
+f*
+0 g
+278.937 400.639 32.7173 0.2005 re
+f*
+1 g
+311.654 400.639 6.2222 0.2005 re
+f*
+0.498 0 0.482 rg
+317.876 400.639 94.1375 0.2005 re
+f*
+0 g
+255.653 400.839 15.6562 0.2006 re
+f*
+1 g
+271.31 400.839 7.6273 0.2006 re
+f*
+0 g
+278.937 400.839 32.7173 0.2006 re
+f*
+1 g
+311.654 400.839 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+317.676 400.839 94.5388 0.2006 re
+f*
+0 g
+255.653 401.04 15.6562 0.2006 re
+f*
+1 g
+271.31 401.04 7.6273 0.2006 re
+f*
+0 g
+278.937 401.04 32.7173 0.2006 re
+f*
+1 g
+311.654 401.04 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+317.676 401.04 94.5388 0.2006 re
+f*
+0 g
+255.453 401.241 15.8569 0.2005 re
+f*
+1 g
+271.31 401.241 7.6273 0.2005 re
+f*
+0 g
+278.937 401.241 32.5165 0.2005 re
+f*
+1 g
+311.453 401.241 6.2224 0.2005 re
+f*
+0.498 0 0.482 rg
+317.676 401.241 94.7395 0.2005 re
+f*
+0 g
+255.252 401.441 16.0576 0.2005 re
+f*
+1 g
+271.31 401.441 7.6273 0.2005 re
+f*
+0 g
+278.937 401.441 32.5165 0.2005 re
+f*
+1 g
+311.453 401.441 6.2224 0.2005 re
+f*
+0.498 0 0.482 rg
+317.676 401.441 94.9402 0.2005 re
+f*
+0 g
+255.252 401.642 16.0576 0.2006 re
+f*
+1 g
+271.31 401.642 7.6273 0.2006 re
+f*
+0 g
+278.937 401.642 32.5165 0.2006 re
+f*
+1 g
+311.453 401.642 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+317.475 401.642 95.141 0.2006 re
+f*
+0 g
+255.051 401.842 16.2583 0.2006 re
+f*
+1 g
+271.31 401.842 7.6273 0.2006 re
+f*
+0 g
+278.937 401.842 32.5165 0.2006 re
+f*
+1 g
+311.453 401.842 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+317.475 401.842 95.3417 0.2006 re
+f*
+0 g
+255.051 402.043 16.2583 0.2005 re
+f*
+1 g
+271.31 402.043 7.6273 0.2005 re
+f*
+0 g
+278.937 402.043 32.3158 0.2005 re
+f*
+1 g
+311.253 402.043 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+317.475 402.043 95.3417 0.2005 re
+f*
+0 g
+254.851 402.243 16.4591 0.2005 re
+f*
+1 g
+271.31 402.243 7.6273 0.2005 re
+f*
+0 g
+278.937 402.243 32.3158 0.2005 re
+f*
+1 g
+311.253 402.243 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+317.475 402.243 95.5425 0.2005 re
+f*
+0 g
+254.851 402.444 16.4591 0.2006 re
+f*
+1 g
+271.31 402.444 7.6273 0.2006 re
+f*
+0 g
+278.937 402.444 32.3158 0.2006 re
+f*
+1 g
+311.253 402.444 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+317.274 402.444 95.7432 0.2006 re
+f*
+0 g
+254.65 402.644 16.6598 0.2006 re
+f*
+1 g
+271.31 402.644 7.6273 0.2006 re
+f*
+0 g
+278.937 402.644 32.1151 0.2006 re
+f*
+1 g
+311.052 402.644 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+317.274 402.644 95.9438 0.2006 re
+f*
+0 g
+254.449 402.845 16.8605 0.2006 re
+f*
+1 g
+271.31 402.845 7.6273 0.2006 re
+f*
+0 g
+278.937 402.845 32.1151 0.2006 re
+f*
+1 g
+311.052 402.845 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+317.274 402.845 95.9438 0.2006 re
+f*
+0 g
+254.449 403.046 16.8605 0.2006 re
+f*
+1 g
+271.31 403.046 7.6273 0.2006 re
+f*
+0 g
+278.937 403.046 32.1151 0.2006 re
+f*
+1 g
+311.052 403.046 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+317.274 403.046 96.1446 0.2006 re
+f*
+0 g
+254.248 403.246 17.0612 0.2005 re
+f*
+1 g
+271.31 403.246 7.6273 0.2005 re
+f*
+0 g
+278.937 403.246 32.1151 0.2005 re
+f*
+1 g
+311.052 403.246 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+317.274 403.246 96.1446 0.2005 re
+f*
+0 g
+254.248 403.447 17.0612 0.2005 re
+f*
+1 g
+271.31 403.447 7.6273 0.2005 re
+f*
+0 g
+278.937 403.447 31.9144 0.2005 re
+f*
+1 g
+310.851 403.447 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+317.274 403.447 96.3453 0.2005 re
+f*
+0 g
+254.048 403.647 17.2619 0.2006 re
+f*
+1 g
+271.31 403.647 7.6273 0.2006 re
+f*
+0 g
+278.937 403.647 31.9144 0.2006 re
+f*
+1 g
+310.851 403.647 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 403.647 96.546 0.2006 re
+f*
+0 g
+254.048 403.848 17.2619 0.2006 re
+f*
+1 g
+271.31 403.848 7.6273 0.2006 re
+f*
+0 g
+278.937 403.848 31.9144 0.2006 re
+f*
+1 g
+310.851 403.848 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 403.848 96.7467 0.2006 re
+f*
+0 g
+253.847 404.048 17.4626 0.2005 re
+f*
+1 g
+271.31 404.048 7.6273 0.2005 re
+f*
+0 g
+278.937 404.048 31.9144 0.2005 re
+f*
+1 g
+310.851 404.048 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 404.048 13.6489 0.2005 re
+f*
+1 g
+330.722 404.048 8.4302 0.2005 re
+f*
+0.498 0 0.482 rg
+339.153 404.048 74.6676 0.2005 re
+f*
+0 g
+253.847 404.249 17.4626 0.2005 re
+f*
+1 g
+271.31 404.249 7.6273 0.2005 re
+f*
+0 g
+278.937 404.249 31.7137 0.2005 re
+f*
+1 g
+310.651 404.249 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 404.249 12.2439 0.2005 re
+f*
+1 g
+329.317 404.249 11.2403 0.2005 re
+f*
+0.498 0 0.482 rg
+340.558 404.249 73.4633 0.2005 re
+f*
+0 g
+253.646 404.449 17.6633 0.2006 re
+f*
+1 g
+271.31 404.449 7.6273 0.2006 re
+f*
+0 g
+278.937 404.449 31.7137 0.2006 re
+f*
+1 g
+310.651 404.449 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 404.449 11.0395 0.2006 re
+f*
+1 g
+328.113 404.449 13.4483 0.2006 re
+f*
+0.498 0 0.482 rg
+341.561 404.449 72.4597 0.2006 re
+f*
+0 g
+253.646 404.65 17.6633 0.2006 re
+f*
+1 g
+271.31 404.65 7.6273 0.2006 re
+f*
+0 g
+278.937 404.65 31.7137 0.2006 re
+f*
+1 g
+310.651 404.65 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 404.65 9.8352 0.2006 re
+f*
+1 g
+326.909 404.65 15.6561 0.2006 re
+f*
+0.498 0 0.482 rg
+342.565 404.65 71.6568 0.2006 re
+f*
+0 g
+253.445 404.851 17.8641 0.2005 re
+f*
+1 g
+271.31 404.851 7.6273 0.2005 re
+f*
+0 g
+278.937 404.851 31.7137 0.2005 re
+f*
+1 g
+310.651 404.851 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 404.851 8.8316 0.2005 re
+f*
+1 g
+325.905 404.851 17.2619 0.2005 re
+f*
+0.498 0 0.482 rg
+343.167 404.851 71.0546 0.2005 re
+f*
+0 g
+253.445 405.051 17.8641 0.2006 re
+f*
+1 g
+271.31 405.051 7.6273 0.2006 re
+f*
+0 g
+278.937 405.051 31.7137 0.2006 re
+f*
+1 g
+310.651 405.051 6.423 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 405.051 8.0288 0.2006 re
+f*
+1 g
+325.102 405.051 7.0251 0.2006 re
+f*
+0 g
+332.127 405.051 6.2223 0.2006 re
+f*
+1 g
+338.35 405.051 5.6201 0.2006 re
+f*
+0.498 0 0.482 rg
+343.97 405.051 70.4526 0.2006 re
+f*
+0 g
+253.445 405.252 17.8641 0.2005 re
+f*
+1 g
+271.31 405.252 7.6273 0.2005 re
+f*
+0 g
+278.937 405.252 31.513 0.2005 re
+f*
+1 g
+310.45 405.252 6.4229 0.2005 re
+f*
+0.498 0 0.482 rg
+316.873 405.252 7.6274 0.2005 re
+f*
+1 g
+324.5 405.252 5.6201 0.2005 re
+f*
+0 g
+330.12 405.252 9.8353 0.2005 re
+f*
+1 g
+339.956 405.252 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+344.572 405.252 69.8504 0.2005 re
+f*
+0 g
+253.245 405.452 18.0648 0.2006 re
+f*
+1 g
+271.31 405.452 7.6273 0.2006 re
+f*
+0 g
+278.937 405.452 31.513 0.2006 re
+f*
+1 g
+310.45 405.452 6.4229 0.2006 re
+f*
+0.498 0 0.482 rg
+316.873 405.452 7.0252 0.2006 re
+f*
+1 g
+323.898 405.452 5.2187 0.2006 re
+f*
+0 g
+329.117 405.452 12.0432 0.2006 re
+f*
+1 g
+341.16 405.452 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+344.973 405.452 69.6497 0.2006 re
+f*
+0 g
+253.245 405.653 18.0648 0.2006 re
+f*
+1 g
+271.31 405.653 7.6273 0.2006 re
+f*
+0 g
+278.937 405.653 31.513 0.2006 re
+f*
+1 g
+310.45 405.653 6.4229 0.2006 re
+f*
+0.498 0 0.482 rg
+316.873 405.653 6.4231 0.2006 re
+f*
+1 g
+323.296 405.653 4.8172 0.2006 re
+f*
+0 g
+328.113 405.653 13.8497 0.2006 re
+f*
+1 g
+341.963 405.653 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+345.576 405.653 69.0475 0.2006 re
+f*
+0 g
+253.044 405.853 18.2655 0.2006 re
+f*
+1 g
+271.31 405.853 7.6273 0.2006 re
+f*
+0 g
+278.937 405.853 31.513 0.2006 re
+f*
+1 g
+310.45 405.853 6.4229 0.2006 re
+f*
+0.498 0 0.482 rg
+316.873 405.853 5.821 0.2006 re
+f*
+1 g
+322.694 405.853 4.8172 0.2006 re
+f*
+0 g
+327.511 405.853 15.0539 0.2006 re
+f*
+1 g
+342.565 405.853 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+345.977 405.853 68.8468 0.2006 re
+f*
+0 g
+253.044 406.054 18.2655 0.2005 re
+f*
+1 g
+271.31 406.054 7.6273 0.2005 re
+f*
+0 g
+278.937 406.054 31.513 0.2005 re
+f*
+1 g
+310.45 406.054 6.4229 0.2005 re
+f*
+0.498 0 0.482 rg
+316.873 406.054 5.2188 0.2005 re
+f*
+1 g
+322.092 406.054 4.6165 0.2005 re
+f*
+0 g
+326.708 406.054 16.459 0.2005 re
+f*
+1 g
+343.167 406.054 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+346.379 406.054 68.4453 0.2005 re
+f*
+0 g
+252.843 406.255 18.4662 0.2005 re
+f*
+1 g
+271.31 406.255 7.6273 0.2005 re
+f*
+0 g
+278.937 406.255 31.3122 0.2005 re
+f*
+1 g
+310.249 406.255 6.6237 0.2005 re
+f*
+0.498 0 0.482 rg
+316.873 406.255 4.8174 0.2005 re
+f*
+1 g
+321.69 406.255 4.6165 0.2005 re
+f*
+0 g
+326.307 406.255 17.4626 0.2005 re
+f*
+1 g
+343.769 406.255 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+346.78 406.255 68.0438 0.2005 re
+f*
+0 g
+252.843 406.455 18.4662 0.2006 re
+f*
+1 g
+271.31 406.455 7.6273 0.2006 re
+f*
+0 g
+278.937 406.455 31.3122 0.2006 re
+f*
+1 g
+310.249 406.455 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+316.873 406.455 4.2152 0.2006 re
+f*
+1 g
+321.088 406.455 4.6165 0.2006 re
+f*
+0 g
+325.704 406.455 18.4662 0.2006 re
+f*
+1 g
+344.171 406.455 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+347.382 406.455 67.6425 0.2006 re
+f*
+0 g
+252.843 406.656 18.4662 0.2006 re
+f*
+1 g
+271.31 406.656 7.6273 0.2006 re
+f*
+0 g
+278.937 406.656 31.3122 0.2006 re
+f*
+1 g
+310.249 406.656 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+316.873 406.656 3.8138 0.2006 re
+f*
+1 g
+320.687 406.656 4.6165 0.2006 re
+f*
+0 g
+325.303 406.656 19.269 0.2006 re
+f*
+1 g
+344.572 406.656 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+347.784 406.656 67.241 0.2006 re
+f*
+0 g
+252.643 406.856 18.6669 0.2005 re
+f*
+1 g
+271.31 406.856 7.6273 0.2005 re
+f*
+0 g
+278.937 406.856 31.3122 0.2005 re
+f*
+1 g
+310.249 406.856 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 406.856 3.0108 0.2005 re
+f*
+1 g
+320.084 406.856 4.8172 0.2005 re
+f*
+0 g
+324.902 406.856 20.0719 0.2005 re
+f*
+1 g
+344.973 406.856 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+348.185 406.856 67.0402 0.2005 re
+f*
+0 g
+252.643 407.057 18.6669 0.2006 re
+f*
+1 g
+271.31 407.057 7.6273 0.2006 re
+f*
+0 g
+278.937 407.057 31.3122 0.2006 re
+f*
+1 g
+310.249 407.057 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 407.057 2.4086 0.2006 re
+f*
+1 g
+319.482 407.057 4.8173 0.2006 re
+f*
+0 g
+324.299 407.057 21.0755 0.2006 re
+f*
+1 g
+345.375 407.057 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+348.386 407.057 66.8395 0.2006 re
+f*
+0 g
+252.442 407.257 18.8677 0.2005 re
+f*
+1 g
+271.31 407.257 7.6273 0.2005 re
+f*
+0 g
+278.937 407.257 31.1115 0.2005 re
+f*
+1 g
+310.048 407.257 7.0252 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 407.257 2.0071 0.2005 re
+f*
+1 g
+319.081 407.257 5.0181 0.2005 re
+f*
+0 g
+324.099 407.257 21.4769 0.2005 re
+f*
+1 g
+345.576 407.257 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+348.787 407.257 66.438 0.2005 re
+f*
+0 g
+252.442 407.458 18.8677 0.2006 re
+f*
+1 g
+271.31 407.458 7.6273 0.2006 re
+f*
+0 g
+278.937 407.458 31.1115 0.2006 re
+f*
+1 g
+310.048 407.458 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 407.458 1.405 0.2006 re
+f*
+1 g
+318.479 407.458 5.2187 0.2006 re
+f*
+0 g
+323.697 407.458 22.2798 0.2006 re
+f*
+1 g
+345.977 407.458 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+349.189 407.458 66.2374 0.2006 re
+f*
+0 g
+252.442 407.658 18.8677 0.2006 re
+f*
+1 g
+271.31 407.658 7.6273 0.2006 re
+f*
+0 g
+278.937 407.658 31.1115 0.2006 re
+f*
+1 g
+310.048 407.658 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+317.074 407.658 1.0035 0.2006 re
+f*
+1 g
+318.077 407.658 5.2188 0.2006 re
+f*
+0 g
+323.296 407.658 22.882 0.2006 re
+f*
+1 g
+346.178 407.658 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+349.389 407.658 66.0367 0.2006 re
+f*
+0 g
+252.241 407.859 19.0684 0.2005 re
+f*
+1 g
+271.31 407.859 7.6273 0.2005 re
+f*
+0 g
+278.937 407.859 31.1115 0.2005 re
+f*
+1 g
+310.048 407.859 7.0252 0.2005 re
+f*
+0.498 0 0.482 rg
+317.074 407.859 0.4014 0.2005 re
+f*
+1 g
+317.475 407.859 5.4194 0.2005 re
+f*
+0 g
+322.894 407.859 23.6849 0.2005 re
+f*
+1 g
+346.579 407.859 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+349.791 407.859 65.8359 0.2005 re
+f*
+0 g
+252.241 408.06 19.0684 0.2006 re
+f*
+1 g
+271.31 408.06 7.6273 0.2006 re
+f*
+0 g
+278.937 408.06 31.1115 0.2006 re
+f*
+1 g
+310.048 408.06 12.6454 0.2006 re
+f*
+0 g
+322.694 408.06 24.0863 0.2006 re
+f*
+1 g
+346.78 408.06 3.2114 0.2006 re
+f*
+0.498 0 0.482 rg
+349.991 408.06 65.6353 0.2006 re
+f*
+0 g
+252.241 408.26 19.0684 0.2005 re
+f*
+1 g
+271.31 408.26 7.6273 0.2005 re
+f*
+0 g
+278.937 408.26 31.1115 0.2005 re
+f*
+1 g
+310.048 408.26 12.2439 0.2005 re
+f*
+0 g
+322.292 408.26 24.8892 0.2005 re
+f*
+1 g
+347.181 408.26 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+350.393 408.26 65.2338 0.2005 re
+f*
+0 g
+252.04 408.461 19.2691 0.2006 re
+f*
+1 g
+271.31 408.461 7.6273 0.2006 re
+f*
+0 g
+278.937 408.461 31.1115 0.2006 re
+f*
+1 g
+310.048 408.461 12.0432 0.2006 re
+f*
+0 g
+322.092 408.461 25.2906 0.2006 re
+f*
+1 g
+347.382 408.461 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+350.594 408.461 65.2338 0.2006 re
+f*
+0 g
+252.04 408.661 19.2691 0.2006 re
+f*
+1 g
+271.31 408.661 7.6273 0.2006 re
+f*
+0 g
+278.937 408.661 30.9108 0.2006 re
+f*
+1 g
+309.848 408.661 11.8425 0.2006 re
+f*
+0 g
+321.69 408.661 25.8927 0.2006 re
+f*
+1 g
+347.583 408.661 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+350.794 408.661 65.033 0.2006 re
+f*
+0 g
+252.04 408.862 19.2691 0.2005 re
+f*
+1 g
+271.31 408.862 7.6273 0.2005 re
+f*
+0 g
+278.937 408.862 30.9108 0.2005 re
+f*
+1 g
+309.848 408.862 11.6417 0.2005 re
+f*
+0 g
+321.489 408.862 26.2943 0.2005 re
+f*
+1 g
+347.784 408.862 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+351.196 408.862 64.6316 0.2005 re
+f*
+0 g
+251.84 409.062 19.4698 0.2006 re
+f*
+1 g
+271.31 409.062 7.6273 0.2006 re
+f*
+0 g
+278.937 409.062 30.9108 0.2006 re
+f*
+1 g
+309.848 409.062 11.2403 0.2006 re
+f*
+0 g
+321.088 409.062 26.8963 0.2006 re
+f*
+1 g
+347.984 409.062 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+351.397 409.062 64.6317 0.2006 re
+f*
+0 g
+251.84 409.263 19.4698 0.2005 re
+f*
+1 g
+271.31 409.263 7.6273 0.2005 re
+f*
+0 g
+278.937 409.263 30.9108 0.2005 re
+f*
+1 g
+309.848 409.263 11.0395 0.2005 re
+f*
+0 g
+320.887 409.263 27.4986 0.2005 re
+f*
+1 g
+348.386 409.263 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+351.597 409.263 64.431 0.2005 re
+f*
+0 g
+251.84 409.463 19.4698 0.2006 re
+f*
+1 g
+271.31 409.463 7.6273 0.2006 re
+f*
+0 g
+278.937 409.463 30.9108 0.2006 re
+f*
+1 g
+309.848 409.463 10.8389 0.2006 re
+f*
+0 g
+320.687 409.463 27.6992 0.2006 re
+f*
+1 g
+348.386 409.463 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+351.999 409.463 64.0296 0.2006 re
+f*
+0 g
+251.639 409.664 19.6705 0.2006 re
+f*
+1 g
+271.31 409.664 7.6273 0.2006 re
+f*
+0 g
+278.937 409.664 30.9108 0.2006 re
+f*
+1 g
+309.848 409.664 10.4374 0.2006 re
+f*
+0 g
+320.285 409.664 28.3014 0.2006 re
+f*
+1 g
+348.586 409.664 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+352.2 409.664 64.0294 0.2006 re
+f*
+0 g
+251.639 409.865 19.6705 0.2005 re
+f*
+1 g
+271.31 409.865 7.6273 0.2005 re
+f*
+0 g
+278.937 409.865 30.9108 0.2005 re
+f*
+1 g
+309.848 409.865 10.2367 0.2005 re
+f*
+0 g
+320.084 409.865 28.7029 0.2005 re
+f*
+1 g
+348.787 409.865 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+352.4 409.865 63.8287 0.2005 re
+f*
+0 g
+251.639 410.065 19.6705 0.2006 re
+f*
+1 g
+271.31 410.065 7.6273 0.2006 re
+f*
+0 g
+278.937 410.065 30.7101 0.2006 re
+f*
+1 g
+309.647 410.065 10.2366 0.2006 re
+f*
+0 g
+319.884 410.065 29.1043 0.2006 re
+f*
+1 g
+348.988 410.065 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+352.601 410.065 63.628 0.2006 re
+f*
+0 g
+251.438 410.266 19.8713 0.2005 re
+f*
+1 g
+271.31 410.266 7.6273 0.2005 re
+f*
+0 g
+278.937 410.266 30.7101 0.2005 re
+f*
+1 g
+309.647 410.266 10.036 0.2005 re
+f*
+0 g
+319.683 410.266 29.5057 0.2005 re
+f*
+1 g
+349.189 410.266 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+352.802 410.266 63.4272 0.2005 re
+f*
+0 g
+251.438 410.466 19.8713 0.2006 re
+f*
+1 g
+271.31 410.466 7.6273 0.2006 re
+f*
+0 g
+278.937 410.466 30.7101 0.2006 re
+f*
+1 g
+309.647 410.466 9.8352 0.2006 re
+f*
+0 g
+319.482 410.466 29.9072 0.2006 re
+f*
+1 g
+349.389 410.466 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+353.002 410.466 63.4274 0.2006 re
+f*
+0 g
+251.438 410.667 19.8713 0.2005 re
+f*
+1 g
+271.31 410.667 7.6273 0.2005 re
+f*
+0 g
+278.937 410.667 30.7101 0.2005 re
+f*
+1 g
+309.647 410.667 9.6345 0.2005 re
+f*
+0 g
+319.281 410.667 30.3086 0.2005 re
+f*
+1 g
+349.59 410.667 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 410.667 63.2266 0.2005 re
+f*
+0 g
+251.438 410.867 19.8713 0.2006 re
+f*
+1 g
+271.31 410.867 7.6273 0.2006 re
+f*
+0 g
+278.937 410.867 30.7101 0.2006 re
+f*
+1 g
+309.647 410.867 9.4337 0.2006 re
+f*
+0 g
+319.081 410.867 30.5094 0.2006 re
+f*
+1 g
+349.59 410.867 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+353.404 410.867 63.0259 0.2006 re
+f*
+0 g
+251.238 411.068 20.072 0.2006 re
+f*
+1 g
+271.31 411.068 7.6273 0.2006 re
+f*
+0 g
+278.937 411.068 30.7101 0.2006 re
+f*
+1 g
+309.647 411.068 9.233 0.2006 re
+f*
+0 g
+318.88 411.068 30.9109 0.2006 re
+f*
+1 g
+349.791 411.068 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+353.605 411.068 62.8252 0.2006 re
+f*
+0 g
+251.238 411.268 20.072 0.2005 re
+f*
+1 g
+271.31 411.268 7.6273 0.2005 re
+f*
+0 g
+278.937 411.268 30.7101 0.2005 re
+f*
+1 g
+309.647 411.268 9.0324 0.2005 re
+f*
+0 g
+318.679 411.268 31.3121 0.2005 re
+f*
+1 g
+349.991 411.268 3.8138 0.2005 re
+f*
+0.498 0 0.482 rg
+353.805 411.268 28.7028 0.2005 re
+f*
+1 g
+382.508 411.268 1.2043 0.2005 re
+f*
+0.498 0 0.482 rg
+383.712 411.268 23.2835 0.2005 re
+f*
+1 g
+406.996 411.268 1.6057 0.2005 re
+f*
+0.498 0 0.482 rg
+408.602 411.268 8.0288 0.2005 re
+f*
+0 g
+251.238 411.469 20.072 0.2006 re
+f*
+1 g
+271.31 411.469 7.6273 0.2006 re
+f*
+0 g
+278.937 411.469 30.7101 0.2006 re
+f*
+1 g
+309.647 411.469 8.8316 0.2006 re
+f*
+0 g
+318.479 411.469 31.5129 0.2006 re
+f*
+1 g
+349.991 411.469 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+354.006 411.469 27.0972 0.2006 re
+f*
+1 g
+381.103 411.469 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+384.917 411.469 20.4734 0.2006 re
+f*
+1 g
+405.39 411.469 4.6166 0.2006 re
+f*
+0.498 0 0.482 rg
+410.007 411.469 6.6237 0.2006 re
+f*
+0 g
+251.238 411.67 20.072 0.2005 re
+f*
+1 g
+271.31 411.67 7.6273 0.2005 re
+f*
+0 g
+278.937 411.67 30.5094 0.2005 re
+f*
+1 g
+309.446 411.67 8.8316 0.2005 re
+f*
+0 g
+318.278 411.67 31.9144 0.2005 re
+f*
+1 g
+350.192 411.67 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+354.207 411.67 26.2942 0.2005 re
+f*
+1 g
+380.501 411.67 4.8173 0.2005 re
+f*
+0.498 0 0.482 rg
+385.318 411.67 19.269 0.2005 re
+f*
+1 g
+404.587 411.67 6.0216 0.2005 re
+f*
+0.498 0 0.482 rg
+410.609 411.67 6.0216 0.2005 re
+f*
+0 g
+251.037 411.87 20.2727 0.2006 re
+f*
+1 g
+271.31 411.87 7.6273 0.2006 re
+f*
+0 g
+278.937 411.87 30.5094 0.2006 re
+f*
+1 g
+309.446 411.87 8.6308 0.2006 re
+f*
+0 g
+318.077 411.87 32.3159 0.2006 re
+f*
+1 g
+350.393 411.87 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+354.407 411.87 6.2223 0.2006 re
+f*
+1 g
+360.63 411.87 9.6345 0.2006 re
+f*
+0.498 0 0.482 rg
+370.264 411.87 9.6346 0.2006 re
+f*
+1 g
+379.899 411.87 5.8208 0.2006 re
+f*
+0.498 0 0.482 rg
+385.72 411.87 2.6093 0.2006 re
+f*
+1 g
+388.329 411.87 0.2008 0.2006 re
+f*
+0.498 0 0.482 rg
+388.53 411.87 6.2223 0.2006 re
+f*
+1 g
+394.752 411.87 0.2007 0.2006 re
+f*
+0.498 0 0.482 rg
+394.953 411.87 9.0324 0.2006 re
+f*
+1 g
+403.985 411.87 7.2259 0.2006 re
+f*
+0.498 0 0.482 rg
+411.211 411.87 5.4194 0.2006 re
+f*
+0 g
+251.037 412.071 20.2727 0.2006 re
+f*
+1 g
+271.31 412.071 7.6273 0.2006 re
+f*
+0 g
+278.937 412.071 30.5094 0.2006 re
+f*
+1 g
+309.446 412.071 8.4301 0.2006 re
+f*
+0 g
+317.876 412.071 32.5166 0.2006 re
+f*
+1 g
+350.393 412.071 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+354.407 412.071 6.2223 0.2006 re
+f*
+1 g
+360.63 412.071 9.6345 0.2006 re
+f*
+0.498 0 0.482 rg
+370.264 412.071 9.2331 0.2006 re
+f*
+1 g
+379.497 412.071 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+382.307 412.071 1.6058 0.2006 re
+f*
+1 g
+383.913 412.071 2.2078 0.2006 re
+f*
+0.498 0 0.482 rg
+386.121 412.071 2.2079 0.2006 re
+f*
+1 g
+388.329 412.071 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+394.953 412.071 8.6309 0.2006 re
+f*
+1 g
+403.584 412.071 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+406.996 412.071 1.8065 0.2006 re
+f*
+1 g
+408.802 412.071 2.6093 0.2006 re
+f*
+0.498 0 0.482 rg
+411.412 412.071 5.4194 0.2006 re
+f*
+0 g
+251.037 412.271 20.2727 0.2005 re
+f*
+1 g
+271.31 412.271 7.6273 0.2005 re
+f*
+0 g
+278.937 412.271 30.5094 0.2005 re
+f*
+1 g
+309.446 412.271 8.2295 0.2005 re
+f*
+0 g
+317.676 412.271 32.9179 0.2005 re
+f*
+1 g
+350.594 412.271 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+354.608 412.271 6.0216 0.2005 re
+f*
+1 g
+360.63 412.271 9.6345 0.2005 re
+f*
+0.498 0 0.482 rg
+370.264 412.271 8.8317 0.2005 re
+f*
+1 g
+379.096 412.271 2.2079 0.2005 re
+f*
+0.498 0 0.482 rg
+381.304 412.271 3.4122 0.2005 re
+f*
+1 g
+384.716 412.271 1.6057 0.2005 re
+f*
+0.498 0 0.482 rg
+386.322 412.271 2.0072 0.2005 re
+f*
+1 g
+388.329 412.271 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+394.953 412.271 8.2295 0.2005 re
+f*
+1 g
+403.182 412.271 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+406.193 412.271 3.613 0.2005 re
+f*
+1 g
+409.806 412.271 2.0071 0.2005 re
+f*
+0.498 0 0.482 rg
+411.813 412.271 5.018 0.2005 re
+f*
+0 g
+251.037 412.472 20.2727 0.2006 re
+f*
+1 g
+271.31 412.472 7.6273 0.2006 re
+f*
+0 g
+278.937 412.472 30.5094 0.2006 re
+f*
+1 g
+309.446 412.472 8.0287 0.2006 re
+f*
+0 g
+317.475 412.472 21.0756 0.2006 re
+f*
+1 g
+338.551 412.472 2.81 0.2006 re
+f*
+0 g
+341.361 412.472 9.2331 0.2006 re
+f*
+1 g
+350.594 412.472 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+354.809 412.472 5.8209 0.2006 re
+f*
+1 g
+360.63 412.472 9.6345 0.2006 re
+f*
+0.498 0 0.482 rg
+370.264 412.472 8.4303 0.2006 re
+f*
+1 g
+378.695 412.472 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+380.902 412.472 4.4158 0.2006 re
+f*
+1 g
+385.318 412.472 1.405 0.2006 re
+f*
+0.498 0 0.482 rg
+386.723 412.472 1.6057 0.2006 re
+f*
+1 g
+388.329 412.472 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+394.953 412.472 7.8281 0.2006 re
+f*
+1 g
+402.781 412.472 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+405.591 412.472 4.8172 0.2006 re
+f*
+1 g
+410.408 412.472 1.8065 0.2006 re
+f*
+0.498 0 0.482 rg
+412.215 412.472 4.6165 0.2006 re
+f*
+0 g
+250.836 412.672 20.4734 0.2005 re
+f*
+1 g
+271.31 412.672 7.6273 0.2005 re
+f*
+0 g
+278.937 412.672 30.5094 0.2005 re
+f*
+1 g
+309.446 412.672 8.0287 0.2005 re
+f*
+0 g
+317.475 412.672 19.8713 0.2005 re
+f*
+1 g
+337.346 412.672 5.0179 0.2005 re
+f*
+0 g
+342.364 412.672 8.4303 0.2005 re
+f*
+1 g
+350.794 412.672 4.215 0.2005 re
+f*
+0.498 0 0.482 rg
+355.01 412.672 8.631 0.2005 re
+f*
+1 g
+363.641 412.672 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 412.672 11.2403 0.2005 re
+f*
+1 g
+378.494 412.672 2.0072 0.2005 re
+f*
+0.498 0 0.482 rg
+380.501 412.672 5.018 0.2005 re
+f*
+1 g
+385.519 412.672 1.4051 0.2005 re
+f*
+0.498 0 0.482 rg
+386.924 412.672 1.4049 0.2005 re
+f*
+1 g
+388.329 412.672 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 412.672 10.6381 0.2005 re
+f*
+1 g
+402.379 412.672 2.8101 0.2005 re
+f*
+0.498 0 0.482 rg
+405.189 412.672 5.6201 0.2005 re
+f*
+1 g
+410.81 412.672 1.6058 0.2005 re
+f*
+0.498 0 0.482 rg
+412.415 412.672 4.4158 0.2005 re
+f*
+0 g
+250.836 412.873 20.4734 0.2006 re
+f*
+1 g
+271.31 412.873 7.6273 0.2006 re
+f*
+0 g
+278.937 412.873 30.5094 0.2006 re
+f*
+1 g
+309.446 412.873 7.828 0.2006 re
+f*
+0 g
+317.274 412.873 19.4698 0.2006 re
+f*
+1 g
+336.744 412.873 6.2223 0.2006 re
+f*
+0 g
+342.966 412.873 8.0287 0.2006 re
+f*
+1 g
+350.995 412.873 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+355.21 412.873 8.4302 0.2006 re
+f*
+1 g
+363.641 412.873 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 412.873 10.8389 0.2006 re
+f*
+1 g
+378.092 412.873 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+380.3 412.873 5.6201 0.2006 re
+f*
+1 g
+385.92 412.873 1.2043 0.2006 re
+f*
+0.498 0 0.482 rg
+387.125 412.873 1.2043 0.2006 re
+f*
+1 g
+388.329 412.873 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 412.873 10.4374 0.2006 re
+f*
+1 g
+402.179 412.873 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+404.989 412.873 6.2223 0.2006 re
+f*
+1 g
+411.211 412.873 1.405 0.2006 re
+f*
+0.498 0 0.482 rg
+412.616 412.873 4.4159 0.2006 re
+f*
+0 g
+250.836 413.073 20.4734 0.2006 re
+f*
+1 g
+271.31 413.073 7.6273 0.2006 re
+f*
+0 g
+278.937 413.073 13.0467 0.2006 re
+f*
+1 g
+291.984 413.073 10.036 0.2006 re
+f*
+0 g
+302.02 413.073 7.4267 0.2006 re
+f*
+1 g
+309.446 413.073 7.6273 0.2006 re
+f*
+0 g
+317.074 413.073 19.0683 0.2006 re
+f*
+1 g
+336.142 413.073 7.2259 0.2006 re
+f*
+0 g
+343.368 413.073 7.6273 0.2006 re
+f*
+1 g
+350.995 413.073 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+355.21 413.073 8.4302 0.2006 re
+f*
+1 g
+363.641 413.073 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 413.073 10.6382 0.2006 re
+f*
+1 g
+377.892 413.073 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+380.099 413.073 6.0215 0.2006 re
+f*
+1 g
+386.121 413.073 1.2043 0.2006 re
+f*
+0.498 0 0.482 rg
+387.325 413.073 1.0036 0.2006 re
+f*
+1 g
+388.329 413.073 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 413.073 10.036 0.2006 re
+f*
+1 g
+401.777 413.073 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+404.788 413.073 6.8244 0.2006 re
+f*
+1 g
+411.612 413.073 1.2043 0.2006 re
+f*
+0.498 0 0.482 rg
+412.817 413.073 4.2152 0.2006 re
+f*
+0 g
+250.836 413.274 20.4734 0.2005 re
+f*
+1 g
+271.31 413.274 7.6273 0.2005 re
+f*
+0 g
+278.937 413.274 13.0467 0.2005 re
+f*
+1 g
+291.984 413.274 10.036 0.2005 re
+f*
+0 g
+302.02 413.274 7.4267 0.2005 re
+f*
+1 g
+309.446 413.274 7.4265 0.2005 re
+f*
+0 g
+316.873 413.274 18.667 0.2005 re
+f*
+1 g
+335.54 413.274 3.2115 0.2005 re
+f*
+0 g
+338.751 413.274 2.8101 0.2005 re
+f*
+1 g
+341.561 413.274 2.2079 0.2005 re
+f*
+0 g
+343.769 413.274 7.4266 0.2005 re
+f*
+1 g
+351.196 413.274 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+355.411 413.274 8.2295 0.2005 re
+f*
+1 g
+363.641 413.274 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 413.274 10.4375 0.2005 re
+f*
+1 g
+377.691 413.274 2.2079 0.2005 re
+f*
+0.498 0 0.482 rg
+379.899 413.274 6.4229 0.2005 re
+f*
+1 g
+386.322 413.274 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+387.325 413.274 1.0036 0.2005 re
+f*
+1 g
+388.329 413.274 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 413.274 9.8352 0.2005 re
+f*
+1 g
+401.576 413.274 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+404.587 413.274 7.2259 0.2005 re
+f*
+1 g
+411.813 413.274 1.2044 0.2005 re
+f*
+0.498 0 0.482 rg
+413.017 413.274 4.0144 0.2005 re
+f*
+0 g
+250.836 413.475 20.4734 0.2006 re
+f*
+1 g
+271.31 413.475 7.6273 0.2006 re
+f*
+0 g
+278.937 413.475 13.0467 0.2006 re
+f*
+1 g
+291.984 413.475 10.036 0.2006 re
+f*
+0 g
+302.02 413.475 7.4267 0.2006 re
+f*
+1 g
+309.446 413.475 7.4265 0.2006 re
+f*
+0 g
+316.873 413.475 18.2655 0.2006 re
+f*
+1 g
+335.138 413.475 3.0108 0.2006 re
+f*
+0 g
+338.149 413.475 4.2151 0.2006 re
+f*
+1 g
+342.364 413.475 1.8065 0.2006 re
+f*
+0 g
+344.171 413.475 7.0252 0.2006 re
+f*
+1 g
+351.196 413.475 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+355.612 413.475 8.0288 0.2006 re
+f*
+1 g
+363.641 413.475 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 413.475 10.2367 0.2006 re
+f*
+1 g
+377.49 413.475 2.208 0.2006 re
+f*
+0.498 0 0.482 rg
+379.698 413.475 6.8244 0.2006 re
+f*
+1 g
+386.522 413.475 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+387.526 413.475 0.8028 0.2006 re
+f*
+1 g
+388.329 413.475 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 413.475 9.6345 0.2006 re
+f*
+1 g
+401.376 413.475 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+404.386 413.475 7.6274 0.2006 re
+f*
+1 g
+412.014 413.475 1.2042 0.2006 re
+f*
+0.498 0 0.482 rg
+413.218 413.475 3.8138 0.2006 re
+f*
+0 g
+250.836 413.675 20.4734 0.2005 re
+f*
+1 g
+271.31 413.675 7.6273 0.2005 re
+f*
+0 g
+278.937 413.675 13.0467 0.2005 re
+f*
+1 g
+291.984 413.675 10.036 0.2005 re
+f*
+0 g
+302.02 413.675 7.4267 0.2005 re
+f*
+1 g
+309.446 413.675 7.2258 0.2005 re
+f*
+0 g
+316.672 413.675 18.2655 0.2005 re
+f*
+1 g
+334.938 413.675 2.8101 0.2005 re
+f*
+0 g
+337.748 413.675 5.018 0.2005 re
+f*
+1 g
+342.766 413.675 1.6057 0.2005 re
+f*
+0 g
+344.371 413.675 6.8245 0.2005 re
+f*
+1 g
+351.196 413.675 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+355.812 413.675 7.8281 0.2005 re
+f*
+1 g
+363.641 413.675 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 413.675 10.036 0.2005 re
+f*
+1 g
+377.289 413.675 2.2079 0.2005 re
+f*
+0.498 0 0.482 rg
+379.497 413.675 7.2259 0.2005 re
+f*
+1 g
+386.723 413.675 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+387.727 413.675 0.6021 0.2005 re
+f*
+1 g
+388.329 413.675 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 413.675 9.4338 0.2005 re
+f*
+1 g
+401.175 413.675 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+404.186 413.675 8.0288 0.2005 re
+f*
+1 g
+412.215 413.675 1.2043 0.2005 re
+f*
+0.498 0 0.482 rg
+413.419 413.675 3.613 0.2005 re
+f*
+0 g
+250.635 413.876 20.6741 0.2006 re
+f*
+1 g
+271.31 413.876 7.6273 0.2006 re
+f*
+0 g
+278.937 413.876 16.0575 0.2006 re
+f*
+1 g
+294.994 413.876 3.613 0.2006 re
+f*
+0 g
+298.607 413.876 10.8389 0.2006 re
+f*
+1 g
+309.446 413.876 7.0251 0.2006 re
+f*
+0 g
+316.471 413.876 18.0648 0.2006 re
+f*
+1 g
+334.536 413.876 2.8101 0.2006 re
+f*
+0 g
+337.346 413.876 5.8208 0.2006 re
+f*
+1 g
+343.167 413.876 1.6058 0.2006 re
+f*
+0 g
+344.773 413.876 6.6237 0.2006 re
+f*
+1 g
+351.397 413.876 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+355.812 413.876 7.8281 0.2006 re
+f*
+1 g
+363.641 413.876 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 413.876 9.8353 0.2006 re
+f*
+1 g
+377.089 413.876 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+379.497 413.876 7.4267 0.2006 re
+f*
+1 g
+386.924 413.876 0.8028 0.2006 re
+f*
+0.498 0 0.482 rg
+387.727 413.876 0.6021 0.2006 re
+f*
+1 g
+388.329 413.876 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 413.876 9.2331 0.2006 re
+f*
+1 g
+400.974 413.876 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+403.985 413.876 8.4302 0.2006 re
+f*
+1 g
+412.415 413.876 1.2043 0.2006 re
+f*
+0.498 0 0.482 rg
+413.62 413.876 3.4123 0.2006 re
+f*
+0 g
+250.635 414.076 20.6741 0.2006 re
+f*
+1 g
+271.31 414.076 7.6273 0.2006 re
+f*
+0 g
+278.937 414.076 16.0575 0.2006 re
+f*
+1 g
+294.994 414.076 3.613 0.2006 re
+f*
+0 g
+298.607 414.076 10.6381 0.2006 re
+f*
+1 g
+309.245 414.076 7.2259 0.2006 re
+f*
+0 g
+316.471 414.076 17.6633 0.2006 re
+f*
+1 g
+334.135 414.076 3.0108 0.2006 re
+f*
+0 g
+337.146 414.076 6.423 0.2006 re
+f*
+1 g
+343.568 414.076 1.405 0.2006 re
+f*
+0 g
+344.973 414.076 6.4231 0.2006 re
+f*
+1 g
+351.397 414.076 4.6165 0.2006 re
+f*
+0.498 0 0.482 rg
+356.013 414.076 7.6274 0.2006 re
+f*
+1 g
+363.641 414.076 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 414.076 9.6346 0.2006 re
+f*
+1 g
+376.888 414.076 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+379.297 414.076 7.6274 0.2006 re
+f*
+1 g
+386.924 414.076 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+387.928 414.076 0.4013 0.2006 re
+f*
+1 g
+388.329 414.076 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 414.076 9.0324 0.2006 re
+f*
+1 g
+400.774 414.076 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+403.784 414.076 8.8316 0.2006 re
+f*
+1 g
+412.616 414.076 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+413.62 414.076 3.6129 0.2006 re
+f*
+0 g
+250.635 414.277 20.6741 0.2005 re
+f*
+1 g
+271.31 414.277 7.6273 0.2005 re
+f*
+0 g
+278.937 414.277 16.0575 0.2005 re
+f*
+1 g
+294.994 414.277 3.613 0.2005 re
+f*
+0 g
+298.607 414.277 10.6381 0.2005 re
+f*
+1 g
+309.245 414.277 7.0252 0.2005 re
+f*
+0 g
+316.271 414.277 17.6633 0.2005 re
+f*
+1 g
+333.934 414.277 3.0108 0.2005 re
+f*
+0 g
+336.945 414.277 6.8245 0.2005 re
+f*
+1 g
+343.769 414.277 1.405 0.2005 re
+f*
+0 g
+345.174 414.277 6.423 0.2005 re
+f*
+1 g
+351.597 414.277 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+356.214 414.277 7.4267 0.2005 re
+f*
+1 g
+363.641 414.277 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 414.277 9.4339 0.2005 re
+f*
+1 g
+376.687 414.277 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+379.297 414.277 7.828 0.2005 re
+f*
+1 g
+387.125 414.277 0.803 0.2005 re
+f*
+0.498 0 0.482 rg
+387.928 414.277 0.4013 0.2005 re
+f*
+1 g
+388.329 414.277 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 414.277 8.8316 0.2005 re
+f*
+1 g
+400.573 414.277 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+403.784 414.277 9.0323 0.2005 re
+f*
+1 g
+412.817 414.277 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+413.82 414.277 3.4122 0.2005 re
+f*
+0 g
+250.635 414.477 20.6741 0.2005 re
+f*
+1 g
+271.31 414.477 7.6273 0.2005 re
+f*
+0 g
+278.937 414.477 16.0575 0.2005 re
+f*
+1 g
+294.994 414.477 3.613 0.2005 re
+f*
+0 g
+298.607 414.477 10.6381 0.2005 re
+f*
+1 g
+309.245 414.477 6.8245 0.2005 re
+f*
+0 g
+316.07 414.477 17.6633 0.2005 re
+f*
+1 g
+333.733 414.477 3.0108 0.2005 re
+f*
+0 g
+336.744 414.477 7.4266 0.2005 re
+f*
+1 g
+344.171 414.477 1.2043 0.2005 re
+f*
+0 g
+345.375 414.477 6.2223 0.2005 re
+f*
+1 g
+351.597 414.477 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+356.214 414.477 7.4267 0.2005 re
+f*
+1 g
+363.641 414.477 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 414.477 9.2331 0.2005 re
+f*
+1 g
+376.486 414.477 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+379.096 414.477 8.0287 0.2005 re
+f*
+1 g
+387.125 414.477 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+388.128 414.477 0.2007 0.2005 re
+f*
+1 g
+388.329 414.477 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 414.477 8.6309 0.2005 re
+f*
+1 g
+400.372 414.477 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+403.584 414.477 9.4339 0.2005 re
+f*
+1 g
+413.017 414.477 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+414.021 414.477 3.2114 0.2005 re
+f*
+0 g
+250.635 414.678 20.6741 0.2006 re
+f*
+1 g
+271.31 414.678 7.6273 0.2006 re
+f*
+0 g
+278.937 414.678 16.0575 0.2006 re
+f*
+1 g
+294.994 414.678 3.613 0.2006 re
+f*
+0 g
+298.607 414.678 10.6381 0.2006 re
+f*
+1 g
+309.245 414.678 6.8245 0.2006 re
+f*
+0 g
+316.07 414.678 17.4626 0.2006 re
+f*
+1 g
+333.533 414.678 3.0108 0.2006 re
+f*
+0 g
+336.543 414.678 7.828 0.2006 re
+f*
+1 g
+344.371 414.678 1.0036 0.2006 re
+f*
+0 g
+345.375 414.678 6.2223 0.2006 re
+f*
+1 g
+351.597 414.678 4.8173 0.2006 re
+f*
+0.498 0 0.482 rg
+356.415 414.678 7.2259 0.2006 re
+f*
+1 g
+363.641 414.678 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 414.678 9.0324 0.2006 re
+f*
+1 g
+376.286 414.678 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+379.096 414.678 8.2294 0.2006 re
+f*
+1 g
+387.325 414.678 0.8029 0.2006 re
+f*
+0.498 0 0.482 rg
+388.128 414.678 0.2007 0.2006 re
+f*
+1 g
+388.329 414.678 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 414.678 8.4302 0.2006 re
+f*
+1 g
+400.171 414.678 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+403.584 414.678 9.4339 0.2006 re
+f*
+1 g
+413.017 414.678 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.021 414.678 3.2114 0.2006 re
+f*
+0 g
+250.635 414.878 20.6741 0.2006 re
+f*
+1 g
+271.31 414.878 7.6273 0.2006 re
+f*
+0 g
+278.937 414.878 16.0575 0.2006 re
+f*
+1 g
+294.994 414.878 3.613 0.2006 re
+f*
+0 g
+298.607 414.878 10.6381 0.2006 re
+f*
+1 g
+309.245 414.878 6.6237 0.2006 re
+f*
+0 g
+315.869 414.878 17.4626 0.2006 re
+f*
+1 g
+333.332 414.878 3.0109 0.2006 re
+f*
+0 g
+336.343 414.878 8.2294 0.2006 re
+f*
+1 g
+344.572 414.878 1.0036 0.2006 re
+f*
+0 g
+345.576 414.878 6.2224 0.2006 re
+f*
+1 g
+351.798 414.878 4.6165 0.2006 re
+f*
+0.498 0 0.482 rg
+356.415 414.878 7.2259 0.2006 re
+f*
+1 g
+363.641 414.878 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 414.878 8.8317 0.2006 re
+f*
+1 g
+376.085 414.878 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+379.096 414.878 8.2294 0.2006 re
+f*
+1 g
+387.325 414.878 4.4159 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 414.878 8.4302 0.2006 re
+f*
+1 g
+400.171 414.878 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+403.383 414.878 9.8352 0.2006 re
+f*
+1 g
+413.218 414.878 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.222 414.878 3.0108 0.2006 re
+f*
+0 g
+250.435 415.079 20.8749 0.2006 re
+f*
+1 g
+271.31 415.079 7.6273 0.2006 re
+f*
+0 g
+278.937 415.079 16.0575 0.2006 re
+f*
+1 g
+294.994 415.079 3.613 0.2006 re
+f*
+0 g
+298.607 415.079 10.6381 0.2006 re
+f*
+1 g
+309.245 415.079 6.4231 0.2006 re
+f*
+0 g
+315.669 415.079 17.4625 0.2006 re
+f*
+1 g
+333.131 415.079 3.0108 0.2006 re
+f*
+0 g
+336.142 415.079 8.631 0.2006 re
+f*
+1 g
+344.773 415.079 0.8028 0.2006 re
+f*
+0 g
+345.576 415.079 6.2224 0.2006 re
+f*
+1 g
+351.798 415.079 4.8172 0.2006 re
+f*
+0.498 0 0.482 rg
+356.615 415.079 7.0252 0.2006 re
+f*
+1 g
+363.641 415.079 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 415.079 8.8317 0.2006 re
+f*
+1 g
+376.085 415.079 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+378.895 415.079 8.6309 0.2006 re
+f*
+1 g
+387.526 415.079 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 415.079 8.2295 0.2006 re
+f*
+1 g
+399.971 415.079 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+403.383 415.079 9.8352 0.2006 re
+f*
+1 g
+413.218 415.079 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.222 415.079 3.0108 0.2006 re
+f*
+0 g
+250.435 415.28 20.8749 0.2005 re
+f*
+1 g
+271.31 415.28 7.6273 0.2005 re
+f*
+0 g
+278.937 415.28 16.0575 0.2005 re
+f*
+1 g
+294.994 415.28 3.613 0.2005 re
+f*
+0 g
+298.607 415.28 10.6381 0.2005 re
+f*
+1 g
+309.245 415.28 6.4231 0.2005 re
+f*
+0 g
+315.669 415.28 17.2618 0.2005 re
+f*
+1 g
+332.93 415.28 3.0108 0.2005 re
+f*
+0 g
+335.941 415.28 15.8569 0.2005 re
+f*
+1 g
+351.798 415.28 5.0179 0.2005 re
+f*
+0.498 0 0.482 rg
+356.816 415.28 6.8245 0.2005 re
+f*
+1 g
+363.641 415.28 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 415.28 8.631 0.2005 re
+f*
+1 g
+375.884 415.28 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+378.895 415.28 8.6309 0.2005 re
+f*
+1 g
+387.526 415.28 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 415.28 8.0288 0.2005 re
+f*
+1 g
+399.77 415.28 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+403.182 415.28 10.2367 0.2005 re
+f*
+1 g
+413.419 415.28 0.8028 0.2005 re
+f*
+0.498 0 0.482 rg
+414.222 415.28 3.2116 0.2005 re
+f*
+0 g
+250.435 415.48 20.8749 0.2006 re
+f*
+1 g
+271.31 415.48 7.6273 0.2006 re
+f*
+0 g
+278.937 415.48 16.0575 0.2006 re
+f*
+1 g
+294.994 415.48 3.613 0.2006 re
+f*
+0 g
+298.607 415.48 10.6381 0.2006 re
+f*
+1 g
+309.245 415.48 6.2223 0.2006 re
+f*
+0 g
+315.468 415.48 17.2619 0.2006 re
+f*
+1 g
+332.73 415.48 3.2115 0.2006 re
+f*
+0 g
+335.941 415.48 16.0575 0.2006 re
+f*
+1 g
+351.999 415.48 4.8173 0.2006 re
+f*
+0.498 0 0.482 rg
+356.816 415.48 6.8245 0.2006 re
+f*
+1 g
+363.641 415.48 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 415.48 8.4303 0.2006 re
+f*
+1 g
+375.684 415.48 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+378.895 415.48 8.8316 0.2006 re
+f*
+1 g
+387.727 415.48 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 415.48 8.0288 0.2006 re
+f*
+1 g
+399.77 415.48 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+403.182 415.48 10.2367 0.2006 re
+f*
+1 g
+413.419 415.48 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.422 415.48 3.0108 0.2006 re
+f*
+0 g
+250.435 415.681 20.8749 0.2005 re
+f*
+1 g
+271.31 415.681 7.6273 0.2005 re
+f*
+0 g
+278.937 415.681 16.0575 0.2005 re
+f*
+1 g
+294.994 415.681 3.613 0.2005 re
+f*
+0 g
+298.607 415.681 10.6381 0.2005 re
+f*
+1 g
+309.245 415.681 6.2223 0.2005 re
+f*
+0 g
+315.468 415.681 17.0612 0.2005 re
+f*
+1 g
+332.529 415.681 3.2115 0.2005 re
+f*
+0 g
+335.74 415.681 16.2582 0.2005 re
+f*
+1 g
+351.999 415.681 5.018 0.2005 re
+f*
+0.498 0 0.482 rg
+357.017 415.681 6.6238 0.2005 re
+f*
+1 g
+363.641 415.681 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 415.681 8.4303 0.2005 re
+f*
+1 g
+375.684 415.681 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+378.895 415.681 8.8316 0.2005 re
+f*
+1 g
+387.727 415.681 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 415.681 7.828 0.2005 re
+f*
+1 g
+399.569 415.681 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+403.182 415.681 10.4374 0.2005 re
+f*
+1 g
+413.62 415.681 0.8029 0.2005 re
+f*
+0.498 0 0.482 rg
+414.422 415.681 3.0108 0.2005 re
+f*
+0 g
+250.435 415.881 20.8749 0.2006 re
+f*
+1 g
+271.31 415.881 7.6273 0.2006 re
+f*
+0 g
+278.937 415.881 16.0575 0.2006 re
+f*
+1 g
+294.994 415.881 3.613 0.2006 re
+f*
+0 g
+298.607 415.881 10.6381 0.2006 re
+f*
+1 g
+309.245 415.881 6.2223 0.2006 re
+f*
+0 g
+315.468 415.881 16.8604 0.2006 re
+f*
+1 g
+332.328 415.881 3.4123 0.2006 re
+f*
+0 g
+335.74 415.881 16.2582 0.2006 re
+f*
+1 g
+351.999 415.881 5.018 0.2006 re
+f*
+0.498 0 0.482 rg
+357.017 415.881 6.6238 0.2006 re
+f*
+1 g
+363.641 415.881 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 415.881 8.2295 0.2006 re
+f*
+1 g
+375.483 415.881 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 415.881 9.0323 0.2006 re
+f*
+1 g
+387.727 415.881 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 415.881 7.828 0.2006 re
+f*
+1 g
+399.569 415.881 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+403.182 415.881 10.4374 0.2006 re
+f*
+1 g
+413.62 415.881 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.623 415.881 2.8101 0.2006 re
+f*
+0 g
+250.435 416.082 20.8749 0.2006 re
+f*
+1 g
+271.31 416.082 7.6273 0.2006 re
+f*
+0 g
+278.937 416.082 16.0575 0.2006 re
+f*
+1 g
+294.994 416.082 3.613 0.2006 re
+f*
+0 g
+298.607 416.082 10.6381 0.2006 re
+f*
+1 g
+309.245 416.082 6.2223 0.2006 re
+f*
+0 g
+315.468 416.082 16.8604 0.2006 re
+f*
+1 g
+332.328 416.082 3.2116 0.2006 re
+f*
+0 g
+335.54 416.082 16.4589 0.2006 re
+f*
+1 g
+351.999 416.082 5.2187 0.2006 re
+f*
+0.498 0 0.482 rg
+357.217 416.082 6.4231 0.2006 re
+f*
+1 g
+363.641 416.082 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 416.082 8.2295 0.2006 re
+f*
+1 g
+375.483 416.082 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 416.082 9.0323 0.2006 re
+f*
+1 g
+387.727 416.082 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 416.082 7.6273 0.2006 re
+f*
+1 g
+399.368 416.082 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 416.082 10.6381 0.2006 re
+f*
+1 g
+413.62 416.082 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+414.623 416.082 2.8101 0.2006 re
+f*
+0 g
+250.435 416.282 20.8749 0.2005 re
+f*
+1 g
+271.31 416.282 7.6273 0.2005 re
+f*
+0 g
+278.937 416.282 16.0575 0.2005 re
+f*
+1 g
+294.994 416.282 3.613 0.2005 re
+f*
+0 g
+298.607 416.282 10.6381 0.2005 re
+f*
+1 g
+309.245 416.282 6.0216 0.2005 re
+f*
+0 g
+315.267 416.282 16.8604 0.2005 re
+f*
+1 g
+332.127 416.282 3.4123 0.2005 re
+f*
+0 g
+335.54 416.282 16.6597 0.2005 re
+f*
+1 g
+352.2 416.282 5.0179 0.2005 re
+f*
+0.498 0 0.482 rg
+357.217 416.282 6.4231 0.2005 re
+f*
+1 g
+363.641 416.282 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 416.282 8.0288 0.2005 re
+f*
+1 g
+375.282 416.282 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+378.695 416.282 9.2331 0.2005 re
+f*
+1 g
+387.928 416.282 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 416.282 7.6273 0.2005 re
+f*
+1 g
+399.368 416.282 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+402.981 416.282 10.8388 0.2005 re
+f*
+1 g
+413.82 416.282 0.8029 0.2005 re
+f*
+0.498 0 0.482 rg
+414.623 416.282 2.8101 0.2005 re
+f*
+0 g
+250.435 416.483 20.8749 0.2005 re
+f*
+1 g
+271.31 416.483 7.6273 0.2005 re
+f*
+0 g
+278.937 416.483 16.0575 0.2005 re
+f*
+1 g
+294.994 416.483 3.613 0.2005 re
+f*
+0 g
+298.607 416.483 10.6381 0.2005 re
+f*
+1 g
+309.245 416.483 6.0216 0.2005 re
+f*
+0 g
+315.267 416.483 16.6597 0.2005 re
+f*
+1 g
+331.927 416.483 3.613 0.2005 re
+f*
+0 g
+335.54 416.483 16.6597 0.2005 re
+f*
+1 g
+352.2 416.483 5.2187 0.2005 re
+f*
+0.498 0 0.482 rg
+357.418 416.483 6.2223 0.2005 re
+f*
+1 g
+363.641 416.483 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 416.483 8.0288 0.2005 re
+f*
+1 g
+375.282 416.483 3.4123 0.2005 re
+f*
+0.498 0 0.482 rg
+378.695 416.483 9.2331 0.2005 re
+f*
+1 g
+387.928 416.483 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 416.483 7.4266 0.2005 re
+f*
+1 g
+399.168 416.483 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.981 416.483 10.8388 0.2005 re
+f*
+1 g
+413.82 416.483 0.2008 0.2005 re
+f*
+0.498 0 0.482 rg
+414.021 416.483 3.4122 0.2005 re
+f*
+0 g
+250.435 416.683 20.8749 0.2006 re
+f*
+1 g
+271.31 416.683 7.6273 0.2006 re
+f*
+0 g
+278.937 416.683 16.0575 0.2006 re
+f*
+1 g
+294.994 416.683 3.613 0.2006 re
+f*
+0 g
+298.607 416.683 10.6381 0.2006 re
+f*
+1 g
+309.245 416.683 6.0216 0.2006 re
+f*
+0 g
+315.267 416.683 16.6597 0.2006 re
+f*
+1 g
+331.927 416.683 3.4123 0.2006 re
+f*
+0 g
+335.339 416.683 16.8604 0.2006 re
+f*
+1 g
+352.2 416.683 5.2187 0.2006 re
+f*
+0.498 0 0.482 rg
+357.418 416.683 6.2223 0.2006 re
+f*
+1 g
+363.641 416.683 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 416.683 7.8281 0.2006 re
+f*
+1 g
+375.081 416.683 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 416.683 9.2331 0.2006 re
+f*
+1 g
+387.928 416.683 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 416.683 7.4266 0.2006 re
+f*
+1 g
+399.168 416.683 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 416.683 14.4518 0.2006 re
+f*
+0 g
+250.234 416.884 21.0756 0.2006 re
+f*
+1 g
+271.31 416.884 7.6273 0.2006 re
+f*
+0 g
+278.937 416.884 16.0575 0.2006 re
+f*
+1 g
+294.994 416.884 3.613 0.2006 re
+f*
+0 g
+298.607 416.884 10.6381 0.2006 re
+f*
+1 g
+309.245 416.884 6.0216 0.2006 re
+f*
+0 g
+315.267 416.884 16.459 0.2006 re
+f*
+1 g
+331.726 416.884 3.613 0.2006 re
+f*
+0 g
+335.339 416.884 16.8604 0.2006 re
+f*
+1 g
+352.2 416.884 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+357.619 416.884 6.0216 0.2006 re
+f*
+1 g
+363.641 416.884 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 416.884 7.8281 0.2006 re
+f*
+1 g
+375.081 416.884 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 416.884 9.2331 0.2006 re
+f*
+1 g
+387.928 416.884 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 416.884 7.4266 0.2006 re
+f*
+1 g
+399.168 416.884 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 416.884 14.4518 0.2006 re
+f*
+0 g
+250.234 417.085 14.2511 0.2005 re
+f*
+1 g
+264.485 417.085 25.8928 0.2005 re
+f*
+0 g
+290.378 417.085 4.6165 0.2005 re
+f*
+1 g
+294.994 417.085 3.613 0.2005 re
+f*
+0 g
+298.607 417.085 10.6381 0.2005 re
+f*
+1 g
+309.245 417.085 5.8209 0.2005 re
+f*
+0 g
+315.066 417.085 16.6597 0.2005 re
+f*
+1 g
+331.726 417.085 3.613 0.2005 re
+f*
+0 g
+335.339 417.085 17.0611 0.2005 re
+f*
+1 g
+352.4 417.085 5.2187 0.2005 re
+f*
+0.498 0 0.482 rg
+357.619 417.085 6.0216 0.2005 re
+f*
+1 g
+363.641 417.085 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 417.085 7.6274 0.2005 re
+f*
+1 g
+374.881 417.085 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 417.085 9.4339 0.2005 re
+f*
+1 g
+387.928 417.085 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 417.085 7.2259 0.2005 re
+f*
+1 g
+398.967 417.085 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 417.085 14.6525 0.2005 re
+f*
+0 g
+250.234 417.285 14.2511 0.2005 re
+f*
+1 g
+264.485 417.285 25.8928 0.2005 re
+f*
+0 g
+290.378 417.285 4.6165 0.2005 re
+f*
+1 g
+294.994 417.285 3.613 0.2005 re
+f*
+0 g
+298.607 417.285 10.6381 0.2005 re
+f*
+1 g
+309.245 417.285 5.8209 0.2005 re
+f*
+0 g
+315.066 417.285 16.459 0.2005 re
+f*
+1 g
+331.525 417.285 3.6129 0.2005 re
+f*
+0 g
+335.138 417.285 17.2619 0.2005 re
+f*
+1 g
+352.4 417.285 5.4194 0.2005 re
+f*
+0.498 0 0.482 rg
+357.82 417.285 5.8209 0.2005 re
+f*
+1 g
+363.641 417.285 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 417.285 7.6274 0.2005 re
+f*
+1 g
+374.881 417.285 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 417.285 9.6345 0.2005 re
+f*
+1 g
+388.128 417.285 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 417.285 7.2259 0.2005 re
+f*
+1 g
+398.967 417.285 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 417.285 14.8532 0.2005 re
+f*
+0 g
+250.234 417.486 14.2511 0.2006 re
+f*
+1 g
+264.485 417.486 25.8928 0.2006 re
+f*
+0 g
+290.378 417.486 4.6165 0.2006 re
+f*
+1 g
+294.994 417.486 3.613 0.2006 re
+f*
+0 g
+298.607 417.486 10.6381 0.2006 re
+f*
+1 g
+309.245 417.486 5.8209 0.2006 re
+f*
+0 g
+315.066 417.486 16.459 0.2006 re
+f*
+1 g
+331.525 417.486 3.6129 0.2006 re
+f*
+0 g
+335.138 417.486 17.2619 0.2006 re
+f*
+1 g
+352.4 417.486 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+357.82 417.486 5.8209 0.2006 re
+f*
+1 g
+363.641 417.486 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 417.486 7.6274 0.2006 re
+f*
+1 g
+374.881 417.486 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 417.486 9.6345 0.2006 re
+f*
+1 g
+388.128 417.486 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 417.486 7.2259 0.2006 re
+f*
+1 g
+398.967 417.486 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 417.486 14.8532 0.2006 re
+f*
+0 g
+250.234 417.686 14.2511 0.2006 re
+f*
+1 g
+264.485 417.686 25.8928 0.2006 re
+f*
+0 g
+290.378 417.686 4.6165 0.2006 re
+f*
+1 g
+294.994 417.686 3.613 0.2006 re
+f*
+0 g
+298.607 417.686 10.6381 0.2006 re
+f*
+1 g
+309.245 417.686 5.8209 0.2006 re
+f*
+0 g
+315.066 417.686 16.2582 0.2006 re
+f*
+1 g
+331.325 417.686 3.8137 0.2006 re
+f*
+0 g
+335.138 417.686 17.2619 0.2006 re
+f*
+1 g
+352.4 417.686 5.6201 0.2006 re
+f*
+0.498 0 0.482 rg
+358.02 417.686 5.6202 0.2006 re
+f*
+1 g
+363.641 417.686 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 417.686 7.4267 0.2006 re
+f*
+1 g
+374.68 417.686 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 417.686 9.6345 0.2006 re
+f*
+1 g
+388.128 417.686 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 417.686 7.0252 0.2006 re
+f*
+1 g
+398.766 417.686 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 417.686 14.8532 0.2006 re
+f*
+0 g
+250.234 417.887 14.2511 0.2006 re
+f*
+1 g
+264.485 417.887 25.8928 0.2006 re
+f*
+0 g
+290.378 417.887 4.6165 0.2006 re
+f*
+1 g
+294.994 417.887 3.613 0.2006 re
+f*
+0 g
+298.607 417.887 10.6381 0.2006 re
+f*
+1 g
+309.245 417.887 5.6201 0.2006 re
+f*
+0 g
+314.866 417.887 16.459 0.2006 re
+f*
+1 g
+331.325 417.887 3.8137 0.2006 re
+f*
+0 g
+335.138 417.887 17.2619 0.2006 re
+f*
+1 g
+352.4 417.887 5.6201 0.2006 re
+f*
+0.498 0 0.482 rg
+358.02 417.887 5.6202 0.2006 re
+f*
+1 g
+363.641 417.887 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 417.887 7.4267 0.2006 re
+f*
+1 g
+374.68 417.887 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 417.887 9.6345 0.2006 re
+f*
+1 g
+388.128 417.887 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 417.887 7.0252 0.2006 re
+f*
+1 g
+398.766 417.887 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 417.887 14.8532 0.2006 re
+f*
+0 g
+250.234 418.087 14.2511 0.2006 re
+f*
+1 g
+264.485 418.087 25.8928 0.2006 re
+f*
+0 g
+290.378 418.087 4.6165 0.2006 re
+f*
+1 g
+294.994 418.087 3.613 0.2006 re
+f*
+0 g
+298.607 418.087 10.6381 0.2006 re
+f*
+1 g
+309.245 418.087 5.6201 0.2006 re
+f*
+0 g
+314.866 418.087 16.459 0.2006 re
+f*
+1 g
+331.325 418.087 3.8137 0.2006 re
+f*
+0 g
+335.138 418.087 17.4626 0.2006 re
+f*
+1 g
+352.601 418.087 5.6201 0.2006 re
+f*
+0.498 0 0.482 rg
+358.221 418.087 5.4195 0.2006 re
+f*
+1 g
+363.641 418.087 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 418.087 7.4267 0.2006 re
+f*
+1 g
+374.68 418.087 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 418.087 9.6345 0.2006 re
+f*
+1 g
+388.128 418.087 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 418.087 7.0252 0.2006 re
+f*
+1 g
+398.766 418.087 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 418.087 14.8532 0.2006 re
+f*
+0 g
+250.234 418.288 14.2511 0.2005 re
+f*
+1 g
+264.485 418.288 25.8928 0.2005 re
+f*
+0 g
+290.378 418.288 4.6165 0.2005 re
+f*
+1 g
+294.994 418.288 3.613 0.2005 re
+f*
+0 g
+298.607 418.288 10.6381 0.2005 re
+f*
+1 g
+309.245 418.288 5.6201 0.2005 re
+f*
+0 g
+314.866 418.288 16.2583 0.2005 re
+f*
+1 g
+331.124 418.288 4.0144 0.2005 re
+f*
+0 g
+335.138 418.288 17.4626 0.2005 re
+f*
+1 g
+352.601 418.288 5.6201 0.2005 re
+f*
+0.498 0 0.482 rg
+358.221 418.288 5.4195 0.2005 re
+f*
+1 g
+363.641 418.288 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 418.288 7.2259 0.2005 re
+f*
+1 g
+374.479 418.288 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 418.288 9.6345 0.2005 re
+f*
+1 g
+388.128 418.288 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 418.288 7.0252 0.2005 re
+f*
+1 g
+398.766 418.288 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 418.288 14.8532 0.2005 re
+f*
+0 g
+250.234 418.489 14.2511 0.2005 re
+f*
+1 g
+264.485 418.489 25.8928 0.2005 re
+f*
+0 g
+290.378 418.489 4.6165 0.2005 re
+f*
+1 g
+294.994 418.489 3.613 0.2005 re
+f*
+0 g
+298.607 418.489 10.8389 0.2005 re
+f*
+1 g
+309.446 418.489 5.4193 0.2005 re
+f*
+0 g
+314.866 418.489 16.2583 0.2005 re
+f*
+1 g
+331.124 418.489 3.8137 0.2005 re
+f*
+0 g
+334.938 418.489 17.6633 0.2005 re
+f*
+1 g
+352.601 418.489 5.6201 0.2005 re
+f*
+0.498 0 0.482 rg
+358.221 418.489 5.4195 0.2005 re
+f*
+1 g
+363.641 418.489 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 418.489 7.2259 0.2005 re
+f*
+1 g
+374.479 418.489 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 418.489 9.6345 0.2005 re
+f*
+1 g
+388.128 418.489 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 418.489 7.0252 0.2005 re
+f*
+1 g
+398.766 418.489 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 418.489 14.8532 0.2005 re
+f*
+0 g
+250.234 418.689 14.2511 0.2006 re
+f*
+1 g
+264.485 418.689 25.8928 0.2006 re
+f*
+0 g
+290.378 418.689 4.6165 0.2006 re
+f*
+1 g
+294.994 418.689 3.613 0.2006 re
+f*
+0 g
+298.607 418.689 10.8389 0.2006 re
+f*
+1 g
+309.446 418.689 5.4193 0.2006 re
+f*
+0 g
+314.866 418.689 16.2583 0.2006 re
+f*
+1 g
+331.124 418.689 3.8137 0.2006 re
+f*
+0 g
+334.938 418.689 17.6633 0.2006 re
+f*
+1 g
+352.601 418.689 5.8209 0.2006 re
+f*
+0.498 0 0.482 rg
+358.422 418.689 5.2187 0.2006 re
+f*
+1 g
+363.641 418.689 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 418.689 7.2259 0.2006 re
+f*
+1 g
+374.479 418.689 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 418.689 9.6345 0.2006 re
+f*
+1 g
+388.128 418.689 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 418.689 6.8244 0.2006 re
+f*
+1 g
+398.566 418.689 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 418.689 14.8532 0.2006 re
+f*
+0 g
+250.234 418.89 14.2511 0.2006 re
+f*
+1 g
+264.485 418.89 25.8928 0.2006 re
+f*
+0 g
+290.378 418.89 4.6165 0.2006 re
+f*
+1 g
+294.994 418.89 3.613 0.2006 re
+f*
+0 g
+298.607 418.89 10.8389 0.2006 re
+f*
+1 g
+309.446 418.89 5.4193 0.2006 re
+f*
+0 g
+314.866 418.89 16.0576 0.2006 re
+f*
+1 g
+330.923 418.89 4.0144 0.2006 re
+f*
+0 g
+334.938 418.89 17.6633 0.2006 re
+f*
+1 g
+352.601 418.89 5.8209 0.2006 re
+f*
+0.498 0 0.482 rg
+358.422 418.89 5.2187 0.2006 re
+f*
+1 g
+363.641 418.89 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 418.89 7.2259 0.2006 re
+f*
+1 g
+374.479 418.89 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 418.89 9.6345 0.2006 re
+f*
+1 g
+388.128 418.89 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 418.89 6.8244 0.2006 re
+f*
+1 g
+398.566 418.89 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 418.89 14.8532 0.2006 re
+f*
+0 g
+250.234 419.09 14.2511 0.2005 re
+f*
+1 g
+264.485 419.09 25.8928 0.2005 re
+f*
+0 g
+290.378 419.09 4.6165 0.2005 re
+f*
+1 g
+294.994 419.09 3.613 0.2005 re
+f*
+0 g
+298.607 419.09 10.8389 0.2005 re
+f*
+1 g
+309.446 419.09 5.4193 0.2005 re
+f*
+0 g
+314.866 419.09 16.0576 0.2005 re
+f*
+1 g
+330.923 419.09 4.0144 0.2005 re
+f*
+0 g
+334.938 419.09 17.6633 0.2005 re
+f*
+1 g
+352.601 419.09 5.8209 0.2005 re
+f*
+0.498 0 0.482 rg
+358.422 419.09 5.2187 0.2005 re
+f*
+1 g
+363.641 419.09 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 419.09 7.2259 0.2005 re
+f*
+1 g
+374.479 419.09 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 419.09 9.6345 0.2005 re
+f*
+1 g
+388.128 419.09 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 419.09 6.8244 0.2005 re
+f*
+1 g
+398.566 419.09 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 419.09 14.8532 0.2005 re
+f*
+0 g
+250.234 419.291 14.2511 0.2006 re
+f*
+1 g
+264.485 419.291 25.8928 0.2006 re
+f*
+0 g
+290.378 419.291 4.6165 0.2006 re
+f*
+1 g
+294.994 419.291 3.613 0.2006 re
+f*
+0 g
+298.607 419.291 10.8389 0.2006 re
+f*
+1 g
+309.446 419.291 5.4193 0.2006 re
+f*
+0 g
+314.866 419.291 16.0576 0.2006 re
+f*
+1 g
+330.923 419.291 4.0144 0.2006 re
+f*
+0 g
+334.938 419.291 17.6633 0.2006 re
+f*
+1 g
+352.601 419.291 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+358.623 419.291 5.018 0.2006 re
+f*
+1 g
+363.641 419.291 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 419.291 7.0252 0.2006 re
+f*
+1 g
+374.279 419.291 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 419.291 9.6345 0.2006 re
+f*
+1 g
+388.128 419.291 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 419.291 6.8244 0.2006 re
+f*
+1 g
+398.566 419.291 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 419.291 14.8532 0.2006 re
+f*
+0 g
+250.234 419.491 14.2511 0.2005 re
+f*
+1 g
+264.485 419.491 25.8928 0.2005 re
+f*
+0 g
+290.378 419.491 4.6165 0.2005 re
+f*
+1 g
+294.994 419.491 3.613 0.2005 re
+f*
+0 g
+298.607 419.491 10.8389 0.2005 re
+f*
+1 g
+309.446 419.491 5.2187 0.2005 re
+f*
+0 g
+314.665 419.491 16.2582 0.2005 re
+f*
+1 g
+330.923 419.491 4.0144 0.2005 re
+f*
+0 g
+334.938 419.491 17.6633 0.2005 re
+f*
+1 g
+352.601 419.491 6.0216 0.2005 re
+f*
+0.498 0 0.482 rg
+358.623 419.491 5.018 0.2005 re
+f*
+1 g
+363.641 419.491 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 419.491 7.0252 0.2005 re
+f*
+1 g
+374.279 419.491 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 419.491 9.6345 0.2005 re
+f*
+1 g
+388.128 419.491 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 419.491 6.8244 0.2005 re
+f*
+1 g
+398.566 419.491 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 419.491 14.8532 0.2005 re
+f*
+0 g
+250.234 419.692 14.2511 0.2006 re
+f*
+1 g
+264.485 419.692 25.8928 0.2006 re
+f*
+0 g
+290.378 419.692 4.6165 0.2006 re
+f*
+1 g
+294.994 419.692 3.613 0.2006 re
+f*
+0 g
+298.607 419.692 10.8389 0.2006 re
+f*
+1 g
+309.446 419.692 5.2187 0.2006 re
+f*
+0 g
+314.665 419.692 16.2582 0.2006 re
+f*
+1 g
+330.923 419.692 4.0144 0.2006 re
+f*
+0 g
+334.938 419.692 17.6633 0.2006 re
+f*
+1 g
+352.601 419.692 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+358.623 419.692 5.018 0.2006 re
+f*
+1 g
+363.641 419.692 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 419.692 7.0252 0.2006 re
+f*
+1 g
+374.279 419.692 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 419.692 9.6345 0.2006 re
+f*
+1 g
+388.128 419.692 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 419.692 6.8244 0.2006 re
+f*
+1 g
+398.566 419.692 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 419.692 14.8532 0.2006 re
+f*
+0 g
+250.234 419.892 14.2511 0.2005 re
+f*
+1 g
+264.485 419.892 6.6237 0.2005 re
+f*
+0 g
+271.109 419.892 0.2008 0.2005 re
+f*
+1 g
+271.31 419.892 7.6273 0.2005 re
+f*
+0 g
+278.937 419.892 0.2007 0.2005 re
+f*
+1 g
+279.138 419.892 11.2403 0.2005 re
+f*
+0 g
+290.378 419.892 4.6165 0.2005 re
+f*
+1 g
+294.994 419.892 3.613 0.2005 re
+f*
+0 g
+298.607 419.892 10.8389 0.2005 re
+f*
+1 g
+309.446 419.892 5.2187 0.2005 re
+f*
+0 g
+314.665 419.892 16.0575 0.2005 re
+f*
+1 g
+330.722 419.892 4.2151 0.2005 re
+f*
+0 g
+334.938 419.892 17.6633 0.2005 re
+f*
+1 g
+352.601 419.892 6.2223 0.2005 re
+f*
+0.498 0 0.482 rg
+358.823 419.892 4.8173 0.2005 re
+f*
+1 g
+363.641 419.892 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 419.892 7.0252 0.2005 re
+f*
+1 g
+374.279 419.892 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 419.892 9.6345 0.2005 re
+f*
+1 g
+388.128 419.892 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 419.892 6.8244 0.2005 re
+f*
+1 g
+398.566 419.892 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 419.892 14.8532 0.2005 re
+f*
+0 g
+250.234 420.093 21.0756 0.2006 re
+f*
+1 g
+271.31 420.093 7.6273 0.2006 re
+f*
+0 g
+278.937 420.093 16.0575 0.2006 re
+f*
+1 g
+294.994 420.093 3.613 0.2006 re
+f*
+0 g
+298.607 420.093 11.0396 0.2006 re
+f*
+1 g
+309.647 420.093 5.018 0.2006 re
+f*
+0 g
+314.665 420.093 16.0575 0.2006 re
+f*
+1 g
+330.722 420.093 4.2151 0.2006 re
+f*
+0 g
+334.938 420.093 17.6633 0.2006 re
+f*
+1 g
+352.601 420.093 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+358.823 420.093 4.8173 0.2006 re
+f*
+1 g
+363.641 420.093 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 420.093 7.0252 0.2006 re
+f*
+1 g
+374.279 420.093 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 420.093 9.6345 0.2006 re
+f*
+1 g
+388.128 420.093 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 420.093 6.8244 0.2006 re
+f*
+1 g
+398.566 420.093 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 420.093 14.8532 0.2006 re
+f*
+0 g
+250.234 420.294 21.0756 0.2005 re
+f*
+1 g
+271.31 420.294 7.6273 0.2005 re
+f*
+0 g
+278.937 420.294 16.0575 0.2005 re
+f*
+1 g
+294.994 420.294 3.613 0.2005 re
+f*
+0 g
+298.607 420.294 11.0396 0.2005 re
+f*
+1 g
+309.647 420.294 5.018 0.2005 re
+f*
+0 g
+314.665 420.294 16.0575 0.2005 re
+f*
+1 g
+330.722 420.294 4.2151 0.2005 re
+f*
+0 g
+334.938 420.294 17.8641 0.2005 re
+f*
+1 g
+352.802 420.294 6.0215 0.2005 re
+f*
+0.498 0 0.482 rg
+358.823 420.294 4.8173 0.2005 re
+f*
+1 g
+363.641 420.294 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 420.294 7.0252 0.2005 re
+f*
+1 g
+374.279 420.294 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 420.294 9.6345 0.2005 re
+f*
+1 g
+388.128 420.294 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 420.294 6.8244 0.2005 re
+f*
+1 g
+398.566 420.294 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 420.294 14.8532 0.2005 re
+f*
+0 g
+250.234 420.494 21.0756 0.2006 re
+f*
+1 g
+271.31 420.494 7.6273 0.2006 re
+f*
+0 g
+278.937 420.494 16.0575 0.2006 re
+f*
+1 g
+294.994 420.494 3.613 0.2006 re
+f*
+0 g
+298.607 420.494 11.0396 0.2006 re
+f*
+1 g
+309.647 420.494 5.018 0.2006 re
+f*
+0 g
+314.665 420.494 16.0575 0.2006 re
+f*
+1 g
+330.722 420.494 4.2151 0.2006 re
+f*
+0 g
+334.938 420.494 17.8641 0.2006 re
+f*
+1 g
+352.802 420.494 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+358.823 420.494 4.8173 0.2006 re
+f*
+1 g
+363.641 420.494 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 420.494 7.0252 0.2006 re
+f*
+1 g
+374.279 420.494 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 420.494 9.6345 0.2006 re
+f*
+1 g
+388.128 420.494 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 420.494 6.8244 0.2006 re
+f*
+1 g
+398.566 420.494 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 420.494 14.8532 0.2006 re
+f*
+0 g
+250.234 420.695 21.0756 0.2006 re
+f*
+1 g
+271.31 420.695 7.6273 0.2006 re
+f*
+0 g
+278.937 420.695 16.0575 0.2006 re
+f*
+1 g
+294.994 420.695 3.613 0.2006 re
+f*
+0 g
+298.607 420.695 11.0396 0.2006 re
+f*
+1 g
+309.647 420.695 5.018 0.2006 re
+f*
+0 g
+314.665 420.695 16.0575 0.2006 re
+f*
+1 g
+330.722 420.695 4.2151 0.2006 re
+f*
+0 g
+334.938 420.695 17.8641 0.2006 re
+f*
+1 g
+352.802 420.695 6.2222 0.2006 re
+f*
+0.498 0 0.482 rg
+359.024 420.695 4.6166 0.2006 re
+f*
+1 g
+363.641 420.695 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 420.695 7.0252 0.2006 re
+f*
+1 g
+374.279 420.695 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 420.695 9.6345 0.2006 re
+f*
+1 g
+388.128 420.695 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 420.695 6.8244 0.2006 re
+f*
+1 g
+398.566 420.695 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 420.695 14.8532 0.2006 re
+f*
+0 g
+250.234 420.895 21.0756 0.2006 re
+f*
+1 g
+271.31 420.895 7.6273 0.2006 re
+f*
+0 g
+278.937 420.895 16.0575 0.2006 re
+f*
+1 g
+294.994 420.895 3.613 0.2006 re
+f*
+0 g
+298.607 420.895 11.0396 0.2006 re
+f*
+1 g
+309.647 420.895 5.018 0.2006 re
+f*
+0 g
+314.665 420.895 16.0575 0.2006 re
+f*
+1 g
+330.722 420.895 4.2151 0.2006 re
+f*
+0 g
+334.938 420.895 17.8641 0.2006 re
+f*
+1 g
+352.802 420.895 6.2222 0.2006 re
+f*
+0.498 0 0.482 rg
+359.024 420.895 4.6166 0.2006 re
+f*
+1 g
+363.641 420.895 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 420.895 7.0252 0.2006 re
+f*
+1 g
+374.279 420.895 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 420.895 9.6345 0.2006 re
+f*
+1 g
+388.128 420.895 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 420.895 6.8244 0.2006 re
+f*
+1 g
+398.566 420.895 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 420.895 14.8532 0.2006 re
+f*
+0 g
+250.234 421.096 21.0756 0.2005 re
+f*
+1 g
+271.31 421.096 7.6273 0.2005 re
+f*
+0 g
+278.937 421.096 16.0575 0.2005 re
+f*
+1 g
+294.994 421.096 3.613 0.2005 re
+f*
+0 g
+298.607 421.096 11.2403 0.2005 re
+f*
+1 g
+309.848 421.096 4.8173 0.2005 re
+f*
+0 g
+314.665 421.096 16.0575 0.2005 re
+f*
+1 g
+330.722 421.096 4.2151 0.2005 re
+f*
+0 g
+334.938 421.096 17.8641 0.2005 re
+f*
+1 g
+352.802 421.096 6.2222 0.2005 re
+f*
+0.498 0 0.482 rg
+359.024 421.096 4.6166 0.2005 re
+f*
+1 g
+363.641 421.096 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 421.096 7.0252 0.2005 re
+f*
+1 g
+374.279 421.096 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 421.096 9.6345 0.2005 re
+f*
+1 g
+388.128 421.096 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 421.096 6.8244 0.2005 re
+f*
+1 g
+398.566 421.096 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 421.096 14.8532 0.2005 re
+f*
+0 g
+250.234 421.296 21.0756 0.2005 re
+f*
+1 g
+271.31 421.296 7.6273 0.2005 re
+f*
+0 g
+278.937 421.296 16.0575 0.2005 re
+f*
+1 g
+294.994 421.296 3.613 0.2005 re
+f*
+0 g
+298.607 421.296 11.2403 0.2005 re
+f*
+1 g
+309.848 421.296 4.8173 0.2005 re
+f*
+0 g
+314.665 421.296 16.0575 0.2005 re
+f*
+1 g
+330.722 421.296 4.2151 0.2005 re
+f*
+0 g
+334.938 421.296 17.6633 0.2005 re
+f*
+1 g
+352.601 421.296 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+359.024 421.296 4.6166 0.2005 re
+f*
+1 g
+363.641 421.296 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 421.296 7.0252 0.2005 re
+f*
+1 g
+374.279 421.296 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 421.296 9.6345 0.2005 re
+f*
+1 g
+388.128 421.296 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 421.296 6.8244 0.2005 re
+f*
+1 g
+398.566 421.296 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 421.296 14.8532 0.2005 re
+f*
+0 g
+250.234 421.497 21.0756 0.2006 re
+f*
+1 g
+271.31 421.497 7.6273 0.2006 re
+f*
+0 g
+278.937 421.497 16.0575 0.2006 re
+f*
+1 g
+294.994 421.497 3.613 0.2006 re
+f*
+0 g
+298.607 421.497 11.2403 0.2006 re
+f*
+1 g
+309.848 421.497 4.8173 0.2006 re
+f*
+0 g
+314.665 421.497 16.0575 0.2006 re
+f*
+1 g
+330.722 421.497 4.2151 0.2006 re
+f*
+0 g
+334.938 421.497 17.6633 0.2006 re
+f*
+1 g
+352.601 421.497 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 421.497 4.4159 0.2006 re
+f*
+1 g
+363.641 421.497 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 421.497 7.0252 0.2006 re
+f*
+1 g
+374.279 421.497 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 421.497 9.6345 0.2006 re
+f*
+1 g
+388.128 421.497 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 421.497 6.8244 0.2006 re
+f*
+1 g
+398.566 421.497 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 421.497 14.8532 0.2006 re
+f*
+0 g
+250.234 421.697 21.0756 0.2006 re
+f*
+1 g
+271.31 421.697 7.6273 0.2006 re
+f*
+0 g
+278.937 421.697 16.0575 0.2006 re
+f*
+1 g
+294.994 421.697 3.613 0.2006 re
+f*
+0 g
+298.607 421.697 11.2403 0.2006 re
+f*
+1 g
+309.848 421.697 4.8173 0.2006 re
+f*
+0 g
+314.665 421.697 16.0575 0.2006 re
+f*
+1 g
+330.722 421.697 4.2151 0.2006 re
+f*
+0 g
+334.938 421.697 17.6633 0.2006 re
+f*
+1 g
+352.601 421.697 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 421.697 4.4159 0.2006 re
+f*
+1 g
+363.641 421.697 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 421.697 7.0252 0.2006 re
+f*
+1 g
+374.279 421.697 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 421.697 9.6345 0.2006 re
+f*
+1 g
+388.128 421.697 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 421.697 6.8244 0.2006 re
+f*
+1 g
+398.566 421.697 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 421.697 12.0431 0.2006 re
+f*
+1 g
+414.824 421.697 0.2008 0.2006 re
+f*
+0.498 0 0.482 rg
+415.025 421.697 2.6093 0.2006 re
+f*
+0 g
+250.234 421.898 21.0756 0.2005 re
+f*
+1 g
+271.31 421.898 7.6273 0.2005 re
+f*
+0 g
+278.937 421.898 16.0575 0.2005 re
+f*
+1 g
+294.994 421.898 3.613 0.2005 re
+f*
+0 g
+298.607 421.898 11.441 0.2005 re
+f*
+1 g
+310.048 421.898 4.6166 0.2005 re
+f*
+0 g
+314.665 421.898 16.0575 0.2005 re
+f*
+1 g
+330.722 421.898 4.2151 0.2005 re
+f*
+0 g
+334.938 421.898 17.6633 0.2005 re
+f*
+1 g
+352.601 421.898 6.6237 0.2005 re
+f*
+0.498 0 0.482 rg
+359.225 421.898 4.4159 0.2005 re
+f*
+1 g
+363.641 421.898 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 421.898 7.2259 0.2005 re
+f*
+1 g
+374.479 421.898 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 421.898 9.6345 0.2005 re
+f*
+1 g
+388.128 421.898 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 421.898 6.8244 0.2005 re
+f*
+1 g
+398.566 421.898 16.4591 0.2005 re
+f*
+0.498 0 0.482 rg
+415.025 421.898 2.6093 0.2005 re
+f*
+0 g
+250.234 422.099 21.0756 0.2006 re
+f*
+1 g
+271.31 422.099 7.6273 0.2006 re
+f*
+0 g
+278.937 422.099 16.0575 0.2006 re
+f*
+1 g
+294.994 422.099 3.613 0.2006 re
+f*
+0 g
+298.607 422.099 11.441 0.2006 re
+f*
+1 g
+310.048 422.099 4.6166 0.2006 re
+f*
+0 g
+314.665 422.099 16.0575 0.2006 re
+f*
+1 g
+330.722 422.099 4.2151 0.2006 re
+f*
+0 g
+334.938 422.099 17.6633 0.2006 re
+f*
+1 g
+352.601 422.099 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 422.099 4.4159 0.2006 re
+f*
+1 g
+363.641 422.099 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 422.099 7.2259 0.2006 re
+f*
+1 g
+374.479 422.099 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 422.099 9.6345 0.2006 re
+f*
+1 g
+388.128 422.099 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 422.099 7.0252 0.2006 re
+f*
+1 g
+398.766 422.099 16.2583 0.2006 re
+f*
+0.498 0 0.482 rg
+415.025 422.099 2.6093 0.2006 re
+f*
+0 g
+250.234 422.299 21.0756 0.2005 re
+f*
+1 g
+271.31 422.299 7.6273 0.2005 re
+f*
+0 g
+278.937 422.299 16.0575 0.2005 re
+f*
+1 g
+294.994 422.299 3.613 0.2005 re
+f*
+0 g
+298.607 422.299 11.441 0.2005 re
+f*
+1 g
+310.048 422.299 4.6166 0.2005 re
+f*
+0 g
+314.665 422.299 16.0575 0.2005 re
+f*
+1 g
+330.722 422.299 4.2151 0.2005 re
+f*
+0 g
+334.938 422.299 17.6633 0.2005 re
+f*
+1 g
+352.601 422.299 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+359.425 422.299 4.2151 0.2005 re
+f*
+1 g
+363.641 422.299 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 422.299 7.2259 0.2005 re
+f*
+1 g
+374.479 422.299 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 422.299 9.6345 0.2005 re
+f*
+1 g
+388.128 422.299 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 422.299 7.0252 0.2005 re
+f*
+1 g
+398.766 422.299 16.2583 0.2005 re
+f*
+0.498 0 0.482 rg
+415.025 422.299 2.4086 0.2005 re
+f*
+0 g
+250.234 422.5 21.0756 0.2006 re
+f*
+1 g
+271.31 422.5 7.6273 0.2006 re
+f*
+0 g
+278.937 422.5 16.0575 0.2006 re
+f*
+1 g
+294.994 422.5 3.613 0.2006 re
+f*
+0 g
+298.607 422.5 11.441 0.2006 re
+f*
+1 g
+310.048 422.5 4.6166 0.2006 re
+f*
+0 g
+314.665 422.5 16.0575 0.2006 re
+f*
+1 g
+330.722 422.5 4.2151 0.2006 re
+f*
+0 g
+334.938 422.5 17.6633 0.2006 re
+f*
+1 g
+352.601 422.5 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 422.5 4.2151 0.2006 re
+f*
+1 g
+363.641 422.5 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 422.5 7.2259 0.2006 re
+f*
+1 g
+374.479 422.5 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 422.5 9.6345 0.2006 re
+f*
+1 g
+388.128 422.5 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 422.5 7.0252 0.2006 re
+f*
+1 g
+398.766 422.5 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 422.5 8.0287 0.2006 re
+f*
+1 g
+410.81 422.5 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+415.025 422.5 2.4086 0.2006 re
+f*
+0 g
+250.234 422.7 21.0756 0.2006 re
+f*
+1 g
+271.31 422.7 7.6273 0.2006 re
+f*
+0 g
+278.937 422.7 16.0575 0.2006 re
+f*
+1 g
+294.994 422.7 3.613 0.2006 re
+f*
+0 g
+298.607 422.7 11.6417 0.2006 re
+f*
+1 g
+310.249 422.7 4.4159 0.2006 re
+f*
+0 g
+314.665 422.7 16.0575 0.2006 re
+f*
+1 g
+330.722 422.7 4.2151 0.2006 re
+f*
+0 g
+334.938 422.7 17.6633 0.2006 re
+f*
+1 g
+352.601 422.7 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 422.7 4.2151 0.2006 re
+f*
+1 g
+363.641 422.7 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 422.7 7.2259 0.2006 re
+f*
+1 g
+374.479 422.7 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 422.7 9.6345 0.2006 re
+f*
+1 g
+388.128 422.7 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 422.7 7.0252 0.2006 re
+f*
+1 g
+398.766 422.7 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 422.7 8.0287 0.2006 re
+f*
+1 g
+410.81 422.7 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+415.025 422.7 2.4086 0.2006 re
+f*
+0 g
+250.435 422.901 20.8749 0.2005 re
+f*
+1 g
+271.31 422.901 7.6273 0.2005 re
+f*
+0 g
+278.937 422.901 16.0575 0.2005 re
+f*
+1 g
+294.994 422.901 3.613 0.2005 re
+f*
+0 g
+298.607 422.901 11.6417 0.2005 re
+f*
+1 g
+310.249 422.901 16.6597 0.2005 re
+f*
+0 g
+326.909 422.901 4.0144 0.2005 re
+f*
+1 g
+330.923 422.901 4.0144 0.2005 re
+f*
+0 g
+334.938 422.901 12.0431 0.2005 re
+f*
+1 g
+346.981 422.901 0.2008 0.2005 re
+f*
+0 g
+347.181 422.901 5.4194 0.2005 re
+f*
+1 g
+352.601 422.901 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+359.425 422.901 4.2151 0.2005 re
+f*
+1 g
+363.641 422.901 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 422.901 7.4267 0.2005 re
+f*
+1 g
+374.68 422.901 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 422.901 9.6345 0.2005 re
+f*
+1 g
+388.128 422.901 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 422.901 7.2259 0.2005 re
+f*
+1 g
+398.967 422.901 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 422.901 8.0287 0.2005 re
+f*
+1 g
+410.81 422.901 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+415.025 422.901 2.4086 0.2005 re
+f*
+0 g
+250.435 423.101 20.8749 0.2006 re
+f*
+1 g
+271.31 423.101 7.6273 0.2006 re
+f*
+0 g
+278.937 423.101 16.0575 0.2006 re
+f*
+1 g
+294.994 423.101 3.613 0.2006 re
+f*
+0 g
+298.607 423.101 11.6417 0.2006 re
+f*
+1 g
+310.249 423.101 16.6597 0.2006 re
+f*
+0 g
+326.909 423.101 4.0144 0.2006 re
+f*
+1 g
+330.923 423.101 16.2583 0.2006 re
+f*
+0 g
+347.181 423.101 5.4194 0.2006 re
+f*
+1 g
+352.601 423.101 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 423.101 4.2151 0.2006 re
+f*
+1 g
+363.641 423.101 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 423.101 7.4267 0.2006 re
+f*
+1 g
+374.68 423.101 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 423.101 9.6345 0.2006 re
+f*
+1 g
+388.128 423.101 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 423.101 7.2259 0.2006 re
+f*
+1 g
+398.967 423.101 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 423.101 8.0287 0.2006 re
+f*
+1 g
+410.81 423.101 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+414.824 423.101 2.6094 0.2006 re
+f*
+0 g
+250.435 423.302 20.8749 0.2005 re
+f*
+1 g
+271.31 423.302 7.6273 0.2005 re
+f*
+0 g
+278.937 423.302 16.0575 0.2005 re
+f*
+1 g
+294.994 423.302 3.613 0.2005 re
+f*
+0 g
+298.607 423.302 11.8425 0.2005 re
+f*
+1 g
+310.45 423.302 4.2151 0.2005 re
+f*
+0 g
+314.665 423.302 8.4302 0.2005 re
+f*
+1 g
+323.095 423.302 3.8136 0.2005 re
+f*
+0 g
+326.909 423.302 4.0144 0.2005 re
+f*
+1 g
+330.923 423.302 16.2583 0.2005 re
+f*
+0 g
+347.181 423.302 5.4194 0.2005 re
+f*
+1 g
+352.601 423.302 6.8245 0.2005 re
+f*
+0.498 0 0.482 rg
+359.425 423.302 4.2151 0.2005 re
+f*
+1 g
+363.641 423.302 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 423.302 7.4267 0.2005 re
+f*
+1 g
+374.68 423.302 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+378.494 423.302 9.6345 0.2005 re
+f*
+1 g
+388.128 423.302 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 423.302 7.2259 0.2005 re
+f*
+1 g
+398.967 423.302 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.781 423.302 8.0287 0.2005 re
+f*
+1 g
+410.81 423.302 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+414.824 423.302 2.6094 0.2005 re
+f*
+0 g
+250.435 423.502 20.8749 0.2006 re
+f*
+1 g
+271.31 423.502 7.6273 0.2006 re
+f*
+0 g
+278.937 423.502 16.0575 0.2006 re
+f*
+1 g
+294.994 423.502 3.613 0.2006 re
+f*
+0 g
+298.607 423.502 11.8425 0.2006 re
+f*
+1 g
+310.45 423.502 4.4157 0.2006 re
+f*
+0 g
+314.866 423.502 8.2296 0.2006 re
+f*
+1 g
+323.095 423.502 3.8136 0.2006 re
+f*
+0 g
+326.909 423.502 4.0144 0.2006 re
+f*
+1 g
+330.923 423.502 16.2583 0.2006 re
+f*
+0 g
+347.181 423.502 5.2187 0.2006 re
+f*
+1 g
+352.4 423.502 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 423.502 4.2151 0.2006 re
+f*
+1 g
+363.641 423.502 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 423.502 7.4267 0.2006 re
+f*
+1 g
+374.68 423.502 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+378.494 423.502 9.6345 0.2006 re
+f*
+1 g
+388.128 423.502 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 423.502 7.4266 0.2006 re
+f*
+1 g
+399.168 423.502 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+402.781 423.502 8.0287 0.2006 re
+f*
+1 g
+410.81 423.502 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+414.824 423.502 2.6094 0.2006 re
+f*
+0 g
+250.435 423.703 20.8749 0.2006 re
+f*
+1 g
+271.31 423.703 7.6273 0.2006 re
+f*
+0 g
+278.937 423.703 16.0575 0.2006 re
+f*
+1 g
+294.994 423.703 3.613 0.2006 re
+f*
+0 g
+298.607 423.703 12.0432 0.2006 re
+f*
+1 g
+310.651 423.703 4.215 0.2006 re
+f*
+0 g
+314.866 423.703 8.2296 0.2006 re
+f*
+1 g
+323.095 423.703 3.8136 0.2006 re
+f*
+0 g
+326.909 423.703 4.0144 0.2006 re
+f*
+1 g
+330.923 423.703 4.0144 0.2006 re
+f*
+0 g
+334.938 423.703 8.0288 0.2006 re
+f*
+1 g
+342.966 423.703 4.2151 0.2006 re
+f*
+0 g
+347.181 423.703 5.2187 0.2006 re
+f*
+1 g
+352.4 423.703 7.2259 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 423.703 4.0144 0.2006 re
+f*
+1 g
+363.641 423.703 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 423.703 7.6274 0.2006 re
+f*
+1 g
+374.881 423.703 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 423.703 9.2331 0.2006 re
+f*
+1 g
+387.928 423.703 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 423.703 7.4266 0.2006 re
+f*
+1 g
+399.168 423.703 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 423.703 7.828 0.2006 re
+f*
+1 g
+410.81 423.703 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+414.824 423.703 2.6094 0.2006 re
+f*
+0 g
+250.435 423.904 20.8749 0.2005 re
+f*
+1 g
+271.31 423.904 7.6273 0.2005 re
+f*
+0 g
+278.937 423.904 16.0575 0.2005 re
+f*
+1 g
+294.994 423.904 3.8137 0.2005 re
+f*
+0 g
+298.808 423.904 11.8425 0.2005 re
+f*
+1 g
+310.651 423.904 4.215 0.2005 re
+f*
+0 g
+314.866 423.904 8.2296 0.2005 re
+f*
+1 g
+323.095 423.904 3.8136 0.2005 re
+f*
+0 g
+326.909 423.904 4.2151 0.2005 re
+f*
+1 g
+331.124 423.904 3.8137 0.2005 re
+f*
+0 g
+334.938 423.904 8.0288 0.2005 re
+f*
+1 g
+342.966 423.904 4.2151 0.2005 re
+f*
+0 g
+347.181 423.904 5.2187 0.2005 re
+f*
+1 g
+352.4 423.904 7.2259 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 423.904 4.0144 0.2005 re
+f*
+1 g
+363.641 423.904 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 423.904 7.6274 0.2005 re
+f*
+1 g
+374.881 423.904 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+378.695 423.904 9.2331 0.2005 re
+f*
+1 g
+387.928 423.904 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 423.904 7.4266 0.2005 re
+f*
+1 g
+399.168 423.904 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+402.981 423.904 7.828 0.2005 re
+f*
+1 g
+410.81 423.904 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+414.824 423.904 2.6094 0.2005 re
+f*
+0 g
+250.435 424.104 20.8749 0.2006 re
+f*
+1 g
+271.31 424.104 7.6273 0.2006 re
+f*
+0 g
+278.937 424.104 16.0575 0.2006 re
+f*
+1 g
+294.994 424.104 3.8137 0.2006 re
+f*
+0 g
+298.808 424.104 11.8425 0.2006 re
+f*
+1 g
+310.651 424.104 4.215 0.2006 re
+f*
+0 g
+314.866 424.104 8.4303 0.2006 re
+f*
+1 g
+323.296 424.104 3.6129 0.2006 re
+f*
+0 g
+326.909 424.104 4.2151 0.2006 re
+f*
+1 g
+331.124 424.104 3.8137 0.2006 re
+f*
+0 g
+334.938 424.104 8.0288 0.2006 re
+f*
+1 g
+342.966 424.104 4.2151 0.2006 re
+f*
+0 g
+347.181 424.104 5.2187 0.2006 re
+f*
+1 g
+352.4 424.104 7.2259 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 424.104 4.0144 0.2006 re
+f*
+1 g
+363.641 424.104 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 424.104 7.8281 0.2006 re
+f*
+1 g
+375.081 424.104 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 424.104 9.2331 0.2006 re
+f*
+1 g
+387.928 424.104 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 424.104 7.6273 0.2006 re
+f*
+1 g
+399.368 424.104 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 424.104 7.828 0.2006 re
+f*
+1 g
+410.81 424.104 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+414.623 424.104 2.8101 0.2006 re
+f*
+0 g
+250.435 424.305 20.8749 0.2005 re
+f*
+1 g
+271.31 424.305 7.6273 0.2005 re
+f*
+0 g
+278.937 424.305 16.0575 0.2005 re
+f*
+1 g
+294.994 424.305 3.8137 0.2005 re
+f*
+0 g
+298.808 424.305 12.0432 0.2005 re
+f*
+1 g
+310.851 424.305 4.0143 0.2005 re
+f*
+0 g
+314.866 424.305 8.4303 0.2005 re
+f*
+1 g
+323.296 424.305 3.6129 0.2005 re
+f*
+0 g
+326.909 424.305 4.2151 0.2005 re
+f*
+1 g
+331.124 424.305 4.0144 0.2005 re
+f*
+0 g
+335.138 424.305 7.8281 0.2005 re
+f*
+1 g
+342.966 424.305 4.2151 0.2005 re
+f*
+0 g
+347.181 424.305 5.2187 0.2005 re
+f*
+1 g
+352.4 424.305 7.2259 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 424.305 4.0144 0.2005 re
+f*
+1 g
+363.641 424.305 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 424.305 7.8281 0.2005 re
+f*
+1 g
+375.081 424.305 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+378.695 424.305 9.2331 0.2005 re
+f*
+1 g
+387.928 424.305 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 424.305 7.6273 0.2005 re
+f*
+1 g
+399.368 424.305 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+402.981 424.305 7.828 0.2005 re
+f*
+1 g
+410.81 424.305 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+414.623 424.305 2.6093 0.2005 re
+f*
+0 g
+250.435 424.505 20.8749 0.2006 re
+f*
+1 g
+271.31 424.505 7.6273 0.2006 re
+f*
+0 g
+278.937 424.505 16.0575 0.2006 re
+f*
+1 g
+294.994 424.505 3.8137 0.2006 re
+f*
+0 g
+298.808 424.505 12.0432 0.2006 re
+f*
+1 g
+310.851 424.505 4.0143 0.2006 re
+f*
+0 g
+314.866 424.505 8.4303 0.2006 re
+f*
+1 g
+323.296 424.505 3.6129 0.2006 re
+f*
+0 g
+326.909 424.505 4.4158 0.2006 re
+f*
+1 g
+331.325 424.505 3.8137 0.2006 re
+f*
+0 g
+335.138 424.505 7.8281 0.2006 re
+f*
+1 g
+342.966 424.505 4.0143 0.2006 re
+f*
+0 g
+346.981 424.505 5.2188 0.2006 re
+f*
+1 g
+352.2 424.505 7.4266 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 424.505 4.0144 0.2006 re
+f*
+1 g
+363.641 424.505 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 424.505 8.0288 0.2006 re
+f*
+1 g
+375.282 424.505 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 424.505 9.2331 0.2006 re
+f*
+1 g
+387.928 424.505 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 424.505 7.828 0.2006 re
+f*
+1 g
+399.569 424.505 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 424.505 7.828 0.2006 re
+f*
+1 g
+410.81 424.505 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+414.623 424.505 2.6093 0.2006 re
+f*
+0 g
+250.635 424.706 20.6741 0.2006 re
+f*
+1 g
+271.31 424.706 7.6273 0.2006 re
+f*
+0 g
+278.937 424.706 16.0575 0.2006 re
+f*
+1 g
+294.994 424.706 3.8137 0.2006 re
+f*
+0 g
+298.808 424.706 12.2439 0.2006 re
+f*
+1 g
+311.052 424.706 3.8136 0.2006 re
+f*
+0 g
+314.866 424.706 8.4303 0.2006 re
+f*
+1 g
+323.296 424.706 3.4122 0.2006 re
+f*
+0 g
+326.708 424.706 4.6165 0.2006 re
+f*
+1 g
+331.325 424.706 3.8137 0.2006 re
+f*
+0 g
+335.138 424.706 7.8281 0.2006 re
+f*
+1 g
+342.966 424.706 4.0143 0.2006 re
+f*
+0 g
+346.981 424.706 5.2188 0.2006 re
+f*
+1 g
+352.2 424.706 7.4266 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 424.706 4.0144 0.2006 re
+f*
+1 g
+363.641 424.706 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 424.706 8.0288 0.2006 re
+f*
+1 g
+375.282 424.706 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+378.695 424.706 9.2331 0.2006 re
+f*
+1 g
+387.928 424.706 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 424.706 7.828 0.2006 re
+f*
+1 g
+399.569 424.706 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+402.981 424.706 7.828 0.2006 re
+f*
+1 g
+410.81 424.706 3.8137 0.2006 re
+f*
+0.498 0 0.482 rg
+414.623 424.706 2.6093 0.2006 re
+f*
+0 g
+250.635 424.906 20.6741 0.2005 re
+f*
+1 g
+271.31 424.906 7.6273 0.2005 re
+f*
+0 g
+278.937 424.906 16.0575 0.2005 re
+f*
+1 g
+294.994 424.906 3.8137 0.2005 re
+f*
+0 g
+298.808 424.906 12.2439 0.2005 re
+f*
+1 g
+311.052 424.906 3.8136 0.2005 re
+f*
+0 g
+314.866 424.906 8.4303 0.2005 re
+f*
+1 g
+323.296 424.906 3.4122 0.2005 re
+f*
+0 g
+326.708 424.906 4.6165 0.2005 re
+f*
+1 g
+331.325 424.906 3.8137 0.2005 re
+f*
+0 g
+335.138 424.906 7.8281 0.2005 re
+f*
+1 g
+342.966 424.906 4.0143 0.2005 re
+f*
+0 g
+346.981 424.906 5.2188 0.2005 re
+f*
+1 g
+352.2 424.906 7.4266 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 424.906 4.0144 0.2005 re
+f*
+1 g
+363.641 424.906 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 424.906 8.2295 0.2005 re
+f*
+1 g
+375.483 424.906 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+378.695 424.906 9.0323 0.2005 re
+f*
+1 g
+387.727 424.906 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 424.906 8.0288 0.2005 re
+f*
+1 g
+399.77 424.906 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+403.182 424.906 7.6273 0.2005 re
+f*
+1 g
+410.81 424.906 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+414.422 424.906 2.81 0.2005 re
+f*
+0 g
+250.635 425.107 20.8748 0.2006 re
+f*
+1 g
+271.51 425.107 7.4266 0.2006 re
+f*
+0 g
+278.937 425.107 16.0575 0.2006 re
+f*
+1 g
+294.994 425.107 4.0144 0.2006 re
+f*
+0 g
+299.009 425.107 5.2187 0.2006 re
+f*
+1 g
+304.227 425.107 2.208 0.2006 re
+f*
+0 g
+306.435 425.107 4.8172 0.2006 re
+f*
+1 g
+311.253 425.107 3.6129 0.2006 re
+f*
+0 g
+314.866 425.107 8.4303 0.2006 re
+f*
+1 g
+323.296 425.107 3.4122 0.2006 re
+f*
+0 g
+326.708 425.107 4.8173 0.2006 re
+f*
+1 g
+331.525 425.107 3.6129 0.2006 re
+f*
+0 g
+335.138 425.107 7.8281 0.2006 re
+f*
+1 g
+342.966 425.107 4.0143 0.2006 re
+f*
+0 g
+346.981 425.107 5.018 0.2006 re
+f*
+1 g
+351.999 425.107 7.6274 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 425.107 4.0144 0.2006 re
+f*
+1 g
+363.641 425.107 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 425.107 8.2295 0.2006 re
+f*
+1 g
+375.483 425.107 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+378.895 425.107 8.8316 0.2006 re
+f*
+1 g
+387.727 425.107 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 425.107 8.2295 0.2006 re
+f*
+1 g
+399.971 425.107 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+403.182 425.107 7.6273 0.2006 re
+f*
+1 g
+410.81 425.107 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+414.422 425.107 2.81 0.2006 re
+f*
+0 g
+250.635 425.308 20.8748 0.2005 re
+f*
+1 g
+271.51 425.308 7.4266 0.2005 re
+f*
+0 g
+278.937 425.308 16.0575 0.2005 re
+f*
+1 g
+294.994 425.308 4.0144 0.2005 re
+f*
+0 g
+299.009 425.308 4.8173 0.2005 re
+f*
+1 g
+303.826 425.308 3.0108 0.2005 re
+f*
+0 g
+306.837 425.308 4.4158 0.2005 re
+f*
+1 g
+311.253 425.308 3.8137 0.2005 re
+f*
+0 g
+315.066 425.308 8.2295 0.2005 re
+f*
+1 g
+323.296 425.308 3.4122 0.2005 re
+f*
+0 g
+326.708 425.308 4.8173 0.2005 re
+f*
+1 g
+331.525 425.308 3.6129 0.2005 re
+f*
+0 g
+335.138 425.308 7.8281 0.2005 re
+f*
+1 g
+342.966 425.308 4.0143 0.2005 re
+f*
+0 g
+346.981 425.308 5.018 0.2005 re
+f*
+1 g
+351.999 425.308 7.6274 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 425.308 4.0144 0.2005 re
+f*
+1 g
+363.641 425.308 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 425.308 8.4303 0.2005 re
+f*
+1 g
+375.684 425.308 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+378.895 425.308 8.8316 0.2005 re
+f*
+1 g
+387.727 425.308 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 425.308 8.2295 0.2005 re
+f*
+1 g
+399.971 425.308 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+403.182 425.308 7.6273 0.2005 re
+f*
+1 g
+410.81 425.308 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+414.222 425.308 3.0108 0.2005 re
+f*
+0 g
+250.635 425.508 20.8748 0.2006 re
+f*
+1 g
+271.51 425.508 7.4266 0.2006 re
+f*
+0 g
+278.937 425.508 16.0575 0.2006 re
+f*
+1 g
+294.994 425.508 4.0144 0.2006 re
+f*
+0 g
+299.009 425.508 4.6166 0.2006 re
+f*
+1 g
+303.625 425.508 3.4122 0.2006 re
+f*
+0 g
+307.038 425.508 4.4158 0.2006 re
+f*
+1 g
+311.453 425.508 3.613 0.2006 re
+f*
+0 g
+315.066 425.508 8.2295 0.2006 re
+f*
+1 g
+323.296 425.508 3.2115 0.2006 re
+f*
+0 g
+326.507 425.508 5.2187 0.2006 re
+f*
+1 g
+331.726 425.508 3.4122 0.2006 re
+f*
+0 g
+335.138 425.508 7.8281 0.2006 re
+f*
+1 g
+342.966 425.508 3.8137 0.2006 re
+f*
+0 g
+346.78 425.508 5.2186 0.2006 re
+f*
+1 g
+351.999 425.508 7.6274 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 425.508 4.0144 0.2006 re
+f*
+1 g
+363.641 425.508 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 425.508 8.4303 0.2006 re
+f*
+1 g
+375.684 425.508 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+378.895 425.508 8.6309 0.2006 re
+f*
+1 g
+387.526 425.508 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 425.508 8.4302 0.2006 re
+f*
+1 g
+400.171 425.508 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+403.383 425.508 7.4266 0.2006 re
+f*
+1 g
+410.81 425.508 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+414.222 425.508 2.8102 0.2006 re
+f*
+0 g
+250.635 425.709 20.8748 0.2005 re
+f*
+1 g
+271.51 425.709 7.4266 0.2005 re
+f*
+0 g
+278.937 425.709 16.0575 0.2005 re
+f*
+1 g
+294.994 425.709 4.2151 0.2005 re
+f*
+0 g
+299.209 425.709 4.4159 0.2005 re
+f*
+1 g
+303.625 425.709 3.6129 0.2005 re
+f*
+0 g
+307.238 425.709 4.4159 0.2005 re
+f*
+1 g
+311.654 425.709 3.4122 0.2005 re
+f*
+0 g
+315.066 425.709 8.0288 0.2005 re
+f*
+1 g
+323.095 425.709 3.4122 0.2005 re
+f*
+0 g
+326.507 425.709 5.2187 0.2005 re
+f*
+1 g
+331.726 425.709 3.613 0.2005 re
+f*
+0 g
+335.339 425.709 7.6273 0.2005 re
+f*
+1 g
+342.966 425.709 3.8137 0.2005 re
+f*
+0 g
+346.78 425.709 5.018 0.2005 re
+f*
+1 g
+351.798 425.709 7.828 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 425.709 4.0144 0.2005 re
+f*
+1 g
+363.641 425.709 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 425.709 8.631 0.2005 re
+f*
+1 g
+375.884 425.709 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+378.895 425.709 8.6309 0.2005 re
+f*
+1 g
+387.526 425.709 4.2151 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 425.709 8.6309 0.2005 re
+f*
+1 g
+400.372 425.709 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+403.383 425.709 7.4266 0.2005 re
+f*
+1 g
+410.81 425.709 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+414.222 425.709 2.8102 0.2005 re
+f*
+0 g
+250.836 425.909 20.6741 0.2006 re
+f*
+1 g
+271.51 425.909 7.4266 0.2006 re
+f*
+0 g
+278.937 425.909 16.0575 0.2006 re
+f*
+1 g
+294.994 425.909 4.2151 0.2006 re
+f*
+0 g
+299.209 425.909 4.4159 0.2006 re
+f*
+1 g
+303.625 425.909 3.6129 0.2006 re
+f*
+0 g
+307.238 425.909 4.4159 0.2006 re
+f*
+1 g
+311.654 425.909 3.4122 0.2006 re
+f*
+0 g
+315.066 425.909 8.0288 0.2006 re
+f*
+1 g
+323.095 425.909 3.2115 0.2006 re
+f*
+0 g
+326.307 425.909 5.6201 0.2006 re
+f*
+1 g
+331.927 425.909 3.4123 0.2006 re
+f*
+0 g
+335.339 425.909 7.6273 0.2006 re
+f*
+1 g
+342.966 425.909 3.8137 0.2006 re
+f*
+0 g
+346.78 425.909 5.018 0.2006 re
+f*
+1 g
+351.798 425.909 7.828 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 425.909 4.0144 0.2006 re
+f*
+1 g
+363.641 425.909 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 425.909 8.8317 0.2006 re
+f*
+1 g
+376.085 425.909 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+379.096 425.909 8.4302 0.2006 re
+f*
+1 g
+387.526 425.909 4.2151 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 425.909 8.6309 0.2006 re
+f*
+1 g
+400.372 425.909 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+403.383 425.909 7.2259 0.2006 re
+f*
+1 g
+410.609 425.909 3.4123 0.2006 re
+f*
+0.498 0 0.482 rg
+414.021 425.909 3.0108 0.2006 re
+f*
+0 g
+250.836 426.11 20.6741 0.2006 re
+f*
+1 g
+271.51 426.11 7.4266 0.2006 re
+f*
+0 g
+278.937 426.11 16.0575 0.2006 re
+f*
+1 g
+294.994 426.11 4.2151 0.2006 re
+f*
+0 g
+299.209 426.11 4.4159 0.2006 re
+f*
+1 g
+303.625 426.11 3.6129 0.2006 re
+f*
+0 g
+307.238 426.11 4.6166 0.2006 re
+f*
+1 g
+311.855 426.11 3.2115 0.2006 re
+f*
+0 g
+315.066 426.11 8.0288 0.2006 re
+f*
+1 g
+323.095 426.11 3.2115 0.2006 re
+f*
+0 g
+326.307 426.11 5.6201 0.2006 re
+f*
+1 g
+331.927 426.11 3.4123 0.2006 re
+f*
+0 g
+335.339 426.11 7.6273 0.2006 re
+f*
+1 g
+342.966 426.11 3.6129 0.2006 re
+f*
+0 g
+346.579 426.11 5.2188 0.2006 re
+f*
+1 g
+351.798 426.11 8.0287 0.2006 re
+f*
+0.498 0 0.482 rg
+359.827 426.11 3.8137 0.2006 re
+f*
+1 g
+363.641 426.11 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 426.11 8.8317 0.2006 re
+f*
+1 g
+376.085 426.11 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+379.096 426.11 8.2294 0.2006 re
+f*
+1 g
+387.325 426.11 4.4159 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 426.11 8.8316 0.2006 re
+f*
+1 g
+400.573 426.11 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+403.584 426.11 7.0252 0.2006 re
+f*
+1 g
+410.609 426.11 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+413.82 426.11 3.2116 0.2006 re
+f*
+0 g
+250.836 426.31 20.6741 0.2005 re
+f*
+1 g
+271.51 426.31 7.4266 0.2005 re
+f*
+0 g
+278.937 426.31 16.0575 0.2005 re
+f*
+1 g
+294.994 426.31 4.4159 0.2005 re
+f*
+0 g
+299.41 426.31 4.2151 0.2005 re
+f*
+1 g
+303.625 426.31 3.8137 0.2005 re
+f*
+0 g
+307.439 426.31 4.4158 0.2005 re
+f*
+1 g
+311.855 426.31 3.4122 0.2005 re
+f*
+0 g
+315.267 426.31 7.8281 0.2005 re
+f*
+1 g
+323.095 426.31 3.2115 0.2005 re
+f*
+0 g
+326.307 426.31 5.8208 0.2005 re
+f*
+1 g
+332.127 426.31 3.2116 0.2005 re
+f*
+0 g
+335.339 426.31 7.6273 0.2005 re
+f*
+1 g
+342.966 426.31 3.6129 0.2005 re
+f*
+0 g
+346.579 426.31 5.018 0.2005 re
+f*
+1 g
+351.597 426.31 8.2295 0.2005 re
+f*
+0.498 0 0.482 rg
+359.827 426.31 3.8137 0.2005 re
+f*
+1 g
+363.641 426.31 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 426.31 9.0324 0.2005 re
+f*
+1 g
+376.286 426.31 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+379.297 426.31 8.0287 0.2005 re
+f*
+1 g
+387.325 426.31 4.4159 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 426.31 9.0324 0.2005 re
+f*
+1 g
+400.774 426.31 2.81 0.2005 re
+f*
+0.498 0 0.482 rg
+403.584 426.31 7.0252 0.2005 re
+f*
+1 g
+410.609 426.31 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+413.82 426.31 3.2116 0.2005 re
+f*
+0 g
+250.836 426.511 20.6741 0.2006 re
+f*
+1 g
+271.51 426.511 7.4266 0.2006 re
+f*
+0 g
+278.937 426.511 16.0575 0.2006 re
+f*
+1 g
+294.994 426.511 4.4159 0.2006 re
+f*
+0 g
+299.41 426.511 4.2151 0.2006 re
+f*
+1 g
+303.625 426.511 3.8137 0.2006 re
+f*
+0 g
+307.439 426.511 4.6165 0.2006 re
+f*
+1 g
+312.056 426.511 3.2115 0.2006 re
+f*
+0 g
+315.267 426.511 7.8281 0.2006 re
+f*
+1 g
+323.095 426.511 3.0107 0.2006 re
+f*
+0 g
+326.106 426.511 6.2223 0.2006 re
+f*
+1 g
+332.328 426.511 3.2116 0.2006 re
+f*
+0 g
+335.54 426.511 7.4266 0.2006 re
+f*
+1 g
+342.966 426.511 3.4122 0.2006 re
+f*
+0 g
+346.379 426.511 5.2187 0.2006 re
+f*
+1 g
+351.597 426.511 8.2295 0.2006 re
+f*
+0.498 0 0.482 rg
+359.827 426.511 3.8137 0.2006 re
+f*
+1 g
+363.641 426.511 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 426.511 9.2331 0.2006 re
+f*
+1 g
+376.486 426.511 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+379.297 426.511 7.828 0.2006 re
+f*
+1 g
+387.125 426.511 4.6166 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 426.511 9.2331 0.2006 re
+f*
+1 g
+400.974 426.511 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+403.784 426.511 6.8244 0.2006 re
+f*
+1 g
+410.609 426.511 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+413.62 426.511 3.4123 0.2006 re
+f*
+0 g
+250.836 426.711 20.6741 0.2005 re
+f*
+1 g
+271.51 426.711 7.4266 0.2005 re
+f*
+0 g
+278.937 426.711 16.0575 0.2005 re
+f*
+1 g
+294.994 426.711 4.6166 0.2005 re
+f*
+0 g
+299.611 426.711 4.0144 0.2005 re
+f*
+1 g
+303.625 426.711 3.8137 0.2005 re
+f*
+0 g
+307.439 426.711 4.8172 0.2005 re
+f*
+1 g
+312.256 426.711 3.0108 0.2005 re
+f*
+0 g
+315.267 426.711 7.8281 0.2005 re
+f*
+1 g
+323.095 426.711 3.0107 0.2005 re
+f*
+0 g
+326.106 426.711 6.2223 0.2005 re
+f*
+1 g
+332.328 426.711 3.2116 0.2005 re
+f*
+0 g
+335.54 426.711 7.4266 0.2005 re
+f*
+1 g
+342.966 426.711 3.4122 0.2005 re
+f*
+0 g
+346.379 426.711 5.018 0.2005 re
+f*
+1 g
+351.397 426.711 8.4302 0.2005 re
+f*
+0.498 0 0.482 rg
+359.827 426.711 3.8137 0.2005 re
+f*
+1 g
+363.641 426.711 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 426.711 9.4339 0.2005 re
+f*
+1 g
+376.687 426.711 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+379.297 426.711 7.6274 0.2005 re
+f*
+1 g
+386.924 426.711 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+387.928 426.711 0.2006 0.2005 re
+f*
+1 g
+388.128 426.711 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 426.711 9.4338 0.2005 re
+f*
+1 g
+401.175 426.711 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+403.784 426.711 6.6237 0.2005 re
+f*
+1 g
+410.408 426.711 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+413.419 426.711 3.4122 0.2005 re
+f*
+0 g
+251.037 426.912 20.4734 0.2006 re
+f*
+1 g
+271.51 426.912 7.4266 0.2006 re
+f*
+0 g
+278.937 426.912 16.0575 0.2006 re
+f*
+1 g
+294.994 426.912 4.6166 0.2006 re
+f*
+0 g
+299.611 426.912 4.0144 0.2006 re
+f*
+1 g
+303.625 426.912 3.8137 0.2006 re
+f*
+0 g
+307.439 426.912 5.0179 0.2006 re
+f*
+1 g
+312.457 426.912 3.0108 0.2006 re
+f*
+0 g
+315.468 426.912 7.6274 0.2006 re
+f*
+1 g
+323.095 426.912 2.81 0.2006 re
+f*
+0 g
+325.905 426.912 6.6238 0.2006 re
+f*
+1 g
+332.529 426.912 3.0108 0.2006 re
+f*
+0 g
+335.54 426.912 7.4266 0.2006 re
+f*
+1 g
+342.966 426.912 3.2115 0.2006 re
+f*
+0 g
+346.178 426.912 5.2187 0.2006 re
+f*
+1 g
+351.397 426.912 8.4302 0.2006 re
+f*
+0.498 0 0.482 rg
+359.827 426.912 3.8137 0.2006 re
+f*
+1 g
+363.641 426.912 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 426.912 9.6346 0.2006 re
+f*
+1 g
+376.888 426.912 2.6093 0.2006 re
+f*
+0.498 0 0.482 rg
+379.497 426.912 7.4267 0.2006 re
+f*
+1 g
+386.924 426.912 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+387.928 426.912 0.2006 0.2006 re
+f*
+1 g
+388.128 426.912 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 426.912 9.6345 0.2006 re
+f*
+1 g
+401.376 426.912 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+403.985 426.912 6.423 0.2006 re
+f*
+1 g
+410.408 426.912 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+413.419 426.912 3.4122 0.2006 re
+f*
+0 g
+251.037 427.113 20.4734 0.2006 re
+f*
+1 g
+271.51 427.113 7.4266 0.2006 re
+f*
+0 g
+278.937 427.113 16.0575 0.2006 re
+f*
+1 g
+294.994 427.113 3.613 0.2006 re
+f*
+0 g
+298.607 427.113 0.2007 0.2006 re
+f*
+1 g
+298.808 427.113 1.0036 0.2006 re
+f*
+0 g
+299.812 427.113 4.0144 0.2006 re
+f*
+1 g
+303.826 427.113 3.613 0.2006 re
+f*
+0 g
+307.439 427.113 5.0179 0.2006 re
+f*
+1 g
+312.457 427.113 3.0108 0.2006 re
+f*
+0 g
+315.468 427.113 7.6274 0.2006 re
+f*
+1 g
+323.095 427.113 2.6093 0.2006 re
+f*
+0 g
+325.704 427.113 7.0252 0.2006 re
+f*
+1 g
+332.73 427.113 3.0108 0.2006 re
+f*
+0 g
+335.74 427.113 7.0252 0.2006 re
+f*
+1 g
+342.766 427.113 3.4122 0.2006 re
+f*
+0 g
+346.178 427.113 5.018 0.2006 re
+f*
+1 g
+351.196 427.113 8.6309 0.2006 re
+f*
+0.498 0 0.482 rg
+359.827 427.113 3.8137 0.2006 re
+f*
+1 g
+363.641 427.113 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 427.113 9.8353 0.2006 re
+f*
+1 g
+377.089 427.113 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+379.698 427.113 7.0251 0.2006 re
+f*
+1 g
+386.723 427.113 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+387.727 427.113 0.4014 0.2006 re
+f*
+1 g
+388.128 427.113 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 427.113 9.8352 0.2006 re
+f*
+1 g
+401.576 427.113 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+403.985 427.113 6.2223 0.2006 re
+f*
+1 g
+410.207 427.113 3.0107 0.2006 re
+f*
+0.498 0 0.482 rg
+413.218 427.113 3.613 0.2006 re
+f*
+0 g
+251.037 427.313 20.4734 0.2005 re
+f*
+1 g
+271.51 427.313 7.4266 0.2005 re
+f*
+0 g
+278.937 427.313 16.0575 0.2005 re
+f*
+1 g
+294.994 427.313 3.613 0.2005 re
+f*
+0 g
+298.607 427.313 0.2007 0.2005 re
+f*
+1 g
+298.808 427.313 1.0036 0.2005 re
+f*
+0 g
+299.812 427.313 4.0144 0.2005 re
+f*
+1 g
+303.826 427.313 3.613 0.2005 re
+f*
+0 g
+307.439 427.313 5.2187 0.2005 re
+f*
+1 g
+312.658 427.313 2.81 0.2005 re
+f*
+0 g
+315.468 427.313 7.6274 0.2005 re
+f*
+1 g
+323.095 427.313 2.6093 0.2005 re
+f*
+0 g
+325.704 427.313 7.2259 0.2005 re
+f*
+1 g
+332.93 427.313 2.8101 0.2005 re
+f*
+0 g
+335.74 427.313 7.0252 0.2005 re
+f*
+1 g
+342.766 427.313 3.2114 0.2005 re
+f*
+0 g
+345.977 427.313 5.2188 0.2005 re
+f*
+1 g
+351.196 427.313 8.6309 0.2005 re
+f*
+0.498 0 0.482 rg
+359.827 427.313 3.8137 0.2005 re
+f*
+1 g
+363.641 427.313 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 427.313 10.036 0.2005 re
+f*
+1 g
+377.289 427.313 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+379.698 427.313 6.8244 0.2005 re
+f*
+1 g
+386.522 427.313 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+387.526 427.313 0.6021 0.2005 re
+f*
+1 g
+388.128 427.313 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 427.313 10.036 0.2005 re
+f*
+1 g
+401.777 427.313 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+404.186 427.313 6.0216 0.2005 re
+f*
+1 g
+410.207 427.313 2.8101 0.2005 re
+f*
+0.498 0 0.482 rg
+413.017 427.313 3.8136 0.2005 re
+f*
+0 g
+251.037 427.514 20.4734 0.2006 re
+f*
+1 g
+271.51 427.514 7.4266 0.2006 re
+f*
+0 g
+278.937 427.514 16.0575 0.2006 re
+f*
+1 g
+294.994 427.514 3.613 0.2006 re
+f*
+0 g
+298.607 427.514 0.4014 0.2006 re
+f*
+1 g
+299.009 427.514 1.0036 0.2006 re
+f*
+0 g
+300.012 427.514 3.8137 0.2006 re
+f*
+1 g
+303.826 427.514 3.613 0.2006 re
+f*
+0 g
+307.439 427.514 5.4194 0.2006 re
+f*
+1 g
+312.858 427.514 2.8101 0.2006 re
+f*
+0 g
+315.669 427.514 7.2258 0.2006 re
+f*
+1 g
+322.894 427.514 2.6094 0.2006 re
+f*
+0 g
+325.504 427.514 7.4266 0.2006 re
+f*
+1 g
+332.93 427.514 3.0108 0.2006 re
+f*
+0 g
+335.941 427.514 6.8245 0.2006 re
+f*
+1 g
+342.766 427.514 3.2114 0.2006 re
+f*
+0 g
+345.977 427.514 5.018 0.2006 re
+f*
+1 g
+350.995 427.514 8.8317 0.2006 re
+f*
+0.498 0 0.482 rg
+359.827 427.514 3.8137 0.2006 re
+f*
+1 g
+363.641 427.514 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 427.514 10.2367 0.2006 re
+f*
+1 g
+377.49 427.514 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+379.899 427.514 6.4229 0.2006 re
+f*
+1 g
+386.322 427.514 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+387.325 427.514 0.8029 0.2006 re
+f*
+1 g
+388.128 427.514 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 427.514 10.2367 0.2006 re
+f*
+1 g
+401.978 427.514 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+404.386 427.514 5.6202 0.2006 re
+f*
+1 g
+410.007 427.514 2.81 0.2006 re
+f*
+0.498 0 0.482 rg
+412.817 427.514 3.8137 0.2006 re
+f*
+0 g
+251.238 427.714 20.2727 0.2005 re
+f*
+1 g
+271.51 427.714 7.4266 0.2005 re
+f*
+0 g
+278.937 427.714 16.0575 0.2005 re
+f*
+1 g
+294.994 427.714 3.613 0.2005 re
+f*
+0 g
+298.607 427.714 0.4014 0.2005 re
+f*
+1 g
+299.009 427.714 1.0036 0.2005 re
+f*
+0 g
+300.012 427.714 4.0144 0.2005 re
+f*
+1 g
+304.027 427.714 3.4123 0.2005 re
+f*
+0 g
+307.439 427.714 5.6201 0.2005 re
+f*
+1 g
+313.059 427.714 2.6094 0.2005 re
+f*
+0 g
+315.669 427.714 7.2258 0.2005 re
+f*
+1 g
+322.894 427.714 2.4087 0.2005 re
+f*
+0 g
+325.303 427.714 7.828 0.2005 re
+f*
+1 g
+333.131 427.714 2.8101 0.2005 re
+f*
+0 g
+335.941 427.714 6.8245 0.2005 re
+f*
+1 g
+342.766 427.714 3.0108 0.2005 re
+f*
+0 g
+345.776 427.714 5.018 0.2005 re
+f*
+1 g
+350.794 427.714 9.0323 0.2005 re
+f*
+0.498 0 0.482 rg
+359.827 427.714 3.8137 0.2005 re
+f*
+1 g
+363.641 427.714 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 427.714 10.4375 0.2005 re
+f*
+1 g
+377.691 427.714 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+380.099 427.714 6.0215 0.2005 re
+f*
+1 g
+386.121 427.714 1.2043 0.2005 re
+f*
+0.498 0 0.482 rg
+387.325 427.714 0.8029 0.2005 re
+f*
+1 g
+388.128 427.714 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 427.714 10.4374 0.2005 re
+f*
+1 g
+402.179 427.714 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+404.587 427.714 5.4195 0.2005 re
+f*
+1 g
+410.007 427.714 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+412.616 427.714 4.0144 0.2005 re
+f*
+0 g
+251.238 427.915 20.2727 0.2006 re
+f*
+1 g
+271.51 427.915 7.4266 0.2006 re
+f*
+0 g
+278.937 427.915 16.0575 0.2006 re
+f*
+1 g
+294.994 427.915 3.613 0.2006 re
+f*
+0 g
+298.607 427.915 0.6021 0.2006 re
+f*
+1 g
+299.209 427.915 1.0036 0.2006 re
+f*
+0 g
+300.213 427.915 3.8137 0.2006 re
+f*
+1 g
+304.027 427.915 3.4123 0.2006 re
+f*
+0 g
+307.439 427.915 5.8208 0.2006 re
+f*
+1 g
+313.26 427.915 2.6093 0.2006 re
+f*
+0 g
+315.869 427.915 7.0252 0.2006 re
+f*
+1 g
+322.894 427.915 2.208 0.2006 re
+f*
+0 g
+325.102 427.915 8.2294 0.2006 re
+f*
+1 g
+333.332 427.915 2.8101 0.2006 re
+f*
+0 g
+336.142 427.915 6.423 0.2006 re
+f*
+1 g
+342.565 427.915 3.0108 0.2006 re
+f*
+0 g
+345.576 427.915 5.2188 0.2006 re
+f*
+1 g
+350.794 427.915 8.8316 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 427.915 4.0144 0.2006 re
+f*
+1 g
+363.641 427.915 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 427.915 10.6382 0.2006 re
+f*
+1 g
+377.892 427.915 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+380.3 427.915 5.6201 0.2006 re
+f*
+1 g
+385.92 427.915 1.2043 0.2006 re
+f*
+0.498 0 0.482 rg
+387.125 427.915 1.0036 0.2006 re
+f*
+1 g
+388.128 427.915 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 427.915 10.8388 0.2006 re
+f*
+1 g
+402.58 427.915 2.208 0.2006 re
+f*
+0.498 0 0.482 rg
+404.788 427.915 5.018 0.2006 re
+f*
+1 g
+409.806 427.915 2.6093 0.2006 re
+f*
+0.498 0 0.482 rg
+412.415 427.915 4.2151 0.2006 re
+f*
+0 g
+251.238 428.115 20.2727 0.2006 re
+f*
+1 g
+271.51 428.115 7.4266 0.2006 re
+f*
+0 g
+278.937 428.115 16.0575 0.2006 re
+f*
+1 g
+294.994 428.115 3.613 0.2006 re
+f*
+0 g
+298.607 428.115 0.8029 0.2006 re
+f*
+1 g
+299.41 428.115 1.0036 0.2006 re
+f*
+0 g
+300.414 428.115 3.6129 0.2006 re
+f*
+1 g
+304.027 428.115 3.4123 0.2006 re
+f*
+0 g
+307.439 428.115 6.0215 0.2006 re
+f*
+1 g
+313.461 428.115 2.4086 0.2006 re
+f*
+0 g
+315.869 428.115 6.8246 0.2006 re
+f*
+1 g
+322.694 428.115 2.2078 0.2006 re
+f*
+0 g
+324.902 428.115 8.631 0.2006 re
+f*
+1 g
+333.533 428.115 2.6093 0.2006 re
+f*
+0 g
+336.142 428.115 6.423 0.2006 re
+f*
+1 g
+342.565 428.115 2.8101 0.2006 re
+f*
+0 g
+345.375 428.115 5.2187 0.2006 re
+f*
+1 g
+350.594 428.115 9.0324 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 428.115 4.0144 0.2006 re
+f*
+1 g
+363.641 428.115 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 428.115 11.0396 0.2006 re
+f*
+1 g
+378.293 428.115 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+380.501 428.115 5.018 0.2006 re
+f*
+1 g
+385.519 428.115 1.4051 0.2006 re
+f*
+0.498 0 0.482 rg
+386.924 428.115 1.2042 0.2006 re
+f*
+1 g
+388.128 428.115 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 428.115 11.0396 0.2006 re
+f*
+1 g
+402.781 428.115 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+404.989 428.115 4.6165 0.2006 re
+f*
+1 g
+409.605 428.115 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+412.014 428.115 4.6165 0.2006 re
+f*
+0 g
+251.238 428.316 20.2727 0.2005 re
+f*
+1 g
+271.51 428.316 7.4266 0.2005 re
+f*
+0 g
+278.937 428.316 16.0575 0.2005 re
+f*
+1 g
+294.994 428.316 3.613 0.2005 re
+f*
+0 g
+298.607 428.316 0.8029 0.2005 re
+f*
+1 g
+299.41 428.316 1.2043 0.2005 re
+f*
+0 g
+300.615 428.316 3.4122 0.2005 re
+f*
+1 g
+304.027 428.316 3.2115 0.2005 re
+f*
+0 g
+307.238 428.316 6.4231 0.2005 re
+f*
+1 g
+313.661 428.316 2.4086 0.2005 re
+f*
+0 g
+316.07 428.316 6.6238 0.2005 re
+f*
+1 g
+322.694 428.316 2.0071 0.2005 re
+f*
+0 g
+324.701 428.316 9.0324 0.2005 re
+f*
+1 g
+333.733 428.316 2.6094 0.2005 re
+f*
+0 g
+336.343 428.316 6.0215 0.2005 re
+f*
+1 g
+342.364 428.316 3.0108 0.2005 re
+f*
+0 g
+345.375 428.316 5.018 0.2005 re
+f*
+1 g
+350.393 428.316 9.2331 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 428.316 1.0036 0.2005 re
+f*
+1 g
+360.63 428.316 0.2007 0.2005 re
+f*
+0.498 0 0.482 rg
+360.83 428.316 2.8101 0.2005 re
+f*
+1 g
+363.641 428.316 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 428.316 11.2403 0.2005 re
+f*
+1 g
+378.494 428.316 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+380.902 428.316 4.215 0.2005 re
+f*
+1 g
+385.117 428.316 1.6058 0.2005 re
+f*
+0.498 0 0.482 rg
+386.723 428.316 1.405 0.2005 re
+f*
+1 g
+388.128 428.316 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 428.316 11.441 0.2005 re
+f*
+1 g
+403.182 428.316 2.2079 0.2005 re
+f*
+0.498 0 0.482 rg
+405.39 428.316 3.8137 0.2005 re
+f*
+1 g
+409.204 428.316 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+411.813 428.316 4.8173 0.2005 re
+f*
+0 g
+251.438 428.516 20.072 0.2006 re
+f*
+1 g
+271.51 428.516 7.4266 0.2006 re
+f*
+0 g
+278.937 428.516 16.0575 0.2006 re
+f*
+1 g
+294.994 428.516 3.613 0.2006 re
+f*
+0 g
+298.607 428.516 1.0036 0.2006 re
+f*
+1 g
+299.611 428.516 1.2043 0.2006 re
+f*
+0 g
+300.815 428.516 3.2115 0.2006 re
+f*
+1 g
+304.027 428.516 3.2115 0.2006 re
+f*
+0 g
+307.238 428.516 6.8245 0.2006 re
+f*
+1 g
+314.063 428.516 2.2079 0.2006 re
+f*
+0 g
+316.271 428.516 6.2223 0.2006 re
+f*
+1 g
+322.493 428.516 2.0072 0.2006 re
+f*
+0 g
+324.5 428.516 9.4338 0.2006 re
+f*
+1 g
+333.934 428.516 2.6094 0.2006 re
+f*
+0 g
+336.543 428.516 5.8208 0.2006 re
+f*
+1 g
+342.364 428.516 2.8101 0.2006 re
+f*
+0 g
+345.174 428.516 5.2187 0.2006 re
+f*
+1 g
+350.393 428.516 9.2331 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 428.516 1.0036 0.2006 re
+f*
+1 g
+360.63 428.516 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 428.516 11.6418 0.2006 re
+f*
+1 g
+378.895 428.516 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+381.304 428.516 3.4122 0.2006 re
+f*
+1 g
+384.716 428.516 1.6057 0.2006 re
+f*
+0.498 0 0.482 rg
+386.322 428.516 1.8065 0.2006 re
+f*
+1 g
+388.128 428.516 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 428.516 11.6417 0.2006 re
+f*
+1 g
+403.383 428.516 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+405.792 428.516 3.0108 0.2006 re
+f*
+1 g
+408.802 428.516 2.6093 0.2006 re
+f*
+0.498 0 0.482 rg
+411.412 428.516 5.018 0.2006 re
+f*
+0 g
+251.438 428.717 20.072 0.2005 re
+f*
+1 g
+271.51 428.717 7.4266 0.2005 re
+f*
+0 g
+278.937 428.717 16.0575 0.2005 re
+f*
+1 g
+294.994 428.717 3.613 0.2005 re
+f*
+0 g
+298.607 428.717 1.2043 0.2005 re
+f*
+1 g
+299.812 428.717 1.2043 0.2005 re
+f*
+0 g
+301.016 428.717 3.0108 0.2005 re
+f*
+1 g
+304.027 428.717 3.2115 0.2005 re
+f*
+0 g
+307.238 428.717 7.0252 0.2005 re
+f*
+1 g
+314.263 428.717 2.2079 0.2005 re
+f*
+0 g
+316.471 428.717 6.0216 0.2005 re
+f*
+1 g
+322.493 428.717 1.8065 0.2005 re
+f*
+0 g
+324.299 428.717 10.0359 0.2005 re
+f*
+1 g
+334.335 428.717 2.4087 0.2005 re
+f*
+0 g
+336.744 428.717 5.4194 0.2005 re
+f*
+1 g
+342.163 428.717 2.81 0.2005 re
+f*
+0 g
+344.973 428.717 5.2188 0.2005 re
+f*
+1 g
+350.192 428.717 9.4338 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 428.717 1.0036 0.2005 re
+f*
+1 g
+360.63 428.717 6.6237 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 428.717 12.0432 0.2005 re
+f*
+1 g
+379.297 428.717 2.81 0.2005 re
+f*
+0.498 0 0.482 rg
+382.107 428.717 1.8066 0.2005 re
+f*
+1 g
+383.913 428.717 2.2078 0.2005 re
+f*
+0.498 0 0.482 rg
+386.121 428.717 2.0072 0.2005 re
+f*
+1 g
+388.128 428.717 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 428.717 12.0432 0.2005 re
+f*
+1 g
+403.784 428.717 2.81 0.2005 re
+f*
+0.498 0 0.482 rg
+406.594 428.717 1.6058 0.2005 re
+f*
+1 g
+408.2 428.717 2.8101 0.2005 re
+f*
+0.498 0 0.482 rg
+411.01 428.717 5.4194 0.2005 re
+f*
+0 g
+251.438 428.918 20.072 0.2006 re
+f*
+1 g
+271.51 428.918 7.4266 0.2006 re
+f*
+0 g
+278.937 428.918 16.0575 0.2006 re
+f*
+1 g
+294.994 428.918 3.613 0.2006 re
+f*
+0 g
+298.607 428.918 1.405 0.2006 re
+f*
+1 g
+300.012 428.918 1.2043 0.2006 re
+f*
+0 g
+301.217 428.918 2.8101 0.2006 re
+f*
+1 g
+304.027 428.918 3.0108 0.2006 re
+f*
+0 g
+307.038 428.918 7.4266 0.2006 re
+f*
+1 g
+314.464 428.918 2.2079 0.2006 re
+f*
+0 g
+316.672 428.918 5.6202 0.2006 re
+f*
+1 g
+322.292 428.918 1.8065 0.2006 re
+f*
+0 g
+324.099 428.918 10.4374 0.2006 re
+f*
+1 g
+334.536 428.918 2.4086 0.2006 re
+f*
+0 g
+336.945 428.918 5.018 0.2006 re
+f*
+1 g
+341.963 428.918 2.8101 0.2006 re
+f*
+0 g
+344.773 428.918 5.2186 0.2006 re
+f*
+1 g
+349.991 428.918 9.6346 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 428.918 1.0036 0.2006 re
+f*
+1 g
+360.63 428.918 6.6237 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 428.918 12.4447 0.2006 re
+f*
+1 g
+379.698 428.918 6.0215 0.2006 re
+f*
+0.498 0 0.482 rg
+385.72 428.918 2.4086 0.2006 re
+f*
+1 g
+388.128 428.918 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 428.918 12.6453 0.2006 re
+f*
+1 g
+404.386 428.918 6.2223 0.2006 re
+f*
+0.498 0 0.482 rg
+410.609 428.918 5.8209 0.2006 re
+f*
+0 g
+251.438 429.118 20.2727 0.2006 re
+f*
+1 g
+271.711 429.118 7.2259 0.2006 re
+f*
+0 g
+278.937 429.118 16.0575 0.2006 re
+f*
+1 g
+294.994 429.118 3.613 0.2006 re
+f*
+0 g
+298.607 429.118 1.6057 0.2006 re
+f*
+1 g
+300.213 429.118 1.2044 0.2006 re
+f*
+0 g
+301.417 429.118 2.4086 0.2006 re
+f*
+1 g
+303.826 429.118 3.2115 0.2006 re
+f*
+0 g
+307.038 429.118 7.828 0.2006 re
+f*
+1 g
+314.866 429.118 2.0072 0.2006 re
+f*
+0 g
+316.873 429.118 5.2188 0.2006 re
+f*
+1 g
+322.092 429.118 1.8064 0.2006 re
+f*
+0 g
+323.898 429.118 10.8389 0.2006 re
+f*
+1 g
+334.737 429.118 2.4086 0.2006 re
+f*
+0 g
+337.146 429.118 4.6166 0.2006 re
+f*
+1 g
+341.762 429.118 2.6093 0.2006 re
+f*
+0 g
+344.371 429.118 5.4195 0.2006 re
+f*
+1 g
+349.791 429.118 9.8352 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 429.118 20.4734 0.2006 re
+f*
+1 g
+380.099 429.118 5.2187 0.2006 re
+f*
+0.498 0 0.482 rg
+385.318 429.118 2.81 0.2006 re
+f*
+1 g
+388.128 429.118 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 429.118 13.0468 0.2006 re
+f*
+1 g
+404.788 429.118 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+410.207 429.118 6.2223 0.2006 re
+f*
+0 g
+251.639 429.319 20.0719 0.2005 re
+f*
+1 g
+271.711 429.319 7.2259 0.2005 re
+f*
+0 g
+278.937 429.319 16.0575 0.2005 re
+f*
+1 g
+294.994 429.319 3.613 0.2005 re
+f*
+0 g
+298.607 429.319 1.8065 0.2005 re
+f*
+1 g
+300.414 429.319 1.405 0.2005 re
+f*
+0 g
+301.819 429.319 1.8065 0.2005 re
+f*
+1 g
+303.625 429.319 3.2115 0.2005 re
+f*
+0 g
+306.837 429.319 8.4302 0.2005 re
+f*
+1 g
+315.267 429.319 1.8065 0.2005 re
+f*
+0 g
+317.074 429.319 4.8172 0.2005 re
+f*
+1 g
+321.891 429.319 1.6058 0.2005 re
+f*
+0 g
+323.497 429.319 11.6417 0.2005 re
+f*
+1 g
+335.138 429.319 2.208 0.2005 re
+f*
+0 g
+337.346 429.319 4.2151 0.2005 re
+f*
+1 g
+341.561 429.319 2.6093 0.2005 re
+f*
+0 g
+344.171 429.319 5.6202 0.2005 re
+f*
+1 g
+349.791 429.319 9.8352 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 429.319 21.2763 0.2005 re
+f*
+1 g
+380.902 429.319 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+384.716 429.319 3.4122 0.2005 re
+f*
+1 g
+388.128 429.319 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 429.319 13.8497 0.2005 re
+f*
+1 g
+405.591 429.319 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+409.404 429.319 6.8244 0.2005 re
+f*
+0 g
+251.639 429.519 20.0719 0.2005 re
+f*
+1 g
+271.711 429.519 7.2259 0.2005 re
+f*
+0 g
+278.937 429.519 13.0467 0.2005 re
+f*
+1 g
+291.984 429.519 0.2008 0.2005 re
+f*
+0 g
+292.184 429.519 2.81 0.2005 re
+f*
+1 g
+294.994 429.519 3.613 0.2005 re
+f*
+0 g
+298.607 429.519 2.0072 0.2005 re
+f*
+1 g
+300.615 429.519 1.6057 0.2005 re
+f*
+0 g
+302.22 429.519 1.2044 0.2005 re
+f*
+1 g
+303.425 429.519 3.2115 0.2005 re
+f*
+0 g
+306.636 429.519 9.0324 0.2005 re
+f*
+1 g
+315.669 429.519 1.8064 0.2005 re
+f*
+0 g
+317.475 429.519 4.2152 0.2005 re
+f*
+1 g
+321.69 429.519 1.405 0.2005 re
+f*
+0 g
+323.095 429.519 12.2439 0.2005 re
+f*
+1 g
+335.339 429.519 2.4086 0.2005 re
+f*
+0 g
+337.748 429.519 3.6129 0.2005 re
+f*
+1 g
+341.361 429.519 2.4087 0.2005 re
+f*
+0 g
+343.769 429.519 5.8208 0.2005 re
+f*
+1 g
+349.59 429.519 10.036 0.2005 re
+f*
+0.498 0 0.482 rg
+359.626 429.519 22.4805 0.2005 re
+f*
+1 g
+382.107 429.519 1.4051 0.2005 re
+f*
+0.498 0 0.482 rg
+383.512 429.519 4.6165 0.2005 re
+f*
+1 g
+388.128 429.519 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 429.519 15.054 0.2005 re
+f*
+1 g
+406.795 429.519 1.6057 0.2005 re
+f*
+0.498 0 0.482 rg
+408.401 429.519 7.828 0.2005 re
+f*
+0 g
+251.639 429.72 20.0719 0.2006 re
+f*
+1 g
+271.711 429.72 7.2259 0.2006 re
+f*
+0 g
+278.937 429.72 13.0467 0.2006 re
+f*
+1 g
+291.984 429.72 6.6238 0.2006 re
+f*
+0 g
+298.607 429.72 2.2079 0.2006 re
+f*
+1 g
+300.815 429.72 5.6202 0.2006 re
+f*
+0 g
+306.435 429.72 9.6345 0.2006 re
+f*
+1 g
+316.07 429.72 1.8064 0.2006 re
+f*
+0 g
+317.876 429.72 3.4123 0.2006 re
+f*
+1 g
+321.289 429.72 1.4051 0.2006 re
+f*
+0 g
+322.694 429.72 13.0467 0.2006 re
+f*
+1 g
+335.74 429.72 2.6093 0.2006 re
+f*
+0 g
+338.35 429.72 2.4087 0.2006 re
+f*
+1 g
+340.758 429.72 2.81 0.2006 re
+f*
+0 g
+343.568 429.72 5.8209 0.2006 re
+f*
+1 g
+349.389 429.72 10.2367 0.2006 re
+f*
+0.498 0 0.482 rg
+359.626 429.72 28.5021 0.2006 re
+f*
+1 g
+388.128 429.72 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 429.72 24.4877 0.2006 re
+f*
+0 g
+251.639 429.92 20.0719 0.2006 re
+f*
+1 g
+271.711 429.92 7.2259 0.2006 re
+f*
+0 g
+278.937 429.92 13.0467 0.2006 re
+f*
+1 g
+291.984 429.92 6.6238 0.2006 re
+f*
+0 g
+298.607 429.92 2.6093 0.2006 re
+f*
+1 g
+301.217 429.92 5.018 0.2006 re
+f*
+0 g
+306.235 429.92 10.4374 0.2006 re
+f*
+1 g
+316.672 429.92 2.0073 0.2006 re
+f*
+0 g
+318.679 429.92 1.8064 0.2006 re
+f*
+1 g
+320.486 429.92 1.6058 0.2006 re
+f*
+0 g
+322.092 429.92 14.0503 0.2006 re
+f*
+1 g
+336.142 429.92 7.0252 0.2006 re
+f*
+0 g
+343.167 429.92 6.0216 0.2006 re
+f*
+1 g
+349.189 429.92 10.2367 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 429.92 28.7028 0.2006 re
+f*
+1 g
+388.128 429.92 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 429.92 24.2871 0.2006 re
+f*
+0 g
+251.84 430.121 19.8712 0.2006 re
+f*
+1 g
+271.711 430.121 7.2259 0.2006 re
+f*
+0 g
+278.937 430.121 13.0467 0.2006 re
+f*
+1 g
+291.984 430.121 6.6238 0.2006 re
+f*
+0 g
+298.607 430.121 2.8101 0.2006 re
+f*
+1 g
+301.417 430.121 4.4158 0.2006 re
+f*
+0 g
+305.833 430.121 11.6417 0.2006 re
+f*
+1 g
+317.475 430.121 4.0144 0.2006 re
+f*
+0 g
+321.489 430.121 15.2547 0.2006 re
+f*
+1 g
+336.744 430.121 6.0216 0.2006 re
+f*
+0 g
+342.766 430.121 6.2222 0.2006 re
+f*
+1 g
+348.988 430.121 10.4375 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 430.121 28.7028 0.2006 re
+f*
+1 g
+388.128 430.121 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 430.121 24.2871 0.2006 re
+f*
+0 g
+251.84 430.322 19.8712 0.2005 re
+f*
+1 g
+271.711 430.322 7.2259 0.2005 re
+f*
+0 g
+278.937 430.322 23.0827 0.2005 re
+f*
+1 g
+302.02 430.322 3.4123 0.2005 re
+f*
+0 g
+305.432 430.322 13.4481 0.2005 re
+f*
+1 g
+318.88 430.322 1.6058 0.2005 re
+f*
+0 g
+320.486 430.322 16.8605 0.2005 re
+f*
+1 g
+337.346 430.322 4.8172 0.2005 re
+f*
+0 g
+342.163 430.322 6.6238 0.2005 re
+f*
+1 g
+348.787 430.322 10.6381 0.2005 re
+f*
+0.498 0 0.482 rg
+359.425 430.322 28.7028 0.2005 re
+f*
+1 g
+388.128 430.322 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 430.322 24.2871 0.2005 re
+f*
+0 g
+251.84 430.522 19.8712 0.2006 re
+f*
+1 g
+271.711 430.522 7.2259 0.2006 re
+f*
+0 g
+278.937 430.522 23.6849 0.2006 re
+f*
+1 g
+302.622 430.522 2.2079 0.2006 re
+f*
+0 g
+304.83 430.522 33.3194 0.2006 re
+f*
+1 g
+338.149 430.522 3.2115 0.2006 re
+f*
+0 g
+341.361 430.522 7.2259 0.2006 re
+f*
+1 g
+348.586 430.522 10.8389 0.2006 re
+f*
+0.498 0 0.482 rg
+359.425 430.522 28.7028 0.2006 re
+f*
+1 g
+388.128 430.522 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 430.522 24.0863 0.2006 re
+f*
+0 g
+252.04 430.723 19.8712 0.2005 re
+f*
+1 g
+271.912 430.723 7.0252 0.2005 re
+f*
+0 g
+278.937 430.723 69.4489 0.2005 re
+f*
+1 g
+348.386 430.723 11.0396 0.2005 re
+f*
+0.498 0 0.482 rg
+359.425 430.723 28.7028 0.2005 re
+f*
+1 g
+388.128 430.723 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 430.723 24.0863 0.2005 re
+f*
+0 g
+252.04 430.923 19.8712 0.2006 re
+f*
+1 g
+271.912 430.923 7.0252 0.2006 re
+f*
+0 g
+278.937 430.923 69.2482 0.2006 re
+f*
+1 g
+348.185 430.923 11.0395 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 430.923 28.9036 0.2006 re
+f*
+1 g
+388.128 430.923 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 430.923 24.0863 0.2006 re
+f*
+0 g
+252.04 431.124 19.8712 0.2006 re
+f*
+1 g
+271.912 431.124 7.0252 0.2006 re
+f*
+0 g
+278.937 431.124 69.0474 0.2006 re
+f*
+1 g
+347.984 431.124 11.2403 0.2006 re
+f*
+0.498 0 0.482 rg
+359.225 431.124 28.9036 0.2006 re
+f*
+1 g
+388.128 431.124 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 431.124 23.8856 0.2006 re
+f*
+0 g
+252.241 431.324 19.6705 0.2005 re
+f*
+1 g
+271.912 431.324 7.0252 0.2005 re
+f*
+0 g
+278.937 431.324 68.8468 0.2005 re
+f*
+1 g
+347.784 431.324 11.4409 0.2005 re
+f*
+0.498 0 0.482 rg
+359.225 431.324 28.9036 0.2005 re
+f*
+1 g
+388.128 431.324 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 431.324 23.8856 0.2005 re
+f*
+0 g
+252.241 431.525 19.6705 0.2005 re
+f*
+1 g
+271.912 431.525 7.0252 0.2005 re
+f*
+0 g
+278.937 431.525 68.4453 0.2005 re
+f*
+1 g
+347.382 431.525 11.8424 0.2005 re
+f*
+0.498 0 0.482 rg
+359.225 431.525 28.9036 0.2005 re
+f*
+1 g
+388.128 431.525 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 431.525 23.8856 0.2005 re
+f*
+0 g
+252.241 431.725 19.6705 0.2006 re
+f*
+1 g
+271.912 431.725 7.0252 0.2006 re
+f*
+0 g
+278.937 431.725 68.2446 0.2006 re
+f*
+1 g
+347.181 431.725 11.8424 0.2006 re
+f*
+0.498 0 0.482 rg
+359.024 431.725 29.1043 0.2006 re
+f*
+1 g
+388.128 431.725 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 431.725 23.6849 0.2006 re
+f*
+0 g
+252.442 431.926 19.6705 0.2006 re
+f*
+1 g
+272.112 431.926 6.8245 0.2006 re
+f*
+0 g
+278.937 431.926 68.0438 0.2006 re
+f*
+1 g
+346.981 431.926 12.0432 0.2006 re
+f*
+0.498 0 0.482 rg
+359.024 431.926 29.1043 0.2006 re
+f*
+1 g
+388.128 431.926 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 431.926 23.6849 0.2006 re
+f*
+0 g
+252.442 432.127 19.6705 0.2005 re
+f*
+1 g
+272.112 432.127 6.8245 0.2005 re
+f*
+0 g
+278.937 432.127 67.6424 0.2005 re
+f*
+1 g
+346.579 432.127 12.4446 0.2005 re
+f*
+0.498 0 0.482 rg
+359.024 432.127 29.1043 0.2005 re
+f*
+1 g
+388.128 432.127 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 432.127 23.4841 0.2005 re
+f*
+0 g
+252.643 432.327 19.4697 0.2005 re
+f*
+1 g
+272.112 432.327 6.8245 0.2005 re
+f*
+0 g
+278.937 432.327 67.4417 0.2005 re
+f*
+1 g
+346.379 432.327 12.6453 0.2005 re
+f*
+0.498 0 0.482 rg
+359.024 432.327 29.1043 0.2005 re
+f*
+1 g
+388.128 432.327 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 432.327 23.4841 0.2005 re
+f*
+0 g
+252.643 432.528 19.4697 0.2006 re
+f*
+1 g
+272.112 432.528 6.8245 0.2006 re
+f*
+0 g
+278.937 432.528 67.0402 0.2006 re
+f*
+1 g
+345.977 432.528 12.8461 0.2006 re
+f*
+0.498 0 0.482 rg
+358.823 432.528 29.305 0.2006 re
+f*
+1 g
+388.128 432.528 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 432.528 23.4841 0.2006 re
+f*
+0 g
+252.643 432.728 19.6705 0.2006 re
+f*
+1 g
+272.313 432.728 6.6237 0.2006 re
+f*
+0 g
+278.937 432.728 66.8396 0.2006 re
+f*
+1 g
+345.776 432.728 13.0467 0.2006 re
+f*
+0.498 0 0.482 rg
+358.823 432.728 29.305 0.2006 re
+f*
+1 g
+388.128 432.728 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 432.728 23.2835 0.2006 re
+f*
+0 g
+252.843 432.929 19.4698 0.2006 re
+f*
+1 g
+272.313 432.929 6.6237 0.2006 re
+f*
+0 g
+278.937 432.929 66.4381 0.2006 re
+f*
+1 g
+345.375 432.929 13.4482 0.2006 re
+f*
+0.498 0 0.482 rg
+358.823 432.929 29.305 0.2006 re
+f*
+1 g
+388.128 432.929 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 432.929 23.2835 0.2006 re
+f*
+0 g
+252.843 433.129 19.4698 0.2006 re
+f*
+1 g
+272.313 433.129 6.6237 0.2006 re
+f*
+0 g
+278.937 433.129 66.0366 0.2006 re
+f*
+1 g
+344.973 433.129 13.649 0.2006 re
+f*
+0.498 0 0.482 rg
+358.623 433.129 29.5057 0.2006 re
+f*
+1 g
+388.128 433.129 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 433.129 23.2835 0.2006 re
+f*
+0 g
+253.044 433.33 19.2691 0.2005 re
+f*
+1 g
+272.313 433.33 6.8244 0.2005 re
+f*
+0 g
+279.138 433.33 65.4345 0.2005 re
+f*
+1 g
+344.572 433.33 14.0504 0.2005 re
+f*
+0.498 0 0.482 rg
+358.623 433.33 29.5057 0.2005 re
+f*
+1 g
+388.128 433.33 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 433.33 23.0827 0.2005 re
+f*
+0 g
+253.044 433.53 19.4698 0.2005 re
+f*
+1 g
+272.514 433.53 6.6237 0.2005 re
+f*
+0 g
+279.138 433.53 65.0331 0.2005 re
+f*
+1 g
+344.171 433.53 14.2511 0.2005 re
+f*
+0.498 0 0.482 rg
+358.422 433.53 29.7064 0.2005 re
+f*
+1 g
+388.128 433.53 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 433.53 23.0827 0.2005 re
+f*
+0 g
+253.044 433.731 19.4698 0.2006 re
+f*
+1 g
+272.514 433.731 6.6237 0.2006 re
+f*
+0 g
+279.138 433.731 64.6317 0.2006 re
+f*
+1 g
+343.769 433.731 14.6525 0.2006 re
+f*
+0.498 0 0.482 rg
+358.422 433.731 29.7064 0.2006 re
+f*
+1 g
+388.128 433.731 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 433.731 22.882 0.2006 re
+f*
+0 g
+253.245 433.932 19.2691 0.2006 re
+f*
+1 g
+272.514 433.932 6.6237 0.2006 re
+f*
+0 g
+279.138 433.932 64.2302 0.2006 re
+f*
+1 g
+343.368 433.932 14.8532 0.2006 re
+f*
+0.498 0 0.482 rg
+358.221 433.932 29.9072 0.2006 re
+f*
+1 g
+388.128 433.932 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 433.932 22.882 0.2006 re
+f*
+0 g
+253.245 434.132 19.2691 0.2005 re
+f*
+1 g
+272.514 434.132 6.6237 0.2005 re
+f*
+0 g
+279.138 434.132 63.8288 0.2005 re
+f*
+1 g
+342.966 434.132 15.2546 0.2005 re
+f*
+0.498 0 0.482 rg
+358.221 434.132 29.9072 0.2005 re
+f*
+1 g
+388.128 434.132 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 434.132 22.6813 0.2005 re
+f*
+0 g
+253.445 434.333 19.2691 0.2006 re
+f*
+1 g
+272.715 434.333 6.423 0.2006 re
+f*
+0 g
+279.138 434.333 15.4554 0.2006 re
+f*
+1 g
+294.593 434.333 2.4086 0.2006 re
+f*
+0 g
+297.002 434.333 45.3626 0.2006 re
+f*
+1 g
+342.364 434.333 15.6561 0.2006 re
+f*
+0.498 0 0.482 rg
+358.02 434.333 30.1079 0.2006 re
+f*
+1 g
+388.128 434.333 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 434.333 22.6813 0.2006 re
+f*
+0 g
+253.445 434.533 19.2691 0.2005 re
+f*
+1 g
+272.715 434.533 6.423 0.2005 re
+f*
+0 g
+279.138 434.533 14.6525 0.2005 re
+f*
+1 g
+293.79 434.533 3.8137 0.2005 re
+f*
+0 g
+297.604 434.533 44.359 0.2005 re
+f*
+1 g
+341.963 434.533 16.0575 0.2005 re
+f*
+0.498 0 0.482 rg
+358.02 434.533 30.1079 0.2005 re
+f*
+1 g
+388.128 434.533 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 434.533 22.4805 0.2005 re
+f*
+0 g
+253.646 434.734 19.0683 0.2006 re
+f*
+1 g
+272.715 434.734 6.423 0.2006 re
+f*
+0 g
+279.138 434.734 14.0504 0.2006 re
+f*
+1 g
+293.188 434.734 4.8172 0.2006 re
+f*
+0 g
+298.005 434.734 43.3554 0.2006 re
+f*
+1 g
+341.361 434.734 16.459 0.2006 re
+f*
+0.498 0 0.482 rg
+357.82 434.734 30.3086 0.2006 re
+f*
+1 g
+388.128 434.734 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 434.734 22.4805 0.2006 re
+f*
+0 g
+253.646 434.934 19.269 0.2005 re
+f*
+1 g
+272.915 434.934 6.2223 0.2005 re
+f*
+0 g
+279.138 434.934 13.6489 0.2005 re
+f*
+1 g
+292.786 434.934 5.6202 0.2005 re
+f*
+0 g
+298.407 434.934 42.3518 0.2005 re
+f*
+1 g
+340.758 434.934 17.0611 0.2005 re
+f*
+0.498 0 0.482 rg
+357.82 434.934 30.3086 0.2005 re
+f*
+1 g
+388.128 434.934 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 434.934 22.4805 0.2005 re
+f*
+0 g
+253.847 435.135 19.0683 0.2006 re
+f*
+1 g
+272.915 435.135 6.2223 0.2006 re
+f*
+0 g
+279.138 435.135 13.2475 0.2006 re
+f*
+1 g
+292.385 435.135 6.2223 0.2006 re
+f*
+0 g
+298.607 435.135 41.5489 0.2006 re
+f*
+1 g
+340.156 435.135 17.4626 0.2006 re
+f*
+0.498 0 0.482 rg
+357.619 435.135 30.5093 0.2006 re
+f*
+1 g
+388.128 435.135 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 435.135 22.2799 0.2006 re
+f*
+0 g
+253.847 435.335 19.0683 0.2005 re
+f*
+1 g
+272.915 435.335 6.2223 0.2005 re
+f*
+0 g
+279.138 435.335 13.0468 0.2005 re
+f*
+1 g
+292.184 435.335 6.6237 0.2005 re
+f*
+0 g
+298.808 435.335 40.7461 0.2005 re
+f*
+1 g
+339.554 435.335 17.864 0.2005 re
+f*
+0.498 0 0.482 rg
+357.418 435.335 30.71 0.2005 re
+f*
+1 g
+388.128 435.335 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 435.335 22.2799 0.2005 re
+f*
+0 g
+254.048 435.536 19.0683 0.2006 re
+f*
+1 g
+273.116 435.536 6.0216 0.2006 re
+f*
+0 g
+279.138 435.536 13.0468 0.2006 re
+f*
+1 g
+292.184 435.536 6.8244 0.2006 re
+f*
+0 g
+299.009 435.536 39.7425 0.2006 re
+f*
+1 g
+338.751 435.536 18.6669 0.2006 re
+f*
+0.498 0 0.482 rg
+357.418 435.536 30.71 0.2006 re
+f*
+1 g
+388.128 435.536 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 435.536 22.0791 0.2006 re
+f*
+0 g
+254.048 435.737 19.0683 0.2006 re
+f*
+1 g
+273.116 435.737 6.0216 0.2006 re
+f*
+0 g
+279.138 435.737 12.846 0.2006 re
+f*
+1 g
+291.984 435.737 7.0252 0.2006 re
+f*
+0 g
+299.009 435.737 39.1403 0.2006 re
+f*
+1 g
+338.149 435.737 19.0683 0.2006 re
+f*
+0.498 0 0.482 rg
+357.217 435.737 30.9108 0.2006 re
+f*
+1 g
+388.128 435.737 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 435.737 22.0791 0.2006 re
+f*
+0 g
+254.248 435.937 19.0683 0.2006 re
+f*
+1 g
+273.317 435.937 5.8209 0.2006 re
+f*
+0 g
+279.138 435.937 12.6453 0.2006 re
+f*
+1 g
+291.783 435.937 7.4266 0.2006 re
+f*
+0 g
+299.209 435.937 38.3375 0.2006 re
+f*
+1 g
+337.547 435.937 19.4697 0.2006 re
+f*
+0.498 0 0.482 rg
+357.017 435.937 31.1115 0.2006 re
+f*
+1 g
+388.128 435.937 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 435.937 21.8784 0.2006 re
+f*
+0 g
+254.248 436.138 19.0683 0.2005 re
+f*
+1 g
+273.317 436.138 6.0216 0.2005 re
+f*
+0 g
+279.338 436.138 12.4446 0.2005 re
+f*
+1 g
+291.783 436.138 7.4266 0.2005 re
+f*
+0 g
+299.209 436.138 37.7353 0.2005 re
+f*
+1 g
+336.945 436.138 20.0719 0.2005 re
+f*
+0.498 0 0.482 rg
+357.017 436.138 7.4267 0.2005 re
+f*
+1 g
+364.443 436.138 1.8064 0.2005 re
+f*
+0.498 0 0.482 rg
+366.25 436.138 21.8784 0.2005 re
+f*
+1 g
+388.128 436.138 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 436.138 21.8784 0.2005 re
+f*
+0 g
+254.449 436.338 19.0684 0.2005 re
+f*
+1 g
+273.517 436.338 5.8208 0.2005 re
+f*
+0 g
+279.338 436.338 12.4446 0.2005 re
+f*
+1 g
+291.783 436.338 7.6274 0.2005 re
+f*
+0 g
+299.41 436.338 36.9324 0.2005 re
+f*
+1 g
+336.343 436.338 20.4733 0.2005 re
+f*
+0.498 0 0.482 rg
+356.816 436.338 7.2259 0.2005 re
+f*
+1 g
+364.042 436.338 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+366.651 436.338 21.4769 0.2005 re
+f*
+1 g
+388.128 436.338 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 436.338 21.6777 0.2005 re
+f*
+0 g
+254.449 436.539 19.0684 0.2006 re
+f*
+1 g
+273.517 436.539 5.8208 0.2006 re
+f*
+0 g
+279.338 436.539 12.4446 0.2006 re
+f*
+1 g
+291.783 436.539 7.8281 0.2006 re
+f*
+0 g
+299.611 436.539 35.9288 0.2006 re
+f*
+1 g
+335.54 436.539 21.0755 0.2006 re
+f*
+0.498 0 0.482 rg
+356.615 436.539 7.2259 0.2006 re
+f*
+1 g
+363.841 436.539 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+366.852 436.539 21.2762 0.2006 re
+f*
+1 g
+388.128 436.539 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 436.539 21.6777 0.2006 re
+f*
+0 g
+254.65 436.739 19.0684 0.2006 re
+f*
+1 g
+273.718 436.739 5.6201 0.2006 re
+f*
+0 g
+279.338 436.739 12.4446 0.2006 re
+f*
+1 g
+291.783 436.739 7.8281 0.2006 re
+f*
+0 g
+299.611 436.739 35.3266 0.2006 re
+f*
+1 g
+334.938 436.739 21.477 0.2006 re
+f*
+0.498 0 0.482 rg
+356.415 436.739 7.2259 0.2006 re
+f*
+1 g
+363.641 436.739 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+367.053 436.739 21.0755 0.2006 re
+f*
+1 g
+388.128 436.739 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 436.739 21.4769 0.2006 re
+f*
+0 g
+254.65 436.94 19.0684 0.2006 re
+f*
+1 g
+273.718 436.94 5.6201 0.2006 re
+f*
+0 g
+279.338 436.94 12.4446 0.2006 re
+f*
+1 g
+291.783 436.94 7.8281 0.2006 re
+f*
+0 g
+299.611 436.94 34.7244 0.2006 re
+f*
+1 g
+334.335 436.94 22.0792 0.2006 re
+f*
+0.498 0 0.482 rg
+356.415 436.94 7.0252 0.2006 re
+f*
+1 g
+363.44 436.94 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+367.253 436.94 20.8748 0.2006 re
+f*
+1 g
+388.128 436.94 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 436.94 21.2763 0.2006 re
+f*
+0 g
+254.851 437.141 19.0684 0.2005 re
+f*
+1 g
+273.919 437.141 5.4194 0.2005 re
+f*
+0 g
+279.338 437.141 12.4446 0.2005 re
+f*
+1 g
+291.783 437.141 8.0288 0.2005 re
+f*
+0 g
+299.812 437.141 33.9216 0.2005 re
+f*
+1 g
+333.733 437.141 22.4805 0.2005 re
+f*
+0.498 0 0.482 rg
+356.214 437.141 7.226 0.2005 re
+f*
+1 g
+363.44 437.141 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 437.141 20.8748 0.2005 re
+f*
+1 g
+388.128 437.141 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 437.141 21.2763 0.2005 re
+f*
+0 g
+254.851 437.341 19.0684 0.2005 re
+f*
+1 g
+273.919 437.341 5.4194 0.2005 re
+f*
+0 g
+279.338 437.341 12.4446 0.2005 re
+f*
+1 g
+291.783 437.341 8.0288 0.2005 re
+f*
+0 g
+299.812 437.341 33.3194 0.2005 re
+f*
+1 g
+333.131 437.341 22.882 0.2005 re
+f*
+0.498 0 0.482 rg
+356.013 437.341 7.2259 0.2005 re
+f*
+1 g
+363.239 437.341 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+367.454 437.341 20.674 0.2005 re
+f*
+1 g
+388.128 437.341 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 437.341 21.0755 0.2005 re
+f*
+0 g
+255.051 437.542 19.0683 0.2006 re
+f*
+1 g
+274.12 437.542 5.4195 0.2006 re
+f*
+0 g
+279.539 437.542 12.2438 0.2006 re
+f*
+1 g
+291.783 437.542 8.0288 0.2006 re
+f*
+0 g
+299.812 437.542 32.5165 0.2006 re
+f*
+1 g
+332.328 437.542 23.4842 0.2006 re
+f*
+0.498 0 0.482 rg
+355.812 437.542 7.4266 0.2006 re
+f*
+1 g
+363.239 437.542 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+367.454 437.542 20.674 0.2006 re
+f*
+1 g
+388.128 437.542 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 437.542 21.0755 0.2006 re
+f*
+0 g
+255.252 437.742 18.8676 0.2006 re
+f*
+1 g
+274.12 437.742 5.4195 0.2006 re
+f*
+0 g
+279.539 437.742 12.2438 0.2006 re
+f*
+1 g
+291.783 437.742 8.0288 0.2006 re
+f*
+0 g
+299.812 437.742 31.9144 0.2006 re
+f*
+1 g
+331.726 437.742 23.8856 0.2006 re
+f*
+0.498 0 0.482 rg
+355.612 437.742 7.6273 0.2006 re
+f*
+1 g
+363.239 437.742 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+367.454 437.742 20.674 0.2006 re
+f*
+1 g
+388.128 437.742 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 437.742 20.8748 0.2006 re
+f*
+0 g
+255.252 437.943 19.0683 0.2005 re
+f*
+1 g
+274.32 437.943 5.2188 0.2005 re
+f*
+0 g
+279.539 437.943 12.2438 0.2005 re
+f*
+1 g
+291.783 437.943 8.2295 0.2005 re
+f*
+0 g
+300.012 437.943 31.1115 0.2005 re
+f*
+1 g
+331.124 437.943 24.2871 0.2005 re
+f*
+0.498 0 0.482 rg
+355.411 437.943 7.828 0.2005 re
+f*
+1 g
+363.239 437.943 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+367.454 437.943 20.674 0.2005 re
+f*
+1 g
+388.128 437.943 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 437.943 20.8748 0.2005 re
+f*
+0 g
+255.453 438.143 19.0684 0.2006 re
+f*
+1 g
+274.521 438.143 5.018 0.2006 re
+f*
+0 g
+279.539 438.143 12.2438 0.2006 re
+f*
+1 g
+291.783 438.143 8.2295 0.2006 re
+f*
+0 g
+300.012 438.143 30.5094 0.2006 re
+f*
+1 g
+330.522 438.143 24.6885 0.2006 re
+f*
+0.498 0 0.482 rg
+355.21 438.143 8.0287 0.2006 re
+f*
+1 g
+363.239 438.143 4.4159 0.2006 re
+f*
+0.498 0 0.482 rg
+367.655 438.143 20.4733 0.2006 re
+f*
+1 g
+388.128 438.143 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 438.143 20.6741 0.2006 re
+f*
+0 g
+255.453 438.344 19.0684 0.2005 re
+f*
+1 g
+274.521 438.344 5.2186 0.2005 re
+f*
+0 g
+279.74 438.344 12.0432 0.2005 re
+f*
+1 g
+291.783 438.344 8.2295 0.2005 re
+f*
+0 g
+300.012 438.344 29.9072 0.2005 re
+f*
+1 g
+329.92 438.344 25.2907 0.2005 re
+f*
+0.498 0 0.482 rg
+355.21 438.344 8.0287 0.2005 re
+f*
+1 g
+363.239 438.344 4.4159 0.2005 re
+f*
+0.498 0 0.482 rg
+367.655 438.344 20.4733 0.2005 re
+f*
+1 g
+388.128 438.344 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 438.344 20.6741 0.2005 re
+f*
+0 g
+255.653 438.544 19.0683 0.2006 re
+f*
+1 g
+274.722 438.544 5.018 0.2006 re
+f*
+0 g
+279.74 438.544 12.2439 0.2006 re
+f*
+1 g
+291.984 438.544 8.0288 0.2006 re
+f*
+0 g
+300.012 438.544 29.3051 0.2006 re
+f*
+1 g
+329.317 438.544 25.692 0.2006 re
+f*
+0.498 0 0.482 rg
+355.01 438.544 8.2295 0.2006 re
+f*
+1 g
+363.239 438.544 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+367.454 438.544 20.674 0.2006 re
+f*
+1 g
+388.128 438.544 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 438.544 20.4734 0.2006 re
+f*
+0 g
+255.653 438.745 19.2691 0.2006 re
+f*
+1 g
+274.922 438.745 4.8172 0.2006 re
+f*
+0 g
+279.74 438.745 12.2439 0.2006 re
+f*
+1 g
+291.984 438.745 8.0288 0.2006 re
+f*
+0 g
+300.012 438.745 28.7029 0.2006 re
+f*
+1 g
+328.715 438.745 26.0935 0.2006 re
+f*
+0.498 0 0.482 rg
+354.809 438.745 8.4302 0.2006 re
+f*
+1 g
+363.239 438.745 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+367.454 438.745 20.674 0.2006 re
+f*
+1 g
+388.128 438.745 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 438.745 20.2727 0.2006 re
+f*
+0 g
+255.854 438.946 19.0684 0.2005 re
+f*
+1 g
+274.922 438.946 4.8172 0.2005 re
+f*
+0 g
+279.74 438.946 12.4447 0.2005 re
+f*
+1 g
+292.184 438.946 7.828 0.2005 re
+f*
+0 g
+300.012 438.946 28.1007 0.2005 re
+f*
+1 g
+328.113 438.946 26.2943 0.2005 re
+f*
+0.498 0 0.482 rg
+354.407 438.946 8.8316 0.2005 re
+f*
+1 g
+363.239 438.946 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+367.454 438.946 20.674 0.2005 re
+f*
+1 g
+388.128 438.946 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 438.946 20.2727 0.2005 re
+f*
+0 g
+256.055 439.146 19.0683 0.2006 re
+f*
+1 g
+275.123 439.146 4.8173 0.2006 re
+f*
+0 g
+279.94 439.146 12.2439 0.2006 re
+f*
+1 g
+292.184 439.146 7.828 0.2006 re
+f*
+0 g
+300.012 439.146 27.4986 0.2006 re
+f*
+1 g
+327.511 439.146 26.6957 0.2006 re
+f*
+0.498 0 0.482 rg
+354.207 439.146 9.0323 0.2006 re
+f*
+1 g
+363.239 439.146 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+367.454 439.146 20.674 0.2006 re
+f*
+1 g
+388.128 439.146 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 439.146 20.0719 0.2006 re
+f*
+0 g
+256.055 439.347 19.269 0.2005 re
+f*
+1 g
+275.324 439.347 4.6166 0.2005 re
+f*
+0 g
+279.94 439.347 12.4446 0.2005 re
+f*
+1 g
+292.385 439.347 7.6273 0.2005 re
+f*
+0 g
+300.012 439.347 26.8964 0.2005 re
+f*
+1 g
+326.909 439.347 27.0971 0.2005 re
+f*
+0.498 0 0.482 rg
+354.006 439.347 9.4339 0.2005 re
+f*
+1 g
+363.44 439.347 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+367.253 439.347 20.8748 0.2005 re
+f*
+1 g
+388.128 439.347 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 439.347 19.8712 0.2005 re
+f*
+0 g
+256.256 439.547 19.2691 0.2006 re
+f*
+1 g
+275.525 439.547 4.4158 0.2006 re
+f*
+0 g
+279.94 439.547 12.4446 0.2006 re
+f*
+1 g
+292.385 439.547 7.6273 0.2006 re
+f*
+0 g
+300.012 439.547 26.495 0.2006 re
+f*
+1 g
+326.507 439.547 27.2979 0.2006 re
+f*
+0.498 0 0.482 rg
+353.805 439.547 9.8352 0.2006 re
+f*
+1 g
+363.641 439.547 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+367.053 439.547 21.0755 0.2006 re
+f*
+1 g
+388.128 439.547 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 439.547 19.8712 0.2006 re
+f*
+0 g
+256.456 439.748 19.0684 0.2006 re
+f*
+1 g
+275.525 439.748 4.6165 0.2006 re
+f*
+0 g
+280.141 439.748 12.4446 0.2006 re
+f*
+1 g
+292.586 439.748 7.2259 0.2006 re
+f*
+0 g
+299.812 439.748 26.0935 0.2006 re
+f*
+1 g
+325.905 439.748 27.6993 0.2006 re
+f*
+0.498 0 0.482 rg
+353.605 439.748 10.2367 0.2006 re
+f*
+1 g
+363.841 439.748 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+367.053 439.748 21.0755 0.2006 re
+f*
+1 g
+388.128 439.748 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 439.748 19.6705 0.2006 re
+f*
+0 g
+256.456 439.948 19.269 0.2005 re
+f*
+1 g
+275.725 439.948 4.4159 0.2005 re
+f*
+0 g
+280.141 439.948 12.6453 0.2005 re
+f*
+1 g
+292.786 439.948 7.0252 0.2005 re
+f*
+0 g
+299.812 439.948 25.6921 0.2005 re
+f*
+1 g
+325.504 439.948 27.6993 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 439.948 10.8388 0.2005 re
+f*
+1 g
+364.042 439.948 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+366.651 439.948 18.4661 0.2005 re
+f*
+1 g
+385.117 439.948 0.2008 0.2005 re
+f*
+0.498 0 0.482 rg
+385.318 439.948 2.81 0.2005 re
+f*
+1 g
+388.128 439.948 3.613 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 439.948 19.6705 0.2005 re
+f*
+0 g
+256.657 440.149 19.2691 0.2006 re
+f*
+1 g
+275.926 440.149 4.2151 0.2006 re
+f*
+0 g
+280.141 440.149 12.6453 0.2006 re
+f*
+1 g
+292.786 440.149 7.0252 0.2006 re
+f*
+0 g
+299.812 440.149 25.2907 0.2006 re
+f*
+1 g
+325.102 440.149 27.8999 0.2006 re
+f*
+0.498 0 0.482 rg
+353.002 440.149 11.2403 0.2006 re
+f*
+1 g
+364.243 440.149 2.208 0.2006 re
+f*
+0.498 0 0.482 rg
+366.451 440.149 18.6668 0.2006 re
+f*
+1 g
+385.117 440.149 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 440.149 19.4698 0.2006 re
+f*
+0 g
+256.858 440.35 19.2691 0.2005 re
+f*
+1 g
+276.127 440.35 4.2151 0.2005 re
+f*
+0 g
+280.342 440.35 12.6453 0.2005 re
+f*
+1 g
+292.987 440.35 6.8245 0.2005 re
+f*
+0 g
+299.812 440.35 24.6885 0.2005 re
+f*
+1 g
+324.5 440.35 28.1007 0.2005 re
+f*
+0.498 0 0.482 rg
+352.601 440.35 12.2439 0.2005 re
+f*
+1 g
+364.845 440.35 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+365.848 440.35 19.269 0.2005 re
+f*
+1 g
+385.117 440.35 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+391.741 440.35 19.2691 0.2005 re
+f*
+0 g
+256.858 440.55 19.4698 0.2006 re
+f*
+1 g
+276.327 440.55 4.0144 0.2006 re
+f*
+0 g
+280.342 440.55 12.6453 0.2006 re
+f*
+1 g
+292.987 440.55 6.6238 0.2006 re
+f*
+0 g
+299.611 440.55 24.4878 0.2006 re
+f*
+1 g
+324.099 440.55 28.3014 0.2006 re
+f*
+0.498 0 0.482 rg
+352.4 440.55 32.7172 0.2006 re
+f*
+1 g
+385.117 440.55 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+391.741 440.55 19.2691 0.2006 re
+f*
+0 g
+257.058 440.751 19.4698 0.2005 re
+f*
+1 g
+276.528 440.751 4.0144 0.2005 re
+f*
+0 g
+280.543 440.751 12.6453 0.2005 re
+f*
+1 g
+293.188 440.751 6.423 0.2005 re
+f*
+0 g
+299.611 440.751 24.0863 0.2005 re
+f*
+1 g
+323.697 440.751 28.3014 0.2005 re
+f*
+0.498 0 0.482 rg
+351.999 440.751 58.8108 0.2005 re
+f*
+0 g
+257.259 440.951 19.4697 0.2006 re
+f*
+1 g
+276.729 440.951 3.8138 0.2006 re
+f*
+0 g
+280.543 440.951 12.6453 0.2006 re
+f*
+1 g
+293.188 440.951 6.423 0.2006 re
+f*
+0 g
+299.611 440.951 23.6849 0.2006 re
+f*
+1 g
+323.296 440.951 28.5022 0.2006 re
+f*
+0.498 0 0.482 rg
+351.798 440.951 58.8107 0.2006 re
+f*
+0 g
+257.259 441.152 19.6705 0.2006 re
+f*
+1 g
+276.93 441.152 3.8136 0.2006 re
+f*
+0 g
+280.743 441.152 12.4447 0.2006 re
+f*
+1 g
+293.188 441.152 6.2223 0.2006 re
+f*
+0 g
+299.41 441.152 23.4841 0.2006 re
+f*
+1 g
+322.894 441.152 28.5022 0.2006 re
+f*
+0.498 0 0.482 rg
+351.397 441.152 59.0115 0.2006 re
+f*
+0 g
+257.46 441.352 19.6705 0.2005 re
+f*
+1 g
+277.13 441.352 3.6129 0.2005 re
+f*
+0 g
+280.743 441.352 12.4447 0.2005 re
+f*
+1 g
+293.188 441.352 6.2223 0.2005 re
+f*
+0 g
+299.41 441.352 23.0827 0.2005 re
+f*
+1 g
+322.493 441.352 28.5021 0.2005 re
+f*
+0.498 0 0.482 rg
+350.995 441.352 59.413 0.2005 re
+f*
+0 g
+257.661 441.553 19.6705 0.2006 re
+f*
+1 g
+277.331 441.553 3.613 0.2006 re
+f*
+0 g
+280.944 441.553 12.4446 0.2006 re
+f*
+1 g
+293.389 441.553 5.8208 0.2006 re
+f*
+0 g
+299.209 441.553 23.0828 0.2006 re
+f*
+1 g
+322.292 441.553 28.5022 0.2006 re
+f*
+0.498 0 0.482 rg
+350.794 441.553 59.4129 0.2006 re
+f*
+0 g
+257.661 441.753 19.8713 0.2005 re
+f*
+1 g
+277.532 441.753 3.4122 0.2005 re
+f*
+0 g
+280.944 441.753 12.4446 0.2005 re
+f*
+1 g
+293.389 441.753 5.8208 0.2005 re
+f*
+0 g
+299.209 441.753 22.6813 0.2005 re
+f*
+1 g
+321.891 441.753 28.5022 0.2005 re
+f*
+0.498 0 0.482 rg
+350.393 441.753 59.6137 0.2005 re
+f*
+0 g
+257.861 441.954 19.8712 0.2006 re
+f*
+1 g
+277.732 441.954 3.4123 0.2006 re
+f*
+0 g
+281.145 441.954 12.2439 0.2006 re
+f*
+1 g
+293.389 441.954 5.6201 0.2006 re
+f*
+0 g
+299.009 441.954 22.4806 0.2006 re
+f*
+1 g
+321.489 441.954 28.5021 0.2006 re
+f*
+0.498 0 0.482 rg
+349.991 441.954 60.0152 0.2006 re
+f*
+0 g
+258.062 442.155 19.8712 0.2006 re
+f*
+1 g
+277.933 442.155 3.4122 0.2006 re
+f*
+0 g
+281.346 442.155 12.0432 0.2006 re
+f*
+1 g
+293.389 442.155 5.6201 0.2006 re
+f*
+0 g
+299.009 442.155 22.2799 0.2006 re
+f*
+1 g
+321.289 442.155 28.1007 0.2006 re
+f*
+0.498 0 0.482 rg
+349.389 442.155 60.4166 0.2006 re
+f*
+0 g
+258.263 442.355 19.8712 0.2005 re
+f*
+1 g
+278.134 442.355 3.4123 0.2005 re
+f*
+0 g
+281.546 442.355 11.8424 0.2005 re
+f*
+1 g
+293.389 442.355 5.4194 0.2005 re
+f*
+0 g
+298.808 442.355 22.0791 0.2005 re
+f*
+1 g
+320.887 442.355 28.1007 0.2005 re
+f*
+0.498 0 0.482 rg
+348.988 442.355 60.6173 0.2005 re
+f*
+0 g
+258.263 442.556 20.0719 0.2006 re
+f*
+1 g
+278.335 442.556 3.2116 0.2006 re
+f*
+0 g
+281.546 442.556 11.8424 0.2006 re
+f*
+1 g
+293.389 442.556 5.2187 0.2006 re
+f*
+0 g
+298.607 442.556 22.0792 0.2006 re
+f*
+1 g
+320.687 442.556 27.6992 0.2006 re
+f*
+0.498 0 0.482 rg
+348.386 442.556 61.0187 0.2006 re
+f*
+0 g
+258.463 442.756 20.072 0.2005 re
+f*
+1 g
+278.535 442.756 3.2114 0.2005 re
+f*
+0 g
+281.747 442.756 11.6418 0.2005 re
+f*
+1 g
+293.389 442.756 5.2187 0.2005 re
+f*
+0 g
+298.607 442.756 21.6777 0.2005 re
+f*
+1 g
+320.285 442.756 27.6992 0.2005 re
+f*
+0.498 0 0.482 rg
+347.984 442.756 61.4202 0.2005 re
+f*
+0 g
+258.664 442.957 20.0719 0.2006 re
+f*
+1 g
+278.736 442.957 3.2116 0.2006 re
+f*
+0 g
+281.948 442.957 11.441 0.2006 re
+f*
+1 g
+293.389 442.957 5.018 0.2006 re
+f*
+0 g
+298.407 442.957 21.6777 0.2006 re
+f*
+1 g
+320.084 442.957 27.2978 0.2006 re
+f*
+0.498 0 0.482 rg
+347.382 442.957 61.8216 0.2006 re
+f*
+0 g
+258.865 443.157 20.2727 0.2006 re
+f*
+1 g
+279.138 443.157 3.0108 0.2006 re
+f*
+0 g
+282.148 443.157 11.2403 0.2006 re
+f*
+1 g
+293.389 443.157 4.8173 0.2006 re
+f*
+0 g
+298.206 443.157 21.6776 0.2006 re
+f*
+1 g
+319.884 443.157 26.8965 0.2006 re
+f*
+0.498 0 0.482 rg
+346.78 443.157 62.223 0.2006 re
+f*
+0 g
+258.865 443.358 20.4734 0.2005 re
+f*
+1 g
+279.338 443.358 3.0108 0.2005 re
+f*
+0 g
+282.349 443.358 11.0396 0.2005 re
+f*
+1 g
+293.389 443.358 4.6165 0.2005 re
+f*
+0 g
+298.005 443.358 21.477 0.2005 re
+f*
+1 g
+319.482 443.358 26.6957 0.2005 re
+f*
+0.498 0 0.482 rg
+346.178 443.358 62.6245 0.2005 re
+f*
+0 g
+259.066 443.558 20.4734 0.2006 re
+f*
+1 g
+279.539 443.558 3.0108 0.2006 re
+f*
+0 g
+282.55 443.558 10.8388 0.2006 re
+f*
+1 g
+293.389 443.558 4.4158 0.2006 re
+f*
+0 g
+297.805 443.558 21.477 0.2006 re
+f*
+1 g
+319.281 443.558 26.2942 0.2006 re
+f*
+0.498 0 0.482 rg
+345.576 443.558 63.2267 0.2006 re
+f*
+0 g
+259.266 443.759 20.4733 0.2005 re
+f*
+1 g
+279.74 443.759 3.0108 0.2005 re
+f*
+0 g
+282.75 443.759 10.4375 0.2005 re
+f*
+1 g
+293.188 443.759 4.4158 0.2005 re
+f*
+0 g
+297.604 443.759 21.4769 0.2005 re
+f*
+1 g
+319.081 443.759 25.8928 0.2005 re
+f*
+0.498 0 0.482 rg
+344.973 443.759 63.6281 0.2005 re
+f*
+0 g
+259.467 443.959 20.6741 0.2006 re
+f*
+1 g
+280.141 443.959 2.8101 0.2006 re
+f*
+0 g
+282.951 443.959 10.2367 0.2006 re
+f*
+1 g
+293.188 443.959 4.2151 0.2006 re
+f*
+0 g
+297.403 443.959 21.4769 0.2006 re
+f*
+1 g
+318.88 443.959 25.6921 0.2006 re
+f*
+0.498 0 0.482 rg
+344.572 443.959 63.8288 0.2006 re
+f*
+0 g
+259.668 444.16 20.6741 0.2006 re
+f*
+1 g
+280.342 444.16 2.8101 0.2006 re
+f*
+0 g
+283.152 444.16 9.8352 0.2006 re
+f*
+1 g
+292.987 444.16 4.2152 0.2006 re
+f*
+0 g
+297.202 444.16 21.477 0.2006 re
+f*
+1 g
+318.679 444.16 25.2905 0.2006 re
+f*
+0.498 0 0.482 rg
+343.97 444.16 64.2303 0.2006 re
+f*
+0 g
+259.868 444.361 20.8748 0.2005 re
+f*
+1 g
+280.743 444.361 2.6094 0.2005 re
+f*
+0 g
+283.353 444.361 9.6345 0.2005 re
+f*
+1 g
+292.987 444.361 4.0144 0.2005 re
+f*
+0 g
+297.002 444.361 21.477 0.2005 re
+f*
+1 g
+318.479 444.361 24.8892 0.2005 re
+f*
+0.498 0 0.482 rg
+343.368 444.361 64.6317 0.2005 re
+f*
+0 g
+259.868 444.561 21.0756 0.2005 re
+f*
+1 g
+280.944 444.561 2.81 0.2005 re
+f*
+0 g
+283.754 444.561 9.0324 0.2005 re
+f*
+1 g
+292.786 444.561 4.0144 0.2005 re
+f*
+0 g
+296.801 444.561 21.477 0.2005 re
+f*
+1 g
+318.278 444.561 24.287 0.2005 re
+f*
+0.498 0 0.482 rg
+342.565 444.561 65.2339 0.2005 re
+f*
+0 g
+260.069 444.762 21.2762 0.2006 re
+f*
+1 g
+281.346 444.762 2.6094 0.2006 re
+f*
+0 g
+283.955 444.762 8.6309 0.2006 re
+f*
+1 g
+292.586 444.762 4.0144 0.2006 re
+f*
+0 g
+296.6 444.762 21.4769 0.2006 re
+f*
+1 g
+318.077 444.762 23.8857 0.2006 re
+f*
+0.498 0 0.482 rg
+341.963 444.762 65.836 0.2006 re
+f*
+0 g
+260.27 444.962 21.2763 0.2006 re
+f*
+1 g
+281.546 444.962 2.81 0.2006 re
+f*
+0 g
+284.356 444.962 8.0288 0.2006 re
+f*
+1 g
+292.385 444.962 3.8137 0.2006 re
+f*
+0 g
+296.199 444.962 21.6776 0.2006 re
+f*
+1 g
+317.876 444.962 23.4842 0.2006 re
+f*
+0.498 0 0.482 rg
+341.361 444.962 66.2374 0.2006 re
+f*
+0 g
+260.471 445.163 21.477 0.2006 re
+f*
+1 g
+281.948 445.163 2.6094 0.2006 re
+f*
+0 g
+284.557 445.163 7.6273 0.2006 re
+f*
+1 g
+292.184 445.163 3.8136 0.2006 re
+f*
+0 g
+295.998 445.163 21.6778 0.2006 re
+f*
+1 g
+317.676 445.163 23.0827 0.2006 re
+f*
+0.498 0 0.482 rg
+340.758 445.163 66.6388 0.2006 re
+f*
+0 g
+260.671 445.363 21.6777 0.2005 re
+f*
+1 g
+282.349 445.363 2.8101 0.2005 re
+f*
+0 g
+285.159 445.363 6.6237 0.2005 re
+f*
+1 g
+291.783 445.363 3.8137 0.2005 re
+f*
+0 g
+295.597 445.363 21.8784 0.2005 re
+f*
+1 g
+317.475 445.363 22.6813 0.2005 re
+f*
+0.498 0 0.482 rg
+340.156 445.363 67.0403 0.2005 re
+f*
+0 g
+260.872 445.564 21.8784 0.2006 re
+f*
+1 g
+282.75 445.564 2.8101 0.2006 re
+f*
+0 g
+285.561 445.564 5.6202 0.2006 re
+f*
+1 g
+291.181 445.564 4.0144 0.2006 re
+f*
+0 g
+295.195 445.564 22.0791 0.2006 re
+f*
+1 g
+317.274 445.564 22.2799 0.2006 re
+f*
+0.498 0 0.482 rg
+339.554 445.564 67.4417 0.2006 re
+f*
+0 g
+261.073 445.765 22.0792 0.2005 re
+f*
+1 g
+283.152 445.765 3.0108 0.2005 re
+f*
+0 g
+286.163 445.765 4.2151 0.2005 re
+f*
+1 g
+290.378 445.765 4.4158 0.2005 re
+f*
+0 g
+294.794 445.765 22.2799 0.2005 re
+f*
+1 g
+317.074 445.765 21.8784 0.2005 re
+f*
+0.498 0 0.482 rg
+338.952 445.765 67.8432 0.2005 re
+f*
+0 g
+261.073 445.965 22.4807 0.2006 re
+f*
+1 g
+283.553 445.965 3.6129 0.2006 re
+f*
+0 g
+287.166 445.965 2.2079 0.2006 re
+f*
+1 g
+289.374 445.965 5.018 0.2006 re
+f*
+0 g
+294.392 445.965 22.4805 0.2006 re
+f*
+1 g
+316.873 445.965 21.477 0.2006 re
+f*
+0.498 0 0.482 rg
+338.35 445.965 68.2446 0.2006 re
+f*
+0 g
+261.274 446.166 22.882 0.2006 re
+f*
+1 g
+284.156 446.166 9.8352 0.2006 re
+f*
+0 g
+293.991 446.166 22.6813 0.2006 re
+f*
+1 g
+316.672 446.166 21.0756 0.2006 re
+f*
+0.498 0 0.482 rg
+337.748 446.166 68.646 0.2006 re
+f*
+0 g
+261.474 446.366 23.2834 0.2005 re
+f*
+1 g
+284.758 446.366 8.631 0.2005 re
+f*
+0 g
+293.389 446.366 23.2834 0.2005 re
+f*
+1 g
+316.672 446.366 20.4734 0.2005 re
+f*
+0.498 0 0.482 rg
+337.146 446.366 69.0475 0.2005 re
+f*
+0 g
+261.675 446.567 23.6849 0.2005 re
+f*
+1 g
+285.36 446.567 7.4266 0.2005 re
+f*
+0 g
+292.786 446.567 23.6849 0.2005 re
+f*
+1 g
+316.471 446.567 19.8713 0.2005 re
+f*
+0.498 0 0.482 rg
+336.343 446.567 69.8503 0.2005 re
+f*
+0 g
+261.876 446.767 24.2871 0.2006 re
+f*
+1 g
+286.163 446.767 5.6201 0.2006 re
+f*
+0 g
+291.783 446.767 24.4878 0.2006 re
+f*
+1 g
+316.271 446.767 19.4698 0.2006 re
+f*
+0.498 0 0.482 rg
+335.74 446.767 70.2518 0.2006 re
+f*
+0 g
+262.076 446.968 25.2907 0.2006 re
+f*
+1 g
+287.367 446.968 3.2115 0.2006 re
+f*
+0 g
+290.579 446.968 25.6921 0.2006 re
+f*
+1 g
+316.271 446.968 18.8676 0.2006 re
+f*
+0.498 0 0.482 rg
+335.138 446.968 70.6533 0.2006 re
+f*
+0 g
+262.277 447.168 53.7928 0.2005 re
+f*
+1 g
+316.07 447.168 18.4662 0.2005 re
+f*
+0.498 0 0.482 rg
+334.536 447.168 71.0547 0.2005 re
+f*
+0 g
+262.478 447.369 53.3913 0.2006 re
+f*
+1 g
+315.869 447.369 18.0648 0.2006 re
+f*
+0.498 0 0.482 rg
+333.934 447.369 71.4561 0.2006 re
+f*
+0 g
+262.679 447.57 53.1906 0.2005 re
+f*
+1 g
+315.869 447.57 17.4626 0.2005 re
+f*
+0.498 0 0.482 rg
+333.332 447.57 71.8576 0.2005 re
+f*
+0 g
+262.879 447.77 52.7893 0.2006 re
+f*
+1 g
+315.669 447.77 17.0611 0.2006 re
+f*
+0.498 0 0.482 rg
+332.73 447.77 72.259 0.2006 re
+f*
+0 g
+263.08 447.971 52.5886 0.2006 re
+f*
+1 g
+315.669 447.971 16.4589 0.2006 re
+f*
+0.498 0 0.482 rg
+332.127 447.971 72.6605 0.2006 re
+f*
+0 g
+263.281 448.171 52.187 0.2006 re
+f*
+1 g
+315.468 448.171 16.0576 0.2006 re
+f*
+0.498 0 0.482 rg
+331.525 448.171 73.0618 0.2006 re
+f*
+0 g
+263.481 448.372 51.9863 0.2005 re
+f*
+1 g
+315.468 448.372 15.4554 0.2005 re
+f*
+0.498 0 0.482 rg
+330.923 448.372 73.4633 0.2005 re
+f*
+0 g
+263.682 448.572 51.5849 0.2005 re
+f*
+1 g
+315.267 448.572 15.0539 0.2005 re
+f*
+0.498 0 0.482 rg
+330.321 448.572 73.8648 0.2005 re
+f*
+0 g
+263.883 448.773 51.3842 0.2006 re
+f*
+1 g
+315.267 448.773 14.4518 0.2006 re
+f*
+0.498 0 0.482 rg
+329.719 448.773 74.2662 0.2006 re
+f*
+0 g
+264.084 448.973 50.9828 0.2006 re
+f*
+1 g
+315.066 448.973 14.2511 0.2006 re
+f*
+0.498 0 0.482 rg
+329.317 448.973 74.4669 0.2006 re
+f*
+0 g
+264.284 449.174 50.782 0.2005 re
+f*
+1 g
+315.066 449.174 13.6489 0.2005 re
+f*
+0.498 0 0.482 rg
+328.715 449.174 74.8683 0.2005 re
+f*
+0 g
+264.485 449.375 50.5813 0.2006 re
+f*
+1 g
+315.066 449.375 13.2475 0.2006 re
+f*
+0.498 0 0.482 rg
+328.314 449.375 75.069 0.2006 re
+f*
+0 g
+264.686 449.575 50.1798 0.2005 re
+f*
+1 g
+314.866 449.575 12.8461 0.2005 re
+f*
+0.498 0 0.482 rg
+327.712 449.575 75.2698 0.2005 re
+f*
+0 g
+264.886 449.776 49.9791 0.2006 re
+f*
+1 g
+314.866 449.776 12.4447 0.2006 re
+f*
+0.498 0 0.482 rg
+327.31 449.776 75.4705 0.2006 re
+f*
+0 g
+265.288 449.976 49.5776 0.2006 re
+f*
+1 g
+314.866 449.976 12.0432 0.2006 re
+f*
+0.498 0 0.482 rg
+326.909 449.976 75.6712 0.2006 re
+f*
+0 g
+265.489 450.177 49.1763 0.2005 re
+f*
+1 g
+314.665 450.177 11.8424 0.2005 re
+f*
+0.498 0 0.482 rg
+326.507 450.177 75.8719 0.2005 re
+f*
+0 g
+265.689 450.377 48.9756 0.2005 re
+f*
+1 g
+314.665 450.377 11.4409 0.2005 re
+f*
+0.498 0 0.482 rg
+326.106 450.377 76.0727 0.2005 re
+f*
+0 g
+265.89 450.578 48.7749 0.2006 re
+f*
+1 g
+314.665 450.578 11.0395 0.2006 re
+f*
+0.498 0 0.482 rg
+325.704 450.578 76.2734 0.2006 re
+f*
+0 g
+266.091 450.778 48.3733 0.2006 re
+f*
+1 g
+314.464 450.778 10.8389 0.2006 re
+f*
+0.498 0 0.482 rg
+325.303 450.778 76.4741 0.2006 re
+f*
+0 g
+266.292 450.979 48.1726 0.2006 re
+f*
+1 g
+314.464 450.979 10.4374 0.2006 re
+f*
+0.498 0 0.482 rg
+324.902 450.979 76.6748 0.2006 re
+f*
+0 g
+266.492 451.18 47.9719 0.2005 re
+f*
+1 g
+314.464 451.18 10.2367 0.2005 re
+f*
+0.498 0 0.482 rg
+324.701 451.18 76.6748 0.2005 re
+f*
+0 g
+266.693 451.38 47.7712 0.2005 re
+f*
+1 g
+314.464 451.38 9.8353 0.2005 re
+f*
+0.498 0 0.482 rg
+324.299 451.38 76.6748 0.2005 re
+f*
+0 g
+267.094 451.581 47.169 0.2006 re
+f*
+1 g
+314.263 451.581 9.8353 0.2006 re
+f*
+0.498 0 0.482 rg
+324.099 451.581 76.6748 0.2006 re
+f*
+0 g
+267.295 451.781 46.9683 0.2006 re
+f*
+1 g
+314.263 451.781 9.4338 0.2006 re
+f*
+0.498 0 0.482 rg
+323.697 451.781 76.8755 0.2006 re
+f*
+0 g
+267.496 451.982 46.7676 0.2006 re
+f*
+1 g
+314.263 451.982 9.2331 0.2006 re
+f*
+0.498 0 0.482 rg
+323.497 451.982 76.8755 0.2006 re
+f*
+0 g
+267.697 452.183 46.5669 0.2005 re
+f*
+1 g
+314.263 452.183 9.0324 0.2005 re
+f*
+0.498 0 0.482 rg
+323.296 452.183 76.6748 0.2005 re
+f*
+0 g
+268.098 452.383 46.1654 0.2005 re
+f*
+1 g
+314.263 452.383 8.6309 0.2005 re
+f*
+0.498 0 0.482 rg
+322.894 452.383 76.8756 0.2005 re
+f*
+0 g
+268.299 452.583 45.764 0.2006 re
+f*
+1 g
+314.063 452.583 8.631 0.2006 re
+f*
+0.498 0 0.482 rg
+322.694 452.583 76.8754 0.2006 re
+f*
+0 g
+268.499 452.784 45.5633 0.2006 re
+f*
+1 g
+314.063 452.784 8.4302 0.2006 re
+f*
+0.498 0 0.482 rg
+322.493 452.784 76.8755 0.2006 re
+f*
+0 g
+268.7 452.985 45.3626 0.2005 re
+f*
+1 g
+314.063 452.985 8.2295 0.2005 re
+f*
+0.498 0 0.482 rg
+322.292 452.985 76.6748 0.2005 re
+f*
+0 g
+269.102 453.185 44.9612 0.2006 re
+f*
+1 g
+314.063 453.185 8.0288 0.2006 re
+f*
+0.498 0 0.482 rg
+322.092 453.185 76.6748 0.2006 re
+f*
+0 g
+269.302 453.386 44.7604 0.2005 re
+f*
+1 g
+314.063 453.386 7.828 0.2005 re
+f*
+0.498 0 0.482 rg
+321.891 453.386 76.6748 0.2005 re
+f*
+0 g
+269.503 453.586 44.5597 0.2006 re
+f*
+1 g
+314.063 453.586 7.6274 0.2006 re
+f*
+0.498 0 0.482 rg
+321.69 453.586 76.6747 0.2006 re
+f*
+0 g
+269.904 453.787 44.1583 0.2006 re
+f*
+1 g
+314.063 453.787 7.4266 0.2006 re
+f*
+0.498 0 0.482 rg
+321.489 453.787 76.4741 0.2006 re
+f*
+0 g
+270.105 453.987 43.9576 0.2005 re
+f*
+1 g
+314.063 453.987 7.2259 0.2005 re
+f*
+0.498 0 0.482 rg
+321.289 453.987 76.4741 0.2005 re
+f*
+0 g
+270.306 454.188 43.7568 0.2006 re
+f*
+1 g
+314.063 454.188 7.2259 0.2006 re
+f*
+0.498 0 0.482 rg
+321.289 454.188 76.2734 0.2006 re
+f*
+0 g
+270.707 454.389 43.3554 0.2005 re
+f*
+1 g
+314.063 454.389 7.0252 0.2005 re
+f*
+0.498 0 0.482 rg
+321.088 454.389 76.0726 0.2005 re
+f*
+0 g
+270.908 454.589 43.1547 0.2006 re
+f*
+1 g
+314.063 454.589 6.8244 0.2006 re
+f*
+0.498 0 0.482 rg
+320.887 454.589 76.0727 0.2006 re
+f*
+0 g
+271.109 454.79 42.954 0.2006 re
+f*
+1 g
+314.063 454.79 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+320.687 454.79 75.8719 0.2006 re
+f*
+0 g
+271.51 454.99 42.3517 0.2005 re
+f*
+1 g
+313.862 454.99 6.8246 0.2005 re
+f*
+0.498 0 0.482 rg
+320.687 454.99 75.6711 0.2005 re
+f*
+0 g
+271.711 455.191 42.151 0.2006 re
+f*
+1 g
+313.862 455.191 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+320.486 455.191 75.6712 0.2006 re
+f*
+0 g
+272.112 455.391 41.7496 0.2005 re
+f*
+1 g
+313.862 455.391 6.4231 0.2005 re
+f*
+0.498 0 0.482 rg
+320.285 455.391 75.4705 0.2005 re
+f*
+0 g
+272.313 455.592 41.5488 0.2006 re
+f*
+1 g
+313.862 455.592 6.4231 0.2006 re
+f*
+0.498 0 0.482 rg
+320.285 455.592 75.2698 0.2006 re
+f*
+0 g
+272.715 455.792 41.1474 0.2005 re
+f*
+1 g
+313.862 455.792 6.2224 0.2005 re
+f*
+0.498 0 0.482 rg
+320.084 455.792 75.069 0.2005 re
+f*
+0 g
+272.915 455.993 41.1475 0.2006 re
+f*
+1 g
+314.063 455.993 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+320.084 455.993 74.8683 0.2006 re
+f*
+0 g
+273.116 456.194 40.9468 0.2006 re
+f*
+1 g
+314.063 456.194 5.8208 0.2006 re
+f*
+0.498 0 0.482 rg
+319.884 456.194 74.6677 0.2006 re
+f*
+0 g
+273.517 456.394 40.5453 0.2005 re
+f*
+1 g
+314.063 456.394 5.8208 0.2005 re
+f*
+0.498 0 0.482 rg
+319.884 456.394 74.4669 0.2005 re
+f*
+0 g
+273.919 456.595 40.1439 0.2006 re
+f*
+1 g
+314.063 456.595 5.6202 0.2006 re
+f*
+0.498 0 0.482 rg
+319.683 456.595 74.2661 0.2006 re
+f*
+0 g
+274.12 456.795 39.9432 0.2005 re
+f*
+1 g
+314.063 456.795 5.6202 0.2005 re
+f*
+0.498 0 0.482 rg
+319.683 456.795 32.5165 0.2005 re
+f*
+1 g
+352.2 456.795 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 456.795 40.5453 0.2005 re
+f*
+0 g
+274.521 456.996 39.5417 0.2006 re
+f*
+1 g
+314.063 456.996 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+319.482 456.996 32.7173 0.2006 re
+f*
+1 g
+352.2 456.996 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 456.996 40.1439 0.2006 re
+f*
+0 g
+274.722 457.196 39.3411 0.2006 re
+f*
+1 g
+314.063 457.196 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+319.482 457.196 32.7173 0.2006 re
+f*
+1 g
+352.2 457.196 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 457.196 39.9431 0.2006 re
+f*
+0 g
+275.123 457.397 38.9396 0.2005 re
+f*
+1 g
+314.063 457.397 5.4194 0.2005 re
+f*
+0.498 0 0.482 rg
+319.482 457.397 32.7173 0.2005 re
+f*
+1 g
+352.2 457.397 1.0036 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 457.397 39.5417 0.2005 re
+f*
+0 g
+275.324 457.597 38.7389 0.2006 re
+f*
+1 g
+314.063 457.597 5.2187 0.2006 re
+f*
+0.498 0 0.482 rg
+319.281 457.597 32.918 0.2006 re
+f*
+1 g
+352.2 457.597 1.0036 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 457.597 39.1403 0.2006 re
+f*
+0 g
+275.725 457.798 38.3375 0.2005 re
+f*
+1 g
+314.063 457.798 5.2187 0.2005 re
+f*
+0.498 0 0.482 rg
+319.281 457.798 32.7172 0.2005 re
+f*
+1 g
+351.999 457.798 1.2044 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 457.798 38.9395 0.2005 re
+f*
+0 g
+276.127 457.999 37.936 0.2006 re
+f*
+1 g
+314.063 457.999 5.2187 0.2006 re
+f*
+0.498 0 0.482 rg
+319.281 457.999 32.7172 0.2006 re
+f*
+1 g
+351.999 457.999 1.2044 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 457.999 38.5381 0.2006 re
+f*
+0 g
+276.327 458.199 37.7353 0.2006 re
+f*
+1 g
+314.063 458.199 5.0179 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 458.199 32.918 0.2006 re
+f*
+1 g
+351.999 458.199 1.2044 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 458.199 38.1367 0.2006 re
+f*
+0 g
+276.729 458.4 37.3339 0.2005 re
+f*
+1 g
+314.063 458.4 5.0179 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 458.4 32.918 0.2005 re
+f*
+1 g
+351.999 458.4 1.2044 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 458.4 37.9359 0.2005 re
+f*
+0 g
+277.13 458.6 37.1331 0.2006 re
+f*
+1 g
+314.263 458.6 4.8172 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 458.6 32.7174 0.2006 re
+f*
+1 g
+351.798 458.6 1.405 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 458.6 37.5345 0.2006 re
+f*
+0 g
+277.532 458.801 36.7316 0.2005 re
+f*
+1 g
+314.263 458.801 4.8172 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 458.801 32.7174 0.2005 re
+f*
+1 g
+351.798 458.801 1.405 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 458.801 37.1331 0.2005 re
+f*
+0 g
+277.732 459.001 36.531 0.2006 re
+f*
+1 g
+314.263 459.001 4.8172 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 459.001 32.7174 0.2006 re
+f*
+1 g
+351.798 459.001 1.405 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 459.001 36.9323 0.2006 re
+f*
+0 g
+278.134 459.202 36.1295 0.2006 re
+f*
+1 g
+314.263 459.202 4.8172 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 459.202 32.7174 0.2006 re
+f*
+1 g
+351.798 459.202 1.405 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 459.202 36.5309 0.2006 re
+f*
+0 g
+278.535 459.403 35.728 0.2005 re
+f*
+1 g
+314.263 459.403 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 459.403 32.7173 0.2005 re
+f*
+1 g
+351.597 459.403 1.6058 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 459.403 36.1295 0.2005 re
+f*
+0 g
+278.736 459.603 35.5274 0.2005 re
+f*
+1 g
+314.263 459.603 4.6165 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 459.603 32.7173 0.2005 re
+f*
+1 g
+351.597 459.603 1.6058 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 459.603 35.7281 0.2005 re
+f*
+0 g
+279.138 459.804 35.3266 0.2006 re
+f*
+1 g
+314.464 459.804 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 459.804 32.7173 0.2006 re
+f*
+1 g
+351.597 459.804 1.6058 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 459.804 35.3266 0.2006 re
+f*
+0 g
+279.539 460.004 34.9251 0.2006 re
+f*
+1 g
+314.464 460.004 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 460.004 32.7173 0.2006 re
+f*
+1 g
+351.597 460.004 1.6058 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 460.004 34.9251 0.2006 re
+f*
+0 g
+279.94 460.205 34.5237 0.2006 re
+f*
+1 g
+314.464 460.205 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 460.205 32.5166 0.2006 re
+f*
+1 g
+351.397 460.205 1.8065 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 460.205 34.7245 0.2006 re
+f*
+0 g
+280.342 460.405 34.1223 0.2005 re
+f*
+1 g
+314.464 460.405 4.4158 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 460.405 32.5166 0.2005 re
+f*
+1 g
+351.397 460.405 1.8065 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 460.405 34.323 0.2005 re
+f*
+0 g
+280.743 460.606 33.9217 0.2006 re
+f*
+1 g
+314.665 460.606 4.215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 460.606 32.5166 0.2006 re
+f*
+1 g
+351.397 460.606 1.8065 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 460.606 33.9215 0.2006 re
+f*
+0 g
+281.145 460.806 33.5202 0.2005 re
+f*
+1 g
+314.665 460.806 4.215 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 460.806 32.3159 0.2005 re
+f*
+1 g
+351.196 460.806 2.0072 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 460.806 33.5201 0.2005 re
+f*
+0 g
+281.546 461.007 33.1187 0.2006 re
+f*
+1 g
+314.665 461.007 4.215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 461.007 32.3159 0.2006 re
+f*
+1 g
+351.196 461.007 2.0072 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 461.007 33.1186 0.2006 re
+f*
+0 g
+281.948 461.208 32.7173 0.2006 re
+f*
+1 g
+314.665 461.208 4.215 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 461.208 32.3159 0.2006 re
+f*
+1 g
+351.196 461.208 2.0072 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 461.208 32.7172 0.2006 re
+f*
+0 g
+282.349 461.408 32.3159 0.2005 re
+f*
+1 g
+314.665 461.408 4.215 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 461.408 32.1151 0.2005 re
+f*
+1 g
+350.995 461.408 2.208 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 461.408 32.3158 0.2005 re
+f*
+0 g
+282.75 461.609 32.1151 0.2005 re
+f*
+1 g
+314.866 461.609 4.0144 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 461.609 32.1151 0.2005 re
+f*
+1 g
+350.995 461.609 2.208 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 461.609 31.9143 0.2005 re
+f*
+0 g
+283.152 461.809 31.7136 0.2006 re
+f*
+1 g
+314.866 461.809 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 461.809 32.1151 0.2006 re
+f*
+1 g
+350.995 461.809 2.208 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 461.809 31.5129 0.2006 re
+f*
+0 g
+283.553 462.01 31.3121 0.2006 re
+f*
+1 g
+314.866 462.01 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 462.01 31.9145 0.2006 re
+f*
+1 g
+350.794 462.01 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 462.01 31.1115 0.2006 re
+f*
+0 g
+283.955 462.21 31.1115 0.2005 re
+f*
+1 g
+315.066 462.21 3.8136 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 462.21 31.9145 0.2005 re
+f*
+1 g
+350.794 462.21 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 462.21 30.7101 0.2005 re
+f*
+0 g
+284.356 462.411 30.7101 0.2006 re
+f*
+1 g
+315.066 462.411 3.8136 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 462.411 31.9145 0.2006 re
+f*
+1 g
+350.794 462.411 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 462.411 30.1079 0.2006 re
+f*
+0 g
+284.758 462.611 30.5094 0.2005 re
+f*
+1 g
+315.267 462.611 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+318.88 462.611 31.7137 0.2005 re
+f*
+1 g
+350.594 462.611 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 462.611 29.7065 0.2005 re
+f*
+0 g
+285.36 462.812 29.9072 0.2006 re
+f*
+1 g
+315.267 462.812 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 462.812 31.7137 0.2006 re
+f*
+1 g
+350.594 462.812 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 462.812 29.305 0.2006 re
+f*
+0 g
+285.761 463.013 29.5058 0.2006 re
+f*
+1 g
+315.267 463.013 3.6129 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 463.013 31.7137 0.2006 re
+f*
+1 g
+350.594 463.013 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 463.013 28.9035 0.2006 re
+f*
+0 g
+286.163 463.213 29.305 0.2006 re
+f*
+1 g
+315.468 463.213 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+318.88 463.213 31.513 0.2006 re
+f*
+1 g
+350.393 463.213 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 463.213 28.5021 0.2006 re
+f*
+0 g
+286.564 463.414 28.9036 0.2005 re
+f*
+1 g
+315.468 463.414 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 463.414 31.3123 0.2005 re
+f*
+1 g
+350.393 463.414 2.8101 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 463.414 27.9 0.2005 re
+f*
+0 g
+287.166 463.614 28.3014 0.2005 re
+f*
+1 g
+315.468 463.614 3.6129 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 463.614 31.1116 0.2005 re
+f*
+1 g
+350.192 463.614 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 463.614 27.4985 0.2005 re
+f*
+0 g
+287.568 463.815 28.1008 0.2006 re
+f*
+1 g
+315.669 463.815 3.4121 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 463.815 31.1116 0.2006 re
+f*
+1 g
+350.192 463.815 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 463.815 27.0971 0.2006 re
+f*
+0 g
+287.969 464.015 27.6994 0.2006 re
+f*
+1 g
+315.669 464.015 3.4121 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 464.015 30.9108 0.2006 re
+f*
+1 g
+349.991 464.015 3.2116 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 464.015 26.495 0.2006 re
+f*
+0 g
+288.571 464.216 27.2978 0.2005 re
+f*
+1 g
+315.869 464.216 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+319.081 464.216 30.9108 0.2005 re
+f*
+1 g
+349.991 464.216 3.2116 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 464.216 26.0935 0.2005 re
+f*
+0 g
+288.973 464.416 26.8964 0.2006 re
+f*
+1 g
+315.869 464.416 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+319.081 464.416 30.7102 0.2006 re
+f*
+1 g
+349.791 464.416 3.4122 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 464.416 25.4914 0.2006 re
+f*
+0 g
+289.575 464.617 26.495 0.2005 re
+f*
+1 g
+316.07 464.617 3.2115 0.2005 re
+f*
+0.498 0 0.482 rg
+319.281 464.617 30.5094 0.2005 re
+f*
+1 g
+349.791 464.617 3.4122 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 464.617 25.0899 0.2005 re
+f*
+0 g
+289.976 464.818 26.0936 0.2006 re
+f*
+1 g
+316.07 464.818 3.2115 0.2006 re
+f*
+0.498 0 0.482 rg
+319.281 464.818 30.3086 0.2006 re
+f*
+1 g
+349.59 464.818 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 464.818 24.4878 0.2006 re
+f*
+0 g
+290.579 465.018 25.6921 0.2006 re
+f*
+1 g
+316.271 465.018 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+319.281 465.018 30.3086 0.2006 re
+f*
+1 g
+349.59 465.018 3.613 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 465.018 24.0863 0.2006 re
+f*
+0 g
+291.181 465.219 25.0899 0.2005 re
+f*
+1 g
+316.271 465.219 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+319.281 465.219 30.1079 0.2005 re
+f*
+1 g
+349.389 465.219 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 465.219 23.4842 0.2005 re
+f*
+0 g
+291.582 465.419 24.8892 0.2005 re
+f*
+1 g
+316.471 465.419 3.0108 0.2005 re
+f*
+0.498 0 0.482 rg
+319.482 465.419 29.9072 0.2005 re
+f*
+1 g
+349.389 465.419 3.8137 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 465.419 22.882 0.2005 re
+f*
+0 g
+292.184 465.62 24.287 0.2006 re
+f*
+1 g
+316.471 465.62 3.0108 0.2006 re
+f*
+0.498 0 0.482 rg
+319.482 465.62 29.7065 0.2006 re
+f*
+1 g
+349.189 465.62 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 465.62 22.4806 0.2006 re
+f*
+0 g
+292.786 465.82 23.8856 0.2006 re
+f*
+1 g
+316.672 465.82 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+319.482 465.82 29.7065 0.2006 re
+f*
+1 g
+349.189 465.82 4.0144 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 465.82 21.8784 0.2006 re
+f*
+0 g
+293.389 466.021 23.2834 0.2006 re
+f*
+1 g
+316.672 466.021 3.0109 0.2006 re
+f*
+0.498 0 0.482 rg
+319.683 466.021 29.3049 0.2006 re
+f*
+1 g
+348.988 466.021 4.2152 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 466.021 21.2762 0.2006 re
+f*
+0 g
+293.79 466.222 23.0827 0.2005 re
+f*
+1 g
+316.873 466.222 2.8102 0.2005 re
+f*
+0.498 0 0.482 rg
+319.683 466.222 29.3049 0.2005 re
+f*
+1 g
+348.988 466.222 4.2152 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 466.222 20.6741 0.2005 re
+f*
+0 g
+294.392 466.422 22.6813 0.2006 re
+f*
+1 g
+317.074 466.422 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+319.683 466.422 29.1043 0.2006 re
+f*
+1 g
+348.787 466.422 4.4158 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 466.422 20.0719 0.2006 re
+f*
+0 g
+295.195 466.623 21.8784 0.2005 re
+f*
+1 g
+317.074 466.623 2.81 0.2005 re
+f*
+0.498 0 0.482 rg
+319.884 466.623 28.7029 0.2005 re
+f*
+1 g
+348.586 466.623 4.6166 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 466.623 19.4698 0.2005 re
+f*
+0 g
+295.797 466.823 21.477 0.2006 re
+f*
+1 g
+317.274 466.823 2.6093 0.2006 re
+f*
+0.498 0 0.482 rg
+319.884 466.823 28.7029 0.2006 re
+f*
+1 g
+348.586 466.823 4.6166 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 466.823 18.8676 0.2006 re
+f*
+0 g
+296.399 467.024 20.8748 0.2006 re
+f*
+1 g
+317.274 467.024 2.8101 0.2006 re
+f*
+0.498 0 0.482 rg
+320.084 467.024 28.3014 0.2006 re
+f*
+1 g
+348.386 467.024 4.8173 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 467.024 18.2654 0.2006 re
+f*
+0 g
+297.002 467.224 20.4734 0.2005 re
+f*
+1 g
+317.475 467.224 2.6094 0.2005 re
+f*
+0.498 0 0.482 rg
+320.084 467.224 28.1007 0.2005 re
+f*
+1 g
+348.185 467.224 5.018 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 467.224 17.6633 0.2005 re
+f*
+0 g
+297.604 467.425 20.072 0.2005 re
+f*
+1 g
+317.676 467.425 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+320.285 467.425 27.9 0.2005 re
+f*
+1 g
+348.185 467.425 5.018 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 467.425 17.0611 0.2005 re
+f*
+0 g
+298.206 467.625 19.6704 0.2006 re
+f*
+1 g
+317.876 467.625 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+320.285 467.625 27.6992 0.2006 re
+f*
+1 g
+347.984 467.625 5.2188 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 467.625 16.2582 0.2006 re
+f*
+0 g
+299.009 467.826 18.8676 0.2006 re
+f*
+1 g
+317.876 467.826 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+320.486 467.826 27.2979 0.2006 re
+f*
+1 g
+347.784 467.826 5.4194 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 467.826 15.6561 0.2006 re
+f*
+0 g
+299.611 468.027 18.4661 0.2005 re
+f*
+1 g
+318.077 468.027 2.6095 0.2005 re
+f*
+0.498 0 0.482 rg
+320.687 468.027 26.8963 0.2005 re
+f*
+1 g
+347.583 468.027 5.6202 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 468.027 14.8532 0.2005 re
+f*
+0 g
+300.414 468.227 17.864 0.2006 re
+f*
+1 g
+318.278 468.227 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+320.687 468.227 26.8963 0.2006 re
+f*
+1 g
+347.583 468.227 5.6202 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 468.227 14.2511 0.2006 re
+f*
+0 g
+301.217 468.428 17.2619 0.2005 re
+f*
+1 g
+318.479 468.428 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+320.887 468.428 26.495 0.2005 re
+f*
+1 g
+347.382 468.428 5.8209 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 468.428 13.4482 0.2005 re
+f*
+0 g
+301.819 468.628 16.6597 0.2006 re
+f*
+1 g
+318.479 468.628 2.6094 0.2006 re
+f*
+0.498 0 0.482 rg
+321.088 468.628 26.0935 0.2006 re
+f*
+1 g
+347.181 468.628 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 468.628 12.6453 0.2006 re
+f*
+0 g
+302.622 468.829 16.0576 0.2006 re
+f*
+1 g
+318.679 468.829 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+321.088 468.829 25.8927 0.2006 re
+f*
+1 g
+346.981 468.829 6.2224 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 468.829 11.8424 0.2006 re
+f*
+0 g
+303.425 469.029 15.4553 0.2005 re
+f*
+1 g
+318.88 469.029 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+321.289 469.029 25.4914 0.2005 re
+f*
+1 g
+346.78 469.029 6.423 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 469.029 11.2403 0.2005 re
+f*
+0 g
+304.227 469.23 14.8532 0.2006 re
+f*
+1 g
+319.081 469.23 2.4087 0.2006 re
+f*
+0.498 0 0.482 rg
+321.489 469.23 25.0899 0.2006 re
+f*
+1 g
+346.579 469.23 6.6238 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 469.23 10.4374 0.2006 re
+f*
+0 g
+305.231 469.43 14.0504 0.2005 re
+f*
+1 g
+319.281 469.43 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+321.69 469.43 24.8891 0.2005 re
+f*
+1 g
+346.579 469.43 6.6238 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 469.43 9.4338 0.2005 re
+f*
+0 g
+306.034 469.631 13.4482 0.2006 re
+f*
+1 g
+319.482 469.631 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+321.891 469.631 24.4878 0.2006 re
+f*
+1 g
+346.379 469.631 6.8245 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 469.631 8.6309 0.2006 re
+f*
+0 g
+306.837 469.832 12.8461 0.2006 re
+f*
+1 g
+319.683 469.832 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+322.092 469.832 24.0863 0.2006 re
+f*
+1 g
+346.178 469.832 7.0252 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 469.832 7.6273 0.2006 re
+f*
+0 g
+307.841 470.032 12.0431 0.2005 re
+f*
+1 g
+319.884 470.032 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+322.292 470.032 23.6848 0.2005 re
+f*
+1 g
+345.977 470.032 7.226 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 470.032 6.8244 0.2005 re
+f*
+0 g
+308.844 470.233 11.2403 0.2006 re
+f*
+1 g
+320.084 470.233 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+322.493 470.233 23.2835 0.2006 re
+f*
+1 g
+345.776 470.233 7.4266 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 470.233 5.6201 0.2006 re
+f*
+0 g
+309.848 470.433 10.4374 0.2005 re
+f*
+1 g
+320.285 470.433 2.4087 0.2005 re
+f*
+0.498 0 0.482 rg
+322.694 470.433 22.6812 0.2005 re
+f*
+1 g
+345.375 470.433 7.8281 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 470.433 4.6165 0.2005 re
+f*
+0 g
+311.052 470.634 9.4338 0.2006 re
+f*
+1 g
+320.486 470.634 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+322.894 470.634 22.2799 0.2006 re
+f*
+1 g
+345.174 470.634 8.0288 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 470.634 3.6129 0.2006 re
+f*
+0 g
+312.256 470.834 8.4303 0.2005 re
+f*
+1 g
+320.687 470.834 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+323.095 470.834 21.8783 0.2005 re
+f*
+1 g
+344.973 470.834 8.2296 0.2005 re
+f*
+0.498 0 0.482 rg
+353.203 470.834 2.4086 0.2005 re
+f*
+0 g
+313.461 471.035 7.6274 0.2006 re
+f*
+1 g
+321.088 471.035 2.2079 0.2006 re
+f*
+0.498 0 0.482 rg
+323.296 471.035 21.477 0.2006 re
+f*
+1 g
+344.773 471.035 8.4302 0.2006 re
+f*
+0.498 0 0.482 rg
+353.203 471.035 1.2043 0.2006 re
+f*
+0 g
+314.665 471.235 6.6237 0.2006 re
+f*
+1 g
+321.289 471.235 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+323.697 471.235 20.8748 0.2006 re
+f*
+0 g
+316.271 471.436 5.2187 0.2005 re
+f*
+1 g
+321.489 471.436 2.4086 0.2005 re
+f*
+0.498 0 0.482 rg
+323.898 471.436 20.2727 0.2005 re
+f*
+0 g
+317.676 471.637 4.215 0.2006 re
+f*
+1 g
+321.891 471.637 2.208 0.2006 re
+f*
+0.498 0 0.482 rg
+324.099 471.637 19.8711 0.2006 re
+f*
+0 g
+319.482 471.837 2.6094 0.2006 re
+f*
+1 g
+322.092 471.837 2.4086 0.2006 re
+f*
+0.498 0 0.482 rg
+324.5 471.837 19.0683 0.2006 re
+f*
+0 g
+321.289 472.038 0.8029 0.2005 re
+f*
+1 g
+322.092 472.038 2.6093 0.2005 re
+f*
+0.498 0 0.482 rg
+324.701 472.038 18.6669 0.2005 re
+f*
+0.498 0 0.482 rg
+325.102 472.238 17.864 0.2006 re
+f*
+0.498 0 0.482 rg
+325.504 472.439 17.2619 0.2005 re
+f*
+0.498 0 0.482 rg
+325.905 472.639 16.459 0.2006 re
+f*
+0.498 0 0.482 rg
+326.307 472.84 15.6561 0.2006 re
+f*
+0.498 0 0.482 rg
+326.909 473.041 14.6526 0.2005 re
+f*
+0.498 0 0.482 rg
+327.511 473.241 13.4482 0.2006 re
+f*
+0.498 0 0.482 rg
+328.113 473.442 12.4447 0.2005 re
+f*
+0.498 0 0.482 rg
+328.715 473.642 11.2403 0.2006 re
+f*
+0.498 0 0.482 rg
+329.518 473.843 9.8352 0.2005 re
+f*
+0.498 0 0.482 rg
+330.321 474.043 8.2296 0.2006 re
+f*
+0.498 0 0.482 rg
+331.525 474.244 6.0216 0.2006 re
+f*
+0.498 0 0.482 rg
+333.533 474.445 2.2079 0.2005 re
+f*
+Q
+showpage
+pdfEndPage
+end
+%%Trailer
+cleartomark
+countdictstack
+exch sub { end } repeat
+restore
+%%EOF
diff --git a/conf/lpr b/conf/lpr
new file mode 100644
index 0000000..fa1c313
--- /dev/null
+++ b/conf/lpr
@@ -0,0 +1 @@
+lpr -h
diff --git a/conf/maxsearchrecordsperpage b/conf/maxsearchrecordsperpage
new file mode 100644
index 0000000..29d6383
--- /dev/null
+++ b/conf/maxsearchrecordsperpage
@@ -0,0 +1 @@
+100
diff --git a/conf/payment_receipt_email b/conf/payment_receipt_email
new file mode 100644
index 0000000..1a0a758
--- /dev/null
+++ b/conf/payment_receipt_email
@@ -0,0 +1,26 @@
+
+{ $date }
+
+Dear { $name },
+
+This message is to inform you that your payment of ${ $paid } has been
+received.
+
+Payment ID: { $paynum }
+Date: { $date }
+Amount: { $paid }
+Type: { $payby } # { $payinfo }
+
+{
+ if ( $balance > 0 ) {
+ $OUT .= "Your current balance is now \$$balance.\n\n";
+ } elsif ( $balance < 0 ) {
+ $OUT .= 'You have a credit balance of $'. sprintf("%.2f",0-$balance).
+ ".\n".
+ "Future charges will be deducted from this balance before billing ".
+ "you again.\n\n";
+
+ }
+}
+Thank you for your business.
+
diff --git a/conf/report_template b/conf/report_template
new file mode 100644
index 0000000..9c6bb2b
--- /dev/null
+++ b/conf/report_template
@@ -0,0 +1,14 @@
+{ sprintf("%-19s", "Page $page of $total_pages"); } {
+ my $spacer = (40 - length($title) > 0) ? 40 - length($title) : 0;
+ $spacer = int($spacer / 2);
+ my $titlelen = 40 - $spacer;
+ sprintf("%*s%-*s", $spacer, " ", $titlelen, $title);
+ } { use Date::Format; time2str("%x %X", $date); }
+
+
+{
+ join("\n", map { $_ } report_lines(57));
+}
+
+
+
diff --git a/conf/shells b/conf/shells
new file mode 100644
index 0000000..a41fc62
--- /dev/null
+++ b/conf/shells
@@ -0,0 +1,5 @@
+
+/bin/sh
+/bin/csh
+/bin/bash
+/bin/false
diff --git a/conf/show-msgcat-codes b/conf/show-msgcat-codes
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/conf/show-msgcat-codes
diff --git a/conf/smtpmachine b/conf/smtpmachine
new file mode 100644
index 0000000..2fbb50c
--- /dev/null
+++ b/conf/smtpmachine
@@ -0,0 +1 @@
+localhost
diff --git a/conf/soadefaultttl b/conf/soadefaultttl
new file mode 100644
index 0000000..92f616f
--- /dev/null
+++ b/conf/soadefaultttl
@@ -0,0 +1 @@
+259200
diff --git a/conf/soaexpire b/conf/soaexpire
new file mode 100644
index 0000000..d235b91
--- /dev/null
+++ b/conf/soaexpire
@@ -0,0 +1 @@
+3600000
diff --git a/conf/soarefresh b/conf/soarefresh
new file mode 100644
index 0000000..9f35f8e
--- /dev/null
+++ b/conf/soarefresh
@@ -0,0 +1 @@
+10800
diff --git a/conf/soaretry b/conf/soaretry
new file mode 100644
index 0000000..bb08106
--- /dev/null
+++ b/conf/soaretry
@@ -0,0 +1 @@
+1800
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 0000000..b51eee8
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,6 @@
+freeside for Debian
+-------------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ -- Ivan Kohler <ivan-debian@420.am>, Thu, 12 Apr 2001 15:49:17 -0700
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..d8283b5
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,9 @@
+freeside (1.4.1-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Ivan Kohler <ivan-debian@420.am> Thu, 12 Apr 2001 15:49:17 -0700
+
+Local variables:
+mode: debian-changelog
+End:
diff --git a/debian/conffiles.ex b/debian/conffiles.ex
new file mode 100644
index 0000000..8686d2a
--- /dev/null
+++ b/debian/conffiles.ex
@@ -0,0 +1,7 @@
+#
+# If you want to use this conffile, remove all comments and put files that
+# you want dpkg to process here using their absolute pathnames.
+# See section 9.1 of the packaging manual.
+#
+# for example:
+# /etc/freeside/freeside.conf
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..d7873b2
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,59 @@
+Source: freeside
+Section: admin
+Priority: optional
+Maintainer: Ivan Kohler <ivan-debian@420.am>
+Build-Depends: debhelper (>> 3.0.0)
+Standards-Version: 3.5.2
+
+Package: freeside
+Architecture: any
+Depends: freeside-lib
+Recommends: freeside-doc, freeside-ui-web
+Suggests: freeside-selfservice-server
+Description: Billing and administration package for ISPs.
+ Freeside is a billing and account administration package for ISPs. It stores
+ customer information in an SQL database, and will update UNIX passwd and
+ shadow files, RADIUS users file and SQL databases, and configuration for
+ sendmail, qmail, BIND and/or Apache. It is also useful as a central database
+ of accounts/domains/web-space for a large number of machines.
+
+Package: freeside-doc
+Architecture: all
+Description: Documentation for freeside
+ This package provides the HTML documentation for Freeside, a billing and
+ account administration package for ISPs.
+
+Package: freeside-lib
+Architecture: all
+Depends: libmime-base64-perl, libdigest-md5-perl, liburi-perl, libhtml-tagset-perl, libhtml-parser-perl, libnet-perl, liblocale-codes-perl, libnet-whois-perl, libwww-perl, libbusiness-creditcard-perl, libmailtools-perl, libtimedate-perl, libdate-manip-perl, libfile-counterfile-perl, libfreezethaw-perl, libtext-template-perl, libdbd-pg-perl, libdbix-datasource-perl, libdbix-dbschema-perl, libnet-ssh-perl, libnet-scp-perl, libapache-asp-perl, libtie-ixhash-perl, libtime-duration-perl, libhtml-widgets-selectlayers-perl, libstorable-perl, libapache-dbi-perl
+Description: Freeside libraries and extension API
+ This package contains the libraries which implement the business logic and
+ backend functions of Freeside, a billing and account administration package
+ for ISPs. This package also contains the manual pages for the library API.
+ (? like a libmodule-perl package)
+
+Package: freeside-ui-web
+Architecture: all
+Depends: libhtml-mason-perl, libstring-approx-perl, freeside-lib, libapache-mod-perl|apache-perl
+Suggests: libapache-mod-ssl|apache-ssl
+Description: Easy-to-use web interface for Freeside
+ This package contains the web interface for Freeside, a billing and account
+ administration package for ISPs. This is what sales or support folks will
+ typically use to add new accounts, edit exiting accounts and so on.
+
+Package: freeside-selfservice-server
+Architecture: all
+Depends: freeside-lib, libnet-ssh-perl, ssh
+Description:
+ This package contains the server side of the customer self-service interface.
+ It is installed on a private backend machine, and opens an outgoing ssh
+ connection to one or more public web server(s).
+
+Package: freeside-selfservice-client
+Architecture: all
+Depends: libstorable-perl, libhttp-browserdetect-perl, libbusiness-creditcard-perl, ssh
+Description:
+ This package contains the client side of the customer self-service interface.
+ It is typically installed on a public webserver and interfaces with
+ freeside-selfservice-server installed on a private backend machine.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..e148fce
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,10 @@
+This package was debianized by Ivan Kohler <ivan-debian@420.am> on
+Thu, 12 Apr 2001 15:49:17 -0700.
+
+It was downloaded from <fill in ftp site>
+
+Upstream Author(s): <put author(s) name and email here>
+
+Copyright:
+
+<Must follow here>
diff --git a/debian/cron.d.ex b/debian/cron.d.ex
new file mode 100644
index 0000000..61c074d
--- /dev/null
+++ b/debian/cron.d.ex
@@ -0,0 +1,4 @@
+#
+# Regular cron jobs for the freeside package
+#
+0 4 * * * root freeside_maintenance
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..ca882bb
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1,2 @@
+usr/bin
+usr/sbin
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..16636bd
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+INSTALL
+README
+TODO
diff --git a/debian/ex.doc-base.package b/debian/ex.doc-base.package
new file mode 100644
index 0000000..2a055d1
--- /dev/null
+++ b/debian/ex.doc-base.package
@@ -0,0 +1,22 @@
+Document: freeside
+Title: Debian freeside Manual
+Author: <insert document author here>
+Abstract: This manual describes what freeside is
+ and how it can be used to
+ manage online manuals on Debian systems.
+Section: unknown
+
+Format: debiandoc-sgml
+Files: /usr/share/doc/freeside/freeside.sgml.gz
+
+Format: postscript
+Files: /usr/share/doc/freeside/freeside.ps.gz
+
+Format: text
+Files: /usr/share/doc/freeside/freeside.text.gz
+
+Format: HTML
+Index: /usr/share/doc/freeside/html/index.html
+Files: /usr/share/doc/freeside/html/*.html
+
+
diff --git a/debian/freeside-doc.docs b/debian/freeside-doc.docs
new file mode 100644
index 0000000..299950c
--- /dev/null
+++ b/debian/freeside-doc.docs
@@ -0,0 +1,2 @@
+#DOCS#
+
diff --git a/debian/freeside-doc.files b/debian/freeside-doc.files
new file mode 100644
index 0000000..299950c
--- /dev/null
+++ b/debian/freeside-doc.files
@@ -0,0 +1,2 @@
+#DOCS#
+
diff --git a/debian/init.d.ex b/debian/init.d.ex
new file mode 100644
index 0000000..5791049
--- /dev/null
+++ b/debian/init.d.ex
@@ -0,0 +1,70 @@
+#! /bin/sh
+#
+# skeleton example file to build /etc/init.d/ scripts.
+# This file should be used to construct scripts for /etc/init.d.
+#
+# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
+# Modified for Debian GNU/Linux
+# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+# Version: @(#)skeleton 1.8 03-Mar-1998 miquels@cistron.nl
+#
+# This file was automatically customized by dh-make on Thu, 12 Apr 2001 15:49:17 -0700
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/freeside
+NAME=freeside
+DESC=freeside
+
+test -f $DAEMON || exit 0
+
+set -e
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON
+ echo "$NAME."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
+ --exec $DAEMON
+ echo "$NAME."
+ ;;
+ #reload)
+ #
+ # If the daemon can reload its config files on the fly
+ # for example by sending it SIGHUP, do it here.
+ #
+ # If the daemon responds to changes in its config file
+ # directly anyway, make this a do-nothing entry.
+ #
+ # echo "Reloading $DESC configuration files."
+ # start-stop-daemon --stop --signal 1 --quiet --pidfile \
+ # /var/run/$NAME.pid --exec $DAEMON
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented, move the "force-reload"
+ # option to the "reload" entry above. If not, "force-reload" is
+ # just the same as "restart".
+ #
+ echo -n "Restarting $DESC: "
+ start-stop-daemon --stop --quiet --pidfile \
+ /var/run/$NAME.pid --exec $DAEMON
+ sleep 1
+ start-stop-daemon --start --quiet --pidfile \
+ /var/run/$NAME.pid --exec $DAEMON
+ echo "$NAME."
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $N {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/debian/manpage.1.ex b/debian/manpage.1.ex
new file mode 100644
index 0000000..ec542bb
--- /dev/null
+++ b/debian/manpage.1.ex
@@ -0,0 +1,60 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH FREESIDE SECTION "April 12, 2001"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+freeside \- program to do something
+.SH SYNOPSIS
+.B freeside
+.RI [ options ] " files" ...
+.br
+.B bar
+.RI [ options ] " files" ...
+.SH DESCRIPTION
+This manual page documents briefly the
+.B freeside
+and
+.B bar
+commands.
+This manual page was written for the Debian GNU/Linux distribution
+because the original program does not have a manual page.
+Instead, it has documentation in the GNU Info format; see below.
+.PP
+.\" TeX users may be more comfortable with the \fB<whatever>\fP and
+.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
+.\" respectively.
+\fBfreeside\fP is a program that...
+.SH OPTIONS
+These programs follow the usual GNU command line syntax, with long
+options starting with two dashes (`-').
+A summary of options is included below.
+For a complete description, see the Info files.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-v, \-\-version
+Show version of program.
+.SH SEE ALSO
+.BR bar (1),
+.BR baz (1).
+.br
+The programs are documented fully by
+.IR "The Rise and Fall of a Fooish Bar" ,
+available via the Info system.
+.SH AUTHOR
+This manual page was written by Ivan Kohler <ivan-debian@420.am>,
+for the Debian GNU/Linux system (but may be used by others).
diff --git a/debian/manpage.sgml.ex b/debian/manpage.sgml.ex
new file mode 100644
index 0000000..9bc3a86
--- /dev/null
+++ b/debian/manpage.sgml.ex
@@ -0,0 +1,143 @@
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+
+<!-- Process this file with docbook-to-man to generate an nroff manual
+ page: `docbook-to-man manpage.sgml > manpage.1'. You may view
+ the manual page with: `docbook-to-man manpage.sgml | nroff -man |
+ less'. A typical entry in a Makefile or Makefile.am is:
+
+manpage.1: manpage.sgml
+ docbook-to-man $< > $@
+ -->
+
+ <!-- Fill in your name for FIRSTNAME and SURNAME. -->
+ <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>">
+ <!ENTITY dhsurname "<surname>SURNAME</surname>">
+ <!-- Please adjust the date whenever revising the manpage. -->
+ <!ENTITY dhdate "<date>April 12, 2001</date>">
+ <!-- SECTION should be 1-8, maybe w/ subsection other parameters are
+ allowed: see man(7), man(1). -->
+ <!ENTITY dhsection "<manvolnum>SECTION</manvolnum>">
+ <!ENTITY dhemail "<email>ivan-debian@420.am</email>">
+ <!ENTITY dhusername "Ivan Kohler">
+ <!ENTITY dhucpackage "<refentrytitle>FREESIDE</refentrytitle>">
+ <!ENTITY dhpackage "freeside">
+
+ <!ENTITY debian "<productname>Debian GNU/Linux</productname>">
+ <!ENTITY gnu "<acronym>GNU</acronym>">
+]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ <copyright>
+ <year>2001</year>
+ <holder>&dhusername;</holder>
+ </copyright>
+ &dhdate;
+ </refentryinfo>
+ <refmeta>
+ &dhucpackage;
+
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>&dhpackage;</refname>
+
+ <refpurpose>program to do something</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>&dhpackage;</command>
+
+ <arg><option>-e <replaceable>this</replaceable></option></arg>
+
+ <arg><option>--example <replaceable>that</replaceable></option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>This manual page documents briefly the
+ <command>&dhpackage;</command> and <command>bar</command>
+ commands.</para>
+
+ <para>This manual page was written for the &debian; distribution
+ because the original program does not have a manual page.
+ Instead, it has documentation in the &gnu;
+ <application>Info</application> format; see below.</para>
+
+ <para><command>&dhpackage;</command> is a program that...</para>
+
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <para>These programs follow the usual GNU command line syntax,
+ with long options starting with two dashes (`-'). A summary of
+ options is included below. For a complete description, see the
+ <application>Info</application> files.</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option>
+ <option>--help</option>
+ </term>
+ <listitem>
+ <para>Show summary of options.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-v</option>
+ <option>--version</option>
+ </term>
+ <listitem>
+ <para>Show version of program.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>bar (1), baz (1).</para>
+
+ <para>The programs are documented fully by <citetitle>The Rise and
+ Fall of a Fooish Bar</citetitle> available via the
+ <application>Info</application> system.</para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>This manual page was written by &dhusername; &dhemail; for
+ the &debian; system (but may be used by others). Permission is
+ granted to copy, distribute and/or modify this document under
+ the terms of the <acronym>GNU</acronym> Free Documentation
+ License, Version 1.1 or any later version published by the Free
+ Software Foundation; with no Invariant Sections, no Front-Cover
+ Texts and no Back-Cover Texts.</para>
+
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:t
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:2
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:nil
+sgml-exposed-tags:nil
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/debian/menu.ex b/debian/menu.ex
new file mode 100644
index 0000000..ddc947e
--- /dev/null
+++ b/debian/menu.ex
@@ -0,0 +1,2 @@
+?package(freeside):needs=X11|text|vc|wm section=Apps/see-menu-manual\
+ title="freeside" command="/usr/bin/freeside"
diff --git a/debian/postinst.ex b/debian/postinst.ex
new file mode 100644
index 0000000..c4d4bfb
--- /dev/null
+++ b/debian/postinst.ex
@@ -0,0 +1,47 @@
+#! /bin/sh
+# postinst script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see /usr/share/doc/packaging-manual/
+#
+# quoting from the policy:
+# Any necessary prompting should almost always be confined to the
+# post-installation script, and should be protected with a conditional
+# so that unnecessary prompting doesn't happen if a package's
+# installation fails and the `postinst' is called with `abort-upgrade',
+# `abort-remove' or `abort-deconfigure'.
+
+case "$1" in
+ configure)
+
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/postrm.ex b/debian/postrm.ex
new file mode 100644
index 0000000..bed8abd
--- /dev/null
+++ b/debian/postrm.ex
@@ -0,0 +1,36 @@
+#! /bin/sh
+# postrm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version>
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+
+
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 0
+
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+
diff --git a/debian/preinst.ex b/debian/preinst.ex
new file mode 100644
index 0000000..0b42bb2
--- /dev/null
+++ b/debian/preinst.ex
@@ -0,0 +1,42 @@
+#! /bin/sh
+# preinst script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+#
+# For details see /usr/share/doc/packaging-manual/
+
+case "$1" in
+ install|upgrade)
+# if [ "$1" = "upgrade" ]
+# then
+# start-stop-daemon --stop --quiet --oknodo \
+# --pidfile /var/run/freeside.pid \
+# --exec /usr/sbin/freeside 2>/dev/null || true
+# fi
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/prerm.ex b/debian/prerm.ex
new file mode 100644
index 0000000..ebb87c5
--- /dev/null
+++ b/debian/prerm.ex
@@ -0,0 +1,37 @@
+#! /bin/sh
+# prerm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <prerm> `remove'
+# * <old-prerm> `upgrade' <new-version>
+# * <new-prerm> `failed-upgrade' <old-version>
+# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+# * <deconfigured's-prerm> `deconfigure' `in-favour'
+# <package-being-installed> <version> `removing'
+# <conflicting-package> <version>
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+ remove|upgrade|deconfigure)
+# install-info --quiet --remove /usr/info/freeside.info.gz
+ ;;
+ failed-upgrade)
+ ;;
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..71016c4
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,113 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 by Joey Hess.
+#
+# This version is for a hypothetical package that builds an
+# architecture-dependant package, as well as an architecture-independent
+# package.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatability version to use.
+export DH_COMPAT=3
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+
+ touch configure-stamp
+
+build: configure-stamp build-stamp
+build-stamp:
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE)
+
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ -$(MAKE) clean
+
+ dh_clean
+
+install: DH_OPTIONS=
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ # Add here commands to install the package into debian/freeside.
+ $(MAKE) install DESTDIR=$(CURDIR)/debian/freeside
+
+ dh_movefiles
+
+# Build architecture-independent files here.
+# Pass -i to all debhelper commands in this target to reduce clutter.
+binary-indep: build install
+ dh_testdir -i
+ dh_testroot -i
+# dh_installdebconf -i
+ dh_installdocs -i
+ dh_installexamples -i
+ dh_installmenu -i
+# dh_installlogrotate -i
+# dh_installemacsen -i
+# dh_installpam -i
+# dh_installmime -i
+# dh_installinit -i
+ dh_installcron -i
+# dh_installman -i
+ dh_installinfo -i
+# dh_undocumented -i
+ dh_installchangelogs -i
+ dh_link -i
+ dh_compress -i
+ dh_fixperms -i
+ dh_installdeb -i
+# dh_perl -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir -a
+ dh_testroot -a
+# dh_installdebconf -a
+ dh_installdocs -a
+ dh_installexamples -a
+ dh_installmenu -a
+# dh_installlogrotate -a
+# dh_installemacsen -a
+# dh_installpam -a
+# dh_installmime -a
+# dh_installinit -a
+ dh_installcron -a
+# dh_installman -a
+ dh_installinfo -a
+# dh_undocumented -a
+ dh_installchangelogs -a
+ dh_strip -a
+ dh_link -a
+ dh_compress -a
+ dh_fixperms -a
+# dh_makeshlibs -a
+ dh_installdeb -a
+# dh_perl -a
+ dh_shlibdeps -a
+ dh_gencontrol -a
+ dh_md5sums -a
+ dh_builddeb -a
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/debian/watch.ex b/debian/watch.ex
new file mode 100644
index 0000000..3f57ae0
--- /dev/null
+++ b/debian/watch.ex
@@ -0,0 +1,5 @@
+# Example watch control file for uscan
+# Rename this file to "watch" and then you can run the "uscan" command
+# to check for upstream updates and more.
+# Site Directory Pattern Version Script
+sunsite.unc.edu /pub/Linux/Incoming freeside-(.*)\.tar\.gz debian uupdate
diff --git a/eg/TEMPLATE_cust_main.import b/eg/TEMPLATE_cust_main.import
new file mode 100755
index 0000000..e91a2f1
--- /dev/null
+++ b/eg/TEMPLATE_cust_main.import
@@ -0,0 +1,198 @@
+#!/usr/bin/perl -w
+#
+# Template for importing legacy customer data
+#
+# $Id: TEMPLATE_cust_main.import,v 1.4 2001-08-21 02:44:47 ivan Exp $
+
+use strict;
+use Date::Parse;
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(fields qsearch qsearchs);
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::pkg_svc;
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# use these for the imported cust_main records (unless you have these in legacy
+# data)
+my($agentnum)=4;
+my($refnum)=5;
+
+# map from legacy billing data to pkgpart, maps imported field
+# LegacyBillingData to pkgpart. your names and pkgparts will be different
+my(%pkgpart)=(
+ 'Employee' => 10,
+ 'Business' => 11,
+ 'Individual' => 12,
+ 'Basic PPP' => 13,
+ 'Slave' => 14,
+ 'Co-Located Server' => 15,
+ 'Virtual Web' => 16,
+ 'Perk Mail' => 17,
+ 'Credit Hold' => 18,
+);
+
+my($file)="legacy_file";
+
+open(CLIENT,$file)
+ or die "Can't open $file: $!";
+
+# put a tab-separated header atop the file, or define @fields
+# (use these names or change them below)
+#
+# for cust_main
+# custnum - unique
+# last - (name)
+# first - (name)
+# company
+# address1
+# address2
+# city
+# state
+# zip
+# country
+# daytime - (phone)
+# night - (phone)
+# fax
+# payby - CARD, BILL or COMP
+# payinfo - Credit card #, P.O. # or COMP authorization
+# paydate - Expiration
+# tax - 'Y' for tax exempt
+# for cust_pkg
+# LegacyBillingData - maps via %pkgpart above to a pkgpart
+# for svc_acct
+# username
+
+my($header);
+$header=<CLIENT>;
+chop $header;
+my(@fields)=map { /^\s*(.*[^\s]+)\s*$/; $1 } split(/\t/,$header);
+#print join("\n",@fields);
+
+my($error);
+my($link,$line)=(0,0);
+while (<CLIENT>) {
+ chop;
+ next if /^[\s\t]*$/; #skip any blank lines
+
+ #define %svc hash for this record
+ my(@record)=split(/\t/);
+ my(%svc);
+ foreach (@fields) {
+ $svc{$_}=shift @record;
+ }
+
+ # might need to massage some data like this
+ $svc{'payby'} =~ s/^Credit Card$/CARD/io;
+ $svc{'payby'} =~ s/^Check$/BILL/io;
+ $svc{'payby'} =~ s/^Cash$/BILL/io;
+ $svc{'payby'} =~ s/^$/BILL/o;
+ $svc{'First'} =~ s/&/and/go;
+ $svc{'Zip'} =~ s/\s+$//go;
+
+ my($cust_main) = new FS::cust_main ( {
+ 'custnum' => $svc{'custnum'},
+ 'agentnum' => $agentnum,
+ 'last' => $svc{'last'},
+ 'first' => $svc{'first'},
+ 'company' => $svc{'company'},
+ 'address1' => $svc{'address1'},
+ 'address2' => $svc{'address2'},
+ 'city' => $svc{'city'},
+ 'state' => $svc{'state'},
+ 'zip' => $svc{'zip'},
+ 'country' => $svc{'country'},
+ 'daytime' => $svc{'daytime'},
+ 'night' => $svc{'night'},
+ 'fax' => $svc{'fax'},
+ 'payby' => $svc{'payby'},
+ 'payinfo' => $svc{'payinfo'},
+ 'paydate' => $svc{'paydate'},
+ 'payname' => $svc{'payname'},
+ 'tax' => $svc{'tax'},
+ 'refnum' => $refnum,
+ } );
+
+ $error=$cust_main->insert;
+
+ if ( $error ) {
+ warn $cust_main->_dump;
+ warn map "$_: ". $svc{$_}. "|\n", keys %svc;
+ die $error;
+ }
+
+ my($cust_pkg)=new FS::cust_pkg ( {
+ 'custnum' => $svc{'custnum'},
+ 'pkgpart' => $pkgpart{$svc{'LegacyBillingData'}},
+ 'setup' => '',
+ 'bill' => '',
+ 'susp' => '',
+ 'expire' => '',
+ 'cancel' => '',
+ } );
+
+ $error=$cust_pkg->insert;
+ if ( $error ) {
+ warn $svc{'LegacyBillingData'};
+ die $error;
+ }
+
+ unless ( $svc{'username'} ) {
+ warn "Empty login";
+ } else {
+ #find svc_acct record (imported with bin/svc_acct.import) for this username
+ my($svc_acct)=qsearchs('svc_acct',{'username'=>$svc{'username'}});
+ unless ( $svc_acct ) {
+ warn "username ", $svc{'username'}, " not found\n";
+ } else {
+ #link to the cust_pkg record we created above
+
+ #find cust_svc record for this svc_acct record
+ my($o_cust_svc)=qsearchs('cust_svc',{
+ 'svcnum' => $svc_acct->svcnum,
+ 'pkgnum' => '',
+ } );
+ unless ( $o_cust_svc ) {
+ warn "No unlinked cust_svc for svcnum ", $svc_acct->svcnum;
+ } else {
+
+ #make sure this svcpart is in pkgpart
+ my($pkg_svc)=qsearchs('pkg_svc',{
+ 'pkgpart' => $pkgpart{$svc{'LegacyBillingData'}},
+ 'svcpart' => $o_cust_svc->svcpart,
+ 'quantity' => 1,
+ });
+ unless ( $pkg_svc ) {
+ warn "login ", $svc{'username'}, ": No svcpart ", $o_cust_svc->svcpart,
+ " for pkgpart ", $pkgpart{$svc{'Acct. Type'}}, "\n" ;
+ } else {
+
+ #create new cust_svc record linked to cust_pkg record
+ my($n_cust_svc) = new FS::cust_svc ({
+ 'svcnum' => $o_cust_svc->svcnum,
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'svcpart' => $pkg_svc->svcpart,
+ });
+ my($error) = $n_cust_svc->replace($o_cust_svc);
+ die $error if $error;
+ $link++;
+ }
+ }
+ }
+ }
+
+ $line++;
+
+}
+
+warn "\n$link of $line lines linked\n";
+
+# ---
+
+sub usage {
+ die "Usage:\n\n cust_main.import user\n";
+}
diff --git a/eg/export_template.pm b/eg/export_template.pm
new file mode 100644
index 0000000..2c199db
--- /dev/null
+++ b/eg/export_template.pm
@@ -0,0 +1,81 @@
+package FS::part_export::myexport;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+ 'regular_option' => { label => 'Option description', default => 'value' },
+ 'select_option' => { label => 'Select option description',
+ type => 'select', options=>[qw(chocolate vanilla)],
+ default => 'vanilla',
+ },
+ 'textarea_option' => { label => 'Textarea option description',
+ type => 'textarea',
+ default => 'Default text.',
+ },
+ 'checkbox_option' => { label => 'Checkbox label', type => 'checkbox' },
+;
+
+%info = (
+ 'svc' => 'svc_acct',
+ #'svc' => [qw( svc_acct svc_forward )],
+ 'desc' =>
+ 'Export short description',
+ 'options' => \%options,
+ 'nodomain' => 'Y',
+ 'notes' => <<'END'
+HTML notes about this export.
+END
+
+sub rebless { shift; }
+
+sub _export_insert {
+ my($self, $svc_something) = (shift, shift);
+ $err_or_queue = $self->myexport_queue( $svc_something->svcnum, 'insert',
+ $svc_something->username, $svc_something->_password );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_replace {
+ my( $self, $new, $old ) = (shift, shift, shift);
+ #return "can't change username with myexport"
+ # if $old->username ne $new->username;
+ #return '' unless $old->_password ne $new->_password;
+ $err_or_queue = $self->myexport_queue( $new->svcnum,
+ 'replace', $new->username, $new->_password );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub _export_delete {
+ my( $self, $svc_something ) = (shift, shift);
+ $err_or_queue = $self->myexport_queue( $svc_something->svcnum,
+ 'delete', $svc_something->username );
+ ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+#a good idea to queue anything that could fail or take any time
+sub myexport_queue {
+ my( $self, $svcnum, $method ) = (shift, shift, shift);
+ my $queue = new FS::queue {
+ 'svcnum' => $svcnum,
+ 'job' => "FS::part_export::myexport::myexport_$method",
+ };
+ $queue->insert( @_ ) or $queue;
+}
+
+sub myexport_insert { #subroutine, not method
+ my( $username, $password ) = @_;
+ #do things with $username and $password
+}
+
+sub myexport_replace { #subroutine, not method
+}
+
+sub myexport_delete { #subroutine, not method
+ my( $username ) = @_;
+ #do things with $username
+}
+
diff --git a/eg/table_template-svc.pm b/eg/table_template-svc.pm
new file mode 100644
index 0000000..7f7ef4b
--- /dev/null
+++ b/eg/table_template-svc.pm
@@ -0,0 +1,161 @@
+package FS::svc_table;
+
+use strict;
+use vars qw(@ISA);
+#use FS::Record qw( qsearch qsearchs );
+use FS::svc_Common;
+use FS::cust_svc;
+
+@ISA = qw(FS::svc_Common);
+
+=head1 NAME
+
+FS::table_name - Object methods for table_name records
+
+=head1 SYNOPSIS
+
+ use FS::table_name;
+
+ $record = new FS::table_name \%hash;
+ $record = new FS::table_name { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+ $error = $record->suspend;
+
+ $error = $record->unsuspend;
+
+ $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::table_name object represents an example. FS::table_name inherits from
+FS::svc_Common. The following fields are currently supported:
+
+=over 4
+
+=item field - description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'table_name'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
+defined. An FS::cust_svc record will be created and inserted.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my $error;
+
+ $error = $self->SUPER::insert;
+ return $error if $error;
+
+ '';
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+ my $self = shift;
+ my $error;
+
+ $error = $self->SUPER::delete;
+ return $error if $error;
+
+ '';
+}
+
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my ( $new, $old ) = ( shift, shift );
+ my $error;
+
+ $error = $new->SUPER::replace($old);
+ return $error if $error;
+
+ '';
+}
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $x = $self->setfixed;
+ return $x unless ref($x);
+ my $part_svc = $x;
+
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
+L<FS::cust_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/eg/table_template.pm b/eg/table_template.pm
new file mode 100644
index 0000000..d609bd5
--- /dev/null
+++ b/eg/table_template.pm
@@ -0,0 +1,112 @@
+package FS::table_name;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::table_name - Object methods for table_name records
+
+=head1 SYNOPSIS
+
+ use FS::table_name;
+
+ $record = new FS::table_name \%hash;
+ $record = new FS::table_name { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::table_name object represents an example. FS::table_name inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item field - description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'table_name'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ ''; #no error
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/etc/abbr_state.txt b/etc/abbr_state.txt
new file mode 100644
index 0000000..7e4f57f
--- /dev/null
+++ b/etc/abbr_state.txt
@@ -0,0 +1,72 @@
+State/Possession Abbreviation
+
+ALABAMA AL
+ALASKA AK
+AMERICAN SAMOA AS
+ARIZONA AZ
+ARKANSAS AR
+CALIFORNIA CA
+COLORADO CO
+CONNECTICUT CT
+DELAWARE DE
+DISTRICT OF COLUMBIA DC
+FEDERATED STATES OF MICRONESIA FM
+FLORIDA FL
+GEORGIA GA
+GUAM GU
+HAWAII HI
+IDAHO ID
+ILLINOIS IL
+INDIANA IN
+IOWA IA
+KANSAS KS
+KENTUCKY KY
+LOUISIANA LA
+MAINE ME
+MARSHALL ISLANDS MH
+MARYLAND MD
+MASSACHUSETTS MA
+MICHIGAN MI
+MINNESOTA MN
+MISSISSIPPI MS
+MISSOURI MO
+MONTANA MT
+NEBRASKA NE
+NEVADA NV
+NEW HAMPSHIRE NH
+NEW JERSEY NJ
+NEW MEXICO NM
+NEW YORK NY
+NORTH CAROLINA NC
+NORTH DAKOTA ND
+NORTHERN MARIANA ISLANDS MP
+OHIO OH
+OKLAHOMA OK
+OREGON OR
+PALAU PW
+PENNSYLVANIA PA
+PUERTO RICO PR
+RHODE ISLAND RI
+SOUTH CAROLINA SC
+SOUTH DAKOTA SD
+TENNESSEE TN
+TEXAS TX
+UTAH UT
+VERMONT VT
+VIRGIN ISLANDS VI
+VIRGINIA VA
+WASHINGTON WA
+WEST VIRGINIA WV
+WISCONSIN WI
+WYOMING WY
+
+
+Military "State" Abbreviation
+
+Armed Forces Africa AE
+Armed Forces Americas AA
+(except Canada)
+Armed Forces Canada AE
+Armed Forces Europe AE
+Armed Forces Middle East AE
+Armed Forces Pacific AP
diff --git a/etc/countries.txt b/etc/countries.txt
new file mode 100644
index 0000000..73c3975
--- /dev/null
+++ b/etc/countries.txt
@@ -0,0 +1,239 @@
+AFGHANISTAN AF AFG 004
+ALBANIA AL ALB 008
+ALGERIA DZ DZA 012
+AMERICAN SAMOA AS ASM 016
+ANDORRA AD AND 020
+ANGOLA AO AGO 024
+ANGUILLA AI AIA 660
+ANTARCTICA AQ ATA 010
+ANTIGUA AND BARBUDA AG ATG 028
+ARGENTINA AR ARG 032
+ARMENIA AM ARM 051
+ARUBA AW ABW 533
+AUSTRALIA AU AUS 036
+AUSTRIA AT AUT 040
+AZERBAIJAN AZ AZE 031
+BAHAMAS BS BHS 044
+BAHRAIN BH BHR 048
+BANGLADESH BD BGD 050
+BARBADOS BB BRB 052
+BELARUS BY BLR 112
+BELGIUM BE BEL 056
+BELIZE BZ BLZ 084
+BENIN BJ BEN 204
+BERMUDA BM BMU 060
+BHUTAN BT BTN 064
+BOLIVIA BO BOL 068
+BOSNIA AND HERZEGOWINA BA BIH 070
+BOTSWANA BW BWA 072
+BOUVET ISLAND BV BVT 074
+BRAZIL BR BRA 076
+BRITISH INDIAN OCEAN TERRITORY IO IOT 086
+BRUNEI DARUSSALAM BN BRN 096
+BULGARIA BG BGR 100
+BURKINA FASO BF BFA 854
+BURUNDI BI BDI 108
+CAMBODIA KH KHM 116
+CAMEROON CM CMR 120
+CANADA CA CAN 124
+CAPE VERDE CV CPV 132
+CAYMAN ISLANDS KY CYM 136
+CENTRAL AFRICAN REPUBLIC CF CAF 140
+CHAD TD TCD 148
+CHILE CL CHL 152
+CHINA CN CHN 156
+CHRISTMAS ISLAND CX CXR 162
+COCOS (KEELING) ISLANDS CC CCK 166
+COLOMBIA CO COL 170
+COMOROS KM COM 174
+CONGO CG COG 178
+COOK ISLANDS CK COK 184
+COSTA RICA CR CRI 188
+COTE D'IVOIRE CI CIV 384
+CROATIA (local name: Hrvatska) HR HRV 191
+CUBA CU CUB 192
+CYPRUS CY CYP 196
+CZECH REPUBLIC CZ CZE 203
+DENMARK DK DNK 208
+DJIBOUTI DJ DJI 262
+DOMINICA DM DMA 212
+DOMINICAN REPUBLIC DO DOM 214
+EAST TIMOR TP TMP 626
+ECUADOR EC ECU 218
+EGYPT EG EGY 818
+EL SALVADOR SV SLV 222
+EQUATORIAL GUINEA GQ GNQ 226
+ERITREA ER ERI 232
+ESTONIA EE EST 233
+ETHIOPIA ET ETH 231
+FALKLAND ISLANDS (MALVINAS) FK FLK 238
+FAROE ISLANDS FO FRO 234
+FIJI FJ FJI 242
+FINLAND FI FIN 246
+FRANCE FR FRA 250
+FRANCE, METROPOLITAN FX FXX 249
+FRENCH GUIANA GF GUF 254
+FRENCH POLYNESIA PF PYF 258
+FRENCH SOUTHERN TERRITORIES TF ATF 260
+GABON GA GAB 266
+GAMBIA GM GMB 270
+GEORGIA GE GEO 268
+GERMANY DE DEU 276
+GHANA GH GHA 288
+GIBRALTAR GI GIB 292
+GREECE GR GRC 300
+GREENLAND GL GRL 304
+GRENADA GD GRD 308
+GUADELOUPE GP GLP 312
+GUAM GU GUM 316
+GUATEMALA GT GTM 320
+GUINEA GN GIN 324
+GUINEA-BISSAU GW GNB 624
+GUYANA GY GUY 328
+HAITI HT HTI 332
+HEARD AND MC DONALD ISLANDS HM HMD 334
+HONDURAS HN HND 340
+HONG KONG HK HKG 344
+HUNGARY HU HUN 348
+ICELAND IS ISL 352
+INDIA IN IND 356
+INDONESIA ID IDN 360
+IRAN (ISLAMIC REPUBLIC OF) IR IRN 364
+IRAQ IQ IRQ 368
+IRELAND IE IRL 372
+ISRAEL IL ISR 376
+ITALY IT ITA 380
+JAMAICA JM JAM 388
+JAPAN JP JPN 392
+JORDAN JO JOR 400
+KAZAKHSTAN KZ KAZ 398
+KENYA KE KEN 404
+KIRIBATI KI KIR 296
+KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF KP PRK 408
+KOREA, REPUBLIC OF KR KOR 410
+KUWAIT KW KWT 414
+KYRGYZSTAN KG KGZ 417
+LAO PEOPLE'S DEMOCRATIC REPUBLIC LA LAO 418
+LATVIA LV LVA 428
+LEBANON LB LBN 422
+LESOTHO LS LSO 426
+LIBERIA LR LBR 430
+LIBYAN ARAB JAMAHIRIYA LY LBY 434
+LIECHTENSTEIN LI LIE 438
+LITHUANIA LT LTU 440
+LUXEMBOURG LU LUX 442
+MACAU MO MAC 446
+MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF MK MKD 807
+MADAGASCAR MG MDG 450
+MALAWI MW MWI 454
+MALAYSIA MY MYS 458
+MALDIVES MV MDV 462
+MALI ML MLI 466
+MALTA MT MLT 470
+MARSHALL ISLANDS MH MHL 584
+MARTINIQUE MQ MTQ 474
+MAURITANIA MR MRT 478
+MAURITIUS MU MUS 480
+MAYOTTE YT MYT 175
+MEXICO MX MEX 484
+MICRONESIA, FEDERATED STATES OF FM FSM 583
+MOLDOVA, REPUBLIC OF MD MDA 498
+MONACO MC MCO 492
+MONGOLIA MN MNG 496
+MONTSERRAT MS MSR 500
+MOROCCO MA MAR 504
+MOZAMBIQUE MZ MOZ 508
+MYANMAR MM MMR 104
+NAMIBIA NA NAM 516
+NAURU NR NRU 520
+NEPAL NP NPL 524
+NETHERLANDS NL NLD 528
+NETHERLANDS ANTILLES AN ANT 530
+NEW CALEDONIA NC NCL 540
+NEW ZEALAND NZ NZL 554
+NICARAGUA NI NIC 558
+NIGER NE NER 562
+NIGERIA NG NGA 566
+NIUE NU NIU 570
+NORFOLK ISLAND NF NFK 574
+NORTHERN MARIANA ISLANDS MP MNP 580
+NORWAY NO NOR 578
+OMAN OM OMN 512
+PAKISTAN PK PAK 586
+PALAU PW PLW 585
+PANAMA PA PAN 591
+PAPUA NEW GUINEA PG PNG 598
+PARAGUAY PY PRY 600
+PERU PE PER 604
+PHILIPPINES PH PHL 608
+PITCAIRN PN PCN 612
+POLAND PL POL 616
+PORTUGAL PT PRT 620
+PUERTO RICO PR PRI 630
+QATAR QA QAT 634
+REUNION RE REU 638
+ROMANIA RO ROM 642
+RUSSIAN FEDERATION RU RUS 643
+RWANDA RW RWA 646
+SAINT KITTS AND NEVIS KN KNA 659
+SAINT LUCIA LC LCA 662
+SAINT VINCENT AND THE GRENADINES VC VCT 670
+SAMOA WS WSM 882
+SAN MARINO SM SMR 674
+SAO TOME AND PRINCIPE ST STP 678
+SAUDI ARABIA SA SAU 682
+SENEGAL SN SEN 686
+SEYCHELLES SC SYC 690
+SIERRA LEONE SL SLE 694
+SINGAPORE SG SGP 702
+SLOVAKIA (Slovak Republic) SK SVK 703
+SLOVENIA SI SVN 705
+SOLOMON ISLANDS SB SLB 090
+SOMALIA SO SOM 706
+SOUTH AFRICA ZA ZAF 710
+SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS GS SGS 239
+SPAIN ES ESP 724
+SRI LANKA LK LKA 144
+ST. HELENA SH SHN 654
+ST. PIERRE AND MIQUELON PM SPM 666
+SUDAN SD SDN 736
+SURINAME SR SUR 740
+SVALBARD AND JAN MAYEN ISLANDS SJ SJM 744
+SWAZILAND SZ SWZ 748
+SWEDEN SE SWE 752
+SWITZERLAND CH CHE 756
+SYRIAN ARAB REPUBLIC SY SYR 760
+TAIWAN, PROVINCE OF CHINA TW TWN 158
+TAJIKISTAN TJ TJK 762
+TANZANIA, UNITED REPUBLIC OF TZ TZA 834
+THAILAND TH THA 764
+TOGO TG TGO 768
+TOKELAU TK TKL 772
+TONGA TO TON 776
+TRINIDAD AND TOBAGO TT TTO 780
+TUNISIA TN TUN 788
+TURKEY TR TUR 792
+TURKMENISTAN TM TKM 795
+TURKS AND CAICOS ISLANDS TC TCA 796
+TUVALU TV TUV 798
+UGANDA UG UGA 800
+UKRAINE UA UKR 804
+UNITED ARAB EMIRATES AE ARE 784
+UNITED KINGDOM GB GBR 826
+UNITED STATES US USA 840
+UNITED STATES MINOR OUTLYING ISLANDS UM UMI 581
+URUGUAY UY URY 858
+UZBEKISTAN UZ UZB 860
+VANUATU VU VUT 548
+VATICAN CITY STATE (HOLY SEE) VA VAT 336
+VENEZUELA VE VEN 862
+VIET NAM VN VNM 704
+VIRGIN ISLANDS (BRITISH) VG VGB 092
+VIRGIN ISLANDS (U.S.) VI VIR 850
+WALLIS AND FUTUNA ISLANDS WF WLF 876
+WESTERN SAHARA EH ESH 732
+YEMEN YE YEM 887
+YUGOSLAVIA YU YUG 891
+ZAIRE ZR ZAR 180
+ZAMBIA ZM ZMB 894
+ZIMBABWE ZW ZWE 716
diff --git a/etc/domain-template.txt b/etc/domain-template.txt
new file mode 100644
index 0000000..8e4983c
--- /dev/null
+++ b/etc/domain-template.txt
@@ -0,0 +1,231 @@
+[ URL ftp://rs.internic.net/templates/domain-template.txt ] [ 03/98 ]
+
+******* Please DO NOT REMOVE Version Number or Sections A-Q ********
+
+Domain Version Number: 4.0
+
+******* Email completed agreement to hostmaster@internic.net *******
+
+ NETWORK SOLUTIONS, INC.
+
+ DOMAIN NAME REGISTRATION AGREEMENT
+
+
+A. Introduction. This domain name registration agreement
+("Registration Agreement") is submitted to NETWORK SOLUTIONS, INC.
+("NSI") for the purpose of applying for and registering a domain name
+on the Internet. If this Registration Agreement is accepted by NSI,
+and a domain name is registered in NSI's domain name database and
+assigned to the Registrant, Registrant ("Registrant") agrees to be
+bound by the terms of this Registration Agreement and the terms of
+NSI's Domain Name Dispute Policy ("Dispute Policy") which is
+incorporated herein by reference and made a part of this Registration
+Agreement. This Registration Agreement shall be accepted at the
+offices of NSI.
+
+B. Fees and Payments.
+
+1) Registration or renewal (re-registration) date through March 31, 1998:
+Registrant agrees to pay a registration fee of One Hundred United States
+Dollars (US$100) as consideration for the registration of each new domain
+name or Fifty United States Dollars (US$50) to renew (re-register) an
+existing registration.
+2) Registration or renewal date on and after April 1, 1998: Registrant
+agrees to pay a registration fee of Seventy United States Dollars (US$70)
+as consideration for the registration of each new domain name or the
+applicable renewal (re-registration) fee (currently Thirty-Five United
+States Dollars (US$35)) at the time of renewal (re-registration).
+3) Period of Service: The non-refundable fee covers a period of two (2)
+years for each new registration, and one (1) year for each renewal,
+and includes any permitted modification(s) to the domain name record
+during the covered period.
+4) Payment: Payment is due to Network Solutions within thirty (30)
+days from the date of the invoice.
+
+C. Dispute Policy. Registrant agrees, as a condition to
+submitting this Registration Agreement, and if the Registration
+Agreement is accepted by NSI, that the Registrant shall be bound by
+NSI's current Dispute Policy. The current version of the Dispute
+Policy may be found at the InterNIC Registration Services web site:
+"http://www.netsol.com/rs/dispute-policy.html".
+
+D. Dispute Policy Changes or Modifications. Registrant agrees
+that NSI, in its sole discretion, may change or modify the Dispute
+Policy, incorporated by reference herein, at any time. Registrant
+agrees that Registrant's maintaining the registration of a domain name
+after changes or modifications to the Dispute Policy become effective
+constitutes Registrant's continued acceptance of these changes or
+modifications. Registrant agrees that if Registrant considers any such
+changes or modifications to be unacceptable, Registrant may request
+that the domain name be deleted from the domain name database.
+
+E. Disputes. Registrant agrees that, if the registration of its
+domain name is challenged by any third party, the Registrant will be
+subject to the provisions specified in the Dispute Policy.
+
+F. Agents. Registrant agrees that if this Registration Agreement
+is completed by an agent for the Registrant, such as an ISP or
+Administrative Contact/Agent, the Registrant is nonetheless bound as a
+principal by all terms and conditions herein, including the Dispute
+Policy.
+
+G. Limitation of Liability. Registrant agrees that NSI shall have
+no liability to the Registrant for any loss Registrant may incur in
+connection with NSI's processing of this Registration Agreement, in
+connection with NSI's processing of any authorized modification to the
+domain name's record during the covered period, as a result of the
+Registrant's ISP's failure to pay either the initial registration fee
+or renewal fee, or as a result of the application of the provisions of
+the Dispute Policy. Registrant agrees that in no event shall the
+maximum liability of NSI under this Agreement for any matter exceed
+Five Hundred United States Dollars (US$500).
+
+H. Indemnity. Registrant agrees, in the event the Registration
+Agreement is accepted by NSI and a subsequent dispute arises with any
+third party, to indemnify and hold NSI harmless pursuant to the terms
+and conditions contained in the Dispute Policy.
+
+I. Breach. Registrant agrees that failure to abide by any
+provision of this Registration Agreement or the Dispute Policy may be
+considered by NSI to be a material breach and that NSI may provide a
+written notice, describing the breach, to the Registrant. If, within
+thirty (30) days of the date of mailing such notice, the Registrant
+fails to provide evidence, which is reasonably satisfactory to NSI,
+that it has not breached its obligations, then NSI may delete
+Registrant's registration of the domain name. Any such breach by a
+Registrant shall not be deemed to be excused simply because NSI did
+not act earlier in response to that, or any other, breach by the
+Registrant.
+
+J. No Guaranty. Registrant agrees that, by registration of a
+domain name, such registration does not confer immunity from objection
+to either the registration or use of the domain name.
+
+K. Warranty. Registrant warrants by submitting this Registration
+Agreement that, to the best of Registrant's knowledge and belief, the
+information submitted herein is true and correct, and that any future
+changes to this information will be provided to NSI in a timely manner
+according to the domain name modification procedures in place at that
+time. Breach of this warranty will constitute a material breach.
+
+L. Revocation. Registrant agrees that NSI may delete a
+Registrant's domain name if this Registration Agreement, or subsequent
+modification(s) thereto, contains false or misleading information, or
+conceals or omits any information NSI would likely consider material
+to its decision to approve this Registration Agreement.
+
+M. Right of Refusal. NSI, in its sole discretion, reserves the
+right to refuse to approve the Registration Agreement for any
+Registrant. Registrant agrees that the submission of this Registration
+Agreement does not obligate NSI to accept this Registration Agreement.
+Registrant agrees that NSI shall not be liable for loss or damages
+that may result from NSI's refusal to accept this Registration
+Agreement.
+
+N. Severability. Registrant agrees that the terms of this
+Registration Agreement are severable. If any term or provision is
+declared invalid, it shall not affect the remaining terms or
+provisions which shall continue to be binding.
+
+O. Entirety. Registrant agrees that this Registration Agreement
+and the Dispute Policy is the complete and exclusive agreement between
+Registrant and NSI regarding the registration of Registrant's domain
+name. This Registration Agreement and the Dispute Policy supersede all
+prior agreements and understandings, whether established by custom,
+practice, policy, or precedent.
+
+P. Governing Law. Registrant agrees that this Registration
+Agreement shall be governed in all respects by and construed in
+accordance with the laws of the Commonwealth of Virginia, United
+States of America. By submitting this Registration Agreement,
+Registrant consents to the exclusive jurisdiction and venue of the
+United States District Court for the Eastern District of Virginia,
+Alexandria Division. If there is no jurisdiction in the United States
+District Court for the Eastern District of Virginia, Alexandria
+Division, then jurisdiction shall be in the Circuit Court of Fairfax
+County, Fairfax, Virginia.
+
+Q. This is Domain Name Registration Agreement Version
+Number 4.0. This Registration Agreement is only for registrations
+under top-level domains: COM, ORG, NET, and EDU. By completing
+and submitting this Registration Agreement for consideration and
+acceptance by NSI, the Registrant agrees that he/she has read and
+agrees to be bound by A through P above.
+
+
+Authorization
+0a. (N)ew (M)odify (D)elete....:###action###
+0b. Auth Scheme................:
+0c. Auth Info..................:
+
+1. Comments...................:###purpose###
+
+2. Complete Domain Name.......:###domain###
+
+Organization Using Domain Name
+
+3a. Organization Name..........:###company###
+###LOOP###
+3b. Street Address.............:###address###
+###ENDLOOP###
+3c. City.......................:###city###
+3d. State......................:###state###
+3e. Postal Code................:###zip###
+3f. Country....................:###country###
+
+Administrative Contact
+4a. NIC Handle (if known)......:
+4b. (I)ndividual (R)ole........:I
+4c. Name (Last, First).........:###last###, ###first###
+4d. Organization Name..........:###company###
+###LOOP###
+4e. Street Address.............:###address###
+###ENDLOOP###
+4f. City.......................:###city###
+4g. State......................:###state###
+4h. Postal Code................:###zip###
+4i. Country....................:###country###
+4j. Phone Number...............:###daytime###
+4k. Fax Number.................:###fax###
+4l. E-Mailbox..................:###email###
+
+Technical Contact
+5a. NIC Handle (if known)......:###tech_contact###
+5b. (I)ndividual (R)ole........:
+5c. Name (Last, First).........:
+5d. Organization Name..........:
+5e. Street Address.............:
+5f. City.......................:
+5g. State......................:
+5h. Postal Code................:
+5i. Country....................:
+5j. Phone Number...............:
+5k. Fax Number.................:
+5l. E-Mailbox..................:
+
+Billing Contact
+6a. NIC Handle (if known)......:
+6b. (I)ndividual (R)ole........:
+6c. Name (Last, First).........:
+6d. Organization Name..........:
+6e. Street Address.............:
+6f. City.......................:
+6g. State......................:
+6h. Postal Code................:
+6i. Country....................:
+6j. Phone Number...............:
+6k. Fax Number.................:
+6l. E-Mailbox..................:
+
+Prime Name Server
+7a. Primary Server Hostname....:###primary###
+7b. Primary Server Netaddress..:###primary_ip###
+
+Secondary Name Server(s)
+###LOOP###
+8a. Secondary Server Hostname..:###secondary###
+8b. Secondary Server Netaddress:###secondary_ip###
+###ENDLOOP###
+
+END OF AGREEMENT
+
diff --git a/etc/megapop.pl b/etc/megapop.pl
new file mode 100755
index 0000000..b250bcd
--- /dev/null
+++ b/etc/megapop.pl
@@ -0,0 +1,116 @@
+#!/usr/bin/perl -Tw
+#
+# $Id: megapop.pl,v 1.1 1999-04-19 10:32:44 ivan Exp $
+#
+# this will break when megapop changes the URL or format of their listing page.
+# that's stupid. perhaps they can provide a machine-readable listing?
+
+use strict;
+use LWP::UserAgent;
+use FS::UID qw(adminsuidsetup);
+use FS::svc_acct_pop;
+
+my $url = "http://www.megapop.com/location.htm";
+
+my $user = shift or die &usage;
+adminsuidsetup($user);
+
+my %state2usps = &state2usps;
+$state2usps{'WASHINGTON STATE'} = 'WA'; #megapop's on crack
+$state2usps{'CANADA'} = 'CANADA'; #freeside's on crack
+
+my $ua = new LWP::UserAgent;
+my $request = new HTTP::Request('GET', $url);
+my $response = $ua->request($request);
+die $response->error_as_HTML unless $response->is_success;
+my $line;
+my $usps = '';
+foreach $line ( split("\n", $response->content) ) {
+ if ( $line =~ /\W(\w[\w\s]*\w)\s+LOCATIONS/i ) {
+ $usps = $state2usps{uc($1)}
+ or warn "warning: unknown state $1\n";
+ } elsif ( $line =~ /(\d{3})\-(\d{3})\-(\d{4})\s+(\w[\w\s]*\w)/ ) {
+ print "$1 $2 $3 $4 $usps\n";
+ my $svc_acct_pop = new FS::svc_acct_pop ( {
+ 'city' => $4,
+ 'state' => $usps,
+ 'ac' => $1,
+ 'exch' => $2,
+ } );
+ my $error = $svc_acct_pop->insert;
+ die $error if $error;
+ }
+}
+
+sub usage {
+ die "Usage:\n $0 user\n";
+}
+
+sub state2usps{ (
+ 'ALABAMA' => 'AL',
+ 'ALASKA' => 'AK',
+ 'AMERICAN SAMOA' => 'AS',
+ 'ARIZONA' => 'AZ',
+ 'ARKANSAS' => 'AR',
+ 'CALIFORNIA' => 'CA',
+ 'COLORADO' => 'CO',
+ 'CONNECTICUT' => 'CT',
+ 'DELAWARE' => 'DE',
+ 'DISTRICT OF COLUMBIA' => 'DC',
+ 'FEDERATED STATES OF MICRONESIA' => 'FM',
+ 'FLORIDA' => 'FL',
+ 'GEORGIA' => 'GA',
+ 'GUAM' => 'GU',
+ 'HAWAII' => 'HI',
+ 'IDAHO' => 'ID',
+ 'ILLINOIS' => 'IL',
+ 'INDIANA' => 'IN',
+ 'IOWA' => 'IA',
+ 'KANSAS' => 'KS',
+ 'KENTUCKY' => 'KY',
+ 'LOUISIANA' => 'LA',
+ 'MAINE' => 'ME',
+ 'MARSHALL ISLANDS' => 'MH',
+ 'MARYLAND' => 'MD',
+ 'MASSACHUSETTS' => 'MA',
+ 'MICHIGAN' => 'MI',
+ 'MINNESOTA' => 'MN',
+ 'MISSISSIPPI' => 'MS',
+ 'MISSOURI' => 'MO',
+ 'MONTANA' => 'MT',
+ 'NEBRASKA' => 'NE',
+ 'NEVADA' => 'NV',
+ 'NEW HAMPSHIRE' => 'NH',
+ 'NEW JERSEY' => 'NJ',
+ 'NEW MEXICO' => 'NM',
+ 'NEW YORK' => 'NY',
+ 'NORTH CAROLINA' => 'NC',
+ 'NORTH DAKOTA' => 'ND',
+ 'NORTHERN MARIANA ISLANDS' => 'MP',
+ 'OHIO' => 'OH',
+ 'OKLAHOMA' => 'OK',
+ 'OREGON' => 'OR',
+ 'PALAU' => 'PW',
+ 'PENNSYLVANIA' => 'PA',
+ 'PUERTO RICO' => 'PR',
+ 'RHODE ISLAND' => 'RI',
+ 'SOUTH CAROLINA' => 'SC',
+ 'SOUTH DAKOTA' => 'SD',
+ 'TENNESSEE' => 'TN',
+ 'TEXAS' => 'TX',
+ 'UTAH' => 'UT',
+ 'VERMONT' => 'VT',
+ 'VIRGIN ISLANDS' => 'VI',
+ 'VIRGINIA' => 'VA',
+ 'WASHINGTON' => 'WA',
+ 'WEST VIRGINIA' => 'WV',
+ 'WISCONSIN' => 'WI',
+ 'WYOMING' => 'WY',
+ 'ARMED FORCES AFRICA' => 'AE',
+ 'ARMED FORCES AMERICAS' => 'AA',
+ 'ARMED FORCES CANADA' => 'AE',
+ 'ARMED FORCES EUROPE' => 'AE',
+ 'ARMED FORCES MIDDLE EAST' => 'AE',
+ 'ARMED FORCES PACIFIC' => 'AP',
+) }
+
diff --git a/etc/sql-reserved-words.txt b/etc/sql-reserved-words.txt
new file mode 100644
index 0000000..dc507ce
--- /dev/null
+++ b/etc/sql-reserved-words.txt
@@ -0,0 +1,103 @@
+From http://epoch.cs.berkeley.edu:8000/sequoia/dba/montage/FAQ/SQL.html
+ by Jean Anderson (jta@postgres.berkeley.edu)
+
+What are the SQL reserved words?
+
+I grep'd the following list out of the sql docs available via anonymous ftp to speckle.ncsl.nist.gov:/isowg3.
+SQL3 words are not set in stone, but you'd do well to avoid them.
+
+ From sql1992.txt:
+
+ AFTER, ALIAS, ASYNC, BEFORE, BOOLEAN, BREADTH,
+ COMPLETION, CALL, CYCLE, DATA, DEPTH, DICTIONARY, EACH, ELSEIF,
+ EQUALS, GENERAL, IF, IGNORE, LEAVE, LESS, LIMIT, LOOP, MODIFY,
+ NEW, NONE, OBJECT, OFF, OID, OLD, OPERATION, OPERATORS, OTHERS,
+ PARAMETERS, PENDANT, PREORDER, PRIVATE, PROTECTED, RECURSIVE, REF,
+ REFERENCING, REPLACE, RESIGNAL, RETURN, RETURNS, ROLE, ROUTINE,
+ ROW, SAVEPOINT, SEARCH, SENSITIVE, SEQUENCE, SIGNAL, SIMILAR,
+ SQLEXCEPTION, SQLWARNING, STRUCTURE, TEST, THERE, TRIGGER, TYPE,
+ UNDER, VARIABLE, VIRTUAL, VISIBLE, WAIT, WHILE, WITHOUT
+
+ From sql1992.txt (Annex E):
+
+ ABSOLUTE, ACTION, ADD, ALLOCATE, ALTER, ARE, ASSERTION, AT, BETWEEN,
+ BIT, BIT
+
+What are the SQL reserved words?
+
+I grep'd the following list out of the sql docs available via anonymous ftp to speckle.ncsl.nist.gov:/isowg3.
+SQL3 words are not set in stone, but you'd do well to avoid them.
+
+ From sql1992.txt:
+
+ AFTER, ALIAS, ASYNC, BEFORE, BOOLEAN, BREADTH,
+ COMPLETION, CALL, CYCLE, DATA, DEPTH, DICTIONARY, EACH, ELSEIF,
+ EQUALS, GENERAL, IF, IGNORE, LEAVE, LESS, LIMIT, LOOP, MODIFY,
+ NEW, NONE, OBJECT, OFF, OID, OLD, OPERATION, OPERATORS, OTHERS,
+ PARAMETERS, PENDANT, PREORDER, PRIVATE, PROTECTED, RECURSIVE, REF,
+ REFERENCING, REPLACE, RESIGNAL, RETURN, RETURNS, ROLE, ROUTINE,
+ ROW, SAVEPOINT, SEARCH, SENSITIVE, SEQUENCE, SIGNAL, SIMILAR,
+ SQLEXCEPTION, SQLWARNING, STRUCTURE, TEST, THERE, TRIGGER, TYPE,
+ UNDER, VARIABLE, VIRTUAL, VISIBLE, WAIT, WHILE, WITHOUT
+
+ From sql1992.txt (Annex E):
+
+ ABSOLUTE, ACTION, ADD, ALLOCATE, ALTER, ARE, ASSERTION, AT, BETWEEN,
+ BIT, BIT
+
+What are the SQL reserved words?
+
+I grep'd the following list out of the sql docs available via anonymous ftp to speckle.ncsl.nist.gov:/isowg3.
+SQL3 words are not set in stone, but you'd do well to avoid them.
+
+ From sql1992.txt:
+
+ AFTER, ALIAS, ASYNC, BEFORE, BOOLEAN, BREADTH,
+ COMPLETION, CALL, CYCLE, DATA, DEPTH, DICTIONARY, EACH, ELSEIF,
+ EQUALS, GENERAL, IF, IGNORE, LEAVE, LESS, LIMIT, LOOP, MODIFY,
+ NEW, NONE, OBJECT, OFF, OID, OLD, OPERATION, OPERATORS, OTHERS,
+ PARAMETERS, PENDANT, PREORDER, PRIVATE, PROTECTED, RECURSIVE, REF,
+ REFERENCING, REPLACE, RESIGNAL, RETURN, RETURNS, ROLE, ROUTINE,
+ ROW, SAVEPOINT, SEARCH, SENSITIVE, SEQUENCE, SIGNAL, SIMILAR,
+ SQLEXCEPTION, SQLWARNING, STRUCTURE, TEST, THERE, TRIGGER, TYPE,
+ UNDER, VARIABLE, VIRTUAL, VISIBLE, WAIT, WHILE, WITHOUT
+
+ From sql1992.txt (Annex E):
+
+ ABSOLUTE, ACTION, ADD, ALLOCATE, ALTER, ARE, ASSERTION, AT, BETWEEN,
+ BIT, BIT_LENGTH, BOTH, CASCADE, CASCADED, CASE, CAST, CATALOG,
+ CHAR_LENGTH, CHARACTER_LENGTH, COALESCE, COLLATE, COLLATION, COLUMN,
+ CONNECT, CONNECTION, CONSTRAINT, CONSTRAINTS, CONVERT, CORRESPONDING,
+ CROSS, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER,
+ DATE, DAY, DEALLOCATE, DEFERRABLE, DEFERRED, DESCRIBE, DESCRIPTOR,
+ DIAGNOSTICS, DISCONNECT, DOMAIN, DROP, ELSE, END-EXEC, EXCEPT,
+ EXCEPTION, EXECUTE, EXTERNAL, EXTRACT, FALSE, FIRST, FULL, GET,
+ GLOBAL, HOUR, IDENTITY, IMMEDIATE, INITIALLY, INNER, INPUT,
+ INSENSITIVE, INTERSECT, INTERVAL, ISOLATION, JOIN, LAST, LEADING,
+ LEFT, LEVEL, LOCAL, LOWER, MATCH, MINUTE, MONTH, NAMES, NATIONAL,
+ NATURAL, NCHAR, NEXT, NO, NULLIF, OCTET_LENGTH, ONLY, OUTER, OUTPUT,
+ OVERLAPS, PAD, PARTIAL, POSITION, PREPARE, PRESERVE, PRIOR, READ,
+ RELATIVE, RESTRICT, REVOKE, RIGHT, ROWS, SCROLL, SECOND, SESSION,
+ SESSION_USER, SIZE, SPACE, SQLSTATE, SUBSTRING, SYSTEM_USER,
+ TEMPORARY, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE,
+ TRAILING, TRANSACTION, TRANSLATE, TRANSLATION, TRIM, TRUE, UNKNOWN,
+ UPPER, USAGE, USING, VALUE, VARCHAR, VARYING, WHEN, WRITE, YEAR, ZONE
+
+ From sql3part2.txt (Annex E)
+
+ ACTION, ACTOR, AFTER, ALIAS, ASYNC, ATTRIBUTES, BEFORE, BOOLEAN,
+ BREADTH, COMPLETION, CURRENT_PATH, CYCLE, DATA, DEPTH, DESTROY,
+ DICTIONARY, EACH, ELEMENT, ELSEIF, EQUALS, FACTOR, GENERAL, HOLD,
+ IGNORE, INSTEAD, LESS, LIMIT, LIST, MODIFY, NEW, NEW_TABLE, NO,
+ NONE, OFF, OID, OLD, OLD_TABLE, OPERATION, OPERATOR, OPERATORS,
+ PARAMETERS, PATH, PENDANT, POSTFIX, PREFIX, PREORDER, PRIVATE,
+ PROTECTED, RECURSIVE, REFERENCING, REPLACE, ROLE, ROUTINE, ROW,
+ SAVEPOINT, SEARCH, SENSITIVE, SEQUENCE, SESSION, SIMILAR, SPACE,
+ SQLEXCEPTION, SQLWARNING, START, STATE, STRUCTURE, SYMBOL, TERM,
+ TEST, THERE, TRIGGER, TYPE, UNDER, VARIABLE, VIRTUAL, VISIBLE,
+ WAIT, WITHOUT
+
+ sql3part4.txt (ANNEX E):
+
+ CALL, DO, ELSEIF, EXCEPTION, IF, LEAVE, LOOP, OTHERS, RESIGNAL,
+ RETURN, RETURNS, SIGNAL, TUPLE, WHILE
diff --git a/fs_passwd/fs_passwd b/fs_passwd/fs_passwd
new file mode 100755
index 0000000..feddb46
--- /dev/null
+++ b/fs_passwd/fs_passwd
@@ -0,0 +1,131 @@
+#!/usr/bin/perl -Tw
+#
+# fs_passwd
+#
+# portions of this script are copied from the `passwd' script in the original
+# (perl 4) camel book, now archived at
+# http://www.perl.com/CPAN/scripts/nutshell/ch6/passwd
+#
+# ivan@sisd.com 98-mar-8
+#
+# password lengths 0,255 instead of 6,8 - we'll let the server process
+# check the data ivan@sisd.com 98-jul-17
+#
+# updated for the exciting new world of self-service 2004-mar-10
+
+use strict;
+use Getopt::Std;
+use FS::SelfService qw(passwd);
+use vars qw($opt_f $opt_s);
+
+my($freeside_uid)=scalar(getpwnam('freeside'));
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+$SIG{__DIE__}= sub { system '/bin/stty', 'echo'; };
+
+die "passwd program isn't running setuid to freeside\n" if $> != $freeside_uid;
+
+unshift @ARGV, "-f" if $0 =~ /chfn$/;
+unshift @ARGV, "-s" if $0 =~ /chsh$/;
+
+getopts('fs');
+
+my($me)='';
+if ( $_ = shift(@ARGV) ) {
+ /^(\w{2,8})$/;
+ $me = $1;
+}
+die "You can't change the password for $me." if $me && $<;
+$me = (getpwuid($<))[0] unless $me;
+
+my($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell)=
+ getpwnam $me;
+
+my($old_password,$new_password,$new_gecos,$new_shell);
+
+if ( $opt_f || $opt_s ) {
+ system '/bin/stty', '-echo';
+ print "Password:";
+ $old_password=<STDIN>;
+ system '/bin/stty', 'echo';
+ chop($old_password);
+ #$old_password =~ /^(.{6,8})$/ or die "\nIllegal password.\n";
+ $old_password =~ /^(.{0,255})$/ or die "\nIllegal password.\n";
+ $old_password = $1;
+
+ $new_password = '';
+
+ if ( $opt_f ) {
+ print "\nChanging gecos for $me.\n";
+ print "Gecos [", $gcos, "]: ";
+ $new_gecos=<STDIN>;
+ chop($new_gecos);
+ $new_gecos ||= $gcos;
+ $new_gecos =~ /^(.{0,255})$/ or die "\nIllegal gecos.\n";
+ } else {
+ $new_gecos = '';
+ }
+
+ if ( $opt_s ) {
+ print "\nChanging shell for $me.\n";
+ print "Shell [", $shell, "]: ";
+ $new_shell=<STDIN>;
+ chop($new_shell);
+ $new_shell ||= $shell;
+ $new_shell =~ /^(.{0,255})$/ or die "\nIllegal shell.\n";
+ } else {
+ $new_shell = '';
+ }
+
+} else {
+
+ print "Changing password for $me.\n";
+ print "Old password:";
+ system '/bin/stty', '-echo';
+ $old_password=<STDIN>;
+ chop $old_password;
+ #$old_password =~ /^(.{6,8})$/ or die "\nIllegal password.\n";
+ $old_password =~ /^(.{0,255})$/ or die "\nIllegal password.\n";
+ $old_password = $1;
+ print "\nEnter the new password (minimum of 6, maximum of 8 characters)\n";
+ print "Please use a combination of upper and lowercase letters and numbers.\n";
+ print "New password:";
+ $new_password=<STDIN>;
+ chop($new_password);
+ #$new_password =~ /^(.{6,8})$/ or die "\nIllegal password.\n";
+ $new_password =~ /^(.{0,255})$/ or die "\nIllegal password.\n";
+ $new_password = $1;
+ print "\nRe-enter new password:";
+ my($check_new_password);
+ $check_new_password=<STDIN>;
+ chop($check_new_password);
+ die "\nThey don't match; try again.\n" unless $check_new_password eq $new_password;
+
+ $new_gecos='';
+ $new_shell='';
+}
+print "\n";
+
+system '/bin/stty', 'echo';
+
+my $rv = passwd(
+ 'username' => $me,
+ 'old_password' => $old_password,
+ 'new_password' => $new_password,
+ 'new_gecos' => $new_gecos,
+ 'new_shell' => $new_shell,
+);
+
+my $error = $rv->{error};
+
+if ($error) {
+ print "\nUpdate error: $error\n";
+} else {
+ print "\nUpdate sucessful.\n";
+}
diff --git a/fs_radlog/fs_radlogd b/fs_radlog/fs_radlogd
new file mode 100755
index 0000000..74c2af3
--- /dev/null
+++ b/fs_radlog/fs_radlogd
@@ -0,0 +1,51 @@
+#!/usr/bin/perl -Tw
+#
+# ivan@sisd.com 98-mar-23
+
+use strict;
+use Date::Parse; #but hopefully not
+
+$|=1;
+
+my($file,$pos)=@_;
+open(FILE,"<$file") or die "Can't open $file: $!";
+seek(FILE,$pos,0) or die "Can't seek: $!";
+
+my($datestr);
+my(%param);
+
+$SIG{'HUP'} = sub { print "EOF\n"; exit; };
+
+while (1) {
+
+ while (<FILE>) {
+ next if /^$/;
+ if ( /^\S/ ) {
+ chop($datestr=$_);
+ undef %param;
+ } else {
+ warn "Unexpected line: $_";
+ }
+ while (<FILE>) {
+ if ( /^$/ ) {
+ #if ( $param{'Acct-Status-Type'} eq 'Stop' ) {
+ print join("\t",
+ tell FILE,
+ %param,
+ ),"\n";
+ #}
+ last;
+ } elsif ( /^\s+([\w\-]+)\s\=\s\"?([\w\.\-]+)\"?\s*$/ ) {
+ $param{$1}=$2;
+ } else {
+ warn "Unexpected line: $_";
+ }
+
+ }
+
+ }
+ sleep 1;
+ seek(FILE,0,1);
+}
+
+
diff --git a/fs_selfadmin/FS-MailAdminServer/MailAdminClient.pm b/fs_selfadmin/FS-MailAdminServer/MailAdminClient.pm
new file mode 100755
index 0000000..46cde4c
--- /dev/null
+++ b/fs_selfadmin/FS-MailAdminServer/MailAdminClient.pm
@@ -0,0 +1,541 @@
+package FS::MailAdminClient;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT_OK $fs_mailadmind_socket);
+use Exporter;
+use Socket;
+use FileHandle;
+use IO::Handle;
+
+$VERSION = '0.01';
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( signup_info authenticate list_packages list_mailboxes delete_mailbox password_mailbox add_mailbox list_forwards list_pkg_forwards delete_forward add_forward new_customer );
+
+$fs_mailadmind_socket = "/usr/local/freeside/fs_mailadmind_socket";
+
+$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+=head1 NAME
+
+FS::MailAdminClient - Freeside mail administration client API
+
+=head1 SYNOPSIS
+
+ use FS::MailAdminClient qw( signup_info list_mailboxes new_customer );
+
+ ( $locales, $packages, $pops ) = signup_info;
+
+ ( $accounts ) = list_mailboxes;
+
+ $error = new_customer ( {
+ 'first' => $first,
+ 'last' => $last,
+ 'ss' => $ss,
+ 'comapny' => $company,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'county' => $county,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'country' => $country,
+ 'daytime' => $daytime,
+ 'night' => $night,
+ 'fax' => $fax,
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ 'paydate' => $paydate,
+ 'payname' => $payname,
+ 'invoicing_list' => $invoicing_list,
+ 'pkgpart' => $pkgpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'popnum' => $popnum,
+ } );
+
+=head1 DESCRIPTION
+
+This module provides an API for a remote mail administration server.
+
+It needs to be run as the freeside user. Because of this, the program which
+calls these subroutines should be written very carefully.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item signup_info
+
+Returns three array references of hash references.
+
+The first set of hash references is of allowable locales. Each hash reference
+has the following keys:
+ taxnum
+ state
+ county
+ country
+
+The second set of hash references is of allowable packages. Each hash
+reference has the following keys:
+ pkgpart
+ pkg
+
+The third set of hash references is of allowable POPs (Points Of Presence).
+Each hash reference has the following keys:
+ popnum
+ city
+ state
+ ac
+ exch
+
+=cut
+
+sub signup_info {
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "signup_info\n";
+ SOCK->flush;
+
+ chop ( my $n_cust_main_county = <SOCK> );
+ my @cust_main_county = map {
+ chop ( my $taxnum = <SOCK> );
+ chop ( my $state = <SOCK> );
+ chop ( my $county = <SOCK> );
+ chop ( my $country = <SOCK> );
+ {
+ 'taxnum' => $taxnum,
+ 'state' => $state,
+ 'county' => $county,
+ 'country' => $country,
+ };
+ } 1 .. $n_cust_main_county;
+
+ chop ( my $n_part_pkg = <SOCK> );
+ my @part_pkg = map {
+ chop ( my $pkgpart = <SOCK> );
+ chop ( my $pkg = <SOCK> );
+ {
+ 'pkgpart' => $pkgpart,
+ 'pkg' => $pkg,
+ };
+ } 1 .. $n_part_pkg;
+
+ chop ( my $n_svc_acct_pop = <SOCK> );
+ my @svc_acct_pop = map {
+ chop ( my $popnum = <SOCK> );
+ chop ( my $city = <SOCK> );
+ chop ( my $state = <SOCK> );
+ chop ( my $ac = <SOCK> );
+ chop ( my $exch = <SOCK> );
+ chop ( my $loc = <SOCK> );
+ {
+ 'popnum' => $popnum,
+ 'city' => $city,
+ 'state' => $state,
+ 'ac' => $ac,
+ 'exch' => $exch,
+ 'loc' => $loc,
+ };
+ } 1 .. $n_svc_acct_pop;
+
+ close SOCK;
+
+ \@cust_main_county, \@part_pkg, \@svc_acct_pop;
+}
+
+=item authenticate
+
+Authentictes against a service on the remote Freeside system. Requires a hash
+reference as a parameter with the following keys:
+ authuser
+ _password
+
+Returns a scalar error message of the form "authuser OK|FAILED" or an error
+message.
+
+=cut
+
+sub authenticate {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "authenticate", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser _password
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item list_packages
+
+Returns one array reference of hash references.
+
+The set of hash references is of existing packages. Each hash reference
+has the following keys:
+ pkgnum
+ domain
+ account
+
+=cut
+
+sub list_packages {
+ my $user = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "list_packages\n", $user, "\n";
+ SOCK->flush;
+
+ chop ( my $n_packages = <SOCK> );
+ my @packages = map {
+ chop ( my $pkgnum = <SOCK> );
+ chop ( my $domain = <SOCK> );
+ chop ( my $account = <SOCK> );
+ {
+ 'pkgnum' => $pkgnum,
+ 'domain' => $domain,
+ 'account' => $account,
+ };
+ } 1 .. $n_packages;
+
+ close SOCK;
+
+ \@packages;
+}
+
+=item list_mailboxes
+
+Returns one array references of hash references.
+
+The set of hash references is of existing accounts. Each hash reference
+has the following keys:
+ svcnum
+ username
+ _password
+
+=cut
+
+sub list_mailboxes {
+ my ($user, $package) = @_;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "list_mailboxes\n", $user, "\n", $package, "\n";
+ SOCK->flush;
+
+ chop ( my $n_svc_acct = <SOCK> );
+ my @svc_acct = map {
+ chop ( my $svcnum = <SOCK> );
+ chop ( my $username = <SOCK> );
+ chop ( my $_password = <SOCK> );
+ {
+ 'svcnum' => $svcnum,
+ 'username' => $username,
+ '_password' => $_password,
+ };
+ } 1 .. $n_svc_acct;
+
+ close SOCK;
+
+ \@svc_acct;
+}
+
+=item delete_mailbox
+
+Deletes a mailbox service from the remote Freeside system. Requires a hash
+reference as a paramater with the following keys:
+ authuser
+ account
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub delete_mailbox {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "delete_mailbox", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser account
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item password_mailbox
+
+Changes the password for a mailbox service on the remote Freeside system.
+ Requires a hash reference as a paramater with the following keys:
+ authuser
+ account
+ _password
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub password_mailbox {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "password_mailbox", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser account _password
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item add_mailbox
+
+Creates a mailbox service on the remote Freeside system. Requires a hash
+reference as a parameter with the following keys:
+ authuser
+ package
+ account
+ _password
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub add_mailbox {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "add_mailbox", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser package account _password
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item list_forwards
+
+Returns one array references of hash references.
+
+The set of hash references is of existing forwards. Each hash reference
+has the following keys:
+ svcnum
+ dest
+
+=cut
+
+sub list_forwards {
+ my ($user, $service) = @_;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "list_forwards\n", $user, "\n", $service, "\n";
+ SOCK->flush;
+
+ chop ( my $n_svc_forward = <SOCK> );
+ my @svc_forward = map {
+ chop ( my $svcnum = <SOCK> );
+ chop ( my $dest = <SOCK> );
+ {
+ 'svcnum' => $svcnum,
+ 'dest' => $dest,
+ };
+ } 1 .. $n_svc_forward;
+
+ close SOCK;
+
+ \@svc_forward;
+}
+
+=item list_pkg_forwards
+
+Returns one array references of hash references.
+
+The set of hash references is of existing forwards. Each hash reference
+has the following keys:
+ svcnum
+ srcsvc
+ dest
+
+=cut
+
+sub list_pkg_forwards {
+ my ($user, $package) = @_;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "list_pkg_forwards\n", $user, "\n", $package, "\n";
+ SOCK->flush;
+
+ chop ( my $n_svc_forward = <SOCK> );
+ my @svc_forward = map {
+ chop ( my $svcnum = <SOCK> );
+ chop ( my $srcsvc = <SOCK> );
+ chop ( my $dest = <SOCK> );
+ {
+ 'svcnum' => $svcnum,
+ 'srcsvc' => $srcsvc,
+ 'dest' => $dest,
+ };
+ } 1 .. $n_svc_forward;
+
+ close SOCK;
+
+ \@svc_forward;
+}
+
+=item delete_forward
+
+Deletes a forward service from the remote Freeside system. Requires a hash
+reference as a paramater with the following keys:
+ authuser
+ svcnum
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub delete_forward {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "delete_forward", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser svcnum
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item add_forward
+
+Creates a forward service on the remote Freeside system. Requires a hash
+reference as a parameter with the following keys:
+ authuser
+ package
+ source
+ dest
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub add_forward {
+ my $hashref = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "add_forward", "\n";
+ SOCK->flush;
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ authuser package source dest
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ close SOCK;
+
+ $error;
+}
+
+=item new_customer HASHREF
+
+Adds a customer to the remote Freeside system. Requires a hash reference as
+a paramater with the following keys:
+ first
+ last
+ ss
+ comapny
+ address1
+ address2
+ city
+ county
+ state
+ zip
+ country
+ daytime
+ night
+ fax
+ payby
+ payinfo
+ paydate
+ payname
+ invoicing_list
+ pkgpart
+ username
+ _password
+ popnum
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub new_customer {
+ my $hashref = shift;
+
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+ print SOCK "new_customer\n";
+
+ print SOCK join("\n", map { $hashref->{$_} } qw(
+ first last ss company address1 address2 city county state zip country
+ daytime night fax payby payinfo paydate payname invoicing_list
+ pkgpart username _password popnum
+ ) ), "\n";
+ SOCK->flush;
+
+ chop( my $error = <SOCK> );
+ $error;
+}
+
+=back
+
+=head1 VERSION
+
+$Id: MailAdminClient.pm,v 1.1 2001-10-18 15:04:54 jeff Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<fs_signupd>, L<FS::SignupServer>, L<FS::cust_main>
+
+=cut
+
+1;
+
diff --git a/fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi b/fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi
new file mode 100755
index 0000000..c26c3dc
--- /dev/null
+++ b/fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi
@@ -0,0 +1,698 @@
+#!/usr/bin/perl
+########################################################################
+# #
+# mailadmin.cgi NCI2000 #
+# Jeff Finucane <jeff@nci2000.net> #
+# 26 April 2001 #
+# #
+########################################################################
+
+use DBI;
+use strict;
+use CGI;
+use FS::MailAdminClient qw(authenticate list_packages list_mailboxes delete_mailbox password_mailbox add_mailbox list_forwards list_pkg_forwards delete_forward add_forward);
+
+my $sessionfile = '/usr/local/apache/htdocs/mailadmin/adminsess'; # session file
+my $tmpdir = '/usr/local/apache/htdocs/mailadmin/tmp'; # Location to store temp files
+my $cookiedomain = ".your.dom"; # domain if THIS server, should prepend with a '.'
+my $cookieexpire = '+12h'; # expire the cookie session after this much idle time
+my $sessexpire = 43200; # expire session after this long of no use (in seconds)
+
+my $body = "<body bgcolor=dddddd>";
+
+#### Should not have to change anything under this line ####
+my $printmainpage = 1;
+my $i = 0;
+my $printheader = 1;
+my $query = new CGI;
+my $cgi = $query->url();
+my $now = getdatetime();
+my $current_package = 0;
+my $current_account = 0;
+my $current_domname = "";
+
+# if they are trying to login we wont check the session yet
+if ($query->param('login') eq '' && $query->param('action') ne 'login') {
+ checksession();
+ printheader();
+}
+
+if ($query->param('login') ne '') {
+
+ my $username = $query->param('username');
+ my $password = $query->param('password');
+
+ if (!checkuserpass($username, $password)) {
+ printheader();
+ error('not_admin');
+ }
+
+ my @alpha = ('A'..'Z', 'a'..'z', 0..9);
+ my $sessid = '';
+ for (my $i = 0; $i < 10; $i++) {
+ $sessid .= @alpha[rand(@alpha)];
+ }
+
+ my $cookie1 = $query->cookie(-name=>'username',
+ -value=>$username,
+ -expires=>$cookieexpire,
+ -domain=>$cookiedomain);
+
+ my $cookie2 = $query->cookie(-name=>'ma_sessionid',
+ -value=>$sessid,
+ -expires=>$cookieexpire,
+ -domain=>$cookiedomain);
+
+ my $now = time();
+ open(NEWSESS, ">>$sessionfile") || error('open');
+ print NEWSESS "$username $sessid $now 0 0\n";
+ close(NEWSESS);
+
+ print $query->header(-COOKIE=>[$cookie1, $cookie2]);
+
+ $printmainpage = 1;
+
+} elsif ($query->param('action') eq 'blankframe') {
+
+ print "<html>$body</body></html>\n";
+ $printmainpage = 0;
+
+} elsif ($query->param('action') eq 'list_packages') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ my $list = list_packages($username);
+ print "<html>$body\n";
+ print "<center><table border=0>\n";
+ print "<tr><td></td><td><p>Package Number</td><td><p>Description</td></tr>\n";
+ foreach my $package ( @{$list} ) {
+ print "<tr>";
+ print "<td></td><td><p>$package->{'pkgnum'}</td><td><p>$package->{'domain'}</td>\n";
+ print "<td></td><td><a href=\"$cgi\?action=select&package=$package->{'pkgnum'}&account=$package->{'account'}&domname=$package->{'domain'}\" target=\"rightmainframe\">select</td>\n";
+ print "</tr>";
+ }
+ print "</table>\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'list_mailboxes') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $list = list_mailboxes($username, $current_package);
+ my $forwardlist = list_pkg_forwards($username, $current_package);
+ print "<html>$body\n";
+ print "<center><table border=0>\n";
+ print "<tr><td></td><td><p>Username</td><td><p>Password</td></tr>\n";
+ foreach my $account ( @{$list} ) {
+ print "<tr>";
+ print "<td></td><td><p>$account->{'username'}</td><td><p>$account->{'_password'}</td>\n";
+ print "<td></td><td><a href=\"$cgi\?action=change&account=$account->{'svcnum'}&mailbox=$account->{'username'}\" target=\"rightmainframe\">change</td>\n";
+ print "</tr>";
+
+# my $forwardlist = list_forwards($username, $account->{'svcnum'});
+# foreach my $forward ( @{$forwardlist} ) {
+# my $label = qq!=> ! . $forward->{'dest'};
+# print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+# }
+ foreach my $forward ( @{$forwardlist} ) {
+ if ($forward->{'srcsvc'} == $account->{'svcnum'}) {
+ my $label = qq!=> ! . $forward->{'dest'};
+ print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+ }
+ }
+
+ }
+ print "</table>\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'select') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ $current_package = $query->param('package');
+ $current_account = $query->param('account');
+ $current_domname = $query->param('domname');
+ set_package();
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<center>\n";
+ print "<p>Selected package $current_package\n";
+ print "</center>\n";
+ print "</form>\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'change') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $mailbox = $query->param('mailbox');
+ my $list = list_forwards($username, $account);
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<center><table border=0>\n";
+ print "<tr><td></td><td><p>Username</td><td><p>$mailbox</td></tr>\n";
+ print "<input type=hidden name=\"account\" value=\"$account\">\n";
+ print "<input type=hidden name=\"mailbox\" value=\"$mailbox\">\n";
+ foreach my $forward ( @{$list} ) {
+ my $label = qq!=> ! . $forward->{'dest'};
+# print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+ print "<tr><td></td><td></td><td><p>$label</td><td><a href=\"$cgi\?action=deleteforward&service=$forward->{'svcnum'}&mailbox=$mailbox&dest=$forward->{'dest'}\" target=\"rightmainframe\">remove</td></tr>\n";
+ }
+ print "<tr><td></td><td><p>Password</td><td><input type=text name=\"_password\" value=\"\"></td></tr>\n";
+ print "</table>\n";
+ print "<input type=submit name=\"deleteaccount\" value=\"Delete This User\">\n";
+ print "<input type=submit name=\"changepassword\" value=\"Change The Password\">\n";
+ print "<input type=submit name=\"addforward\" value=\"Add Forwarding\">\n";
+ print "</center>\n";
+ print "</form>\n";
+ print "<br>\n";
+ print "<p> You may delete this user and all mailforwarding by pressing <B>Delete This User</B>.\n";
+ print "<p> To set or change the password for this user, type the new password in the box next to <B>Password</B> and press <B>Change The Password</B>.\n";
+ print "<p> If you would like to have mail destined for this user forwarded to another email address then press the <B>Add Forwarding</B> button.\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('deleteaccount') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $mailbox = $query->param('mailbox');
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<p>Are you certain you want to delete user $mailbox?\n";
+ print "<p><input type=hidden name=\"account\" value=\"$account\">\n";
+ print "<input type=submit name=\"deleteaccounty\" value=\"Confirm\">\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('deleteaccounty') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+
+ if ( my $error = delete_mailbox ( {
+ 'authuser' => $username,
+ 'account' => $account,
+ } ) ) {
+ print "<html>$body\n";
+ print "<p>$error\n";
+ print "</body></html>\n";
+
+ } else {
+ print "<html>$body\n";
+ print "<p>Deleted\n";
+ print "</body></html>\n";
+ }
+
+ $printmainpage=0;
+
+} elsif ($query->param('changepassword') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $_password = $query->param('_password');
+
+ if ( my $error = password_mailbox ( {
+ 'authuser' => $username,
+ 'account' => $account,
+ '_password' => $_password,
+ } ) ) {
+ print "<html>$body\n";
+ print "<p>$error\n";
+ print "</body></html>\n";
+
+ } else {
+ print "<html>$body\n";
+ print "<p>Changed\n";
+ print "</body></html>\n";
+ }
+
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'newmailbox') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<center><table border=0>\n";
+ print "<tr><td></td><td><p>Username </td><td><input type=text name=\"account\" value=\"\"></td><td>@ " . $current_domname . "</td></tr>\n";
+ print "<tr><td></td><td><p>Password</td><td><input type=text name=\"_password\" value=\"\"></td></tr>\n";
+ print "</table>\n";
+ print "<input type=submit name=\"addmailbox\" value=\"Add This User\">\n";
+ print "</center>\n";
+ print "</form>\n";
+ print "<br>\n";
+ print "<p>Use this screen to add a new mailbox user. If the domain name of the email address (the part after the <B>@</B> sign) is not what you expect then you may need to use <B>List Packages</B> to select the package with the correct domain.\n";
+ print "<p>Enter the first portion of the email address in the box adjacent to <B>Username</B> and enter the password for that user in the space next to <B>Password</B>. Then press the button labeled <B>Add The User</B>.\n";
+ print "<p>If you do not want to add a new user at this time then select a choice from the menu at the left, such as <B>List Mailboxes</B>.\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('addmailbox') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $_password = $query->param('_password');
+
+ if ( my $error = add_mailbox ( {
+ 'authuser' => $username,
+ 'package' => $current_package,
+ 'account' => $account,
+ '_password' => $_password,
+ } ) ) {
+ print "<html>$body\n";
+ print "<p>$error\n";
+ print "</body></html>\n";
+
+ } else {
+ print "<html>$body\n";
+ print "<p>Created\n";
+ print "</body></html>\n";
+ }
+
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'deleteforward') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $svcnum = $query->param('service');
+ my $mailbox = $query->param('mailbox');
+ my $dest = $query->param('dest');
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<p>Are you certain you want to remove the forwarding from $mailbox to $dest?\n";
+ print "<p><input type=hidden name=\"service\" value=\"$svcnum\">\n";
+ print "<input type=submit name=\"deleteforwardy\" value=\"Confirm\">\n";
+ print "</body></html>\n";
+ $printmainpage=0;
+
+} elsif ($query->param('deleteforwardy') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $service = $query->param('service');
+
+ if ( my $error = delete_forward ( {
+ 'authuser' => $username,
+ 'svcnum' => $service,
+ } ) ) {
+ print "<html>$body\n";
+ print "<p>$error\n";
+ print "</body></html>\n";
+
+ } else {
+ print "<html>$body\n";
+ print "<p>Forwarding Removed\n";
+ print "</body></html>\n";
+ }
+
+ $printmainpage=0;
+
+} elsif ($query->param('addforward') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $mailbox = $query->param('mailbox');
+
+ print "<html>$body\n";
+ print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+ print "<center><table border=0>\n";
+ print "<input type=hidden name=\"account\" value=\"$account\">\n";
+ print "<input type=hidden name=\"mailbox\" value=\"$mailbox\">\n";
+ print "<tr><td>Forward mail from </td><td><p>$mailbox:</td><td> to </td></tr>\n";
+ print "<tr><td></td><td><p>Destination:</td><td><input type=text name=\"dest\" value=\"\"></td></tr>\n";
+ print "</table>\n";
+ print "<input type=submit name=\"addforwarddst\" value=\"Add the Forwarding\">\n";
+ print "</center>\n";
+ print "</form>\n";
+ print "<br>\n";
+ print "<p> If you would like mail originally destined for the above address to be forwarded to a different email address then type that email address in the box next to <B>Destination:</B> and press the <B>Add the Forwarding</B> button.\n";
+ print "<p> If you do not want to add mail forwarding then select a choice from the menu at the left, such as <B>List Accounts</B>.\n";
+
+ $printmainpage=0;
+
+} elsif ($query->param('addforwarddst') ne '') {
+
+ my $username = $query->cookie(-name=>'username'); # session checked
+ select_package($username) unless $current_package;
+ my $account = $query->param('account');
+ my $dest = $query->param('dest');
+
+ if ( my $error = add_forward ( {
+ 'authuser' => $username,
+ 'package' => $current_package,
+ 'source' => $account,
+ 'dest' => $dest,
+ } ) ) {
+ print "<html>$body\n";
+ print "<p>$error\n";
+ print "</body></html>\n";
+
+ } else {
+ print "<html>$body\n";
+ print "<p>Forwarding Created\n";
+ print "</body></html>\n";
+ }
+
+ $printmainpage=0;
+
+} elsif ($query->param('action') eq 'navframe') {
+
+ print "<html><body bgcolor=bbbbbb>\n";
+ print "<center><h2>NCI2000 MAIL ADMIN Web Interface</h2></center>\n";
+
+ print "<br><center>Choose Action:</center><br>\n";
+ print "<center><table border=0>\n";
+ print "<ul>\n";
+ print "<tr><td><li><a href=\"$cgi\?action=logout\" target=\"_top\">Log Off</a></td><tr>\n";
+ print "<tr><td><li><a href=\"$cgi\?action=list_packages\" target=\"rightmainframe\">List Packages</a></td><tr>\n";
+ print "<tr><td><li><a href=\"$cgi\?action=list_mailboxes\" target=\"rightmainframe\">List Accounts</a></td><tr>\n";
+ print "<tr><td><li><a href=\"$cgi\?action=newmailbox\" target=\"rightmainframe\">Add Account</a></td><tr>\n";
+ print "</ul>\n";
+ print "</table></center>\n";
+
+ print "<br><br><br>\n";
+ print "</body></html>\n";
+
+ $printmainpage = 0;
+
+} elsif ($query->param('action') eq 'rightmainframe') {
+
+ print "<html>$body\n";
+ print "<br><br><br>\n";
+ print "<font size=4><----- Please choose function on the left menu</font>\n";
+ print "<br><br>\n";
+ print "<p> Choose <B>Log Off</B> when you are finished. This helps prevent unauthorized access to your accounts.\n";
+ print "<p> Use <B>List Packages</B> when you administer multiple packages. When you have multiple domains at NCI2000 you are likely to have multiple packages. Use of <B>List Packages</B> is not necessary if administer only one package.\n";
+ print "<p> Use <B>List Accounts</B> to view your current arrangement of mailboxes. From this list you my choose to make changes to existing mailboxes or delete mailboxes. If you would like to modify the forwarding associated with a mailbox then choose it from this list.\n";
+ print "<p> Use <B>Add Account</B> when you would like an additional mailbox. After you have added the mailbox you may choose to make additional changes from the list provided by <B>List Accounts<B>.\n";
+ print "</body></html>\n";
+
+ $printmainpage = 0;
+
+}
+
+
+if ($query->param('action') eq 'login') {
+
+ printheader();
+ printlogin();
+
+} elsif ($query->param('action') eq 'logout') {
+
+ destroysession();
+ printheader();
+ printlogin();
+
+} elsif ($printmainpage) {
+
+
+ print "<html><head><title>NCI2000 MAIL ADMIN Web Interface</title></head>\n";
+ print "<FRAMESET cols=\"160,*\" BORDER=\"3\">\n";
+ print "<FRAME NAME=\"navframe\" src=\"$cgi?action=navframe\">\n";
+ print "<FRAME NAME=\"rightmainframe\" src=\"$cgi?action=rightmainframe\">\n";
+ print "</FRAMESET>\n";
+ print "</html>\n";
+
+
+}
+
+sub getdatetime {
+ my $today = localtime(time());
+ my ($day,$mon,$dayofmon,$time,$year) = split(/\s+/,$today);
+ my @datemonths = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
+
+ my $numidx = "01";
+ my ($nummon);
+ foreach my $mons (@datemonths) {
+ if ($mon eq $mons) {
+ $nummon = $numidx;
+ }
+ $numidx++;
+ }
+
+ return "$year-$nummon-$dayofmon $time";
+
+}
+
+sub error {
+
+ my $error = shift;
+ my $arg1 = shift;
+
+ printheader();
+
+ if ($error eq 'not_admin') {
+ print "<html><head><title>Error!</title></head>\n";
+ print "$body\n";
+ print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+ print "<font face=arial>Unauthorized attempt to access mail administration.</font>\n";
+ print "<br><font face=arial>Please login again if you think this is an error.</font>\n";
+ print "<form><input type=button value=\"<<Back\" OnClick=\"history.back()\"></form>\n";
+ print "</body></html>\n";
+ } elsif ($error eq 'exists') {
+ print "<html><head><title>Error!</title></head>\n";
+ print "$body\n";
+ print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+ print "<font face=arial>The user you are trying to enter already exists. Please go back and enter a different username</font>\n";
+ print "</font></body></html>\n";
+ } elsif ($error eq 'ingroup') {
+ print "<html><head><title>Error!</title></head>\n";
+ print "$body\n";
+ print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+ print "<font face=arial>This user is already in the group <i>$arg1</i>. Please go back and deselect group <i>$arg1</i> from the list.</font>\n";
+ print "<form><input type=button value=\"<<Back\" OnClick=\"history.back()\"></form>\n";
+ print "</font></body></html>\n";
+ } elsif ($error eq 'sess_expired') {
+ print "<html>$body\n";
+ print "<center><font size=4>Your session has expired.</font></center>\n";
+ print "<br><br><center>Please login again <a href=\"$cgi\?action=login\" target=\"_top\"> HERE</a></center>\n";
+ print "</body></html>\n";
+ } elsif ($error eq 'open') {
+ print "<html>$body\n";
+ print "<center><font size=4>Unable to open or rename file.</font></center>\n";
+ print "<br><br><center>If this continues, please contact your administrator</center>\n";
+ print "</body></html>\n";
+ }
+
+
+ exit;
+
+}
+
+
+#print a html header if not printed yet
+sub printheader {
+
+ if ($printheader) {
+ print "Content-Type: text/html\n\n";
+ $printheader = 0;
+ }
+
+}
+
+
+#verify user can access administration
+sub checksession {
+
+ my $username = $query->cookie(-name=>'username');
+ my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+ if ($sessionid eq '') {
+ printheader();
+ if ($query->param()) {
+ error('sess_expired');
+ } else {
+ printlogin();
+ exit;
+ }
+ }
+
+ my $now = time();
+ my $founduser = 0;
+ open(SESSFILE, "$sessionfile") || error('open');
+ error('open') if -l "$tmpdir/adminsess.$$";
+ open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+ while (<SESSFILE>) {
+ chomp();
+ my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+ next if $now - $sessexpire > $time;
+ if ($username eq $user && !$founduser) {
+ if ($sess eq $sessionid) {
+ $founduser = 1;
+ print NEWSESS "$user $sess $now $pkgnum $svcdomain $domname\n";
+ $current_package=$pkgnum;
+ $current_account=$svcdomain;
+ $current_domname=$domname;
+ next;
+ }
+ }
+ print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+ }
+ close(SESSFILE);
+ close(NEWSESS);
+ system("mv $tmpdir/adminsess.$$ $sessionfile");
+ error('sess_expired') unless $founduser;
+
+ my $cookie1 = $query->cookie(-name=>'username',
+ -value=>$username,
+ -expires=>$cookieexpire,
+ -domain=>$cookiedomain);
+
+ my $cookie2 = $query->cookie(-name=>'ma_sessionid',
+ -value=>$sessionid,
+ -expires=>$cookieexpire,
+ -domain=>$cookiedomain);
+
+ print $query->header(-COOKIE=>[$cookie1, $cookie2]);
+
+ $printheader = 0;
+
+ return 0;
+
+}
+
+sub destroysession {
+
+ my $username = $query->cookie(-name=>'username');
+ my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+ if ($sessionid eq '') {
+ printheader();
+ if ($query->param()) {
+ error('sess_expired');
+ } else {
+ printlogin();
+ exit;
+ }
+ }
+
+ my $now = time();
+ my $founduser = 0;
+ open(SESSFILE, "$sessionfile") || error('open');
+ error('open') if -l "$tmpdir/adminsess.$$";
+ open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+ while (<SESSFILE>) {
+ chomp();
+ my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+ next if $now - $sessexpire > $time;
+ if ($username eq $user && !$founduser) {
+ if ($sess eq $sessionid) {
+ $founduser = 1;
+ next;
+ }
+ }
+ print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+ }
+ close(SESSFILE);
+ close(NEWSESS);
+ system("mv $tmpdir/adminsess.$$ $sessionfile");
+ error('sess_expired') unless $founduser;
+
+ $printheader = 0;
+
+ return 0;
+
+}
+
+# checks the username and pass against the database
+sub checkuserpass {
+
+ my $username = shift;
+ my $password = shift;
+
+ my $error = authenticate ( {
+ 'authuser' => $username,
+ '_password' => $password,
+ } );
+
+ if ($error eq "$username OK") {
+ return 1;
+ }else{
+ return 0;
+ }
+
+}
+
+#printlogin prints a login page
+sub printlogin {
+
+ print "<html>$body\n";
+ print "<center><font size=4>Please login to access MAIL ADMIN</font></center>\n";
+ print "<form action=\"$cgi\" method=post>\n";
+ print "<center>Email Address: &nbsp; <input type=text name=\"username\">\n";
+ print "<br>Email Password: <input type=password name=\"password\">\n";
+ print "<br><input type=submit name=\"login\" value=\"Login\">\n";
+ print "</form></center>\n";
+ print "</body></html>\n";
+}
+
+
+#select_package chooses a administrable package if more than one exists
+sub select_package {
+ my $user = shift;
+ my $packages = list_packages($user);
+ if (scalar(@{$packages}) eq 1) {
+ $current_package = @{$packages}[0]->{'pkgnum'};
+ set_package();
+ }
+ if (scalar(@{$packages}) > 1) {
+# print $query->redirect("$cgi\?action=list_packages");
+ print "<p>No package selected. You must first <a href=\"$cgi\?action=list_packages\" target=\"rightmainframe\">select a package</a>.\n";
+ exit;
+ }
+}
+
+sub set_package {
+
+ my $username = $query->cookie(-name=>'username');
+ my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+ if ($sessionid eq '') {
+ printheader();
+ if ($query->param()) {
+ error('sess_expired');
+ } else {
+ printlogin();
+ exit;
+ }
+ }
+
+ my $now = time();
+ my $founduser = 0;
+ open(SESSFILE, "$sessionfile") || error('open');
+ error('open') if -l "$tmpdir/adminsess.$$";
+ open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+ while (<SESSFILE>) {
+ chomp();
+ my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+ next if $now - $sessexpire > $time;
+ if ($username eq $user && !$founduser) {
+ if ($sess eq $sessionid) {
+ $founduser = 1;
+ print NEWSESS "$user $sess $time $current_package $current_account $current_domname\n";
+ next;
+ }
+ }
+ print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+ }
+ close(SESSFILE);
+ close(NEWSESS);
+ system("mv $tmpdir/adminsess.$$ $sessionfile");
+ error('sess_expired') unless $founduser;
+
+ $printheader = 0;
+
+ return 0;
+
+}
+
diff --git a/fs_selfadmin/FS-MailAdminServer/fs_mailadmind b/fs_selfadmin/FS-MailAdminServer/fs_mailadmind
new file mode 100755
index 0000000..746d782
--- /dev/null
+++ b/fs_selfadmin/FS-MailAdminServer/fs_mailadmind
@@ -0,0 +1,366 @@
+#!/usr/bin/perl -Tw
+
+eval 'exec /usr/bin/perl -Tw -S $0 ${1+"$@"}'
+ if 0; # not running under some shell
+#
+# fs_mailadmind
+#
+# This is run REMOTELY over ssh by fs_mailadmin_server.
+#
+
+use strict;
+use Socket;
+
+use vars qw( $Debug );
+
+$Debug = 0;
+
+my($fs_mailadmind_socket)="/usr/local/freeside/fs_mailadmind_socket";
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+$|=1;
+
+warn "[fs_mailadmind] Reading locales...\n" if $Debug;
+chomp( my $n_cust_main_county = <STDIN> );
+my @cust_main_county = map {
+ chomp( my $taxnum = <STDIN> );
+ chomp( my $state = <STDIN> );
+ chomp( my $county = <STDIN> );
+ chomp( my $country = <STDIN> );
+ {
+ 'taxnum' => $taxnum,
+ 'state' => $state,
+ 'county' => $county,
+ 'country' => $country,
+ };
+} ( 1 .. $n_cust_main_county );
+
+warn "[fs_mailadmind] Reading package definitions...\n" if $Debug;
+chomp( my $n_part_pkg = <STDIN> );
+my @part_pkg = map {
+ chomp( my $pkgpart = <STDIN> );
+ chomp( my $pkg = <STDIN> );
+ {
+ 'pkgpart' => $pkgpart,
+ 'pkg' => $pkg,
+ };
+} ( 1 .. $n_part_pkg );
+
+warn "[fs_mailadmind] Reading POPs...\n" if $Debug;
+chomp( my $n_svc_acct_pop = <STDIN> );
+my @svc_acct_pop = map {
+ chomp( my $popnum = <STDIN> );
+ chomp( my $city = <STDIN> );
+ chomp( my $state = <STDIN> );
+ chomp( my $ac = <STDIN> );
+ chomp( my $exch = <STDIN> );
+ chomp( my $loc = <STDIN> );
+ {
+ 'popnum' => $popnum,
+ 'city' => $city,
+ 'state' => $state,
+ 'ac' => $ac,
+ 'exch' => $exch,
+ 'loc' => $loc,
+ };
+} ( 1 .. $n_svc_acct_pop );
+
+warn "[fs_mailadmind] Creating $fs_mailadmind_socket\n" if $Debug;
+my $uaddr = sockaddr_un($fs_mailadmind_socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($fs_mailadmind_socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+warn "[fs_mailadmind] Entering main loop...\n" if $Debug;
+my $paddr;
+for ( ; $paddr = accept(Client,Server); close Client) {
+
+ chop( my $command = <Client> );
+
+ if ( $command eq "signup_info" ) {
+ warn "[fs_mailadmind] sending signup info...\n" if $Debug;
+ print Client join("\n", $n_cust_main_county,
+ map {
+ $_->{taxnum},
+ $_->{state},
+ $_->{county},
+ $_->{country},
+ } @cust_main_county
+ ), "\n";
+
+ print Client join("\n", $n_part_pkg,
+ map {
+ $_->{pkgpart},
+ $_->{pkg},
+ } @part_pkg
+ ), "\n";
+
+ print Client join("\n", $n_svc_acct_pop,
+ map {
+ $_->{popnum},
+ $_->{city},
+ $_->{state},
+ $_->{ac},
+ $_->{exch},
+ $_->{loc},
+ } @svc_acct_pop
+ ), "\n";
+
+ } elsif ( $command eq "new_customer" ) {
+ warn "[fs_mailadmind] reading customer signup...\n" if $Debug;
+ my(
+ $first, $last, $ss, $company, $address1, $address2, $city, $county,
+ $state, $zip, $country, $daytime, $night, $fax, $payby, $payinfo,
+ $paydate, $payname, $invoicing_list, $pkgpart, $username, $password,
+ $popnum,
+ ) = map { scalar(<Client>) } ( 1 .. 23 );
+
+ warn "[fs_mailadmind] sending customer data to remote server...\n" if $Debug;
+ print
+ $first, $last, $ss, $company, $address1, $address2, $city, $county,
+ $state, $zip, $country, $daytime, $night, $fax, $payby, $payinfo,
+ $paydate, $payname, $invoicing_list, $pkgpart, $username, $password,
+ $popnum,
+ ;
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "authenticate" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading authentication material...\n" if $Debug;
+ chop( my $password = <Client> );
+ warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+ print "authenticate\n", $user, "\n", $password, "\n";
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "list_packages" ) {
+ warn "[fs_mailadmind] reading user information to list_packages...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+ print "list_packages\n", $user, "\n";
+
+ warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+ chomp( my $n_packages = <STDIN> );
+ my @packages = map {
+ chomp( my $pkgnum = <STDIN> );
+ chomp( my $domain = <STDIN> );
+ chomp( my $account = <STDIN> );
+ {
+ 'pkgnum' => $pkgnum,
+ 'domain' => $domain,
+ 'account' => $account,
+ };
+ } ( 1 .. $n_packages );
+
+ warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+ print Client join("\n", $n_packages,
+ map {
+ $_->{pkgnum},
+ $_->{domain},
+ $_->{account},
+ } @packages
+ ), "\n";
+
+ } elsif ( $command eq "list_mailboxes" ) {
+ warn "[fs_mailadmind] reading user information to list_mailboxes...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading package number to list_mailboxes...\n" if $Debug;
+ chop( my $package = <Client> );
+ warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+ print "list_mailboxes\n", $user, "\n", $package, "\n";
+
+ warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+ chomp( my $n_svc_acct = <STDIN> );
+ my @svc_acct = map {
+ chomp( my $svcnum = <STDIN> );
+ chomp( my $username = <STDIN> );
+ chomp( my $_password = <STDIN> );
+ {
+ 'svcnum' => $svcnum,
+ 'username' => $username,
+ '_password' => $_password,
+ };
+ } ( 1 .. $n_svc_acct );
+
+ warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+ print Client join("\n", $n_svc_acct,
+ map {
+ $_->{svcnum},
+ $_->{username},
+ $_->{_password},
+ } @svc_acct
+ ), "\n";
+
+ } elsif ( $command eq "delete_mailbox" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading account information to delete...\n" if $Debug;
+ chop( my $account = <Client> );
+ warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+ print "delete_mailbox\n", $user, "\n", $account, "\n";
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "password_mailbox" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading account information to password...\n" if $Debug;
+ my(
+ $account, $_password,
+ ) = map { scalar(<Client>) } ( 1 .. 2 );
+
+ warn "[fs_mailadmind] sending password data to remote server...\n" if $Debug;
+ print "password_mailbox", "\n";
+ print
+ $user, "\n", $account, $_password,
+ ;
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "add_mailbox" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading account information to create...\n" if $Debug;
+ my(
+ $package, $account, $_password,
+ ) = map { scalar(<Client>) } ( 1 .. 3 );
+
+ warn "[fs_mailadmind] sending service data to remote server...\n" if $Debug;
+ print "add_mailbox", "\n";
+ print
+ $user, "\n", $package, $account, $_password,
+ ;
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "add_forward" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading forward information to create...\n" if $Debug;
+ my(
+ $package, $source, $dest,
+ ) = map { scalar(<Client>) } ( 1 .. 3 );
+
+ warn "[fs_mailadmind] sending service data to remote server...\n" if $Debug;
+ print "add_forward", "\n";
+ print
+ $user, "\n", $package, $source, $dest,
+ ;
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "delete_forward" ) {
+ warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading forward information to delete...\n" if $Debug;
+ chop( my $service = <Client> );
+ warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+ print "delete_forward\n", $user, "\n", $service, "\n";
+
+ warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+ my $error = <STDIN>;
+
+ warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+ print Client $error;
+
+ } elsif ( $command eq "list_forwards" ) {
+ warn "[fs_mailadmind] reading user information to list_forwards...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading service number to list_forwards...\n" if $Debug;
+ chop( my $service = <Client> );
+ warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+ print "list_forwards\n", $user, "\n", $service, "\n";
+
+ warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+ chomp( my $n_svc_forward = <STDIN> );
+ my @svc_forward = map {
+ chomp( my $svcnum = <STDIN> );
+ chomp( my $dest = <STDIN> );
+ {
+ 'svcnum' => $svcnum,
+ 'dest' => $dest,
+ };
+ } ( 1 .. $n_svc_forward );
+
+ warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+ print Client join("\n", $n_svc_forward,
+ map {
+ $_->{svcnum},
+ $_->{dest},
+ } @svc_forward
+ ), "\n";
+
+ } elsif ( $command eq "list_pkg_forwards" ) {
+ warn "[fs_mailadmind] reading user information to list_pkg_forwards...\n" if $Debug;
+ chop( my $user = <Client> );
+ warn "[fs_mailadmind] reading service number to list_forwards...\n" if $Debug;
+ chop( my $package = <Client> );
+ warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+ print "list_pkg_forwards\n", $user, "\n", $package, "\n";
+
+ warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+ chomp( my $n_svc_forward = <STDIN> );
+ my @svc_forward = map {
+ chomp( my $svcnum = <STDIN> );
+ chomp( my $srcsvc = <STDIN> );
+ chomp( my $dest = <STDIN> );
+ {
+ 'svcnum' => $svcnum,
+ 'srcsvc' => $srcsvc,
+ 'dest' => $dest,
+ };
+ } ( 1 .. $n_svc_forward );
+
+ warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+ print Client join("\n", $n_svc_forward,
+ map {
+ $_->{svcnum},
+ $_->{srcsvc},
+ $_->{dest},
+ } @svc_forward
+ ), "\n";
+
+ } else {
+ die "unexpected command from client: $command";
+ }
+
+}
+
diff --git a/fs_selfadmin/README b/fs_selfadmin/README
new file mode 100644
index 0000000..d9857f0
--- /dev/null
+++ b/fs_selfadmin/README
@@ -0,0 +1,27 @@
+
+This collection of files implements a 'self-administered mail service.'
+Configuration is similar to fs_signupd
+
+Additionally you will need to modify the database:
+
+CREATE TABLE svc_acct_admin (
+ svcnum int primary key,
+ adminsvc int not null
+);
+
+creating both as keys might be good
+
+(and perform the dbdef-create)
+
+
+As it exists now, a package containing one svc_domain, at least one
+svc_acct_admin, and other services can have its svc_acct's and svc_forward's
+manipulated by the svc_acct referenced by a svc_acct_admin in the package.
+
+One svc_acct may be referenced as svc_acct_admin for multiple packages.
+
+fs_mailadmin_server contains hard coded references to service numbers which
+will require editing for your system.
+
+It's not a lot, but it might provide inspiration.
+
diff --git a/fs_selfadmin/fs_mailadmin_server b/fs_selfadmin/fs_mailadmin_server
new file mode 100755
index 0000000..ef47885
--- /dev/null
+++ b/fs_selfadmin/fs_mailadmin_server
@@ -0,0 +1,642 @@
+#!/usr/bin/perl -Tw
+#
+# fs_mailadmin_server
+#
+
+use strict;
+use IO::Handle;
+use FS::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_county;
+use FS::cust_main;
+use FS::svc_acct_admin;
+
+use vars qw( $opt $Debug $conf $default_domain );
+
+$Debug = 1;
+
+#my @payby = qw(CARD PREPAY);
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user );
+
+$conf = new FS::Conf;
+$default_domain = $conf->config('domain');
+
+my $machine = shift or die &usage;
+
+my $agentnum = shift or die &usage;
+my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } ) or die &usage;
+my $pkgpart = $agent->pkgpart_hashref;
+
+my $refnum = shift or die &usage;
+
+#causing trouble for some folks
+#$SIG{CHLD} = sub { wait() };
+
+my($fs_mailadmind)=$conf->config('fs_mailadmind');
+
+while (1) {
+ my($reader,$writer)=(new IO::Handle, new IO::Handle);
+ $writer->autoflush(1);
+ warn "[fs_mailadmin_server] Connecting to $machine...\n" if $Debug;
+ sshopen2($machine,$reader,$writer,$fs_mailadmind);
+
+ my $data;
+
+ warn "[fs_mailadmin_server] Sending locales...\n" if $Debug;
+ my @cust_main_county = qsearch('cust_main_county', {} );
+ print $writer $data = join("\n",
+ ( scalar(@cust_main_county) || die "no tax rates (cust_main_county records)" ),
+ map {
+ $_->taxnum,
+ $_->state,
+ $_->county,
+ $_->country,
+ } @cust_main_county
+ ),"\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+ warn "[fs_mailadmin_server] Sending package definitions...\n" if $Debug;
+ my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
+ qsearch( 'part_pkg', {} );
+ print $writer $data = join("\n",
+ ( scalar(@part_pkg) || die "no usable package definitions, agent $agentnum" ),
+ map {
+ $_->pkgpart,
+ $_->pkg,
+ } @part_pkg
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+ warn "[fs_mailadmin_server] Sending POPs...\n" if $Debug;
+ my @svc_acct_pop = qsearch ('svc_acct_pop',{} );
+ print $writer $data = join("\n",
+ ( scalar(@svc_acct_pop) || die "No points of presence (svc_acct_pop records)" ),
+ map {
+ $_->popnum,
+ $_->city,
+ $_->state,
+ $_->ac,
+ $_->exch,
+ $_->loc,
+ } @svc_acct_pop
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+ warn "[fs_mailadmin_server] Entering main loop...\n" if $Debug;
+COMMAND: while (1) {
+ warn "[fs_mailadmin_server] Reading (waiting for) command...\n" if $Debug;
+ chop( my($command, $user) = map { scalar(<$reader>) } ( 1 .. 2 ) );
+ my $domain = $default_domain;
+ $user =~ /^([\w\.\-]+)\@(([\w\-]+\.)+\w+)$/;
+ ($user, $domain) = ($1, $2);
+
+ if ($command eq 'authenticate'){
+ warn "[fs_mailadmin_server] Processing authenticate command for $user \n" if $Debug;
+ chop( my($password) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+
+ my $error = '';
+
+ my @svc_domain = qsearchs('svc_domain', { 'domain' => $domain });
+
+ if (scalar(@svc_domain) != 1) {
+ warn "Nonexistant or duplicate service account for \"$domain\"";
+ next COMMAND;
+ }
+
+ my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
+ 'domsvc' => $svc_domain[0]->svcnum });
+ if (scalar(@svc_acct) != 1) {
+ die "Nonexistant or duplicate service account for \"$user\"";
+ next COMMAND;
+ }
+
+ if ($svc_acct[0]->_password eq $password) {
+ $error = "$user\@$domain OK";
+ }else{
+ $error = "$user\@$domain FAILED";
+ }
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+ }
+ elsif ($command eq 'list_packages'){
+ warn "[fs_mailadmin_server] Processing list_packages command for $user \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval {find_administrable_packages( $user, $domain )};
+ warn "$@" if $@;
+
+ my %packages;
+ my %accounts;
+
+ foreach my $package (@packages) {
+ $packages{my $pkgnum = $package->getfield('pkgnum')} = $default_domain;
+ $accounts{$pkgnum} = 0;
+ my @services = qsearch('cust_svc', { 'pkgnum' => $pkgnum });
+ foreach my $service (@services) {
+ if ($service->getfield('svcpart') eq '4'){
+ my $account=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
+ $packages{$pkgnum}=$account->getfield('domain');
+ $accounts{$pkgnum}=$account->getfield('svcnum');
+ }
+ }
+ }
+
+ print $writer $data = join("\n",
+ ( scalar(keys(%packages)) ),
+ map {
+ $_,
+ $packages{$_},
+ $accounts{$_},
+ } keys(%packages)
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+ }elsif ($command eq 'list_mailboxes'){
+
+ warn "[fs_mailadmin_server] Processing list_mailboxes command for $user" if $Debug;
+ chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+ warn "package $pkgnum \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval {find_administrable_packages( $user, $domain )};
+ warn "$@" if $@;
+
+ my @accounts;
+
+ foreach my $package (@packages) {
+ next unless ($pkgnum eq $package->getfield('pkgnum'));
+ my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+ foreach my $service (@services) {
+ if ($service->getfield('svcpart') eq '2'){
+ my $account=qsearchs('svc_acct', { 'svcnum' => $service->getfield('svcnum') });
+# $accounts[$#accounts+1]=$account->getfield('username');
+ $accounts[$#accounts+1]=$account;
+ }
+ }
+ }
+
+ print $writer $data = join("\n",
+# ( scalar(@accounts) || die "No accounts (svc_acct records)" ),
+ ( scalar(@accounts) ),
+ map {
+ $_->svcnum,
+# $_->username,
+ $_->email,
+# $_->_password,
+ '*****',
+ } @accounts
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+
+ } elsif ($command eq 'delete_mailbox'){
+ warn "[fs_mailadmin_server] Processing delete_mailbox command for $user " if $Debug;
+ chop( my($account) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+ warn "account $account \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval { find_administrable_packages($user, $domain) };
+ warn "$@" if $@;
+ $error ||= "$@" if $@;
+
+ my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
+ if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
+ if (! $error && check_administrator(\@packages, $svc_acct[0])){
+# not sure about the next three lines... do we delete? or return error
+ foreach my $svc_forward (qsearch('svc_forward', { 'dstsvc' => $svc_acct[0]->getfield('svcnum') })) {
+ $error ||= $svc_forward->delete;
+ }
+ foreach my $svc_forward (qsearch('svc_forward', { 'srcsvc' => $svc_acct[0]->getfield('svcnum') })) {
+ $error ||= $svc_forward->delete;
+ }
+ $error ||= $svc_acct[0]->delete;
+ } else {
+ $error ||= "Illegal attempt to remove service";
+ }
+
+
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ } elsif ($command eq 'password_mailbox'){
+ warn "[fs_mailadmin_server] Processing password_mailbox command for $user " if $Debug;
+ chop( my($account, $_password) = map { scalar(<$reader>) } ( 1 .. 2 ) );
+ warn "account $account with password $_password \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval { find_administrable_packages($user, $domain) };
+ warn "$@" if $@;
+ $error ||= "$@" if $@;
+
+ my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
+ if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account.' };
+
+ if (! $error && check_administrator(\@packages, $svc_acct[0])){
+ my $new = new FS::svc_acct ({$svc_acct[0]->hash});
+ $new->setfield('_password' => $_password);
+ $error ||= $new->replace($svc_acct[0]);
+ } else {
+ $error ||= "Illegal attempt to change password";
+ }
+
+
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ } elsif ($command eq 'add_mailbox'){
+ warn "[fs_mailadmin_server] Processing add_mailbox command for $user " if $Debug;
+ chop( my($target_package, $account, $_password) = map { scalar(<$reader>) } ( 1 .. 3 ) );
+ warn "in package $target_package account $account with password $_password \n" if $Debug;
+
+ my $found_package;
+ my $domainsvc=0;
+ my $svcpart=2; # this is 'email box'
+ my $svcpartsm=3; # this is 'domain alias'
+ my $error = '';
+ my $found = 0;
+
+ my @packages = eval { find_administrable_packages($user, $domain) };
+ warn "$@" if $@;
+ $error ||= "$@" if $@;
+
+ foreach my $package (@packages) {
+ if ($package->getfield('pkgnum') eq $target_package) {
+ $found = 1;
+ $found_package=$package;
+ my @services = qsearch('cust_svc', { 'pkgnum' => $target_package });
+ foreach my $service (@services) {
+ if ($service->getfield('svcpart') eq '4'){
+ my @svc_domain=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
+ if (scalar(@svc_domain) eq 1) {
+ $domainsvc=$svc_domain[0]->getfield('svcnum');
+ }
+ }
+ }
+ last;
+ }
+ }
+ warn "User $user does not have administration rights to package $target_package\n" unless $found;
+ $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
+
+ my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
+
+ #list of services this pkgpart includes (although at the moment we only care
+ # about $svcpart
+ my $pkg_svc;
+ my %pkg_svc = ();
+ foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
+ $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
+ }
+
+ my @services = qsearch('cust_svc', {'pkgnum' => $found_package->getfield('pkgnum'),
+ 'svcpart' => $svcpart,
+ });
+
+ if (scalar(@services) >= $pkg_svc{$svcpart}) {
+ $error="Maximum allowed already reached.";
+ }
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'pkgnum' => $found_package->pkgnum,
+ 'svcpart' => $svcpart,
+ 'username' => $account,
+ 'domsvc' => $domainsvc,
+ '_password' => $_password,
+ } );
+
+ my $y = $svc_acct->setdefault; # arguably should be in new method
+ $error ||= $y unless ref($y);
+ #and just in case you were silly
+ $svc_acct->pkgnum($found_package->pkgnum);
+ $svc_acct->svcpart($svcpart);
+ $svc_acct->username($account);
+ $svc_acct->domsvc($domainsvc);
+ $svc_acct->_password($_password);
+
+ $error ||= $svc_acct->check;
+
+ if ( ! $error ) { #in this case, $cust_pkg should always
+ #be definied, but....
+ $error ||= $svc_acct->insert;
+ warn "WARNING: $error on pre-checked svc_acct record!" if $error;
+ }
+
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ }elsif ($command eq 'list_forwards'){
+
+ warn "[fs_mailadmin_server] Processing list_forwards command for $user" if $Debug;
+ chop( my($svcnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+ warn "service $svcnum \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval {find_administrable_packages( $user, $domain )};
+ warn "$@" if $@;
+
+ my @forwards;
+
+ foreach my $package (@packages) {
+# next unless ($pkgnum eq $package->getfield('pkgnum'));
+ my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+ foreach my $service (@services) {
+ if ($service->getfield('svcpart') eq '10'){
+ my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
+ $forwards[$#forwards+1]=$forward if ($forward->getfield('srcsvc') == $svcnum);
+ }
+ }
+ }
+
+ print $writer $data = join("\n",
+ ( scalar(@forwards) ),
+ map {
+ $_->svcnum,
+ ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
+ } @forwards
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+
+ }elsif ($command eq 'list_pkg_forwards'){
+
+ warn "[fs_mailadmin_server] Processing list_pkg_forwards command for $user" if $Debug;
+ chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+ warn "package $pkgnum \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval {find_administrable_packages( $user, $domain )};
+ warn "$@" if $@;
+
+ my @forwards;
+
+ foreach my $package (@packages) {
+ next unless ($pkgnum eq $package->getfield('pkgnum'));
+ my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+ foreach my $service (@services) {
+ if ($service->getfield('svcpart') eq '10'){
+ my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
+ $forwards[$#forwards+1]=$forward;
+ }
+ }
+ }
+
+ print $writer $data = join("\n",
+ ( scalar(@forwards) ),
+ map {
+ $_->svcnum,
+ $_->srcsvc,
+ ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
+ } @forwards
+ ), "\n";
+ warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+
+ } elsif ($command eq 'delete_forward'){
+ warn "[fs_mailadmin_server] Processing delete_forward command for $user " if $Debug;
+ chop( my($forward) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+ warn "forward $forward \n" if $Debug;
+
+ my $error = '';
+
+ my @packages = eval { find_administrable_packages($user, $domain) };
+ warn "$@" if $@;
+ $error ||= "$@" if $@;
+
+ my @svc_forward = qsearchs('svc_forward', { 'svcnum' => $forward }) unless $error;
+ if (scalar(@svc_forward) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
+ if (! $error && check_administrator(\@packages, $svc_forward[0])){
+# not sure about the next three lines... do we delete? or return error
+ $error ||= $svc_forward[0]->delete;
+ } else {
+ $error ||= "Illegal attempt to remove service";
+ }
+
+
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ } elsif ($command eq 'add_forward'){
+ warn "[fs_mailadmin_server] Processing add_forward command for $user " if $Debug;
+ chop( my($target_package, $source, $dest) = map { scalar(<$reader>) } ( 1 .. 3 ) );
+ warn "in package $target_package source $source with destination $dest \n" if $Debug;
+
+ my $found_package;
+ my $domainsvc=0;
+ my $svcpart=10; # this is 'forward service'
+ my $error = '';
+ my $found = 0;
+
+ my @packages = eval { find_administrable_packages($user, $domain) };
+ warn "$@" if $@;
+ $error ||= "$@" if $@;
+
+ foreach my $package (@packages) {
+ if ($package->getfield('pkgnum') eq $target_package) {
+ $found = 1;
+ $found_package=$package;
+ last;
+ }
+ }
+ warn "User $user does not have administration rights to package $target_package\n" unless $found;
+ $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
+
+ my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $source });
+ warn "Forwarding source $source does not exist.\n" unless $svc_acct;
+ $error ||= "Forwarding source $source does not exist.\n" unless $svc_acct;
+
+ my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $source });
+ warn "Forwarding source $source not attached to any account.\n" unless $cust_svc;
+ $error ||= "Forwarding source $source not attached to any account.\n" unless $cust_svc;
+
+ if ( ! $error ) {
+ warn "Forwarding source $source is not in package $target_package\n"
+ unless ($cust_svc->getfield('pkgnum') == $target_package);
+ $error ||= "Forwarding source $source is not in package $target_package\n"
+ unless ($cust_svc->getfield('pkgnum') == $target_package);
+ }
+
+ my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
+
+ #list of services this pkgpart includes (although at the moment we only care
+ # about $svcpart
+ my $pkg_svc;
+ my %pkg_svc = ();
+ foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
+ $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
+ }
+
+ my @services = qsearch('cust_svc', {'pkgnum' => $found_package->getfield('pkgnum'),
+ 'svcpart' => $svcpart,
+ });
+
+ if (scalar(@services) >= $pkg_svc{$svcpart}) {
+ $error="Maximum allowed already reached.";
+ }
+
+ my $svc_forward = new FS::svc_forward ( {
+ 'pkgnum' => $found_package->pkgnum,
+ 'svcpart' => $svcpart,
+ 'srcsvc' => $source,
+ 'dstsvc' => 0,
+ 'dst' => $dest,
+ } );
+
+ my $y = $svc_forward->setdefault; # arguably should be in new method
+ $error ||= $y unless ref($y);
+ #and just in case you were silly
+ $svc_forward->pkgnum($found_package->pkgnum);
+ $svc_forward->svcpart($svcpart);
+ $svc_forward->srcsvc($source);
+ $svc_forward->dstsvc(0);
+ $svc_forward->dst($dest);
+
+ $error ||= $svc_forward->check;
+
+ if ( ! $error ) { #in this case, $cust_pkg should always
+ #be definied, but....
+ $error ||= $svc_forward->insert;
+ warn "WARNING: $error on pre-checked svc_forward record!" if $error;
+ }
+
+ warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ } else {
+ warn "[fs_mailadmin_server] Bad command: $command \n" if $Debug;
+ print $writer "Bad command \n";
+ }
+ }
+ close $writer;
+ close $reader;
+ warn "connection to $machine lost! waiting 60 seconds...\n";
+ sleep 60;
+ warn "reconnecting...\n";
+}
+
+sub usage {
+ die "Usage:\n\n fs_mailadmin_server user machine agentnum refnum\n";
+}
+
+#sub find_administrable_packages {
+# my $user = shift;
+#
+# my $error = '';
+#
+# my @svc_acct = qsearchs('svc_acct', { 'username' => $user });
+# if (scalar(@svc_acct) != 1) {
+# die "Nonexistant or duplicate service account for \"$user\"";
+# }
+#
+# my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct[0]->getfield('svcnum') });
+# if (scalar(@cust_svc) != 1 ) {
+# die "Nonexistant or duplicate customer service for \"$user\"";
+# }
+#
+# my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+# if (scalar(@cust_pkg) != 1) {
+# die "Nonexistant or duplicate customer package for \"$user\"";
+# }
+#
+# my @cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg[0]->getfield('custnum') });
+# if (scalar(@cust_main) != 1 ) {
+# die "Nonexistant or duplicate customer for \"$user\"";
+# }
+#
+# my @packages = $cust_main[0]->ncancelled_pkgs;
+#}
+
+sub find_administrable_packages {
+ my $user = shift;
+ my $domain = shift;
+
+ my @packages;
+ my $error = '';
+
+ my @svc_domain = qsearchs('svc_domain', { 'domain' => $domain });
+
+ if (scalar(@svc_domain) != 1) {
+ die "Nonexistant or duplicate service account for \"$domain\"";
+ }
+
+ my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
+ 'domsvc' => $svc_domain[0]->svcnum });
+ if (scalar(@svc_acct) != 1) {
+ die "Nonexistant or duplicate service account for \"$user\"";
+ }
+
+ my @svc_acct_admin = qsearch('svc_acct_admin', {'adminsvc' => $svc_acct[0]->getfield('svcnum') });
+ die "Nonexistant or duplicate customer service for \"$user\"" unless scalar(@svc_acct_admin);
+
+ foreach my $svc_acct_admin (@svc_acct_admin) {
+ my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_admin->getfield('svcnum') });
+ if (scalar(@cust_svc) != 1 ) {
+ die "Nonexistant or duplicate customer service for admin \"$svc_acct_admin->getfield('svcnum')\"";
+ }
+
+ my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+ if (scalar(@cust_pkg) != 1) {
+ die "Nonexistant or duplicate customer package for admin \"$user\"";
+ }
+
+ push @packages, $cust_pkg[0] unless $cust_pkg[0]->getfield('cancel');
+
+ }
+ (@packages);
+}
+
+sub check_administrator {
+ my ($allowed_packages_aref, $svc_acct_ref) = @_;
+
+ my $error = '';
+ my $found = 0;
+
+ {
+ my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_ref->getfield('svcnum') });
+ if (scalar(@cust_svc) != 1 ) {
+ warn "Nonexistant or duplicate customer service for \"$svc_acct_ref->getfield('username')\"";
+ last;
+ }
+
+ my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+ if (scalar(@cust_pkg) != 1) {
+ warn "Nonexistant or duplicate customer package for \"$svc_acct_ref->getfield('username')\"";
+ last;
+ }
+
+ foreach my $package (@$allowed_packages_aref) {
+ if ($package->getfield('pkgnum') eq $cust_pkg[0]->getfield('pkgnum')) {
+ $found = 1;
+ last;
+ }
+ }
+ }
+
+ $found;
+}
+
+sub check_add {
+ my ($allowed_packages_aref, $target_package) = @_;
+
+ my $error = '';
+ my $found = 0;
+
+ foreach my $package (@$allowed_packages_aref) {
+ if ($package->getfield('pkgnum') eq $target_package) {
+ $found = 1;
+ last;
+ }
+ }
+
+ $found;
+}
+
diff --git a/fs_selfservice/DEPLOY b/fs_selfservice/DEPLOY
new file mode 100755
index 0000000..7420df7
--- /dev/null
+++ b/fs_selfservice/DEPLOY
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+kill `cat /var/run/freeside-selfservice-server.fs_selfservice.pid`
+
+( cd ..; make deploy; cd fs_selfservice )
+
+cd FS-SelfService
+perl Makefile.PL && make && make install
+
+cp /home/ivan/freeside/fs_selfservice/FS-SelfService/cgi/* /var/www/MyAccount
+chown freeside /var/www/MyAccount/selfservice.cgi /var/www/MyAccount/agent.cgi
+chmod 755 /var/www/MyAccount/selfservice.cgi /var/www/MyAccount/agent.cgi
+ln -s /var/www/MyAccount/selfservice.cgi /var/www/MyAccount/index.cgi || true
+
+cp /home/ivan/freeside/fs_signup/FS-SignupClient/cgi/* /var/www/signup/
+#mv /var/www/signup/signup-snarf.html /var/www/signup/signup.html #!!!!!
+chown freeside /var/www/signup/signup.cgi
+chmod 755 /var/www/signup/signup.cgi
+ln -s /var/www/signup/signup.cgi /var/www/signup/index.cgi || true
+
diff --git a/fs_selfservice/FS-SelfService/Changes b/fs_selfservice/FS-SelfService/Changes
new file mode 100644
index 0000000..b9e26b7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/Changes
@@ -0,0 +1,6 @@
+Revision history for Perl extension FS::SelfService.
+
+0.01 Tue May 28 16:49:41 2002
+ - original version; created by h2xs 1.21 with options
+ -A -X -n FS::SelfService
+
diff --git a/fs_selfservice/FS-SelfService/MANIFEST b/fs_selfservice/FS-SelfService/MANIFEST
new file mode 100644
index 0000000..ebd0d3b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/MANIFEST
@@ -0,0 +1,6 @@
+Changes
+Makefile.PL
+MANIFEST
+SelfService.pm
+test.pl
+freeside-selfservice-clientd
diff --git a/fs_selfservice/FS-SelfService/Makefile.PL b/fs_selfservice/FS-SelfService/Makefile.PL
new file mode 100644
index 0000000..0b7fc46
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/Makefile.PL
@@ -0,0 +1,19 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'FS::SelfService',
+ 'VERSION_FROM' => 'SelfService.pm', # finds $VERSION
+ 'EXE_FILES' => [ 'freeside-selfservice-clientd',
+ #'freeside-selfservice-xmlrpc-server',
+ ],
+ 'INSTALLSCRIPT' => '/usr/local/sbin',
+ 'INSTALLSITEBIN' => '/usr/local/sbin',
+ 'PERM_RWX' => '750',
+ 'PREREQ_PM' => {
+ 'Storable' => 2.09,
+ }, # e.g., Module::Name => 1.1
+ ($] >= 5.005 ? ## Add these new keywords supported since 5.005
+ (ABSTRACT_FROM => 'SelfService.pm', # retrieve abstract from module
+ AUTHOR => 'Ivan Kohler <ivan-freeside-selfservice@420.am>') : ()),
+);
diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm
new file mode 100644
index 0000000..6e3ca3b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/SelfService.pm
@@ -0,0 +1,1083 @@
+package FS::SelfService;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT_OK $socket %autoload $tag);
+use Exporter;
+use Socket;
+use FileHandle;
+#use IO::Handle;
+use IO::Select;
+use Storable 2.09 qw(nstore_fd fd_retrieve);
+
+$VERSION = '0.03';
+
+@ISA = qw( Exporter );
+
+$socket = "/usr/local/freeside/selfservice_socket";
+$socket .= '.'.$tag if defined $tag && length($tag);
+
+#maybe should ask ClientAPI for this list
+%autoload = (
+ 'passwd' => 'passwd/passwd',
+ 'chfn' => 'passwd/passwd',
+ 'chsh' => 'passwd/passwd',
+ 'login' => 'MyAccount/login',
+ 'logout' => 'MyAccount/logout',
+ 'customer_info' => 'MyAccount/customer_info',
+ 'edit_info' => 'MyAccount/edit_info', #add to ss cgi!
+ 'invoice' => 'MyAccount/invoice',
+ 'list_invoices' => 'MyAccount/list_invoices', #?
+ 'cancel' => 'MyAccount/cancel', #add to ss cgi!
+ 'payment_info' => 'MyAccount/payment_info',
+ 'process_payment' => 'MyAccount/process_payment',
+ 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss cgi!
+ 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi!
+ 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi!
+ 'charge' => 'MyAccount/charge', #?
+ 'part_svc_info' => 'MyAccount/part_svc_info',
+ 'provision_acct' => 'MyAccount/provision_acct',
+ 'provision_external' => 'MyAccount/provision_external',
+ 'unprovision_svc' => 'MyAccount/unprovision_svc',
+ 'signup_info' => 'Signup/signup_info',
+ 'new_customer' => 'Signup/new_customer',
+ 'agent_login' => 'Agent/agent_login',
+ 'agent_logout' => 'Agent/agent_logout',
+ 'agent_info' => 'Agent/agent_info',
+ 'agent_list_customers' => 'Agent/agent_list_customers',
+);
+@EXPORT_OK = ( keys(%autoload), qw( regionselector expselect popselector ) );
+
+$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+foreach my $autoload ( keys %autoload ) {
+
+ my $eval =
+ "sub $autoload { ". '
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+
+ $param->{_packet} = \''. $autoload{$autoload}. '\';
+
+ simple_packet($param);
+ }';
+
+ eval $eval;
+ die $@ if $@;
+
+}
+
+sub simple_packet {
+ my $packet = shift;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($socket)) or die "connect: $!";
+ nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
+ SOCK->flush;
+
+ #shoudl trap: Magic number checking on storable file failed at blib/lib/Storable.pm (autosplit into blib/lib/auto/Storable/fd_retrieve.al) line 337, at /usr/local/share/perl/5.6.1/FS/SelfService.pm line 71
+
+ #block until there is a message on socket
+# my $w = new IO::Select;
+# $w->add(\*SOCK);
+# my @wait = $w->can_read;
+ my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
+ die $return->{'_error'} if defined $return->{_error} && $return->{_error};
+
+ $return;
+}
+
+=head1 NAME
+
+FS::SelfService - Freeside self-service API
+
+=head1 SYNOPSIS
+
+ # password and shell account changes
+ use FS::SelfService qw(passwd chfn chsh);
+
+ # "my account" functionality
+ use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );
+
+ my $rv = login( { 'username' => $username,
+ 'domain' => $domain,
+ 'password' => $password,
+ }
+ );
+
+ if ( $rv->{'error'} ) {
+ #handle login error...
+ } else {
+ #successful login
+ my $session_id = $rv->{'session_id'};
+ }
+
+ my $customer_info = customer_info( { 'session_id' => $session_id } );
+
+ #payment_info and process_payment are available in 1.5+ only
+ my $payment_info = payment_info( { 'session_id' => $session_id } );
+
+ #!!! process_payment example
+
+ #!!! list_pkgs example
+
+ #!!! order_pkg example
+
+ #!!! cancel_pkg example
+
+ # signup functionality
+ use FS::SelfService qw( signup_info new_customer );
+
+ my $signup_info = signup_info;
+
+ $rv = new_customer( {
+ 'first' => $first,
+ 'last' => $last,
+ 'company' => $company,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'country' => $country,
+ 'daytime' => $daytime,
+ 'night' => $night,
+ 'fax' => $fax,
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ 'paycvv' => $paycvv,
+ 'paydate' => $paydate,
+ 'payname' => $payname,
+ 'invoicing_list' => $invoicing_list,
+ 'referral_custnum' => $referral_custnum,
+ 'pkgpart' => $pkgpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'popnum' => $popnum,
+ 'agentnum' => $agentnum,
+ }
+ );
+
+ my $error = $rv->{'error'};
+ if ( $error eq '_decline' ) {
+ print_decline();
+ } elsif ( $error ) {
+ reprint_signup();
+ } else {
+ print_success();
+ }
+
+=head1 DESCRIPTION
+
+Use this API to implement your own client "self-service" module.
+
+If you just want to customize the look of the existing "self-service" module,
+see XXXX instead.
+
+=head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
+
+=over 4
+
+=item passwd
+
+=item chfn
+
+=item chsh
+
+=back
+
+=head1 "MY ACCOUNT" FUNCTIONS
+
+=over 4
+
+=item login HASHREF
+
+Creates a user session. Takes a hash reference as parameter with the
+following keys:
+
+=over 4
+
+=item username
+
+=item domain
+
+=item password
+
+=back
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors.
+
+=item session_id
+
+Session identifier for successful logins
+
+=back
+
+=item customer_info HASHREF
+
+Returns general customer information.
+
+Takes a hash reference as parameter with a single key: B<session_id>
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item name
+
+Customer name
+
+=item balance
+
+Balance owed
+
+=item open
+
+Array reference of hash references of open inoices. Each hash reference has
+the following keys: invnum, date, owed
+
+=item small_custview
+
+An HTML fragment containing shipping and billing addresses.
+
+=item The following fields are also returned: first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo payname month year invoicing_list postal_invoicing
+
+=back
+
+=item edit_info HASHREF
+
+Takes a hash reference as parameter with any of the following keys:
+
+first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo paycvv payname month year invoicing_list postal_invoicing
+
+If a field exists, the customer record is updated with the new value of that
+field. If a field does not exist, that field is not changed on the customer
+record.
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors
+
+=item invoice HASHREF
+
+Returns an invoice. Takes a hash reference as parameter with two keys:
+session_id and invnum
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors
+
+=item invnum
+
+Invoice number
+
+=item invoice_text
+
+Invoice text
+
+=back
+
+=item list_invoices HASHREF
+
+Returns a list of all customer invoices. Takes a hash references with a single
+key, session_id.
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors
+
+=item invoices
+
+Reference to array of hash references with the following keys:
+
+=over 4
+
+=item invnum
+
+Invoice ID
+
+=item _date
+
+Invoice date, in UNIX epoch time
+
+=back
+
+=back
+
+=item cancel HASHREF
+
+Cancels this customer.
+
+Takes a hash reference as parameter with a single key: B<session_id>
+
+Returns a hash reference with a single key, B<error>, which is empty on
+success or an error message on errors.
+
+=item payment_info HASHREF
+
+Returns information that may be useful in displaying a payment page.
+
+Takes a hash reference as parameter with a single key: B<session_id>.
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors
+
+=item balance
+
+Balance owed
+
+=item payname
+
+Exact name on credit card (CARD/DCRD)
+
+=item address1
+
+=item address2
+
+=item city
+
+=item state
+
+=item zip
+
+=item payby
+
+Customer's current default payment type.
+
+=item card_type
+
+For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)
+
+=item payinfo
+
+For CARD/DCRD payment types, the card number
+
+=item month
+
+For CARD/DCRD payment types, expiration month
+
+=item year
+
+For CARD/DCRD payment types, expiration year
+
+=item cust_main_county
+
+County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>). Note these are not FS::cust_main_county objects, but hash references of columns and values.
+
+=item states
+
+Array reference of all states in the current default country.
+
+=item card_types
+
+Hash reference of card types; keys are card types, values are the exact strings
+passed to the process_payment function
+
+=item paybatch
+
+Unique transaction identifier (prevents multiple charges), passed to the
+process_payment function
+
+=back
+
+=item process_payment HASHREF
+
+Processes a payment and possible change of address or payment type. Takes a
+hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+=item save
+
+If true, address and card information entered will be saved for subsequent
+transactions.
+
+=item auto
+
+If true, future credit card payments will be done automatically (sets payby to
+CARD). If false, future credit card payments will be done on-demand (sets
+payby to DCRD). This option only has meaning if B<save> is set true.
+
+=item payname
+
+=item address1
+
+=item address2
+
+=item city
+
+=item state
+
+=item zip
+
+=item payinfo
+
+Card number
+
+=item month
+
+Card expiration month
+
+=item year
+
+Card expiration year
+
+=item paybatch
+
+Unique transaction identifier, returned from the payment_info function.
+Prevents multiple charges.
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors
+
+=item list_pkgs
+
+Returns package information for this customer.
+
+Takes a hash reference as parameter with a single key: B<session_id>
+
+Returns a hash reference containing customer package information. The hash reference contains the following keys:
+
+=over 4
+
+
+=item cust_pkg HASHREF
+
+Array reference of hash references, each of which has the fields of a cust_pkg
+record (see L<FS::cust_pkg>) as well as the fields below. Note these are not
+the internal FS:: objects, but hash references of columns and values.
+
+=item all fields of part_pkg (XXXpare this down to a secure subset)
+
+=item part_svc - An array of hash references, each of which has the following keys:
+
+=over 4
+
+=item all fields of part_svc (XXXpare this down to a secure subset)
+
+=item avail
+
+=back
+
+=item error
+
+Empty on success, or an error message on errors.
+
+=back
+
+=item order_pkg
+
+Orders a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+=item pkgpart
+
+=item svcpart
+
+optional svcpart, required only if the package definition does not contain
+one svc_acct service definition with quantity 1 (it may contain others with
+quantity >1)
+
+=item username
+
+=item _password
+
+=item sec_phrase
+
+=item popnum
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors. The special error '_decline' is returned for
+declined transactions.
+
+=item cancel_pkg
+
+Cancels a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+=item pkgpart
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.
+
+=back
+
+=head1 SIGNUP FUNCTIONS
+
+=over 4
+
+=item signup_info HASHREF
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id - Optional agent/reseller interface session
+
+=back
+
+Returns a hash reference containing information that may be useful in
+displaying a signup page. The hash reference contains the following keys:
+
+=over 4
+
+=item cust_main_county
+
+County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>). Note these are not FS::cust_main_county objects, but hash references of columns and values.
+
+=item part_pkg
+
+Available packages - array reference of hash references, each of which has the fields of a part_pkg record (see L<FS::part_pkg>). Each hash reference also has an additional 'payby' field containing an array reference of acceptable payment types specific to this package (see below and L<FS::part_pkg/payby>). Note these are not FS::part_pkg objects, but hash references of columns and values. Requires the 'signup_server-default_agentnum' configuration value to be set, or
+an agentnum specified explicitly via reseller interface session_id in the
+options.
+
+=item agent
+
+Array reference of hash references, each of which has the fields of an agent record (see L<FS::agent>). Note these are not FS::agent objects, but hash references of columns and values.
+
+=item agentnum2part_pkg
+
+Hash reference; keys are agentnums, values are array references of available packages for that agent, in the same format as the part_pkg arrayref above.
+
+=item svc_acct_pop
+
+Access numbers - array reference of hash references, each of which has the fields of an svc_acct_pop record (see L<FS::svc_acct_pop>). Note these are not FS::svc_acct_pop objects, but hash references of columns and values.
+
+=item security_phrase
+
+True if the "security_phrase" feature is enabled
+
+=item payby
+
+Array reference of acceptable payment types for signup
+
+=over 4
+
+=item CARD (credit card - automatic)
+
+=item DCRD (credit card - on-demand - version 1.5+ only)
+
+=item CHEK (electronic check - automatic)
+
+=item DCHK (electronic check - on-demand - version 1.5+ only)
+
+=item LECB (Phone bill billing)
+
+=item BILL (billing, not recommended for signups)
+
+=item COMP (free, definately not recommended for signups)
+
+=item PREPAY (special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL)
+
+=back
+
+=item cvv_enabled
+
+True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)
+
+=item msgcat
+
+Hash reference of message catalog values, to support error message customization. Currently available keys are: passwords_dont_match, invalid_card, unknown_card_type, and not_a (as in "Not a Discover card"). Values are configured in the web interface under "View/Edit message catalog".
+
+=item statedefault
+
+Default state
+
+=item countrydefault
+
+Default country
+
+=back
+
+=item new_customer HASHREF
+
+Creates a new customer. Takes a hash reference as parameter with the
+following keys:
+
+=over 4
+
+=item first - first name (required)
+
+=item last - last name (required)
+
+=item ss (not typically collected; mostly used for ACH transactions)
+
+=item company
+
+=item address1 (required)
+
+=item address2
+
+=item city (required)
+
+=item county
+
+=item state (required)
+
+=item zip (required)
+
+=item daytime - phone
+
+=item night - phone
+
+=item fax - phone
+
+=item payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)
+
+=item payinfo - Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
+
+=item paycvv - Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
+
+=item paydate - Expiration date for CARD/DCRD
+
+=item payname - Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
+
+=item invoicing_list - comma-separated list of email addresses for email invoices. The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses),
+
+=item referral_custnum - referring customer number
+
+=item pkgpart - pkgpart of initial package
+
+=item username
+
+=item _password
+
+=item sec_phrase - security phrase
+
+=item popnum - access number (index, not the literal number)
+
+=item agentnum - agent number
+
+=back
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error Empty on success, or an error message on errors. The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Sysadmin | View/Edit message catalog)
+
+=back
+
+=item regionselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item selected_county
+
+=item selected_state
+
+=item selected_country
+
+=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
+
+=item onchange - Specify a javascript subroutine to call on changes
+
+=item default_state
+
+=item default_country
+
+=item locales - An arrayref of hash references specifying regions. Normally you can just pass the value of the I<cust_main_county> field returned by B<signup_info>.
+
+=back
+
+Returns a list consisting of three HTML fragments for county selection,
+state selection and country selection, respectively.
+
+=cut
+
+#false laziness w/FS::cust_main_county (this is currently the "newest" version)
+sub regionselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ $param->{'selected_country'} ||= $param->{'default_country'};
+ $param->{'selected_state'} ||= $param->{'default_state'};
+
+ my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
+
+ my $countyflag = 0;
+
+ my %cust_main_county;
+
+# unless ( @cust_main_county ) { #cache
+ #@cust_main_county = qsearch('cust_main_county', {} );
+ #foreach my $c ( @cust_main_county ) {
+ foreach my $c ( @{ $param->{'locales'} } ) {
+ #$countyflag=1 if $c->county;
+ $countyflag=1 if $c->{county};
+ #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
+ #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
+ $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
+ }
+# }
+ $countyflag=1 if $param->{selected_county};
+
+ my $script_html = <<END;
+ <SCRIPT>
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+ function ${prefix}country_changed(what) {
+ country = what.options[what.selectedIndex].text;
+ for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
+ what.form.${prefix}state.options[i] = null;
+END
+ #what.form.${prefix}state.options[0] = new Option('', '', false, true);
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ my $text = $state || '(n/a)';
+ $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+
+ $script_html .= <<END;
+ }
+ function ${prefix}state_changed(what) {
+END
+
+ if ( $countyflag ) {
+ $script_html .= <<END;
+ state = what.options[what.selectedIndex].text;
+ country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
+ for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
+ what.form.${prefix}county.options[i] = null;
+END
+
+ foreach my $country ( sort keys %cust_main_county ) {
+ $script_html .= "\nif ( country == \"$country\" ) {\n";
+ foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
+ $script_html .= "\nif ( state == \"$state\" ) {\n";
+ #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
+ foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
+ my $text = $county || '(n/a)';
+ $script_html .=
+ qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
+ }
+ $script_html .= "}\n";
+ }
+ $script_html .= "}\n";
+ }
+ }
+
+ $script_html .= <<END;
+ }
+ </SCRIPT>
+END
+
+ my $county_html = $script_html;
+ if ( $countyflag ) {
+ $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
+ $county_html .= '</SELECT>';
+ } else {
+ $county_html .=
+ qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
+ }
+
+ my $state_html = qq!<SELECT NAME="${prefix}state" !.
+ qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
+ foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
+ my $text = $state || '(n/a)';
+ my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
+ $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
+ }
+ $state_html .= '</SELECT>';
+
+ $state_html .= '</SELECT>';
+
+ my $country_html = qq!<SELECT NAME="${prefix}country" !.
+ qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
+ my $countrydefault = $param->{default_country} || 'US';
+ foreach my $country (
+ sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
+ keys %cust_main_county
+ ) {
+ my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
+ $country_html .= "\n<OPTION$selected>$country</OPTION>"
+ }
+ $country_html .= '</SELECT>';
+
+ ($county_html, $state_html, $country_html);
+
+}
+
+#=item expselect HASHREF | LIST
+#
+#Takes as input a hashref or list of key/value pairs with the following keys:
+#
+#=over 4
+#
+#=item prefix - Specify a unique prefix string if you intend to use the HTML output multiple time son one page.
+#
+#=item date - current date, in yyyy-mm-dd or m-d-yyyy format
+#
+#=back
+
+=item expselect PREFIX [ DATE ]
+
+Takes as input a unique prefix string and the current expiration date, in
+yyyy-mm-dd or m-d-yyyy format
+
+Returns an HTML fragments for expiration date selection.
+
+=cut
+
+sub expselect {
+ #my $param;
+ #if ( ref($_[0]) ) {
+ # $param = shift;
+ #} else {
+ # $param = { @_ };
+ #my $prefix = $param->{'prefix'};
+ #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
+ #my $date = exists($param->{'date'}) ? $param->{'date'} : '';
+ my $prefix = shift;
+ my $date = scalar(@_) ? shift : '';
+
+ my( $m, $y ) = ( 0, 0 );
+ if ( $date =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
+ ( $m, $y ) = ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $m, $y ) = ( $1, $3 );
+ }
+ my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
+ for ( 1 .. 12 ) {
+ $return .= "<OPTION";
+ $return .= " SELECTED" if $_ == $m;
+ $return .= ">$_";
+ }
+ $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
+ my @t = localtime;
+ my $thisYear = $t[5] + 1900;
+ for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. 2037 ) {
+ $return .= "<OPTION";
+ $return .= " SELECTED" if $_ == $y;
+ $return .= ">$_";
+ }
+ $return .= "</SELECT>";
+
+ $return;
+}
+
+=item popselector HASHREF | LIST
+
+Takes as input a hashref or list of key/value pairs with the following keys:
+
+=over 4
+
+=item popnum
+
+=item pops - An arrayref of hash references specifying access numbers. Normally you can just pass the value of the I<svc_acct_pop> field returned by B<signup_info>.
+
+=back
+
+Returns an HTML fragment for access number selection.
+
+=cut
+
+#horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
+sub popselector {
+ my $param;
+ if ( ref($_[0]) ) {
+ $param = shift;
+ } else {
+ $param = { @_ };
+ }
+ my $popnum = $param->{'popnum'};
+ my $pops = $param->{'pops'};
+
+ return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
+ return $pops->[0]{city}. ', '. $pops->[0]{state}.
+ ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
+ '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
+ if scalar(@$pops) == 1;
+
+ my %pop = ();
+ my %popnum2pop = ();
+ foreach (@$pops) {
+ push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
+ $popnum2pop{$_->{popnum}} = $_;
+ }
+
+ my $text = <<END;
+ <SCRIPT>
+ function opt(what,href,text) {
+ var optionName = new Option(text, href, false, false)
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+END
+
+ my $init_popstate = $param->{'init_popstate'};
+ if ( $init_popstate ) {
+ $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
+ $init_popstate. '">';
+ } else {
+ $text .= <<END;
+ function acstate_changed(what) {
+ state = what.options[what.selectedIndex].text;
+ what.form.popac.options.length = 0
+ what.form.popac.options[0] = new Option("Area code", "-1", false, true);
+END
+ }
+
+ my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
+ foreach my $state ( sort { $a cmp $b } @states ) {
+ $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;
+
+ foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
+ $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
+ if ($ac eq $param->{'popac'}) {
+ $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
+ }
+ }
+ $text .= "}\n" unless $init_popstate;
+ }
+ $text .= "popac_changed(what.form.popac)}\n";
+
+ $text .= <<END;
+ function popac_changed(what) {
+ ac = what.options[what.selectedIndex].text;
+ what.form.popnum.options.length = 0;
+ what.form.popnum.options[0] = new Option("City", "-1", false, true);
+
+END
+
+ foreach my $state ( @states ) {
+ foreach my $popac ( keys %{ $pop{$state} } ) {
+ $text .= "\nif ( ac == \"$popac\" ) {\n";
+
+ foreach my $pop ( @{$pop{$state}->{$popac}}) {
+ my $o_popnum = $pop->{popnum};
+ my $poptext = $pop->{city}. ', '. $pop->{state}.
+ ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
+
+ $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
+ if ($popnum == $o_popnum) {
+ $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
+ }
+ }
+ $text .= "}\n";
+ }
+ }
+
+
+ $text .= "}\n</SCRIPT>\n";
+
+ $text .=
+ qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
+ qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
+ $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
+ ">$_" foreach sort { $a cmp $b } @states;
+ $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD>';
+
+ $text .=
+ qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
+ qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;
+
+ $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;
+
+
+ #comment this block to disable initial list polulation
+ my @initial_select = ();
+ if ( scalar( @$pops ) > 100 ) {
+ push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
+ } else {
+ @initial_select = @$pops;
+ }
+ foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
+ $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
+ ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
+ $pop->{city}. ', '. $pop->{state}.
+ ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
+ }
+
+ $text .= qq!</SELECT></TD></TR></TABLE>!;
+
+ $text;
+
+}
+
+=back
+
+=head1 RESELLER FUNCTIONS
+
+Note: Resellers can also use the B<signup_info> and B<new_customer> functions
+with their active session, and the B<customer_info> and B<order_pkg> functions
+with their active session and an additonal I<custnum> parameter.
+
+=over 4
+
+=item agent_login
+
+=item agent_info
+
+=item agent_list_customers
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
+
+=cut
+
+1;
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent.cgi b/fs_selfservice/FS-SelfService/cgi/agent.cgi
new file mode 100644
index 0000000..92c76f3
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent.cgi
@@ -0,0 +1,448 @@
+#!/usr/bin/perl -Tw
+
+#some false laziness w/selfservice.cgi
+
+use strict;
+use vars qw($DEBUG $me $cgi $session_id $form_max $template_dir);
+use subs qw(do_template);
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use Business::CreditCard;
+use Text::Template;
+#use HTML::Entities;
+use FS::SelfService qw( agent_login agent_logout agent_info
+ agent_list_customers
+ signup_info new_customer
+ customer_info list_pkgs order_pkg
+ part_svc_info provision_acct provision_external
+ unprovision_svc
+ );
+
+$DEBUG = 0;
+$me = 'agent.cgi:';
+
+$template_dir = '.';
+
+$form_max = 255;
+
+warn "$me starting\n" if $DEBUG;
+
+warn "$me initializing CGI\n" if $DEBUG;
+$cgi = new CGI;
+
+unless ( defined $cgi->param('session') ) {
+ warn "$me no session defined, sending login page\n" if $DEBUG;
+ do_template('agent_login',{});
+ exit;
+}
+
+if ( $cgi->param('session') eq 'login' ) {
+
+ warn "$me processing login\n" if $DEBUG;
+
+ $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i
+ or die "illegal username";
+ my $username = $1;
+
+ $cgi->param('password') =~ /^(.{0,$form_max})$/
+ or die "illegal password";
+ my $password = $1;
+
+ my $rv = agent_login(
+ 'username' => $username,
+ 'password' => $password,
+ );
+ if ( $rv->{error} ) {
+ do_template('agent_login', {
+ 'error' => $rv->{error},
+ 'username' => $username,
+ } );
+ exit;
+ } else {
+ $cgi->param('session' => $rv->{session_id} );
+ $cgi->param('action' => 'agent_main' );
+ }
+}
+
+$session_id = $cgi->param('session');
+
+warn "$me checking action\n" if $DEBUG;
+$cgi->param('action') =~
+ /^(agent_main|signup|process_signup|list_customers|view_customer|agent_provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|agent_order_pkg|process_order_pkg|logout)$/
+ or die "unknown action ". $cgi->param('action');
+my $action = $1;
+
+warn "$me running $action\n" if $DEBUG;
+my $result = eval "&$action();";
+die $@ if $@;
+
+if ( $result->{error} eq "Can't resume session" ) { #ick
+ do_template('agent_login',{});
+ exit;
+}
+
+warn "$me processing template $action\n" if $DEBUG;
+do_template($action, {
+ 'session_id' => $session_id,
+ %{$result}
+});
+warn "$me done processing template $action\n" if $DEBUG;
+
+#--
+
+sub logout {
+ $action = 'agent_logout';
+ agent_logout( 'session_id' => $session_id );
+}
+
+sub agent_main { agent_info( 'session_id' => $session_id ); }
+
+sub signup { signup_info( 'session_id' => $session_id ); }
+
+sub process_signup {
+
+ my $init_data = signup_info( 'session_id' => $session_id );
+ if ( $init_data->{'error'} ) {
+ if ( $init_data->{'error'} eq "Can't resume session" ) { #ick
+ do_template('agent_login',{});
+ exit;
+ } else { #?
+ die $init_data->{'error'};
+ }
+ }
+
+ my $error = '';
+
+ #some false laziness w/signup.cgi
+ my $payby = $cgi->param('payby');
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ #$payinfo = join('@', map { $cgi->param( $payby. "_payinfo$_" ) } (1,2) );
+ $cgi->param('payinfo' => $cgi->param($payby. '_payinfo1'). '@'.
+ $cgi->param($payby. '_payinfo2')
+ );
+ } else {
+ $cgi->param('payinfo' => $cgi->param( $payby. '_payinfo' ) );
+ }
+ $cgi->param('paydate' => $cgi->param( $payby. '_month' ). '-'.
+ $cgi->param( $payby. '_year' )
+ );
+ $cgi->param('payname' => $cgi->param( $payby. '_payname' ) );
+ $cgi->param('paycvv' => defined $cgi->param( $payby. '_paycvv' )
+ ? $cgi->param( $payby. '_paycvv' )
+ : ''
+ );
+
+ if ( $cgi->param('invoicing_list') ) {
+ $cgi->param('invoicing_list' => $cgi->param('invoicing_list'). ', POST')
+ if $cgi->param('invoicing_list_POST');
+ } else {
+ $cgi->param('invoicing_list' => 'POST' );
+ }
+
+ if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+ $error = $init_data->{msgcat}{passwords_dont_match}; #msgcat
+ $cgi->param('_password', '');
+ $cgi->param('_password2', '');
+ }
+
+ if ( $payby =~ /^(CARD|DCRD)$/ && $cgi->param('CARD_type') ) {
+ my $payinfo = $cgi->param('payinfo');
+ $payinfo =~ s/\D//g;
+
+ $payinfo =~ /^(\d{13,16})$/
+ or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ $payinfo = $1;
+ validate($payinfo)
+ or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ cardtype($payinfo) eq $cgi->param('CARD_type')
+ or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
+ }
+
+ unless ( $error ) {
+ my $rv = new_customer ( {
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) }
+ qw( last first ss company
+ address1 address2 city county state zip country
+ daytime night fax
+ payby payinfo paycvv paydate payname invoicing_list
+ pkgpart username sec_phrase _password popnum refnum
+ ),
+ grep { /^snarf_/ } $cgi->param
+ } );
+ $error = $rv->{'error'};
+ }
+
+ if ( $error ) {
+ $action = 'signup';
+ my $r = {
+ $cgi->Vars,
+ %{$init_data},
+ 'error' => $error,
+ };
+ #warn join('\n', map "$_ => $r->{$_}", keys %$r )."\n";
+ $r;
+ } else {
+ $action = 'agent_main';
+ my $agent_info = agent_info( 'session_id' => $session_id );
+ $agent_info->{'message'} = 'Signup sucessful';
+ $agent_info;
+ }
+
+}
+
+sub list_customers {
+
+ my $results =
+ agent_list_customers( 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) }
+ grep defined($cgi->param($_)),
+ qw(prospect active susp cancel),
+ 'search',
+ );
+
+ if ( scalar( @{$results->{'customers'}} ) == 1 ) {
+ $action = 'view_customer';
+ customer_info (
+ 'agent_session_id' => $session_id,
+ 'custnum' => $results->{'customers'}[0]{'custnum'},
+ );
+ } else {
+ $results;
+ }
+
+}
+
+sub view_customer {
+
+ #my $init_data = signup_info( 'session_id' => $session_id );
+ #if ( $init_data->{'error'} ) {
+ # if ( $init_data->{'error'} eq "Can't resume session" ) { #ick
+ # do_template('agent_login',{});
+ # exit;
+ # } else { #?
+ # die $init_data->{'error'};
+ # }
+ #}
+ #
+ #my $customer_info =
+ customer_info (
+ 'agent_session_id' => $session_id,
+ 'custnum' => $cgi->param('custnum'),
+ );
+ #
+ #return {
+ # ( map { $_ => $init_data->{$_} }
+ # qw( part_pkg security_phrase svc_acct_pop ),
+ # ),
+ # %$customer_info,
+ #};
+}
+
+sub agent_order_pkg {
+
+ my $init_data = signup_info( 'session_id' => $session_id );
+ if ( $init_data->{'error'} ) {
+ if ( $init_data->{'error'} eq "Can't resume session" ) { #ick
+ do_template('agent_login',{});
+ exit;
+ } else { #?
+ die $init_data->{'error'};
+ }
+ }
+
+ my $customer_info = customer_info (
+ 'agent_session_id' => $session_id,
+ 'custnum' => $cgi->param('custnum'),
+ );
+
+ return {
+ ( map { $_ => $init_data->{$_} }
+ qw( part_pkg security_phrase svc_acct_pop ),
+ ),
+ %$customer_info,
+ };
+
+}
+
+sub agent_provision {
+ my $result = list_pkgs(
+ 'agent_session_id' => $session_id,
+ 'custnum' => $cgi->param('custnum'),
+ );
+ die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+ $result;
+}
+
+sub provision_svc {
+
+ my $result = part_svc_info(
+ 'agent_session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( pkgnum svcpart custnum ),
+ );
+ die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+
+ $result->{'svcdb'} =~ /^svc_(.*)$/
+ #or return { 'error' => 'Unknown svcdb '. $result->{'svcdb'} };
+ or die 'Unknown svcdb '. $result->{'svcdb'};
+ $action .= "_$1";
+ $action = "agent_$action";
+
+ $result;
+}
+
+sub process_svc_acct {
+
+ my $result = provision_acct (
+ 'agent_session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw(
+ custnum pkgnum svcpart username _password _password2 sec_phrase popnum )
+ );
+
+ if ( exists $result->{'error'} && $result->{'error'} ) {
+ #warn "$result $result->{'error'}";
+ $action = 'provision_svc_acct';
+ $action = "agent_$action";
+ return {
+ $cgi->Vars,
+ %{ part_svc_info( 'agent_session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw(pkgnum svcpart custnum)
+ )
+ },
+ 'error' => $result->{'error'},
+ };
+ } else {
+ #warn "$result $result->{'error'}";
+ $action = 'agent_provision';
+ return {
+ %{agent_provision()},
+ 'message' => $result->{'svc'}. ' setup sucessfully.',
+ };
+ }
+
+}
+
+sub process_svc_external {
+
+ my $result = provision_external (
+ 'agent_session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( custnum pkgnum svcpart )
+ );
+
+ #warn "$result $result->{'error'}";
+ $action = 'agent_provision';
+ return {
+ %{agent_provision()},
+ 'message' => $result->{'error'}
+ ? '<FONT COLOR="#FF0000">'. $result->{'error'}. '</FONT>'
+ : $result->{'svc'}. ' setup sucessfully'.
+ ': serial number '.
+ sprintf('%010d', $result->{'id'}). '-'. $result->{'title'}
+ };
+
+}
+
+sub delete_svc {
+ my $result = unprovision_svc(
+ 'agent_session_id' => $session_id,
+ 'custnum' => $cgi->param('custnum'),
+ 'svcnum' => $cgi->param('svcnum'),
+ );
+
+ $action = 'agent_provision';
+
+ return {
+ %{agent_provision()},
+ 'message' => $result->{'error'}
+ ? '<FONT COLOR="#FF0000">'. $result->{'error'}. '</FONT>'
+ : $result->{'svc'}. ' removed.'
+ };
+
+}
+
+sub process_order_pkg {
+
+ my $results = '';
+
+ unless ( length($cgi->param('_password')) ) {
+ my $init_data = signup_info( 'session_id' => $session_id );
+ #die $init_data->{'error'} if $init_data->{'error'};
+ $results = { 'error' => $init_data->{msgcat}{empty_password} };
+ }
+ if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+ my $init_data = signup_info( 'session_id' => $session_id );
+ $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
+ $cgi->param('_password', '');
+ $cgi->param('_password2', '');
+ }
+
+ $results ||= order_pkg (
+ 'agent_session_id' => $session_id,
+ map { $_ => $cgi->param($_) }
+ qw( custnum pkgpart username _password _password2 sec_phrase popnum )
+ );
+
+ if ( $results->{'error'} ) {
+ $action = 'agent_order_pkg';
+ return {
+ $cgi->Vars,
+ %{agent_order_pkg()},
+ #'message' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ 'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+ };
+ } else {
+ $action = 'view_customer';
+ #$cgi->delete( grep { $_ ne 'custnum' } $cgi->param );
+ return {
+ %{view_customer()},
+ 'message' => 'Package order sucessful.',
+ };
+ }
+
+}
+
+#--
+
+sub do_template {
+ my $name = shift;
+ my $fill_in = shift;
+ #warn join(' / ', map { "$_=>".$fill_in->{$_} } keys %$fill_in). "\n";
+
+ $cgi->delete_all();
+ $fill_in->{'selfurl'} = $cgi->self_url;
+ $fill_in->{'cgi'} = \$cgi;
+
+ my $template = new Text::Template( TYPE => 'FILE',
+ SOURCE => "$template_dir/$name.html",
+ DELIMITERS => [ '<%=', '%>' ],
+ UNTAINT => 1, )
+ or die $Text::Template::ERROR;
+
+ local $^W = 0;
+ print $cgi->header( '-expires' => 'now' ),
+ $template->fill_in( PACKAGE => 'FS::SelfService::_agentcgi',
+ HASH => $fill_in
+ );
+}
+
+package FS::SelfService::_agentcgi;
+
+use HTML::Entities;
+use FS::SelfService qw(regionselector expselect popselector);
+
+#false laziness w/selfservice.cgi
+sub include {
+ my $name = shift;
+ my $template = new Text::Template( TYPE => 'FILE',
+ SOURCE => "$main::template_dir/$name.html",
+ DELIMITERS => [ '<%=', '%>' ],
+ UNTAINT => 1,
+ )
+ or die $Text::Template::ERROR;
+
+ $template->fill_in( PACKAGE => 'FS::SelfService::_agentcgi',
+ #HASH => $fill_in
+ );
+
+}
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html b/fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html
new file mode 100644
index 0000000..603fc0b
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html
@@ -0,0 +1,7 @@
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TD VALIGN="top" HEIGHT=384 BGCOLOR="#dddddd">
+<A HREF="<%= $url %>agent_provision">Setup services</A><BR><BR>
+<A HREF="<%= $url %>agent_order_pkg">Purchase additional package</A><BR><BR>
+
+</TD>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html b/fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html
new file mode 100644
index 0000000..e8be07e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html
@@ -0,0 +1,20 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<%= $small_custview %>
+<BR>
+<%= if ( $error ) {
+
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!;
+} else {
+ $OUT .= "<FONT SIZE=4>$svc removed.</FONT>";
+} %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_login.html b/fs_selfservice/FS-SelfService/cgi/agent_login.html
new file mode 100644
index 0000000..4b0778e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_login.html
@@ -0,0 +1,22 @@
+<HTML><HEAD><TITLE>Reseller Login</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=5>Reseller Login</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="login">
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+<TR>
+ <TH ALIGN="right">Username </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>">
+ </TD>
+</TR>
+<TR>
+ <TH ALIGN="right">Password </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+</TR>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" VALUE="Login">
+</FORM></BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_logout.html b/fs_selfservice/FS-SelfService/cgi/agent_logout.html
new file mode 100644
index 0000000..9809467
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_logout.html
@@ -0,0 +1,5 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+You have been logged out.
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_main.html b/fs_selfservice/FS-SelfService/cgi/agent_main.html
new file mode 100644
index 0000000..9dd3383
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_main.html
@@ -0,0 +1,37 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+ ? "<FONT SIZE=\"+2\"><B>$message</B></FONT>"
+ : "Hello $agent!"
+%><BR><BR>
+
+<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">
+<TR><TH BGCOLOR="#cccccc">Customer summary</TH></TR>
+<TR><TD BGCOLOR="#dddddd">
+
+ <B><%= $num_prospect %></B>
+ <%= $num_prospect ? qq!<A HREF="${url}list_customers;prospect=1">! : '' %>prospects</A>
+
+ <BR><FONT COLOR="#00CC00"><B><%= $num_active %></B></FONT>
+ <%= $num_active ? qq!<A HREF="${url}list_customers;active=1">! : '' %>active</A>
+
+ <BR><FONT COLOR="#FF9900"><B><%= $num_susp %></B></FONT>
+ <%= $num_susp ? qq!<A HREF="${url}list_customers;susp=1">! : '' %>suspended</A>
+
+ <BR><FONT COLOR="#FF0000"><B><%= $num_cancel %></B></FONT>
+ <%= $num_cancel ? qq!<A HREF="${url}list_customers;cancel=1">! : '' %>cancelled</A>
+
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_menu.html b/fs_selfservice/FS-SelfService/cgi/agent_menu.html
new file mode 100644
index 0000000..84a2953
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_menu.html
@@ -0,0 +1,15 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TD VALIGN="top" HEIGHT=384 BGCOLOR="#dddddd">
+
+<A HREF="<%= $url %>agent_main">Overview</A><BR><BR>
+<A HREF="<%= $url %>signup">New customer<!--/prospect--></A><BR><BR>
+<FORM ACTION="<%= $selfurl %>">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="list_customers">
+<INPUT TYPE="text" NAME="search" SIZE=20><BR>
+<SMALL><I>cust&nbsp;#,&nbsp;last&nbsp;name,&nbsp;or&nbsp;company</I></SMALL><BR>
+<INPUT TYPE="submit" VALUE="Search customers"><BR>
+</FORM>
+<A HREF="<%= $url %>logout">Logout</A><BR><BR>
+
+</TD>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html
new file mode 100644
index 0000000..0a665c9
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html
@@ -0,0 +1,19 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+<%= $small_custview %>
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('order_pkg') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_provision.html b/fs_selfservice/FS-SelfService/cgi/agent_provision.html
new file mode 100644
index 0000000..8770e2f
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_provision.html
@@ -0,0 +1,25 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+ ? "<FONT SIZE=\"+2\"><B>$message</B></FONT><BR><BR>"
+ : ''
+%>
+
+<%= $small_custview %>
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('provision_list') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html
new file mode 100644
index 0000000..8d299cd
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html
@@ -0,0 +1,19 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+<%= $small_custview %>
+<BR>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('svc_acct') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/cvv2.html b/fs_selfservice/FS-SelfService/cgi/cvv2.html
new file mode 100644
index 0000000..b178c85
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/cvv2.html
@@ -0,0 +1,25 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ CVV2 information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ The CVV2 number (also called CVC2 or CID) is a three- or four-digit
+ security code used to reduce credit card fraud.<BR><BR>
+ <TABLE BORDER=0 CELLSPACING=4>
+ <TR>
+ <TH>Visa / MasterCard / Discover</TH>
+ <TH>American Express</TH>
+ </TR>
+ <TR>
+ <TD>
+ <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="cvv2.png">
+ </TD>
+ <TD>
+ <IMG BORDER=0 ALT="American Express" SRC="cvv2_amex.png">
+ </TD>
+ </TABLE>
+ <CENTER><A HREF="javascript:close()">(close window)</A></CENTER>
+ </BODY>
+</HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/cvv2.png b/fs_selfservice/FS-SelfService/cgi/cvv2.png
new file mode 100644
index 0000000..4610dcb
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/cvv2.png
Binary files differ
diff --git a/fs_selfservice/FS-SelfService/cgi/cvv2_amex.png b/fs_selfservice/FS-SelfService/cgi/cvv2_amex.png
new file mode 100644
index 0000000..21c36a0
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/cvv2_amex.png
Binary files differ
diff --git a/fs_selfservice/FS-SelfService/cgi/delete_svc.html b/fs_selfservice/FS-SelfService/cgi/delete_svc.html
new file mode 100644
index 0000000..16054a7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/delete_svc.html
@@ -0,0 +1,18 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!;
+} else {
+ $OUT .= "<FONT SIZE=4>$svc removed.</FONT>";
+} %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/list_customers.html b/fs_selfservice/FS-SelfService/cgi/list_customers.html
new file mode 100644
index 0000000..858e5e9
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/list_customers.html
@@ -0,0 +1,39 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%=
+ if ( @customers ) {
+ $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#cccccc" COLSPAN=3>Customers</TH><TD>';
+ my $col1 = "ffffff";
+ my $col2 = "dddddd";
+ my $col = $col1;
+
+ foreach my $customer ( @customers ) {
+ my $td = qq!<TD BGCOLOR="#$col">!;
+ my $a = qq!<A HREF="${url}view_customer;custnum=!.
+ $customer->{'custnum'}. '">';
+ $OUT .=
+ '<TR>'.
+ "$td<FONT COLOR=\"". $customer->{'statuscolor'}. '">'.
+ ucfirst($customer->{'status'}). "</TD>". "$td</TD>".
+ "$td$a". $customer->{'name'}. "</A></TD>".
+ '</TR>';
+ #"$td</TD>".
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+ $OUT .= '</TABLE>';
+ } else {
+ $OUT .= 'No customers.<BR><BR>';
+ }
+%>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/login.html b/fs_selfservice/FS-SelfService/cgi/login.html
new file mode 100644
index 0000000..5607de7
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/login.html
@@ -0,0 +1,29 @@
+<HTML><HEAD><TITLE>Login</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=5>Login</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="login">
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+<TR>
+ <TH ALIGN="right">Username </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>">
+ </TD>
+</TR>
+<TR>
+ <TH ALIGN="right">Domain </TH>
+ <TD>
+ <INPUT TYPE="text" NAME="domain" VALUE="<%= $domain %>">
+ </TD>
+</TR>
+<!--<INPUT TYPE="hidden" NAME="domain" VALUE="myisp.com">-->
+<TR>
+ <TH ALIGN="right">Password </TH>
+ <TD>
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+</TR>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" VALUE="Login">
+</FORM></BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/logout.html b/fs_selfservice/FS-SelfService/cgi/logout.html
new file mode 100644
index 0000000..0e774e9
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/logout.html
@@ -0,0 +1,5 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+You have been logged out.
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/make_payment.html b/fs_selfservice/FS-SelfService/cgi/make_payment.html
new file mode 100644
index 0000000..3522c08
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/make_payment.html
@@ -0,0 +1,119 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Make a payment</FONT><BR><BR>
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="<%=$selfurl%>" onSubmit="document.OneTrueForm.process.disabled=true">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
+<INPUT TYPE="hidden" NAME="action" VALUE="payment_results">
+<TABLE BGCOLOR="#cccccc">
+<TR>
+ <TD ALIGN="right">Amount&nbsp;Due</TD>
+ <TD>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<%=sprintf("%.2f",$balance)%>
+ </TD></TR></TABLE>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Payment&nbsp;amount</TD>
+ <TD>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<%=sprintf("%.2f",$balance)%>">
+ </TD></TR></TABLE>
+ </TD>
+</TR><TR>
+ <TD ALIGN="right">Card&nbsp;type</TD>
+ <TD>
+ <SELECT NAME="card_type"><OPTION></OPTION>
+ <%= foreach ( keys %card_types ) {
+ $selected = $card_type eq $card_types{$_} ? ' SELECTED' : '';
+ $OUT .= qq(<OPTION$selected VALUE="). $card_types{$_}. qq(">$_\n);
+ } %>
+ </SELECT>
+ </TD>
+</TD><TR>
+ <TD ALIGN="right">Card&nbsp;number</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%=$payinfo%>"> </TD>
+ <TD>Exp.</TD>
+ <TD>
+ <SELECT NAME="month">
+ <%= for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) {
+ $OUT .= '<OPTION'. ($_ == $month ? ' SELECTED' : ''). ">$_\n";
+ } %>
+ </SELECT>
+ </TD>
+ <TD> / </TD>
+ <TD>
+ <SELECT NAME="year">
+ <%= my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) {
+ $OUT .= '<OPTION'. ($_ == $year ? ' SELECTED' : ''). ">$_\n";
+ } %>
+ </SELECT>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+</TR><TR>
+ <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+</TR><TR>
+ <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%=$address1%>">
+ </TD>
+</TR><TR>
+ <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%=$address2%>">
+ </TD>
+</TR><TR>
+ <TD ALIGN="right">City</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%=$city%>">
+ </TD>
+ <TD>State</TD>
+ <TD>
+ <SELECT NAME="state">
+ <%= for ( @states ) {
+ $OUT .= '<OPTION'. ($_ eq $state ? ' SELECTED' : '' ). ">$_\n";
+ } %>
+ </SELECT>
+ </TD>
+ <TD>Zip</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%=$zip%>">
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+</TR><TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
+ Remember this information
+ </TD>
+</TR><TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox"<%= $payby eq 'CARD' ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
+ Charge future payments to this card automatically
+ </TD>
+</TR>
+</TABLE>
+<BR>
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%=$paybatch%>">
+<INPUT TYPE="submit" NAME="process" VALUE="Process payment"> <!-- onClick="this.disabled=true"> -->
+</FORM>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount.html b/fs_selfservice/FS-SelfService/cgi/myaccount.html
new file mode 100644
index 0000000..9997d70
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/myaccount.html
@@ -0,0 +1,46 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+Hello <%= $name %>!<BR><BR>
+<%= $small_custview %>
+<BR>
+<%= if ( $balance > 0 ) {
+ $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR><BR>!;
+} %>
+<%=
+ if ( @open_invoices ) {
+ $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+ '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>';
+ my $link = qq!<A HREF="<%= $url %>myaccount!;
+ my $col1 = "ffffff";
+ my $col2 = "dddddd";
+ my $col = $col1;
+
+ foreach my $invoice ( @open_invoices ) {
+ my $td = qq!<TD BGCOLOR="#$col">!;
+ my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">';
+ $OUT .=
+ "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td</TD>".
+ "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>".
+ qq!<TD BGCOLOR="#$col" ALIGN="right">$a\$!. $invoice->{'owed'}.
+ '</A></TD>'.
+ '</TR>';
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+ $OUT .= '</TABLE>';
+ } else {
+ $OUT .= 'You have no outstanding invoices.<BR><BR>';
+ }
+%>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
new file mode 100644
index 0000000..ba3b3f2
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
@@ -0,0 +1,13 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TD VALIGN="top" HEIGHT=384 BGCOLOR="#dddddd">
+
+<A HREF="<%= $url %>myaccount">Overview</A><BR><BR>
+<!--A HREF="<%= $url %>change_bill"-->Change&nbsp;payment&nbsp;info</A>&nbsp;*<BR><BR>
+<!--A HREF="<%= $url %>change_ship"-->Change&nbsp;service&nbsp;address</A>&nbsp;*<BR><BR>
+<A HREF="<%= $url %>provision">Setup&nbsp;my&nbsp;services</A><BR><BR>
+<!--A HREF="<%= $url %>order"-->Purchase&nbsp;additional&nbsp;package</A>&nbsp;*<BR><BR>
+<!--<A HREF="<%= $url %>pw_list">Change&nbsp;password(s)</A>&nbsp;*<BR><BR>-->
+<A HREF="passwd.html">Change&nbsp;password(s)</A><BR><BR>
+<A HREF="<%= $url %>logout">Logout</A><BR><BR>
+*&nbsp;coming&nbsp;soon
+</TD>
diff --git a/fs_selfservice/FS-SelfService/cgi/order_pkg.html b/fs_selfservice/FS-SelfService/cgi/order_pkg.html
new file mode 100644
index 0000000..9cdd4cd
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/order_pkg.html
@@ -0,0 +1,75 @@
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+ if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) {
+ document.OrderPkgForm.submit.disabled = false;
+ } else {
+ document.OrderPkgForm.submit.disabled = true;
+ }
+}
+</SCRIPT>
+<FONT SIZE=4>Purchase additional package</FONT><BR><BR>
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+<FORM NAME="OrderPkgForm" ACTION="<%= $selfurl %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_order_pkg">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>">
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart" onChange="enable_order_pkg()">
+ <OPTION VALUE="">
+
+ <%=
+ foreach my $part_pkg ( @part_pkg ) {
+ $OUT .= '<OPTION VALUE="'. $part_pkg->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED' if $pkgpart && $part_pkg->{'pkgpart'} == $pkgpart;
+ $OUT .= '>'. $part_pkg->{'pkg'};
+ }
+ %>
+
+ </SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $_password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $_password2 %>"></TD>
+</TR>
+<%=
+ if ( $security_phrase ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( @svc_acct_pop ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector( 'popnum' => $popnum,
+ 'pops' => \@svc_acct_pop,
+ 'init_popstate' => $init_popstate,
+ 'popac' => $popac,
+ 'acstate' => $acstate,
+ ).
+ '</TD></TR>';
+ } else {
+ $OUT .= popselector(popnum=>$popnum, pops=>\@svc_acct_pop);
+ }
+%>
+</TABLE>
+<INPUT NAME="submit" TYPE="submit" VALUE="Purchase" disabled>
+</FORM>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.cgi b/fs_selfservice/FS-SelfService/cgi/passwd.cgi
new file mode 100755
index 0000000..d77876e
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/passwd.cgi
@@ -0,0 +1,60 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use Getopt::Std;
+use FS::SelfService qw(passwd);
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+die "passwd.cgi isn't running as freeside user\n" if $> != $freeside_uid;
+
+my $cgi = new CGI;
+
+$cgi->param('username') =~ /^([^\n]{0,255}$)/ or die "Illegal username";
+my $me = $1;
+
+$cgi->param('domain') =~ /^([^\n]{0,255}$)/ or die "Illegal domain";
+my $domain = $1;
+
+$cgi->param('old_password') =~ /^([^\n]{0,255}$)/ or die "Illegal old_password";
+my $old_password = $1;
+
+$cgi->param('new_password') =~ /^([^\n]{0,255}$)/ or die "Illegal new_password";
+my $new_password = $1;
+
+die "New passwords don't match"
+ unless $new_password eq $cgi->param('new_password2');
+
+my $rv = passwd(
+ 'username' => $me,
+ 'domain' => $domain,
+ 'old_password' => $old_password,
+ 'new_password' => $new_password,
+);
+
+my $error = $rv->{error};
+
+if ($error) {
+ die $error;
+} else {
+ print $cgi->header(), <<END;
+<html>
+ <head>
+ <title>Password changed</title>
+ </head>
+ <body bgcolor="#e8e8e8">
+ <h3>Password changed</h3>
+<br>Your password has been changed.
+ </body>
+</html>
+END
+}
diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.html b/fs_selfservice/FS-SelfService/cgi/passwd.html
new file mode 100644
index 0000000..459c96a
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/passwd.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <title>Change password</title>
+ </head>
+ <body bgcolor="#e8e8e8">
+ <h3>Change password</h3>
+ <form action="passwd.cgi" method="post">
+ <table bgcolor="#cccccc" border=0 cellspacing=2>
+ <tr><th align="right">Username</th>
+ <td><input type="text" name="username" size="18"></td>
+ </tr>
+ <tr><th align="right">Domain</th>
+ <td><input type="text" name="domain" size="18"></td>
+ </tr>
+ <tr><th align="right">Current password</th>
+ <td><input type="password" name="old_password" size="18"></td>
+ </tr>
+ <tr><th align="right">New password</th>
+ <td><input type="password" name="new_password" size="18"></td>
+ </tr>
+ <tr><th align="right">Re-enter new password</th>
+ <td><input type="password" name="new_password2" size="18"></td>
+ </tr>
+ </table>
+ <br><input type="submit" value="Change password">
+ </body>
+</html>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/payment_results.html b/fs_selfservice/FS-SelfService/cgi/payment_results.html
new file mode 100644
index 0000000..44289de
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/payment_results.html
@@ -0,0 +1,17 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Payment results</FONT><BR><BR>
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!;
+} else {
+ $OUT .= 'Your payment was processed sucessfully. Thank you.';
+} %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html
new file mode 100644
index 0000000..7052059
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html
@@ -0,0 +1,14 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE=4><%= $svc %> setup sucessfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html
new file mode 100644
index 0000000..772cf08
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html
@@ -0,0 +1,16 @@
+<HTML><HEAD><TITLE><%= $error ? 'MyAccount' : sprintf("Your serial number is %010d-$title", $id) %></TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE=4><%= $svc %> setup sucessfully.</FONT>
+
+<BR><BR>Your serial number is <%= sprintf("%010d-$title", $id) %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/provision.html b/fs_selfservice/FS-SelfService/cgi/provision.html
new file mode 100644
index 0000000..6d80e89
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision.html
@@ -0,0 +1,11 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<%= include('provision_list') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/provision_list.html b/fs_selfservice/FS-SelfService/cgi/provision_list.html
new file mode 100644
index 0000000..0c8e050
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision_list.html
@@ -0,0 +1,87 @@
+<FONT SIZE=4>Setup services</FONT><BR><BR>
+
+<SCRIPT>
+function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#ffffff">
+
+<%= foreach my $pkg (
+ grep { scalar(@{$_->{part_svc}})
+ || scalar(@{$_->{cust_svc}})
+ } @cust_pkg
+ ) {
+
+ $OUT .= #'<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#ffffff">'.
+ '<TR><TH BGCOLOR="#6666ff" COLSPAN=3>'.
+ $pkg->{'pkg'}.
+ '</TH></TR>';
+
+ my $col1 = "ffffff";
+ my $col2 = "dddddd";
+ my $col = $col1;
+
+ foreach my $cust_svc ( @{ $pkg->{cust_svc} } ) {
+ my $td = qq!<TD BGCOLOR="#$col"!;
+
+ $OUT .= '<TR>'.
+ "$td ALIGN=right>". $cust_svc->{label}[0]. ': </TD>'.
+ "$td><B>". $cust_svc->{label}[1]. '</B>';
+ $OUT .= '<BR><I>password: '. encode_entities($cust_svc->{_password}). '</I>'
+ if exists($cust_svc->{_password});
+ $OUT .= '</TD>'.
+ "$td><FONT SIZE=-1>";
+
+ #if ( $cust_svc->{label}[2] eq 'svc_acct' ) {
+ # $OUT .= qq!(<A HREF="${url}changepw;svcnum=$cust_svc->{'svcnum'}">!.
+ # 'change&nbsp;pw) ';
+ #}
+
+ unless ( $cust_svc->{'svcnum'} == $svcnum ) {
+ $OUT .= qq!(<A HREF="javascript:areyousure('${url}delete_svc;svcnum=$cust_svc->{svcnum}', 'This will perminantly delete the $cust_svc->{label}[1] $cust_svc->{label}[0]. Are you sure?')">!.
+ 'delete</A>)';
+
+ }
+ $OUT .= '</FONT></TD></TR>';
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+
+ $OUT .= '<TR><TD COLSPAN=3 BGCOLOR="#000000"></TD></TR>'
+ if scalar(@{$pkg->{part_svc}}) && scalar(@{$pkg->{cust_svc}});
+
+ $col = $col1;
+
+ foreach my $part_svc ( @{ $pkg->{part_svc} } ) {
+
+ my $td = qq!<TD BGCOLOR="#$col"!;
+
+ my $link;
+
+ if ( $part_svc->{'svcdb'} eq 'svc_external'
+ #&& $conf->exists('svc_external-skip_manual')
+ ) {
+ $link = "${url}process_svc_external;".
+ "pkgnum=$pkg->{'pkgnum'};".
+ "svcpart=$part_svc->{'svcpart'}";
+ } else {
+ $link = "${url}provision_svc;".
+ "pkgnum=$pkg->{'pkgnum'};".
+ "svcpart=$part_svc->{'svcpart'}";
+ }
+
+ $OUT .= "<TR>$td COLSPAN=3 ALIGN=center>".
+ qq!<A HREF="$link">!. 'Setup '. $part_svc->{'svc'}. '</A> '.
+ '('. $part_svc->{'num_avail'}. ' available)'.
+ '</TD></TR>';
+ $col = $col eq $col1 ? $col2 : $col1;
+ }
+
+ #$OUT .= '</TABLE><BR>';
+ $OUT .= '<TR><TD BGCOLOR="#eeeeee" COLSPAN=3>&nbsp;</TD></TR>';
+
+} %>
+
+</TABLE>
diff --git a/fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html
new file mode 100644
index 0000000..cf35857
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html
@@ -0,0 +1,12 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<%= include('svc_acct') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
new file mode 100644
index 0000000..0816758
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi
@@ -0,0 +1,287 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($cgi $session_id $form_max $template_dir);
+use subs qw(do_template);
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use Text::Template;
+use HTML::Entities;
+use FS::SelfService qw( login customer_info invoice
+ payment_info process_payment
+ list_pkgs
+ part_svc_info provision_acct provision_external
+ unprovision_svc
+ );
+
+$template_dir = '.';
+
+$form_max = 255;
+
+$cgi = new CGI;
+
+unless ( defined $cgi->param('session') ) {
+ do_template('login',{});
+ exit;
+}
+
+if ( $cgi->param('session') eq 'login' ) {
+
+ $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i
+ or die "illegal username";
+ my $username = $1;
+
+ $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/
+ or die "illegal domain";
+ my $domain = $1;
+
+ $cgi->param('password') =~ /^(.{0,$form_max})$/
+ or die "illegal password";
+ my $password = $1;
+
+ my $rv = login(
+ 'username' => $username,
+ 'domain' => $domain,
+ 'password' => $password,
+ );
+ if ( $rv->{error} ) {
+ do_template('login', {
+ 'error' => $rv->{error},
+ 'username' => $username,
+ 'domain' => $domain,
+ } );
+ exit;
+ } else {
+ $cgi->param('session' => $rv->{session_id} );
+ $cgi->param('action' => 'myaccount' );
+ }
+}
+
+$session_id = $cgi->param('session');
+
+#order|pw_list XXX ???
+$cgi->param('action') =~
+ /^(myaccount|view_invoice|make_payment|payment_results|logout|change_bill|change_ship|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc)$/
+ or die "unknown action ". $cgi->param('action');
+my $action = $1;
+
+my $result = eval "&$action();";
+die $@ if $@;
+
+if ( $result->{error} eq "Can't resume session" ) { #ick
+ do_template('login',{});
+ exit;
+}
+
+#warn $result->{'open_invoices'};
+#warn scalar(@{$result->{'open_invoices'}});
+
+warn "processing template $action\n";
+do_template($action, {
+ 'session_id' => $session_id,
+ %{$result}
+});
+
+#--
+
+sub myaccount { customer_info( 'session_id' => $session_id ); }
+
+sub view_invoice {
+
+ $cgi->param('invnum') =~ /^(\d+)$/ or die "illegal invnum";
+ my $invnum = $1;
+
+ invoice( 'session_id' => $session_id,
+ 'invnum' => $invnum,
+ );
+
+}
+
+sub make_payment {
+ payment_info( 'session_id' => $session_id );
+}
+
+sub payment_results {
+
+ use Business::CreditCard;
+
+ $cgi->param('amount') =~ /^\s*(\d+(\.\d{2})?)\s*$/
+ or die "illegal amount"; #!!!
+ my $amount = $1;
+
+ my $payinfo = $cgi->param('payinfo');
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^(\d{13,16})$/
+ #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ or die "illegal card"; #!!!
+ $payinfo = $1;
+ validate($payinfo)
+ #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ or die "invalid card"; #!!!
+ cardtype($payinfo) eq $cgi->param('card_type')
+ #or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
+ or die "not a ". $cgi->param('card_type');
+
+ $cgi->param('month') =~ /^(\d{2})$/ or die "illegal month";
+ my $month = $1;
+ $cgi->param('year') =~ /^(\d{4})$/ or die "illegal year";
+ my $year = $1;
+
+ $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
+ my $payname = $1;
+
+ $cgi->param('address1') =~ /^(.{0,80})$/ or die "illegal address1";
+ my $address1 = $1;
+
+ $cgi->param('address2') =~ /^(.{0,80})$/ or die "illegal address2";
+ my $address2 = $1;
+
+ $cgi->param('city') =~ /^(.{0,80})$/ or die "illegal city";
+ my $city = $1;
+
+ $cgi->param('state') =~ /^(.{2})$/ or die "illegal state";
+ my $state = $1;
+
+ $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
+ my $zip = $1;
+
+ my $save = 0;
+ $save = 1 if $cgi->param('save');
+
+ my $auto = 0;
+ $auto = 1 if $cgi->param('auto');
+
+ $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
+ my $paybatch = $1;
+
+ process_payment(
+ 'session_id' => $session_id,
+ 'amount' => $amount,
+ 'payinfo' => $payinfo,
+ 'month' => $month,
+ 'year' => $year,
+ 'payname' => $payname,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'save' => $save,
+ 'auto' => $auto,
+ 'paybatch' => $paybatch,
+ );
+
+}
+
+sub logout {
+ FS::SelfService::logout( 'session_id' => $session_id );
+}
+
+sub provision {
+ my $result = list_pkgs( 'session_id' => $session_id );
+ die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+ $result;
+}
+
+sub provision_svc {
+
+ my $result = part_svc_info(
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( pkgnum svcpart ),
+ );
+ die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+
+ $result->{'svcdb'} =~ /^svc_(.*)$/
+ #or return { 'error' => 'Unknown svcdb '. $result->{'svcdb'} };
+ or die 'Unknown svcdb '. $result->{'svcdb'};
+ $action .= "_$1";
+
+ $result;
+}
+
+sub process_svc_acct {
+
+ my $result = provision_acct (
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw(
+ pkgnum svcpart username _password _password2 sec_phrase popnum )
+ );
+
+ if ( exists $result->{'error'} && $result->{'error'} ) {
+ #warn "$result $result->{'error'}";
+ $action = 'provision_svc_acct';
+ return {
+ $cgi->Vars,
+ %{ part_svc_info( 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
+ )
+ },
+ 'error' => $result->{'error'},
+ };
+ } else {
+ #warn "$result $result->{'error'}";
+ return $result;
+ }
+
+}
+
+sub process_svc_external {
+ provision_external (
+ 'session_id' => $session_id,
+ map { $_ => $cgi->param($_) } qw( pkgnum svcpart )
+ );
+}
+
+sub delete_svc {
+ unprovision_svc(
+ 'session_id' => $session_id,
+ 'svcnum' => $cgi->param('svcnum'),
+ );
+}
+
+#--
+
+sub do_template {
+ my $name = shift;
+ my $fill_in = shift;
+
+ $cgi->delete_all();
+ $fill_in->{'selfurl'} = $cgi->self_url;
+ $fill_in->{'cgi'} = \$cgi;
+
+ my $template = new Text::Template( TYPE => 'FILE',
+ SOURCE => "$template_dir/$name.html",
+ DELIMITERS => [ '<%=', '%>' ],
+ UNTAINT => 1, )
+ or die $Text::Template::ERROR;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
+ HASH => $fill_in
+ );
+}
+
+#*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
+
+package FS::SelfService::_selfservicecgi;
+
+#use FS::SelfService qw(regionselector expselect popselector);
+use HTML::Entities;
+use FS::SelfService qw(popselector);
+
+#false laziness w/agent.cgi
+sub include {
+ my $name = shift;
+ my $template = new Text::Template( TYPE => 'FILE',
+ SOURCE => "$main::template_dir/$name.html",
+ DELIMITERS => [ '<%=', '%>' ],
+ UNTAINT => 1,
+ )
+ or die $Text::Template::ERROR;
+
+ $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
+ #HASH => $fill_in
+ );
+
+}
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup.html b/fs_selfservice/FS-SelfService/cgi/signup.html
new file mode 100755
index 0000000..3532527
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/signup.html
@@ -0,0 +1,233 @@
+<HTML><HEAD><TITLE><%= $agent || 'ISP' %> Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8" onUnload="myclose()">
+<script language="JavaScript"><!--
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1
+ }
+//--></script>
+<FONT SIZE=7><%= $agent || 'ISP' %> Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM NAME="OneTrueForm" ACTION="<%= $selfurl %>" METHOD=POST onSubmit="document.OneTrueForm.signup.disabled=true">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_signup">
+<INPUT TYPE="hidden" NAME="ref" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+Where did you hear about our service? <SELECT NAME="refnum">
+<%=
+ $OUT .= '<OPTION VALUE="">' unless $refnum;
+ foreach my $part_referral ( @part_referral ) {
+ $OUT .= '<OPTION VALUE="'. $part_referral->{'refnum'}. '"';
+ $OUT .= ' SELECTED' if $part_referral->{'refnum'} eq $refnum;
+ $OUT .= '>'. $part_referral->{'referral'};
+ }
+%>
+</SELECT><BR><BR>
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ regionselector( {
+ selected_county => $county,
+ selected_state => $state,
+ selected_country => $country,
+ default_state => $statedefault,
+ default_country => $countrydefault,
+ locales => \@cust_main_county,
+ } );
+
+ "$county_html $state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+ <TD><%= $country_html %></TD>
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+<BR>Billing information<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR><TD>
+
+ <%=
+ $OUT .= '<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"';
+ my @invoicing_list = split(', ', $invoicing_list );
+ $OUT .= ' CHECKED'
+ if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list;
+ $OUT .= '>';
+ %>
+
+ Postal mail invoice
+</TD></TR>
+<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>">
+</TD></TR>
+<%= scalar(@payby) > 1 ? '<TR><TD>Billing type</TD></TR>' : '' %>
+</TABLE>
+<TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%">
+<TR>
+
+ <%=
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ my %types = (
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ );
+ foreach ( keys %types ) {
+ $selected = $CARD_type eq $types{$_} ? 'SELECTED' : '';
+ $cardselect .= qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!;
+ }
+ $cardselect .= '</SELECT>';
+
+ my %payby = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!,
+ );
+
+ if ( $cvv_enabled ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $payby{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+ my %paybychecked = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", $paydate). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("COMP", $paydate),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="$payinfo" MAXLENGTH=80>!,
+ );
+
+ if ( $cvv_enabled ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $paybychecked{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ for (@payby) {
+ if ( scalar(@payby) == 1) {
+ $OUT .= '<TD VALIGN=TOP>'.
+ qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$_">!.
+ "$paybychecked{$_}</TD>";
+ } else {
+ $OUT .= qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($payby eq $_) {
+ $OUT .= qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ $OUT .= qq!> $payby{$_}</TD>!;
+ }
+
+ }
+ }
+ %>
+
+</TR></TABLE><font color="#ff0000">*</font> required fields for each billing type
+<BR><BR>First package
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart"><OPTION VALUE="">(none)
+
+ <%=
+ foreach my $part_pkg ( @part_pkg ) {
+ $OUT .= '<OPTION VALUE="'. $part_pkg->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED' if $pkgpart && $part_pkg->{'pkgpart'} == $pkgpart;
+ $OUT .= '>'. $part_pkg->{'pkg'};
+ }
+ %>
+
+ </SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $_password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $_password2 %>"></TD>
+</TR>
+<%=
+ if ( $security_phrase ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( @svc_acct_pop ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector( 'popnum' => $popnum,
+ 'pops' => \@svc_acct_pop,
+ 'init_popstate' => $init_popstate,
+ 'popac' => $popac,
+ 'acstate' => $acstate,
+ ).
+ '</TD></TR>';
+ } else {
+ $OUT .= popselector(popnum=>$popnum, pops=>\@svc_acct_pop);
+ }
+%>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup">
+</FORM></BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/svc_acct.html b/fs_selfservice/FS-SelfService/cgi/svc_acct.html
new file mode 100644
index 0000000..abed878
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/svc_acct.html
@@ -0,0 +1,55 @@
+<FONT SIZE=4>Setup <%= $svc %></FONT><BR><BR>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error setting up $svc: $error!.
+ '</FONT><BR><BR>';
+} ''; %>
+<FORM ACTION="<%= $selfurl %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_svc_acct">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>">
+<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#cccccc">
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $_password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $_password2 %>"></TD>
+</TR>
+<%=
+ if ( $security_phrase ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( @svc_acct_pop ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector( 'popnum' => $popnum,
+ 'pops' => \@svc_acct_pop,
+ 'init_popstate' => $init_popstate,
+ 'popac' => $popac,
+ 'acstate' => $acstate,
+ ).
+ '</TD></TR>';
+ } else {
+ $OUT .= popselector(popnum=>$popnum, pops=>\@svc_acct_pop);
+ }
+%>
+</TABLE>
+<INPUT TYPE="submit" VALUE="Setup">
+</FORM>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_customer.html b/fs_selfservice/FS-SelfService/cgi/view_customer.html
new file mode 100644
index 0000000..11e4432
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_customer.html
@@ -0,0 +1,29 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+ ? "<FONT SIZE=\"+2\"><B>$message</B></FONT><BR><BR>"
+ : ''
+%>
+
+<%= $small_custview %>
+
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/view_invoice.html b/fs_selfservice/FS-SelfService/cgi/view_invoice.html
new file mode 100644
index 0000000..46f7318
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/cgi/view_invoice.html
@@ -0,0 +1,18 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE="-1"><PRE>
+<%= $invoice_text %>
+</FONT></PRE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd
new file mode 100644
index 0000000..ededfa6
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd
@@ -0,0 +1,271 @@
+#!/usr/bin/perl -w
+#
+# freeside-selfservice-clientd
+#
+# This is run REMOTELY over ssh by freeside-selfservice-server
+
+use strict;
+use subs qw(spawn logmsg lock_write unlock_write);
+use Fcntl qw(:flock);
+use POSIX qw(:sys_wait_h);
+use Socket;
+use Storable 2.09 qw(nstore_fd fd_retrieve);
+use IO::Handle qw(_IONBF);
+use IO::Select;
+use IO::File;
+
+#STDOUT->setbuf('');
+
+my $tag = scalar(@ARGV) ? '.'.shift : '';
+
+use vars qw( $Debug );
+$Debug = 2; #2 will turn on child logging
+ #3 will log packet contents,#including passwords
+ #4 will log receipts of all packets from server including
+ # keepalives (big!)
+
+my $socket = "/usr/local/freeside/selfservice_socket$tag";
+my $pid_file = "$socket.pid";
+
+my $log_file = "/usr/local/freeside/selfservice$tag.log";
+
+my $lock_file = "/usr/local/freeside/selfservice$tag.writelock";
+
+#my $me = '[client]';
+
+$|=1;
+
+$SIG{__WARN__} = \&_logmsg;
+
+#read data to be cached or something
+#warn "$me Reading init data\n" if $Debug;
+#my $signup_init =
+
+warn "Creating $lock_file\n" if $Debug;
+open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+close LOCKFILE;
+
+warn "Creating $socket\n" if $Debug;
+my $uaddr = sockaddr_un($socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+if ( -e $pid_file ) {
+ open(PIDFILE,"<$pid_file");
+ my $old_pid = <PIDFILE>;
+ close PIDFILE;
+ $old_pid =~ /^(\d+)$/;
+ kill 'TERM', $1;
+}
+open(PIDFILE,">$pid_file");
+print PIDFILE "$$\n";
+close PIDFILE;
+
+#my $waitedpid;
+#sub REAPER { $waitedpid = wait; $SIG{CHLD} = \&REAPER; }
+#$SIG{CHLD} = \&REAPER;
+
+warn "enabling keep alives\n" if $Debug;
+nstore_fd( { _packet => '_enable_keepalive' } , \*STDOUT );
+
+warn "entering main loop\n" if $Debug;
+
+my %kids;
+
+my $s = new IO::Select;
+$s->add(\*STDIN);
+$s->add(\*Server);
+
+#for ( $waitedpid = 0;
+# accept(Client,Server) || $waitedpid;
+# $waitedpid = 0, close Client)
+#{
+# next if $waitedpid;
+
+#$SIG{PIPE} = sub { warn "SIGPIPE received" };
+#$SIG{CHLD} = sub { warn "SIGCHLD received" };
+
+#sub REAPER { warn "SIGCHLD received"; my $pid = wait; $SIG{CHLD} = \&REAPER; }
+#sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; }
+#sub REAPER { my $pid = wait; delete $kids{$pid}; $SIG{CHLD} = \&REAPER; }
+#$SIG{CHLD} = \&REAPER;
+
+my $undisp = 0;
+while (1) {
+
+ &reap_kids;
+
+ warn "waiting for connection\n" if $Debug && !$undisp;
+
+ #my @handles = $s->can_read();
+ my @handles = $s->can_read(5);
+ $undisp = !scalar(@handles);
+ foreach my $handle ( @handles ) {
+
+ if ( $handle == \*STDIN ) {
+
+ warn "receiving packet from server\n" if $Debug > 3;
+
+ my $packet = fd_retrieve(\*STDIN);
+ my $token = $packet->{'_token'};
+
+ if ( $token eq '_keepalive' ) {
+ $undisp = 1;
+ next;
+ }
+
+ warn "received packet from server with token $token\n".
+ ( $Debug > 2
+ ? join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+ : '' )
+ if $Debug;
+
+ if ( exists($kids{$token}) ) {
+ warn "sending return packet to $token via $kids{$token}\n"
+ if $Debug;
+ nstore_fd($packet, $kids{$token});
+ warn "flushing to $token\n" if $Debug;
+ until ( $kids{$token}->flush ) {
+ warn "WARNING: error flushing: $!";
+ sleep 1;
+ }
+ #no close or delete here - will block waiting for child
+ warn "done with $token\n" if $Debug;
+ } else {
+ warn "WARNING: unknown token $token, discarding message";
+ }
+
+ } elsif ( $handle == \*Server ) {
+
+ until ( accept(Client, Server) ) {
+ warn "WARNING: accept failed: $!";
+ next;
+ }
+
+ warn "received local connection; forking\n" if $Debug;
+
+ spawn sub { #child
+ warn "[child-$$] reading packet from local client" if $Debug > 1;
+ my $packet = fd_retrieve(\*Client);
+ warn "[child-$$] packet received:\n".
+ join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+ if $Debug > 2;
+ my $command = $packet->{'command'};
+ #handle some commands weirdly?
+ $packet->{_token}=$$;
+
+ warn "[child-$$] locking write stream\n" if $Debug > 1;
+ lock_write;
+
+ warn "[child-$$] sending packet to remote server\n" if $Debug > 1;
+ nstore_fd($packet, \*STDOUT) or die "FATAL: can't send response: $!";
+
+ warn "[child-$$] flushing write stream\n" if $Debug > 1;
+ STDOUT->flush or die "FATAL: can't flush: $!";
+
+ warn "[child-$$] releasing write lock\n" if $Debug > 1;
+ unlock_write;
+
+ warn "[child-$$] closing write stream\n" if $Debug > 1;
+ close STDOUT or die "FATAL: can't close write stream: $!"; #??!
+
+ warn "[child-$$] waiting for response from parent\n" if $Debug > 1;
+ my $w = new IO::Select;
+ $w->add(\*STDIN);
+ until ( $w->can_read ) {
+ warn "[child-$$] WARNING: interrupted select: $!\n";
+ }
+ my $rv = fd_retrieve(\*STDIN);
+
+ #close STDIN;
+
+ warn "[child-$$] sending response to local client" if $Debug > 1;
+ nstore_fd($rv, \*Client);
+ Client->flush or die "FATAL: can't flush to local client: $!";
+ close Client or die "FATAL: can't close connection to local client: $!";
+
+ warn "[child-$$] child exiting" if $Debug > 1;
+ exit;
+
+ }; #eo child
+
+ #close Client;
+
+ } else {
+ die "wtf? $handle";
+ }
+
+ }
+
+}
+
+sub reap_kids {
+ #warn "reaping kids\n";
+ foreach my $pid ( keys %kids ) {
+ my $kid = waitpid($pid, WNOHANG);
+ if ( $kid > 0 ) {
+ close $kids{$kid};
+ delete $kids{$kid};
+ }
+ }
+ #warn "done reaping\n";
+}
+
+sub spawn {
+ my $coderef = shift;
+
+ unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
+ use Carp;
+ confess "usage: spawn CODEREF";
+ }
+
+ my $pid;
+ #if (!defined($pid = fork)) {
+ my $kid = new IO::Handle;
+ if (!defined($pid = open($kid, '|-'))) {
+ warn "WARNING: cannot fork: $!";
+ return;
+ } elsif ($pid) {
+ warn "begat $pid" if $Debug;
+ $kids{$pid} = $kid;
+ #$kids{$pid}->autoflush;
+ return; # I'm the parent
+ }
+ # else I'm the child -- go spawn
+
+# open(STDIN, "<&Client") || die "can't dup client to stdin";
+# open(STDOUT, ">&Client") || die "can't dup client to stdout";
+# open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr";
+ exit &$coderef();
+}
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ die "can't open $log_file: $!" unless defined($log);
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[client] [". scalar(localtime). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+ close $log;
+}
+
+sub lock_write {
+ #broken on freebsd?
+ #flock(STDOUT, LOCK_EX) or die "FATAL: can't lock write stream: $!";
+
+ #open a new one for each kid to get a unique lock
+ open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+
+ flock(LOCKFILE, LOCK_EX) or die "FATAL: can't lock $lock_file: $!";
+}
+
+sub unlock_write {
+ #broken on freebsd?
+ #flock(STDOUT, LOCK_UN) or die "FATAL: can't release write lock: $!";
+
+ flock(LOCKFILE, LOCK_UN) or die "FATAL: can't unlock $lock_file: $!";
+}
diff --git a/fs_selfservice/FS-SelfService/test.pl b/fs_selfservice/FS-SelfService/test.pl
new file mode 100644
index 0000000..7468ea4
--- /dev/null
+++ b/fs_selfservice/FS-SelfService/test.pl
@@ -0,0 +1,17 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test;
+BEGIN { plan tests => 1 };
+use FS::SelfService;
+ok(1); # If we made it this far, we're ok.
+
+#########################
+
+# Insert your test code below, the Test module is use()ed here so read
+# its man page ( perldoc Test ) for help writing this test script.
+
diff --git a/fs_selfservice/fs_passwd_test b/fs_selfservice/fs_passwd_test
new file mode 100755
index 0000000..4f8b8a8
--- /dev/null
+++ b/fs_selfservice/fs_passwd_test
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -w
+
+use strict;
+use FS::SelfService qw(passwd);
+
+my $rv = passwd(
+ 'username' => 'ivan',
+ 'old_password' => 'heyhoo',
+ 'new_password' => 'haloo',
+);
+my $error = $rv->{error};
+
+if ( $error eq 'Incorrect password.' ) {
+ exit;
+} else {
+ die $error if $error;
+ die "no error";
+}
+
diff --git a/fs_sesmon/FS-SessionClient/Changes b/fs_sesmon/FS-SessionClient/Changes
new file mode 100644
index 0000000..390a7b9
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/Changes
@@ -0,0 +1,5 @@
+Revision history for Perl extension FS::SessionClient
+
+0.01 Wed Oct 18 16:34:36 1999
+ - original version; created by ivan 1.0
+
diff --git a/fs_sesmon/FS-SessionClient/MANIFEST b/fs_sesmon/FS-SessionClient/MANIFEST
new file mode 100644
index 0000000..162d4e4
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/MANIFEST
@@ -0,0 +1,11 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+SessionClient.pm
+test.pl
+fs_sessiond
+cgi/login.cgi
+cgi/logout.cgi
+bin/freeside-login
+bin/freeside-logout
diff --git a/fs_sesmon/FS-SessionClient/MANIFEST.SKIP b/fs_sesmon/FS-SessionClient/MANIFEST.SKIP
new file mode 100644
index 0000000..ae335e7
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/MANIFEST.SKIP
@@ -0,0 +1 @@
+CVS/
diff --git a/fs_sesmon/FS-SessionClient/Makefile.PL b/fs_sesmon/FS-SessionClient/Makefile.PL
new file mode 100644
index 0000000..137b6b8
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/Makefile.PL
@@ -0,0 +1,10 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'FS::SessionClient',
+ 'VERSION_FROM' => 'SessionClient.pm', # finds $VERSION
+ 'EXE_FILES' => [ qw(fs_sessiond bin/freeside-login bin/freeside-logout) ],
+ 'INSTALLSCRIPT' => '/usr/local/sbin',
+ 'PERM_RWX' => '750',
+);
diff --git a/fs_sesmon/FS-SessionClient/SessionClient.pm b/fs_sesmon/FS-SessionClient/SessionClient.pm
new file mode 100644
index 0000000..8a0ff70
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/SessionClient.pm
@@ -0,0 +1,122 @@
+package FS::SessionClient;
+
+use strict;
+use vars qw($AUTOLOAD $VERSION @ISA @EXPORT_OK $fs_sessiond_socket);
+use Exporter;
+use Socket;
+use FileHandle;
+use IO::Handle;
+
+$VERSION = '0.01';
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( login logout portnum );
+
+$fs_sessiond_socket = "/usr/local/freeside/fs_sessiond_socket";
+
+$ENV{'PATH'} ='/usr/bin:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+=head1 NAME
+
+FS::SessionClient - Freeside session client API
+
+=head1 SYNOPSIS
+
+ use FS::SessionClient qw( login portnum logout );
+
+ $error = login ( {
+ 'username' => $username,
+ 'password' => $password,
+ 'login' => $timestamp,
+ 'portnum' => $portnum,
+ } );
+
+ $portnum = portnum( { 'ip' => $ip } ) or die "unknown ip!"
+ $portnum = portnum( { 'nasnum' => $nasnum, 'nasport' => $nasport } )
+ or die "unknown nasnum/nasport";
+
+ $error = logout ( {
+ 'username' => $username,
+ 'password' => $password,
+ 'logout' => $timestamp,
+ 'portnum' => $portnum,
+ } );
+
+=head1 DESCRIPTION
+
+This modules provides an API for a remote session application.
+
+It needs to be run as the freeside user. Because of this, the program which
+calls these subroutines should be written very carefully.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item login HASHREF
+
+HASHREF should have the following keys: username, password, login and portnum.
+login is a UNIX timestamp; if not specified, will default to the current time.
+Starts a new session for the specified user and portnum. The password is
+optional, but must be correct if specified.
+
+Returns a scalar error message, or the empty string for success.
+
+=item portnum
+
+HASHREF should contain a single key: ip, or the two keys: nasnum and nasport.
+Returns a portnum suitable for the login and logout subroutines, or false
+on error.
+
+=item logout HASHREF
+
+HASHREF should have the following keys: usrename, password, logout and portnum.
+logout is a UNIX timestamp; if not specified, will default to the current time.
+Starts a new session for the specified user and portnum. The password is
+optional, but must be correct if specified.
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub AUTOLOAD {
+ my $hashref = shift;
+ my $method = $AUTOLOAD;
+ $method =~ s/^.*:://;
+ socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+ connect(SOCK, sockaddr_un($fs_sessiond_socket)) or die "connect: $!";
+ print SOCK "$method\n";
+
+ print SOCK join("\n", %{$hashref}, 'END' ), "\n";
+ SOCK->flush;
+
+ chomp( my $r = <SOCK> );
+ $r;
+}
+
+=back
+
+=head1 VERSION
+
+$Id: SessionClient.pm,v 1.3 2000-12-03 20:25:20 ivan Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<fs_sessiond>
+
+=cut
+
+1;
+
+
+
diff --git a/fs_sesmon/FS-SessionClient/bin/freeside-login b/fs_sesmon/FS-SessionClient/bin/freeside-login
new file mode 100644
index 0000000..a6d4751
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/bin/freeside-login
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w freeside-logout
+
+use strict;
+use FS::SessionClient qw( login portnum );
+
+my $username = shift;
+
+my $portnum;
+if ( scalar(@ARGV) == 1 ) {
+ my $arg = shift;
+ if ( $arg =~ /^(\d+)$/ ) {
+ $portnum = $1;
+ } elsif ( $arg =~ /^([\d\.]+)$/ ) {
+ $portnum = portnum( { 'ip' => $1 } ) or die "unknown ip!"
+ } else {
+ &usage;
+ }
+} elsif ( scalar(@ARGV) == 2 ) {
+ $portnum = portnum( { 'nasnum' => shift, 'nasport' => shift } )
+ or die "unknown nasnum/nasport";
+} else {
+ &usage;
+}
+
+my $error = login ( {
+ 'username' => $username,
+ 'portnum' => $portnum,
+} );
+
+warn $error if $error;
+
+sub usage {
+ die "Usage:\n\n freeside-login username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/bin/freeside-logout b/fs_sesmon/FS-SessionClient/bin/freeside-logout
new file mode 100644
index 0000000..9b4ecfe
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/bin/freeside-logout
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w freeside-login
+
+use strict;
+use FS::SessionClient qw( logout portnum );
+
+my $username = shift;
+
+my $portnum;
+if ( scalar(@ARGV) == 1 ) {
+ my $arg = shift;
+ if ( $arg =~ /^(\d+)$/ ) {
+ $portnum = $1;
+ } elsif ( $arg =~ /^([\d\.]+)$/ ) {
+ $portnum = portnum( { 'ip' => $1 } ) or die "unknown ip!"
+ } else {
+ &usage;
+ }
+} elsif ( scalar(@ARGV) == 2 ) {
+ $portnum = portnum( { 'nasnum' => shift, 'nasport' => shift } )
+ or die "unknown nasnum/nasport";
+} else {
+ &usage;
+}
+
+my $error = logout ( {
+ 'username' => $username,
+ 'portnum' => $portnum,
+} );
+
+warn $error if $error;
+
+sub usage {
+ die "Usage:\n\n freeside-logout username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/cgi/login.cgi b/fs_sesmon/FS-SessionClient/cgi/login.cgi
new file mode 100644
index 0000000..0307c5a
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/cgi/login.cgi
@@ -0,0 +1,108 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w logout.cgi
+
+use strict;
+use vars qw( $cgi $username $password $error $ip $portnum );
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use FS::SessionClient qw( login portnum );
+
+$cgi = new CGI;
+
+if ( defined $cgi->param('magic') ) {
+ $cgi->param('username') =~ /^\s*(\w{1,255})\s*$/ or do {
+ $error = "Illegal username";
+ &print_form;
+ exit;
+ };
+ $username = $1;
+ $cgi->param('password') =~ /^([^\n]{0,255})$/ or die "guru meditation #420";
+ $password = $1;
+ #$ip = $cgi->remote_host;
+ $ip = $ENV{REMOTE_ADDR};
+ $ip =~ /^([\d\.]+)$/ or die "illegal ip: $ip";
+ $ip = $1;
+ $portnum = portnum( { 'ip' => $1 } ) or do {
+ $error = "You appear to be coming from an unknown IP address. Verify ".
+ "that your computer is set to obtain an IP address automatically ".
+ "via DHCP.";
+ &print_form;
+ exit;
+ };
+
+ ( $error = login ( {
+ 'username' => $username,
+ 'portnum' => $portnum,
+ 'password' => $password,
+ } ) )
+ ? &print_form()
+ : &print_okay();
+
+} else {
+ $username = '';
+ $password = '';
+ $error = '';
+ &print_form;
+}
+
+sub print_form {
+ my $self_url = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>login</TITLE></HEAD>
+<BODY BGCOLOR="#FFFFFF">
+END
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! if $error;
+
+print <<END;
+<FORM ACTION="$self_url" METHOD="POST">
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="4" ALIGN="center">
+<TR>
+ <TD ALIGN="center" COLSPAN="2">
+ <STRONG>Welcome</STRONG>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ Username
+ </TD>
+ <TD ALIGN="left">
+ <INPUT TYPE="text" NAME="username" VALUE="$username">
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ Password
+ </TD>
+ <TD ALIGN="left">
+ <INPUT TYPE="password" NAME="password">
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="center" COLSPAN="2">
+ <INPUT TYPE="submit" VALUE=" Login ">
+ </TD>
+</TR>
+</TABLE>
+</FORM>
+</BODY>
+</HTML>
+END
+
+}
+
+sub print_okay {
+ print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>login sucessful</TITLE></HEAD>
+<BODY>login successful, etc.
+</BODY>
+</HTML>
+END
+}
+
+sub usage {
+ die "Usage:\n\n freeside-login username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/cgi/logout.cgi b/fs_sesmon/FS-SessionClient/cgi/logout.cgi
new file mode 100644
index 0000000..95cef98
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/cgi/logout.cgi
@@ -0,0 +1,83 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w login.cgi
+
+use strict;
+use vars qw( $cgi $username $password $error $ip $portnum );
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use FS::SessionClient qw( logout portnum );
+
+$cgi = new CGI;
+
+if ( defined $cgi->param('magic') ) {
+ $cgi->param('username') =~ /^\s*(\w{1,255})\s*$/ or do {
+ $error = "Illegal username";
+ &print_form;
+ exit;
+ };
+ $username = $1;
+ $cgi->param('password') =~ /^([^\n]{0,255})$/ or die "guru meditation #420";
+ $password = $1;
+ #$ip = $cgi->remote_host;
+ $ip = $ENV{REMOTE_ADDR};
+ $ip =~ /^([\d\.]+)$/ or die "illegal ip: $ip";
+ $ip = $1;
+ $portnum = portnum( { 'ip' => $1 } ) or do {
+ $error = "You appear to be coming from an unknown IP address. Verify ".
+ "that your computer is set to obtain an IP address automatically ".
+ "via DHCP.";
+ &print_form;
+ exit;
+ };
+
+ ( $error = logout ( {
+ 'username' => $username,
+ 'portnum' => $portnum,
+ 'password' => $password,
+ } ) )
+ ? &print_form()
+ : &print_okay();
+
+} else {
+ $username = '';
+ $password = '';
+ $error = '';
+ &print_form;
+}
+
+sub print_form {
+ my $self_url = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>logout</TITLE></HEAD>
+<BODY>
+END
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! if $error;
+
+print <<END;
+<FORM ACTION="$self_url" METHOD=POST>
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+Username <INPUT TYPE="text" NAME="username" VALUE="$username"><BR>
+Password <INPUT TYPE="password" NAME="password"><BR>
+<INPUT TYPE="submit">
+</FORM>
+</BODY>
+</HTML>
+END
+
+}
+
+sub print_okay {
+ print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>logout sucessful</TITLE></HEAD>
+<BODY>logout successful, etc.
+</BODY>
+</HTML>
+END
+}
+
+sub usage {
+ die "Usage:\n\n freeside-logout username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/fs_sessiond b/fs_sesmon/FS-SessionClient/fs_sessiond
new file mode 100644
index 0000000..bfdb20a
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/fs_sessiond
@@ -0,0 +1,65 @@
+#!/usr/bin/perl -Tw
+#
+# fs_sessiond
+#
+# This is run REMOTELY over ssh by fs_session_server
+#
+
+use strict;
+use Socket;
+
+use vars qw( $Debug );
+
+$Debug = 1;
+
+my $fs_sessiond_socket = "/usr/local/freeside/fs_sessiond_socket";
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+$|=1;
+
+my $me = "[fs_sessiond]";
+
+warn "$me starting\n" if $Debug;
+#nothing to read from server
+
+warn "$me creating $fs_sessiond_socket\n" if $Debug;
+my $uaddr = sockaddr_un($fs_sessiond_socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($fs_sessiond_socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+warn "$me entering main loop\n" if $Debug;
+my $paddr;
+for ( ; $paddr = accept(Client,Server); close Client) {
+
+ chomp( my $command = <Client> );
+
+ if ( $command eq 'login' || $command eq 'logout' || $command eq 'portnum' ) {
+ warn "$me reading data from local client\n" if $Debug;
+ my @data;
+ my $dos = 0;
+ push @data, scalar(<Client>) until $dos++ == 99 || $data[$#data] eq "END\n";
+ if ( $dos == 99 ) {
+ warn "$me WARNING: DoS attempt!"
+ } else {
+ warn "$me sending data to remote server\n" if $Debug;
+ print "$command\n", @data;
+ warn "$me reading result from remote server\n" if $Debug;
+ my $error = <STDIN>;
+ warn "$me sending error to local client\n" if $Debug;
+ print Client $error;
+ }
+ } else {
+ warn "$me WARNING: unexpected command from client: $command";
+ }
+
+}
+
diff --git a/fs_sesmon/FS-SessionClient/test.pl b/fs_sesmon/FS-SessionClient/test.pl
new file mode 100644
index 0000000..4b9ae17
--- /dev/null
+++ b/fs_sesmon/FS-SessionClient/test.pl
@@ -0,0 +1,21 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+######################### We start with some black magic to print on failure.
+
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+#use FS::SessionClient;
+#sigh, "not running as the freeside user"
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+# Insert your test code below (better if it prints "ok 13"
+# (correspondingly "not ok 13") depending on the success of chunk 13
+# of the test code):
+
diff --git a/fs_sesmon/fs_session_server b/fs_sesmon/fs_session_server
new file mode 100644
index 0000000..00229f8
--- /dev/null
+++ b/fs_sesmon/fs_session_server
@@ -0,0 +1,140 @@
+#!/usr/bin/perl -Tw
+#
+# fs_session_server
+#
+
+use strict;
+use vars qw( $opt $Debug );
+use IO::Handle;
+use Net::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record qw( qsearchs ); #qsearch );
+#use FS::cust_main_county;
+#use FS::cust_main;
+use FS::session;
+use FS::port;
+use FS::svc_acct;
+
+#require "configfile";
+$Debug = 1;
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user );
+
+my $machine = shift or die &usage;
+
+my $fs_sessiond = "/usr/local/sbin/fs_sessiond";
+
+my $me = "[fs_session_server]";
+
+while (1) {
+ my($reader, $writer) = (new IO::Handle, new IO::Handle);
+ $writer->autoflush(1);
+ warn "$me Connecting to $machine\n" if $Debug;
+ sshopen2($machine,$reader,$writer,$fs_sessiond);
+
+ warn "$me Entering main loop\n" if $Debug;
+ while (1) {
+ warn "$me Reading (waiting for) data\n" if $Debug;
+ my $command = scalar(<$reader>);
+ chomp $command;
+ #DoS protection here too, to protect against a compromised client? *sigh*
+ my %hash;
+ while ( ( my $key = scalar(<$reader>) ) ne "END\n" ) {
+ chomp $key;
+ chomp( $hash{$key} = scalar(<$reader>) );
+ }
+
+ if ( $command eq 'login' ) {
+ my $error = &login(\%hash);
+ print $writer "$error\n";
+ } elsif ( $command eq 'logout' ) {
+ my $error = &logout(\%hash);
+ print $writer "$error\n";
+ } elsif ( $command eq 'portnum' ) {
+ my $port;
+ if ( exists $hash{'ip'} ) {
+ $hash{'ip'} =~ /^([\d\.]+)$/ or $1='nomatch';
+ $port = qsearchs('port', { 'ip' => $1 } );
+ } else {
+ $hash{'nasnum'} =~ /^(\d+)$/ and my $nasnum = $1;
+ $hash{'nasport'} =~ /^(\d+)$/ and my $nasport = $1;
+ $port = qsearchs('port', { 'nasnum'=>$nasnum, 'nasport'=>$nasport } );
+ }
+ print $writer ( $port ? $port->portnum : '' ), "\n";
+ } else {
+ warn "$me WARNING: unrecognized command: $command";
+ }
+ }
+ #won't ever reach without code above to throw out of loop, but...
+ close $writer;
+ close $reader;
+ warn "connection to $machine lost!\n";
+ sleep 5;
+ warn "reconnecting...\n";
+}
+
+sub login {
+ my $href = shift;
+ $href->{'username'} =~ /^([a-z0-9_\-\.]+)$/ or return "Illegal username";
+ my $username = $1;
+ my $svc_acct = qsearchs('svc_acct', { 'username' => $username } )
+ or return "Unknown user";
+ return "Incorrect password"
+ if exists($href->{'password'})
+ && $href->{'password'} ne $svc_acct->_password;
+ return "Time limit exceeded" unless $svc_acct->seconds;
+ my $session = new FS::session {
+ 'portnum' => $href->{'portnum'},
+ 'svcnum' => $svc_acct->svcnum,
+ 'login' => $href->{'login'},
+ };
+ $session->insert;
+}
+
+sub logout {
+ my $href = shift;
+ $href->{'username'} =~ /^([a-z0-9_\-\.]+)$/ or return "Illegal username";
+ my $username = $1;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+ my $svc_acct =
+ qsearchs('svc_acct', { 'username' => $username }, '', 'FOR UPDATE' )
+ or return "Unknown user";
+ return "Incorrect password"
+ if exists($href->{'password'})
+ && $href->{'password'} ne $svc_acct->_password;
+ my $session = qsearchs( 'session', {
+ 'portnum' => $href->{'portnum'},
+ 'svcnum' => $svc_acct->svcnum,
+ 'logout' => '',
+ },
+ '', 'FOR UPDATE'
+ );
+ unless ( $session ) {
+ $dbh->rollback;
+ return "No currently open sessions found for that user/port!";
+ }
+ my $nsession = new FS::session ( { $session->hash } );
+ warn "$nsession replacing $session";
+ my $error = $nsession->replace($session);
+ if ( $error ) {
+ $dbh->rollback;
+ return "can't logout: $error";
+ }
+ my $time = $nsession->logout - $nsession->login;
+ my $new_svc_acct = new FS::svc_acct ( { $svc_acct->hash } );
+ my $seconds = $new_svc_acct->seconds;
+ $seconds -= $time;
+ $seconds = 0 if $seconds < 0;
+ $new_svc_acct->seconds( $seconds );
+ $error = $new_svc_acct->replace( $svc_acct );
+ warn "can't debit time: $error\n"; #don't want to rollback, though
+ $dbh->commit or die $dbh->errstr;
+ ''
+}
+
+sub usage {
+ die "Usage:\n\n fs_session_server user machine\n";
+}
+
diff --git a/fs_signup/FS-SignupClient/Changes b/fs_signup/FS-SignupClient/Changes
new file mode 100644
index 0000000..e750a82
--- /dev/null
+++ b/fs_signup/FS-SignupClient/Changes
@@ -0,0 +1,5 @@
+Revision history for Perl extension FS::SignupClient.
+
+0.01 Mon Aug 23 01:12:46 1999
+ - original version; created by h2xs 1.19
+
diff --git a/fs_signup/FS-SignupClient/MANIFEST b/fs_signup/FS-SignupClient/MANIFEST
new file mode 100644
index 0000000..365ae66
--- /dev/null
+++ b/fs_signup/FS-SignupClient/MANIFEST
@@ -0,0 +1,7 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+SignupClient.pm
+test.pl
+cgi/signup.cgi
diff --git a/fs_signup/FS-SignupClient/MANIFEST.SKIP b/fs_signup/FS-SignupClient/MANIFEST.SKIP
new file mode 100644
index 0000000..ae335e7
--- /dev/null
+++ b/fs_signup/FS-SignupClient/MANIFEST.SKIP
@@ -0,0 +1 @@
+CVS/
diff --git a/fs_signup/FS-SignupClient/Makefile.PL b/fs_signup/FS-SignupClient/Makefile.PL
new file mode 100644
index 0000000..9850c87
--- /dev/null
+++ b/fs_signup/FS-SignupClient/Makefile.PL
@@ -0,0 +1,17 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'FS::SignupClient',
+ 'VERSION_FROM' => 'SignupClient.pm', # finds $VERSION
+# 'EXE_FILES' => [ 'fs_signupd' ],
+# 'INSTALLSCRIPT' => '/usr/local/sbin',
+# 'INSTALLSITEBIN' => '/usr/local/sbin',
+ 'PERM_RWX' => '750',
+ 'PREREQ_PM' => {
+ 'Business::CreditCard' => 0,
+ 'HTTP::BrowserDetect' => 0,
+ 'Text::Template' => 0,
+ 'FS::SelfService' => 0,
+ },
+);
diff --git a/fs_signup/FS-SignupClient/SignupClient.pm b/fs_signup/FS-SignupClient/SignupClient.pm
new file mode 100644
index 0000000..284fddd
--- /dev/null
+++ b/fs_signup/FS-SignupClient/SignupClient.pm
@@ -0,0 +1,208 @@
+package FS::SignupClient;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT_OK $init_data); # $fs_signupd_socket);
+use Exporter;
+#use Socket;
+#use FileHandle;
+#use IO::Handle;
+#use Storable qw(nstore_fd fd_retrieve);
+use FS::SelfService; # qw( new_customer signup_info );
+
+$VERSION = '0.04';
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( signup_info new_customer regionselector expselect popselector);
+
+=head1 NAME
+
+FS::SignupClient - Freeside signup client API
+
+=head1 SYNOPSIS
+
+ use FS::SignupClient qw( signup_info new_customer );
+
+ #this is the backwards-compatibility bit
+ ( $locales, $packages, $pops, $real_signup_info ) = signup_info;
+
+ #this is compatible with FS::SelfService::new_customer
+ $error = new_customer ( {
+ 'first' => $first,
+ 'last' => $last,
+ 'ss' => $ss,
+ 'comapny' => $company,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'county' => $county,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'country' => $country,
+ 'daytime' => $daytime,
+ 'night' => $night,
+ 'fax' => $fax,
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ 'paycvv' => $paycvv,
+ 'paydate' => $paydate,
+ 'payname' => $payname,
+ 'invoicing_list' => $invoicing_list,
+ 'referral_custnum' => $referral_custnum,
+ 'comments' => $comments,
+ 'pkgpart' => $pkgpart,
+ 'username' => $username,
+ '_password' => $password,
+ 'sec_phrase' => $sec_phrase,
+ 'popnum' => $popnum,
+ 'agentnum' => $agentnum, #optional
+ } );
+
+=head1 DESCRIPTION
+
+This module provides an API for a remote signup server.
+
+It needs to be run as the freeside user. Because of this, the program which
+calls these subroutines should be written very carefully.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item signup_info
+
+Returns three array references of hash references.
+
+The first set of hash references is of allowable locales. Each hash reference
+has the following keys:
+ taxnum
+ state
+ county
+ country
+
+The second set of hash references is of allowable packages. Each hash
+reference has the following keys:
+ pkgpart
+ pkg
+
+The third set of hash references is of allowable POPs (Points Of Presence).
+Each hash reference has the following keys:
+ popnum
+ city
+ state
+ ac
+ exch
+
+(Future expansion: fourth argument is the $init_data hash reference)
+
+=cut
+
+#compatibility bit
+sub signup_info {
+
+ $init_data = FS::SelfService::signup_info();
+
+ (map { $init_data->{$_} } qw( cust_main_county part_pkg svc_acct_pop ) ),
+ $init_data;
+
+}
+
+=item new_customer HASHREF
+
+Adds a customer to the remote Freeside system. Requires a hash reference as
+a paramater with the following keys:
+ first
+ last
+ ss
+ comapny
+ address1
+ address2
+ city
+ county
+ state
+ zip
+ country
+ daytime
+ night
+ fax
+ payby
+ payinfo
+ paycvv
+ paydate
+ payname
+ invoicing_list
+ referral_custnum
+ comments
+ pkgpart
+ username
+ _password
+ sec_phrase
+ popnum
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+#compatibility bit
+sub new_customer {
+ my $hash = FS::SelfService::new_customer(@_);
+ $hash->{'error'};
+}
+
+=item regionselector SELECTED_COUNTY, SELECTED_STATE, SELECTED_COUNTRY, PREFIX, ONCHANGE
+
+=cut
+
+sub regionselector {
+ my ( $selected_county, $selected_state, $selected_country,
+ $prefix, $onchange ) = @_;
+ signup_info() unless $init_data;
+ FS::SelfService::regionselector({
+ selected_county => $selected_county,
+ selected_state => $selected_state,
+ selected_country => $selected_country,
+ prefix => $prefix,
+ onchange => $onchange,
+ default_country => $init_data->{countrydefault},
+ locales => $init_data->{cust_main_county},
+ });
+ #default_state => $init_data->{statedefault},
+}
+
+=item expselect PREFIX, DATE
+
+=cut
+
+sub expselect {
+ FS::SelfService::expselect(@_);
+}
+
+=item popselector
+
+=cut
+
+sub popselector {
+ my( $popnum ) = @_;
+ signup_info() unless $init_data;
+ FS::SelfService::popselector({
+ popnum => $popnum,
+ pops => $init_data->{svc_acct_pop},
+ });
+ #popac =>
+ #acstate =>
+}
+
+=back
+
+=head1 BUGS
+
+This is just a wrapper around FS::SelfService functions for backwards
+compatibility and will probably be deprecated soon.
+
+=head1 SEE ALSO
+
+L<fs_signupd>, L<FS::cust_main>
+
+=cut
+
+1;
+
diff --git a/fs_signup/FS-SignupClient/cgi/cvv2.html b/fs_signup/FS-SignupClient/cgi/cvv2.html
new file mode 100644
index 0000000..b178c85
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/cvv2.html
@@ -0,0 +1,25 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ CVV2 information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ The CVV2 number (also called CVC2 or CID) is a three- or four-digit
+ security code used to reduce credit card fraud.<BR><BR>
+ <TABLE BORDER=0 CELLSPACING=4>
+ <TR>
+ <TH>Visa / MasterCard / Discover</TH>
+ <TH>American Express</TH>
+ </TR>
+ <TR>
+ <TD>
+ <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="cvv2.png">
+ </TD>
+ <TD>
+ <IMG BORDER=0 ALT="American Express" SRC="cvv2_amex.png">
+ </TD>
+ </TABLE>
+ <CENTER><A HREF="javascript:close()">(close window)</A></CENTER>
+ </BODY>
+</HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/cvv2.png b/fs_signup/FS-SignupClient/cgi/cvv2.png
new file mode 100644
index 0000000..4610dcb
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/cvv2.png
Binary files differ
diff --git a/fs_signup/FS-SignupClient/cgi/cvv2_amex.png b/fs_signup/FS-SignupClient/cgi/cvv2_amex.png
new file mode 100644
index 0000000..21c36a0
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/cvv2_amex.png
Binary files differ
diff --git a/fs_signup/FS-SignupClient/cgi/decline.html b/fs_signup/FS-SignupClient/cgi/decline.html
new file mode 100644
index 0000000..a37ba3a
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/decline.html
@@ -0,0 +1,5 @@
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account. Please contact customer
+support.
+</BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/map.gif b/fs_signup/FS-SignupClient/cgi/map.gif
new file mode 100644
index 0000000..ef884d8
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/map.gif
Binary files differ
diff --git a/fs_signup/FS-SignupClient/cgi/signup-agentselect.html b/fs_signup/FS-SignupClient/cgi/signup-agentselect.html
new file mode 100755
index 0000000..7851c56
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/signup-agentselect.html
@@ -0,0 +1,195 @@
+<HTML><HEAD><TITLE>ISP Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>ISP Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM NAME="OneTrueForm" ACTION="<%= $self_url %>" METHOD=POST onSubmit="document.OneTrueForm.signup.disabled=true">
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<INPUT TYPE="hidden" NAME="ref" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+Agent <SELECT NAME="agentnum">
+<%=
+ warn $init_data;
+ warn $init_data->{'agent'};
+ foreach my $agent ( @{$init_data->{'agent'}} ) {
+ $OUT .= '<OPTION VALUE="'. $agent->{'agentnum'}. '"';
+ $OUT .= ' SELECTED' if $agent->{'agentnum'} eq $agentnum;
+ $OUT .= '>'. $agent->{'agent'};
+ }
+%>
+</SELECT><BR><BR>
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ regionselector( $county, $state, $country );
+
+ "$county_html $state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+ <TD><%= $country_html %></TD>
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+<BR>Billing information<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR><TD>
+
+ <%=
+ $OUT .= '<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"';
+ my @invoicing_list = split(', ', $invoicing_list );
+ $OUT .= ' CHECKED'
+ if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list;
+ $OUT .= '>';
+ %>
+
+ Postal mail invoice
+</TD></TR>
+<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>">
+</TD></TR>
+<%= scalar(@payby) > 1 ? '<TR><TD>Billing type</TD></TR>' : '' %>
+</TABLE>
+<TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%">
+<TR>
+
+ <%=
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ my %types = (
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ );
+ foreach ( keys %types ) {
+ $selected = $cgi->param('CARD_type') eq $types{$_} ? 'SELECTED' : '';
+ $cardselect .= qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!;
+ }
+ $cardselect .= '</SELECT>';
+
+ my %payby = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!,
+ );
+
+ my( $account, $aba ) = split('@', $payinfo);
+ my %paybychecked = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", $paydate). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("COMP", $paydate),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="$payinfo" MAXLENGTH=80>!,
+ );
+
+ for (@payby) {
+ if ( scalar(@payby) == 1) {
+ $OUT .= '<TD VALIGN=TOP>'.
+ qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$_">!.
+ "$paybychecked{$_}</TD>";
+ } else {
+ $OUT .= qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($payby eq $_) {
+ $OUT .= qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ $OUT .= qq!> $payby{$_}</TD>!;
+ }
+
+ }
+ }
+ %>
+
+</TR></TABLE><font color="#ff0000">*</font> required fields for each billing type
+<BR><BR>First package
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart"><OPTION VALUE="">(none)
+
+ <%=
+ foreach my $package ( @{$packages} ) {
+ $OUT .= '<OPTION VALUE="'. $package->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED' if $pkgpart && $package->{'pkgpart'} == $pkgpart;
+ $OUT .= '>'. $package->{'pkg'};
+ }
+ %>
+
+ </SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $password2 %>"></TD>
+</TR>
+<%=
+ if ( $init_data->{'security_phrase'} ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( scalar(@$pops) ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector($popnum). '</TD></TR>';
+ } else {
+ $OUT .= popselector($popnum);
+ }
+%>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup" >
+</FORM></BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/signup-alternate.html b/fs_signup/FS-SignupClient/cgi/signup-alternate.html
new file mode 100755
index 0000000..490cefa
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/signup-alternate.html
@@ -0,0 +1,218 @@
+<HTML><HEAD><TITLE>ISP Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>ISP Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM NAME="dummy">
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<INPUT TYPE="hidden" NAME="ref" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="3">
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD><SELECT NAME="state" SIZE="1">
+
+ <%=
+ foreach ( @{$locales} ) {
+ my $value = $_->{'state'};
+ $value .= ' ('. $_->{'county'}. ')' if $_->{'county'};
+ $value .= ' / '. $_->{'country'};
+
+ $OUT .= qq(<OPTION VALUE="$value");
+ $OUT .= ' SELECTED' if ( $state eq $_->{'state'}
+ && $county eq $_->{'county'}
+ && $country eq $_->{'country'}
+ );
+ $OUT .= ">$value</OPTION>";
+ }
+ %>
+
+ </SELECT></TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+
+<BR><BR>
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Username</TH>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Password</TH>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $password %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Re-enter Password</TH>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $password2 %>"></TD>
+</TR>
+
+<%= if ( $init_data->{'security_phrase'} ) {
+ <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+
+<%= if ( scalar(@$pops) ) {
+ '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector($popnum). '</TD></TR>';
+ } else {
+ popselector($popnum);
+ }
+%>
+
+</TABLE><font color="#ff0000">*</font> required fields
+
+<BR><BR>First package
+
+ <%= use Tie::IxHash;
+ my %pkgpart2payby = map { $_->{pkgpart} => $_->{payby}[0] } @{$packages};
+ tie my %options, 'Tie::IxHash',
+ '' => '(none)',
+ map { $_->{pkgpart} => $_->{pkg} }
+ sort { $a->{recur} <=> $b->{recur} }
+ @{$packages}
+ ;
+
+ use HTML::Widgets::SelectLayers 0.02;
+ my @form_text = qw( magic ref ss agentnum
+ last first company address1 address2
+ city zip daytime night fax
+ username _password _password2 sec_phrase );
+ my @form_select = qw( state ); #county country
+ if ( scalar(@$pops) == 0 or scalar(@$pops) == 1 ) {
+ push @form_text, 'popnum',
+ } else {
+ push @form_select, 'popnum',
+ }
+ my $widget = new HTML::Widgets::SelectLayers(
+ options => \%options,
+ selected_layer => $pkgpart,
+ form_name => 'dummy',
+ form_action => $self_url,
+ form_text => \@form_text,
+ form_select => \@form_select,
+ layer_callback => sub {
+ my $layer = shift;
+ my $html = qq( <INPUT TYPE="hidden" NAME="pkgpart" VALUE="$layer">);
+
+ if ( $pkgpart2payby{$layer} eq 'BILL' ) {
+ $html .= <<ENDOUT;
+<INPUT TYPE="hidden" NAME="payby" VALUE="BILL">
+<INPUT TYPE="hidden" NAME="invoicing_list_POST" VALUE="">
+<INPUT TYPE="hidden" NAME="BILL_payinfo" VALUE="">
+<INPUT TYPE="hidden" NAME="BILL_month" VALUE="12">
+<INPUT TYPE="hidden" NAME="BILL_year" VALUE="2037">
+<INPUT TYPE="hidden" NAME="BILL_payname" VALUE="">
+<BR><BR><INPUT TYPE="submit" VALUE="Signup">
+ENDOUT
+ } elsif ( $pkgpart2payby{$layer} eq 'CARD' ) {
+ my $postal_checked = '';
+ my @invoicing_list = split(', ', $invoicing_list );
+ $postal_checked = 'CHECKED'
+ if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list;
+
+ $invoicing_list= join(', ', grep { $_ ne 'POST' } @invoicing_list );
+
+ my $expselect = expselect("CARD", $paydate);
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ my %types = (
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ );
+ foreach ( keys %types ) {
+ $selected =
+ $cgi->param('CARD_type') eq $types{$_} ? 'SELECTED' : '';
+ $cardselect .=
+ qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!;
+ }
+ $cardselect .= '</SELECT>';
+
+ $html .= <<ENDOUT;
+<INPUT TYPE="hidden" NAME="payby" VALUE="CARD">
+<BR><BR>Billing information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0>
+<INPUT TYPE="hidden" NAME="invoicing_list_POST" VALUE="">
+<TR>
+ <TD ALIGN="right">Email statement to </TD>
+ <TD><INPUT TYPE="text" NAME="invoicing_list" VALUE="$invoicing_list"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Credit card type</TH>
+ <TD>$cardselect</TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Card number</TH>
+ <TD><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>*</font>Exp</TH>
+ <TD>$expselect</TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Name on card</TH>
+ <TD><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname"></TD>
+</TR>
+</TABLE>
+<font color="#ff0000">*</font> required fields
+<BR><BR><INPUT TYPE="submit" VALUE="Signup">
+ENDOUT
+ } else {
+ $html = <<ENDOUT;
+<BR>Please select a package.<BR>
+ENDOUT
+
+ }
+
+ $html;
+
+ },
+ );
+
+ $widget->html;
+
+
+ %>
+</BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/signup-snarf.html b/fs_signup/FS-SignupClient/cgi/signup-snarf.html
new file mode 100755
index 0000000..d167efb
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/signup-snarf.html
@@ -0,0 +1,228 @@
+<HTML><HEAD><TITLE>ISP Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8" onUnload="myclose()">
+<script language="JavaScript"><!--
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1
+ }
+//--></script>
+<FONT SIZE=7>ISP Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<INPUT TYPE="hidden" NAME="ref" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ regionselector( $county, $state, $country );
+
+ "$county_html $state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+ <TD><%= $country_html %></TD>
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+<BR>Billing information<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR><TD>
+
+ <%=
+ $OUT .= '<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"';
+ my @invoicing_list = split(', ', $invoicing_list );
+ $OUT .= ' CHECKED'
+ if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list;
+ $OUT .= '>';
+ %>
+
+ Postal mail invoice
+</TD></TR>
+<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>">
+</TD></TR>
+<%= scalar(@payby) > 1 ? '<TR><TD>Billing type</TD></TR>' : '' %>
+</TABLE>
+<TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%">
+<TR>
+
+ <%=
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ my %types = (
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ );
+ foreach ( keys %types ) {
+ $selected = $cgi->param('CARD_type') eq $types{$_} ? 'SELECTED' : '';
+ $cardselect .= qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!;
+ }
+ $cardselect .= '</SELECT>';
+
+ my %payby = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!,
+ );
+
+ if ( $init_data->{'cvv_enabled'} ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $payby{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+ my %paybychecked = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", $paydate). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("COMP", $paydate),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="$payinfo" MAXLENGTH=80>!,
+ );
+
+ if ( $init_data->{'cvv_enabled'} ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $paybychecked{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ for (@payby) {
+ if ( scalar(@payby) == 1) {
+ $OUT .= '<TD VALIGN=TOP>'.
+ qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$_">!.
+ "$paybychecked{$_}</TD>";
+ } else {
+ $OUT .= qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($payby eq $_) {
+ $OUT .= qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ $OUT .= qq!> $payby{$_}</TD>!;
+ }
+
+ }
+ }
+ %>
+
+</TR></TABLE><font color="#ff0000">*</font> required fields for each billing type
+<BR><BR>First package
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart"><OPTION VALUE="">(none)
+
+ <%=
+ foreach my $package ( @{$packages} ) {
+ $OUT .= '<OPTION VALUE="'. $package->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED' if $pkgpart && $package->{'pkgpart'} == $pkgpart;
+ $OUT .= '>'. $package->{'pkg'};
+ }
+ %>
+
+ </SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $password2 %>"></TD>
+</TR>
+<%=
+ if ( $init_data->{'security_phrase'} ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( scalar(@$pops) ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector($popnum). '</TD></TR>';
+ } else {
+ $OUT .= popselector($popnum);
+ }
+%>
+</TABLE>
+<BR><BR>Enter up to ten external accounts from which to retrieve email
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="left">Mail server</TH>
+ <TH ALIGN="left">Username</TH>
+ <TH ALIGN="left">Password</TH>
+</TR>
+<%=
+ for my $num ( 1..10 ) {
+ no strict 'vars';
+ $OUT .= qq!<TR><TD><INPUT TYPE="text" NAME="snarf_machine$num" VALUE="${"snarf_machine$num"}"></TD>!.
+ qq!<INPUT TYPE="hidden" NAME="snarf_protocol$num" VALUE="pop3">!.
+ qq!<TD><INPUT TYPE="text" NAME="snarf_username$num" VALUE="${"snarf_username$num"}"></TD>!.
+ qq!<TD><INPUT TYPE="password" NAME="snarf_password$num" VALUE="${"snarf_password$num"}"></TD>!.
+ qq!</TR>!;
+ }
+%>
+</TABLE>
+
+<BR><BR><INPUT TYPE="submit" VALUE="Signup">
+</FORM></BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/signup.cgi b/fs_signup/FS-SignupClient/cgi/signup.cgi
new file mode 100755
index 0000000..4f9efff
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/signup.cgi
@@ -0,0 +1,389 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+#
+# $Id: signup.cgi,v 1.52 2004-10-01 01:38:02 ivan Exp $
+
+use strict;
+use vars qw( @payby $cgi $locales $packages
+ $pops %pop %popnum2pop
+ $init_data $error
+ $last $first $ss $company $address1 $address2 $city $state $county
+ $country $zip $daytime $night $fax $invoicing_list $payby $payinfo
+ $paycvv $paydate $payname $referral_custnum $init_popstate
+ $pkgpart $username $password $password2 $sec_phrase $popnum
+ $agentnum $refnum
+ $ieak_file $ieak_template
+ $signup_html $signup_template
+ $success_html $success_template
+ $decline_html $decline_template
+ $ac $exch $loc
+ $email_name $pkg
+ $self_url
+ );
+use subs qw( print_form print_okay print_decline
+ success_default decline_default );
+use CGI;
+#use CGI::Carp qw(fatalsToBrowser);
+use Text::Template;
+use Business::CreditCard;
+use HTTP::BrowserDetect;
+use FS::SignupClient 0.03 qw( signup_info new_customer
+ regionselector expselect popselector);
+
+#acceptable payment methods
+#
+#@payby = qw( CARD BILL COMP );
+#@payby = qw( CARD BILL );
+#@payby = qw( CARD );
+@payby = qw( CARD PREPAY );
+
+$ieak_file = '/usr/local/freeside/ieak.template';
+$signup_html = -e 'signup.html'
+ ? 'signup.html'
+ : '/usr/local/freeside/signup.html';
+$success_html = -e 'success.html'
+ ? 'success.html'
+ : '/usr/local/freeside/success.html';
+$decline_html = -e 'decline.html'
+ ? 'decline.html'
+ : '/usr/local/freeside/decline.html';
+
+
+if ( -e $ieak_file ) {
+ my $ieak_txt = Text::Template::_load_text($ieak_file)
+ or die $Text::Template::ERROR;
+ $ieak_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $ieak_txt = $1;
+ $ieak_txt =~ s/\r//g; # don't double \r on old templates
+ $ieak_txt =~ s/\n/\r\n/g;
+ $ieak_template = new Text::Template ( TYPE => 'STRING', SOURCE => $ieak_txt )
+ or die $Text::Template::ERROR;
+} else {
+ $ieak_template = '';
+}
+
+$agentnum = '';
+if ( -e $signup_html ) {
+ my $signup_txt = Text::Template::_load_text($signup_html)
+ or die $Text::Template::ERROR;
+ $signup_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $signup_txt = $1;
+ $signup_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $signup_txt,
+ DELIMITERS => [ '<%=', '%>' ]
+ )
+ or die $Text::Template::ERROR;
+ if ( $signup_txt =~
+ /<\s*INPUT TYPE="?hidden"?\s+NAME="?agentnum"?\s+VALUE="?(\d+)"?\s*>/si
+ ) {
+ $agentnum = $1;
+ }
+} else {
+ #too much maintenance hassle to keep in this file
+ die "can't find ./signup.html or /usr/local/freeside/signup.html";
+ #$signup_template = new Text::Template ( TYPE => 'STRING',
+ # SOURCE => &signup_default,
+ # DELIMITERS => [ '<%=', '%>' ]
+ # )
+ # or die $Text::Template::ERROR;
+}
+
+if ( -e $success_html ) {
+ my $success_txt = Text::Template::_load_text($success_html)
+ or die $Text::Template::ERROR;
+ $success_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $success_txt = $1;
+ $success_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $success_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $success_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &success_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
+if ( -e $decline_html ) {
+ my $decline_txt = Text::Template::_load_text($decline_html)
+ or die $Text::Template::ERROR;
+ $decline_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $decline_txt = $1;
+ $decline_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $decline_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $decline_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &decline_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
+
+( $locales, $packages, $pops, $init_data ) = signup_info();
+@payby = @{$init_data->{'payby'}} if @{$init_data->{'payby'}};
+$packages = $init_data->{agentnum2part_pkg}{$agentnum} if $agentnum;
+%pop = ();
+%popnum2pop = ();
+foreach (@$pops) {
+ push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
+ $popnum2pop{$_->{popnum}} = $_;
+}
+
+$cgi = new CGI;
+
+if ( defined $cgi->param('magic') ) {
+ if ( $cgi->param('magic') eq 'process' ) {
+
+ if ( $cgi->param('state') =~ /^(\w*)( \(([\w ]+)\))? ?\/ ?(\w+)$/ ) {
+ $state = $1;
+ $county = $3 || '';
+ $country = $4;
+ } elsif ( $cgi->param('state') =~ /^(\w*)$/ ) {
+ $state = $1;
+ $cgi->param('county') =~ /^([\w ]*)$/
+ or die "illegal county: ". $cgi->param('county');
+ $county = $1;
+ $cgi->param('country') =~ /^(\w+)$/
+ or die "illegal country: ". $cgi->param('country');
+ $country = $1;
+ } else {
+ die "illegal state: ". $cgi->param('state');
+ }
+
+ $payby = $cgi->param('payby');
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ #$payinfo = join('@', map { $cgi->param( $payby. "_payinfo$_" ) } (1,2) );
+ $payinfo = $cgi->param($payby. '_payinfo1'). '@'.
+ $cgi->param($payby. '_payinfo2');
+ } else {
+ $payinfo = $cgi->param( $payby. '_payinfo' );
+ }
+ $paydate =
+ $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' );
+ $payname = $cgi->param( $payby. '_payname' );
+ $paycvv = defined $cgi->param( $payby. '_paycvv' )
+ ? $cgi->param( $payby. '_paycvv' )
+ : '';
+
+ if ( $invoicing_list = $cgi->param('invoicing_list') ) {
+ $invoicing_list .= ', POST' if $cgi->param('invoicing_list_POST');
+ } else {
+ $invoicing_list = 'POST';
+ }
+
+ $error = '';
+
+ $last = $cgi->param('last');
+ $first = $cgi->param('first');
+ $ss = $cgi->param('ss');
+ $company = $cgi->param('company');
+ $address1 = $cgi->param('address1');
+ $address2 = $cgi->param('address2');
+ $city = $cgi->param('city');
+ #$county,
+ #$state,
+ $zip = $cgi->param('zip');
+ #$country,
+ $daytime = $cgi->param('daytime');
+ $night = $cgi->param('night');
+ $fax = $cgi->param('fax');
+ #$payby,
+ #$payinfo,
+ #$paydate,
+ #$payname,
+ #$invoicing_list,
+ $referral_custnum = $cgi->param('ref');
+ $pkgpart = $cgi->param('pkgpart');
+ $username = $cgi->param('username');
+ $sec_phrase = $cgi->param('sec_phrase');
+ $password = $cgi->param('_password');
+ $popnum = $cgi->param('popnum');
+ #$agentnum, # = $cgi->param('agentnum'),
+ $agentnum ||= $cgi->param('agentnum');
+ $init_popstate = $cgi->param('init_popstate');
+ $refnum = $cgi->param('refnum');
+
+ if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+ $error = $init_data->{msgcat}{passwords_dont_match}; #msgcat
+ $password = '';
+ $password2 = '';
+ } else {
+ $password2 = $cgi->param('_password2');
+
+ if ( $payby =~ /^(CARD|DCRD)$/ && $cgi->param('CARD_type') ) {
+ $payinfo =~ s/\D//g;
+
+ $payinfo =~ /^(\d{13,16})$/
+ or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ $payinfo = $1;
+ validate($payinfo)
+ or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+ cardtype($payinfo) eq $cgi->param('CARD_type')
+ or $error ||= $init_data->{msgcat}{not_a}. $cgi->param('CARD_type');
+ }
+
+ $error ||= new_customer ( {
+ 'last' => $last,
+ 'first' => $first,
+ 'ss' => $ss,
+ 'company' => $company,
+ 'address1' => $address1,
+ 'address2' => $address2,
+ 'city' => $city,
+ 'county' => $county,
+ 'state' => $state,
+ 'zip' => $zip,
+ 'country' => $country,
+ 'daytime' => $daytime,
+ 'night' => $night,
+ 'fax' => $fax,
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ 'paycvv' => $paycvv,
+ 'paydate' => $paydate,
+ 'payname' => $payname,
+ 'invoicing_list' => $invoicing_list,
+ 'referral_custnum' => $referral_custnum,
+ 'pkgpart' => $pkgpart,
+ 'username' => $username,
+ 'sec_phrase' => $sec_phrase,
+ '_password' => $password,
+ 'popnum' => $popnum,
+ 'agentnum' => $agentnum,
+ 'refnum' => $refnum,
+ map { $_ => $cgi->param($_) } grep { /^snarf_/ } $cgi->param
+ } );
+
+ }
+
+ if ( $error eq '_decline' ) {
+ print_decline();
+ } elsif ( $error ) {
+ #fudge the snarf info
+ no strict 'refs';
+ ${$_} = $cgi->param($_) foreach grep { /^snarf_/ } $cgi->param;
+ print_form();
+ } else {
+ print_okay();
+ }
+
+ } else {
+ die "unrecognized magic: ". $cgi->param('magic');
+ }
+} else {
+ $error = '';
+ $last = '';
+ $first = '';
+ $ss = '';
+ $company = '';
+ $address1 = '';
+ $address2 = '';
+ $city = '';
+ $state = $init_data->{statedefault};
+ $county = '';
+ $country = $init_data->{countrydefault};
+ $zip = '';
+ $daytime = '';
+ $night = '';
+ $fax = '';
+ $invoicing_list = '';
+ $payby = '';
+ $payinfo = '';
+ $paydate = '';
+ $payname = '';
+ $pkgpart = '';
+ $username = '';
+ $password = '';
+ $password2 = '';
+ $sec_phrase = '';
+ $popnum = '';
+ $referral_custnum = $cgi->param('ref') || '';
+ $init_popstate = $cgi->param('init_popstate') || '';
+ $refnum = $init_data->{'refnum'};
+ print_form;
+}
+
+sub print_form {
+
+ $cgi->delete('ref');
+ $cgi->delete('init_popstate');
+ $self_url = $cgi->self_url;
+
+ $error = "Error: $error" if $error;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $signup_template->fill_in();
+
+}
+
+sub print_decline {
+ print $cgi->header( '-expires' => 'now' ),
+ $decline_template->fill_in();
+}
+
+sub print_okay {
+ my $user_agent = new HTTP::BrowserDetect $ENV{HTTP_USER_AGENT};
+
+ $cgi->param('username') =~ /^(.+)$/
+ or die "fatal: invalid username got past FS::SignupClient::new_customer";
+ my $username = $1;
+ $cgi->param('_password') =~ /^(.+)$/
+ or die "fatal: invalid password got past FS::SignupClient::new_customer";
+ my $password = $1;
+ ( $cgi->param('first'). ' '. $cgi->param('last') ) =~ /^(.*)$/
+ or die "fatal: invalid email_name got past FS::SignupClient::new_customer";
+ $email_name = $1; #global for template
+
+ my $pop = $popnum2pop{$cgi->param('popnum')};
+ #or die "fatal: invalid popnum got past FS::SignupClient::new_customer";
+ if ( $pop ) {
+ ( $ac, $exch, $loc ) = ( $pop->{'ac'}, $pop->{'exch'}, $pop->{'loc'} );
+ } else {
+ ( $ac, $exch, $loc ) = ( '', '', ''); #presumably you're not using them.
+ }
+
+ #global for template
+ $pkg = ( grep { $_->{'pkgpart'} eq $pkgpart } @$packages )[0]->{'pkg'};
+
+ if ( $ieak_template && $user_agent->windows && $user_agent->ie ) {
+ #send an IEAK config
+ print $cgi->header('application/x-Internet-signup'),
+ $ieak_template->fill_in();
+ } else { #send a simple confirmation
+ print $cgi->header( '-expires' => 'now' ),
+ $success_template->fill_in();
+ }
+}
+
+sub success_default { #html to use if you don't specify a success file
+ <<'END';
+<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
+Thanks for signing up!
+<BR><BR>
+Signup information for <%= $email_name %>:
+<BR><BR>
+Username: <%= $username %><BR>
+Password: <%= $password %><BR>
+Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR>
+Package: <%= $pkg %><BR>
+</BODY></HTML>
+END
+}
+
+sub decline_default { #html to use if there is a decline
+ <<'END';
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account. Please contact customer
+support.
+</BODY></HTML>
+END
+}
+
diff --git a/fs_signup/FS-SignupClient/cgi/signup.html b/fs_signup/FS-SignupClient/cgi/signup.html
new file mode 100755
index 0000000..a6cbf21
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/signup.html
@@ -0,0 +1,219 @@
+<HTML><HEAD><TITLE>ISP Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8" onUnload="myclose()">
+<script language="JavaScript"><!--
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1
+ }
+//--></script>
+<FONT SIZE=7>ISP Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM NAME="OneTrueForm" ACTION="<%= $self_url %>" METHOD=POST onSubmit="document.OneTrueForm.signup.disabled=true">
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<INPUT TYPE="hidden" NAME="ref" VALUE="<%= $referral_custnum %>">
+<INPUT TYPE="hidden" NAME="ss" VALUE="">
+Where did you hear about our service? <SELECT NAME="refnum">
+<%=
+ $OUT .= '<OPTION VALUE="">' unless $refnum;
+ foreach my $part_referral ( @{$init_data->{'part_referral'}} ) {
+ $OUT .= '<OPTION VALUE="'. $part_referral->{'refnum'}. '"';
+ $OUT .= ' SELECTED' if $part_referral->{'refnum'} eq $refnum;
+ $OUT .= '>'. $part_referral->{'referral'};
+ }
+%>
+</SELECT><BR><BR>
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+ <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">&nbsp;</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+ <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+ <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+ <TD>
+ <%=
+ ($county_html, $state_html, $country_html) =
+ regionselector( $county, $state, $country );
+
+ "$county_html $state_html";
+ %>
+ </TD>
+ <TH><font color="#ff0000">*</font>Zip</TH>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+ <TD><%= $country_html %></TD>
+<TR>
+ <TD ALIGN="right">Day Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Night Phone</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+</TABLE><font color="#ff0000">*</font> required fields<BR>
+<BR>Billing information<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR><TD>
+
+ <%=
+ $OUT .= '<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"';
+ my @invoicing_list = split(', ', $invoicing_list );
+ $OUT .= ' CHECKED'
+ if ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list;
+ $OUT .= '>';
+ %>
+
+ Postal mail invoice
+</TD></TR>
+<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="<%= join(', ', grep { $_ ne 'POST' } split(', ', $invoicing_list ) ) %>">
+</TD></TR>
+<%= scalar(@payby) > 1 ? '<TR><TD>Billing type</TD></TR>' : '' %>
+</TABLE>
+<TABLE BGCOLOR="#c0c0c0" BORDER=1 WIDTH="100%">
+<TR>
+
+ <%=
+
+ my $cardselect = '<SELECT NAME="CARD_type"><OPTION></OPTION>';
+ my %types = (
+ 'VISA' => 'VISA card',
+ 'MasterCard' => 'MasterCard',
+ 'Discover' => 'Discover card',
+ 'American Express' => 'American Express card',
+ );
+ foreach ( keys %types ) {
+ $selected = $cgi->param('CARD_type') eq $types{$_} ? 'SELECTED' : '';
+ $cardselect .= qq!<OPTION $selected VALUE="$types{$_}">$_</OPTION>!;
+ }
+ $cardselect .= '</SELECT>';
+
+ my %payby = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD"). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!,
+ );
+
+ if ( $init_data->{'cvv_enabled'} ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $payby{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+ my %paybychecked = (
+ 'CARD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("CARD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card<BR><font color="#ff0000">*</font>$cardselect<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR><font color="#ff0000">*</font>Exp !. expselect("DCRD", $paydate). qq!<BR><font color="#ff0000">*</font>Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account" MAXLENGTH=10><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("BILL", $paydate). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("COMP", $paydate),
+ 'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="$payinfo" MAXLENGTH=80>!,
+ );
+
+ if ( $init_data->{'cvv_enabled'} ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+ $paybychecked{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ for (@payby) {
+ if ( scalar(@payby) == 1) {
+ $OUT .= '<TD VALIGN=TOP>'.
+ qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$_">!.
+ "$paybychecked{$_}</TD>";
+ } else {
+ $OUT .= qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($payby eq $_) {
+ $OUT .= qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ $OUT .= qq!> $payby{$_}</TD>!;
+ }
+
+ }
+ }
+ %>
+
+</TR></TABLE><font color="#ff0000">*</font> required fields for each billing type
+<BR><BR>First package
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+ <TD COLSPAN=2><SELECT NAME="pkgpart"><OPTION VALUE="">(none)
+
+ <%=
+ foreach my $package ( @{$packages} ) {
+ $OUT .= '<OPTION VALUE="'. $package->{'pkgpart'}. '"';
+ $OUT .= ' SELECTED' if $pkgpart && $package->{'pkgpart'} == $pkgpart;
+ $OUT .= '>'. $package->{'pkg'};
+ }
+ %>
+
+ </SELECT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $password %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $password2 %>"></TD>
+</TR>
+<%=
+ if ( $init_data->{'security_phrase'} ) {
+ $OUT .= <<ENDOUT;
+<TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+ </TD>
+</TR>
+ENDOUT
+ } else {
+ $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+ }
+%>
+<%=
+ if ( scalar(@$pops) ) {
+ $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+ popselector($popnum). '</TD></TR>';
+ } else {
+ $OUT .= popselector($popnum);
+ }
+%>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" NAME="signup" VALUE="Signup">
+</FORM></BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/stateselect.html b/fs_signup/FS-SignupClient/cgi/stateselect.html
new file mode 100644
index 0000000..ba55bff
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/stateselect.html
@@ -0,0 +1,134 @@
+<HTML><HEAD><TITLE>ISP Signup</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>ISP Signup - state selection</FONT><BR><BR>
+<SCRIPT>
+function gotoURL(object) {
+ window.location.href = object.options[object.selectedIndex].value;
+}
+</SCRIPT>
+<FORM>
+Select your state from the map or dropdown:
+<MAP NAME=usmap>
+<area shape=poly COORDS="264,157,286,155,292,193,276,195,270,199,264,157" href="signup.cgi?init_popstate=AL">
+<area shape=poly COORDS="28,197,46,185,72,199,72,241,88,243,102,261,92,263,70,241,42,243,28,257,12,259,34,243,20,233,16,223,34,215,22,207,30,205,28,197" href="../states/Alaska.html">
+<area shape=poly COORDS="70,137,106,137,100,189,84,187,60,173,70,133,70,137,70,137" href="signup.cgi?init_popstate=AZ">
+<area shape=poly COORDS="250,153,242,179,220,177,218,171,216,145,252,143,250,155,250,153" href="signup.cgi?init_popstate=AR">
+<area shape=poly COORDS="10,79,38,81,30,109,62,151,56,173,40,169,20,145,4,101,10,75,26,79,10,79,10,79" href="signup.cgi?init_popstate=CA">
+<area shape=poly COORDS="108,103,158,107,154,141,104,137,110,101,128,103,108,103" href="signup.cgi?init_popstate=CO">
+<area shape=poly COORDS="374,107,405,105,405,123,372,125,374,107" href="signup.cgi?init_popstate=CT">
+<area shape=poly COORDS="370,143,402,145,405,157,362,157,370,143" href="signup.cgi?init_popstate=DE">
+<area shape=poly COORDS="275,193,325,187,327,197,341,219,341,233,335,237,317,215,315,205,307,195,293,203,275,193" href="signup.cgi?init_popstate=FL">
+<area shape=poly COORDS="297,153,283,155,297,191,321,189,321,169,297,153" href="signup.cgi?init_popstate=GA">
+<area shape=poly COORDS="98,233,142,263,156,251,162,239,164,229,136,231,94,221,100,235,98,233" href="signup.cgi?init_popstate=HI">
+<area shape=poly COORDS="68,21,76,21,72,35,80,47,80,55,84,65,100,69,94,93,56,83,66,51,70,19,68,21" href="signup.cgi?init_popstate=ID">
+<area shape=poly COORDS="242,91,258,89,266,123,256,139,234,109,248,87,242,91" href="signup.cgi?init_popstate=IL">
+<area shape=poly COORDS="261,95,265,123,265,131,285,117,277,91,261,95" href="signup.cgi?init_popstate=IN">
+<area shape=poly COORDS="198,87,206,111,232,109,240,99,240,91,232,79,198,87" href="signup.cgi?init_popstate=IA">
+<area shape=poly COORDS="158,111,158,135,214,139,214,127,208,113,158,111" href="signup.cgi?init_popstate=KS">
+<area shape=poly COORDS="263,133,275,129,289,115,303,121,307,129,299,135,251,141,269,131,263,133" href="signup.cgi?init_popstate=KY">
+<area shape=poly COORDS="222,179,246,179,244,197,258,193,262,213,226,209,224,177,222,179" href="signup.cgi?init_popstate=LA">
+<area shape=poly COORDS="363,37,373,59,373,47,387,31,377,9,365,15,363,37" href="signup.cgi?init_popstate=ME">
+<area shape=poly COORDS="376,159,405,159,405,175,374,177,376,159" href="signup.cgi?init_popstate=MD">
+<area shape=poly COORDS="378,74,380,88,404,88,404,72,378,74" href="signup.cgi?init_popstate=MA">
+<area shape=poly COORDS="265,73,269,83,265,93,293,91,295,71,281,53,271,53,267,69,265,73,265,73" href="signup.cgi?init_popstate=MI">
+<area shape=poly COORDS="194,31,222,33,242,35,224,51,222,63,222,73,234,79,196,85,194,31" href="signup.cgi?init_popstate=MN">
+<area shape=poly COORDS="265,159,271,199,257,201,259,195,241,197,251,155,265,159" href="signup.cgi?init_popstate=MS">
+<area shape=poly COORDS="206,113,234,111,256,139,248,147,214,145,208,111,206,113" href="signup.cgi?init_popstate=MO">
+<area shape=poly COORDS="78,23,148,31,146,67,84,63,78,35,80,19,78,23" href="signup.cgi?init_popstate=MT">
+<area shape=poly COORDS="146,85,148,103,158,105,164,109,206,109,198,85,144,87,146,85" href="signup.cgi?init_popstate=NE">
+<area shape=poly COORDS="40,83,76,87,64,151,32,109,40,83,40,83" href="signup.cgi?init_popstate=NV">
+<area shape=poly COORDS="298,11,330,9,330,25,298,25,298,11" href="signup.cgi?init_popstate=NH">
+<area shape=poly COORDS="372,127,404,125,405,141,368,139,376,125,372,127" href="signup.cgi?init_popstate=NJ">
+<area shape=poly COORDS="106,137,100,191,122,187,148,187,150,139,106,137,106,137" href="signup.cgi?init_popstate=NM">
+<area shape=poly COORDS="313,79,331,63,337,45,349,45,359,65,357,79,345,65,315,77,313,79,313,79" href="signup.cgi?init_popstate=NY">
+<area shape=poly COORDS="309,137,295,151,319,149,337,153,357,131,351,129,309,137,309,137" href="signup.cgi?init_popstate=NC">
+<area shape=poly COORDS="146,31,148,57,198,57,190,31,146,31,146,31" href="signup.cgi?init_popstate=ND">
+<area shape=poly COORDS="281,93,285,113,299,121,311,101,309,85,299,93,281,93,281,93" href="signup.cgi?init_popstate=OH">
+<area shape=poly COORDS="148,145,174,145,174,163,218,171,216,143,150,139,150,145,156,143,148,145,148,145" href="signup.cgi?init_popstate=OK">
+<area shape=poly COORDS="20,41,8,73,16,77,22,77,28,77,36,79,42,81,48,83,56,83,66,49,20,41,20,41" href="signup.cgi?init_popstate=OR">
+<area shape=poly COORDS="309,83,345,71,351,93,313,105,309,83,309,83" href="signup.cgi?init_popstate=PA">
+<area shape=poly COORDS="376,93,405,93,405,107,376,105,376,93" href="signup.cgi?init_popstate=RI">
+<area shape=poly COORDS="301,155,321,149,337,155,325,175,301,157,301,155,301,155" href="signup.cgi?init_popstate=SC">
+<area shape=poly COORDS="146,59,198,61,198,83,146,83,148,57,146,59,146,59" href="signup.cgi?init_popstate=SD">
+<area shape=poly COORDS="255,145,251,157,297,153,311,133,255,145,255,145" href="signup.cgi?init_popstate=TN">
+<area shape=poly COORDS="150,145,172,145,174,167,198,173,218,173,228,207,204,221,198,231,202,247,180,241,154,207,146,219,120,189,154,189,152,145,150,145,150,145" href="signup.cgi?init_popstate=TX">
+<area shape=poly COORDS="78,89,96,91,96,103,110,103,106,135,70,133,78,89,78,89" href="signup.cgi?init_popstate=UT">
+<area shape=poly COORDS="298,29,332,29,332,47,294,45,298,29" href="signup.cgi?init_popstate=VT">
+<area shape=poly COORDS="307,127,297,137,351,127,349,113,341,111,341,105,329,107,315,131,307,127,307,127" href="signup.cgi?init_popstate=VA">
+<area shape=poly COORDS="32,13,68,19,64,47,20,39,20,13,30,19,32,13,32,13" href="signup.cgi?init_popstate=WA">
+<area shape=poly COORDS="303,119,313,129,329,103,311,105,299,121,313,127,303,119,303,119" href="signup.cgi?init_popstate=WV">
+<area shape=poly COORDS="228,51,256,55,254,89,238,89,234,77,224,71,230,49,236,53,228,51,228,51" href="signup.cgi?init_popstate=WI">
+<area shape=poly COORDS="146,71,144,103,96,99,102,63,148,69,146,71,146,71" href="signup.cgi?init_popstate=WY">
+</MAP>
+<IMG SRC="map.gif" usemap=#usmap WIDTH=405 HEIGHT=270 border=0><BR>
+<SELECT NAME="init_popstate" onChange="gotoURL(this.form.init_popstate)">
+<OPTION VALUE="stateselect.html"></OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AL">Alabama</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AK">Alaska</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=AS">American Samoa</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=AZ">Arizona</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AR">Arkansas</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=CA">California</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=CO">Colorado</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=CT">Connecticut</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=DE">Delaware</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=DC">District of Columbia</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=FM">Federated States of Micronesia</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=FL">Florida</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=GA">Georgia</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=GU">Guam</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=HI">Hawaii</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=ID">Idaho</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=IL">Illinois</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=IN">Indiana</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=IA">Iowa</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=KS">Kansas</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=KY">Kentucky</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=LA">Louisiana</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=ME">Maine</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=MH">Marshall Islands</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=MD">Maryland</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MA">Massachusetts</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MI">Michigan</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MN">Minnesota</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MS">Mississippi</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MO">Missouri</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=MT">Montana</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NE">Nebraska</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NV">Nevada</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NH">New Hampshire</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NJ">New Jersey</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NM">New Mexico</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NY">New York</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=NC">North Carolina</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=ND">North Dakota</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=MP">Northern Mariana Islands</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=OH">Ohio</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=OK">Oklahoma</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=OR">Oregon</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=PW">Palau</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=PA">Pennsylvania</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=PR">Puerto Rico</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=RI">Rhode Island</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=SC">South Carolina</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=SD">South Dakota</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=TN">Tennessee</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=TX">Texas</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=UT">Utah</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=VT">Vermont</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=VI">Virgin Islands</OPTION>-->
+<OPTION VALUE="signup.cgi?init_popstate=VA">Virginia</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=WA">Washington</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=WV">West Virginia</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=WI">Wisconsin</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=WY">Wyoming</OPTION>
+<!--<OPTION VALUE="signup.cgi?init_popstate=AE">Armed Forces Africa</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AA">Armed Forces Americas</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AE">Armed Forces Canada</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AE">Armed Forces Europe</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AE">Armed Forces Middle East</OPTION>
+<OPTION VALUE="signup.cgi?init_popstate=AP">Armed Forces Pacific</OPTION>
+-->
+</SELECT>
+</FORM>
+</BODY>
+</HTML>
diff --git a/fs_signup/FS-SignupClient/cgi/success.html b/fs_signup/FS-SignupClient/cgi/success.html
new file mode 100644
index 0000000..397cc6c
--- /dev/null
+++ b/fs_signup/FS-SignupClient/cgi/success.html
@@ -0,0 +1,11 @@
+<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
+Thanks for signing up!
+<BR><BR>
+Signup information for <%= $email_name %>:
+<BR><BR>
+Username: <%= $username %><BR>
+Password: <%= $password %><BR>
+Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR>
+Package: <%= $pkg %><BR>
+</BODY></HTML>
diff --git a/fs_signup/FS-SignupClient/test.pl b/fs_signup/FS-SignupClient/test.pl
new file mode 100644
index 0000000..b613695
--- /dev/null
+++ b/fs_signup/FS-SignupClient/test.pl
@@ -0,0 +1,20 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+######################### We start with some black magic to print on failure.
+
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+#blah#use FS::SignupClient;
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+# Insert your test code below (better if it prints "ok 13"
+# (correspondingly "not ok 13") depending on the success of chunk 13
+# of the test code):
+
diff --git a/fs_signup/cck.template b/fs_signup/cck.template
new file mode 100644
index 0000000..f1db554
--- /dev/null
+++ b/fs_signup/cck.template
@@ -0,0 +1,14 @@
+SITE_FILE 8chrfile
+SITE_NAME YourISP
+LOGIN { $username }
+PASSWORD { $password }
+PHONE_NUM +1({ $ac }){ $exch }-{ $loc }
+DNS_ADDR 10.0.0.1
+DNS_ADDR2 10.0.0.2
+NNTP_HOST news.yourisp.com
+SMTP_HOST mail.yourisp.com
+DOMAIN_NAME yourisp.com
+POP_SERVER { $username }@mail.yourisp.com
+POP_PASSWORD { $password }
+HOME_URL http://www.yourisp.com
+EMAIL_ADDR { $username }@yourisp.com
diff --git a/fs_signup/fs_signup_server b/fs_signup/fs_signup_server
new file mode 100755
index 0000000..d6eb4a8
--- /dev/null
+++ b/fs_signup/fs_signup_server
@@ -0,0 +1,289 @@
+#!/usr/bin/perl -Tw
+#
+# fs_signup_server
+#
+
+use strict;
+use vars qw($pid);
+use IO::Handle;
+use Storable qw(nstore_fd fd_retrieve);
+use Tie::RefHash;
+use Net::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_county;
+use FS::cust_main;
+use FS::cust_bill;
+use FS::cust_pkg;
+use FS::Msgcat qw(gettext);
+
+use vars qw( $opt $Debug );
+
+$Debug = 2;
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user );
+
+my $conf = new FS::Conf;
+
+if ($conf->exists('signup_server-quiet')) {
+ $FS::cust_bill::quiet = 1;
+ $FS::cust_pkg::quiet = 1;
+}
+
+#my @payby = qw(CARD PREPAY);
+my @payby = $conf->config('signup_server-payby');
+my $smtpmachine = $conf->config('smtpmachine');
+
+my $machine = shift or die &usage;
+
+my $agentnum = shift or die &usage;
+my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } ) or die &usage;
+my $pkgpart_href = $agent->pkgpart_hashref;
+
+my $refnum = shift or die &usage;
+
+#causing trouble for some folks
+#$SIG{CHLD} = sub { wait() };
+
+$SIG{HUP} = \&killssh;
+$SIG{INT} = \&killssh;
+$SIG{QUIT} = \&killssh;
+$SIG{TERM} = \&killssh;
+$SIG{PIPE} = \&killssh;
+sub killssh { kill 'TERM', $pid if $pid; exit; };
+
+my($fs_signupd)="/usr/local/sbin/fs_signupd";
+
+while (1) {
+ my($reader,$writer)=(new IO::Handle, new IO::Handle);
+ #seems to be broken - calling ->flush explicitly# $writer->autoflush(1);
+ warn "[fs_signup_server] Connecting to $machine...\n" if $Debug;
+ $pid = sshopen2($machine,$reader,$writer,$fs_signupd);
+
+ my @pops = qsearch('svc_acct_pop',{} );
+ my $init_data = {
+
+ #'_protocol' => 'signup',
+ #'_version' => '0.1',
+ #'_packet' => 'init'
+
+ 'cust_main_county' =>
+ [ map { $_->hashref } qsearch('cust_main_county', {}) ],
+
+ 'part_pkg' =>
+ [
+ #map { $_->hashref }
+ map { { 'payby' => [ $_->payby ], %{$_->hashref} } }
+ grep { $_->svcpart('svc_acct') && $pkgpart_href->{ $_->pkgpart } }
+ qsearch( 'part_pkg', { 'disabled' => '' } )
+ ],
+
+ 'agentnum2part_pkg' =>
+ {
+ map {
+ my $href = $_->pkgpart_hashref;
+ $_->agentnum =>
+ [
+ map { { 'payby' => [ $_->payby ], %{$_->hashref} } }
+ grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } }
+ qsearch( 'part_pkg', { 'disabled' => '' } )
+ ];
+ } qsearch('agent', {} )
+ },
+
+ 'svc_acct_pop' => [ map { $_->hashref } @pops ],
+
+ 'security_phrase' => $conf->exists('security_phrase'),
+
+ 'payby' => [ $conf->config('signup_server-payby') ],
+
+ 'msgcat' => { map { $_=>gettext($_) } qw(
+ passwords_dont_match invalid_card unknown_card_type not_a
+ ) },
+
+ 'statedefault' => $conf->config('statedefault') || 'CA',
+
+ 'countrydefault' => $conf->config('countrydefault') || 'US',
+
+ };
+
+ warn "[fs_signup_server] Sending init data...\n" if $Debug;
+ nstore_fd($init_data, $writer) or die "can't send init data: $!";
+ $writer->flush;
+
+ warn "[fs_signup_server] Entering main loop...\n" if $Debug;
+ while (1) {
+ warn "[fs_signup_server] Reading (waiting for) signup data...\n" if $Debug;
+ my $signup_data = fd_retrieve($reader);
+
+ if ( $Debug > 1 ) {
+ warn join('',
+ map { " $_ => ". $signup_data->{$_}. "\n" } keys %$signup_data );
+ }
+
+ warn "[fs_signup_server] Processing signup...\n" if $Debug;
+
+ my $error = '';
+
+ #things that aren't necessary in base class, but are for signup server
+ #return "Passwords don't match"
+ # if $hashref->{'_password'} ne $hashref->{'_password2'}
+ $error ||= gettext('empty_password') unless $signup_data->{'_password'};
+ $error ||= gettext('no_access_number_selected')
+ unless $signup_data->{'popnum'} || !scalar(@pops);
+
+ #shares some stuff with htdocs/edit/process/cust_main.cgi... take any
+ # common that are still here and library them.
+ my $cust_main = new FS::cust_main ( {
+ #'custnum' => '',
+ 'agentnum' => $signup_data->{agentnum} || $agentnum,
+ 'refnum' => $refnum,
+
+ map { $_ => $signup_data->{$_} } qw(
+ last first ss company address1 address2 city county state zip country
+ daytime night fax payby payinfo paydate payname referral_custnum comments
+ ),
+
+ } );
+
+ $error ||= "Illegal payment type"
+ unless grep { $_ eq $signup_data->{'payby'} } @payby;
+
+ $cust_main->payinfo($cust_main->daytime)
+ if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
+
+ my @invoicing_list = split( /\s*\,\s*/, $signup_data->{'invoicing_list'} );
+
+ $signup_data->{'pkgpart'} =~ /^(\d+)$/ or '' =~ /^()$/;
+ my $pkgpart = $1;
+
+ my $part_pkg =
+ qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } )
+ or $error ||= "WARNING: unknown pkgpart: $pkgpart";
+ my $svcpart = $part_pkg->svcpart('svc_acct') unless $error;
+
+ my $cust_pkg = new FS::cust_pkg ( {
+ #later#'custnum' => $custnum,
+ 'pkgpart' => $signup_data->{'pkgpart'},
+ } );
+ $error ||= $cust_pkg->check;
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ map { $_ => $signup_data->{$_} }
+ qw( username _password sec_phrase popnum ),
+ } );
+
+ my $y = $svc_acct->setdefault; # arguably should be in new method
+ $error ||= $y unless ref($y);
+
+ $error ||= $svc_acct->check;
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => [ $svc_acct ] );
+ $error ||= $cust_main->insert( \%hash, \@invoicing_list ); #msgcat
+
+ if ( ! $error && $conf->exists('signup_server-realtime') ) {
+
+ warn "[fs_signup_server] Billing customer...\n" if $Debug;
+
+ my $bill_error = $cust_main->bill;
+ warn "[fs_signup_server] error billing new customer: $bill_error"
+ if $bill_error;
+
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ $bill_error = $cust_main->collect;
+ warn "[fs_signup_server] error collecting from new customer: $bill_error"
+ if $bill_error;
+
+ if ( $cust_main->balance > 0 ) {
+
+ #this makes sense. credit is "un-doing" the invoice
+ $cust_main->credit( $cust_main->balance, 'signup server decline' );
+ $cust_main->apply_credits;
+
+ #should check list for errors...
+ #$cust_main->suspend;
+ $cust_main->cancel;
+
+ $error = '_decline';
+ }
+ }
+
+ warn "[fs_signup_server] Sending results...\n" if $Debug;
+ print $writer $error, "\n";
+
+ next if $error;
+
+ if ( $conf->config('signup_server-email') ) {
+ warn "[fs_signup_server] Sending email...\n" if $Debug;
+
+ #false laziness w/FS::cust_bill::send & FS::cust_pay::delete
+ use Mail::Header;
+ use Mail::Internet 1.44;
+ use Date::Format;
+ my $from = $conf->config('invoice_from'); #??? as good as any
+ $ENV{MAILADDRESS} = $from;
+ my $header = new Mail::Header ( [
+ "From: $from",
+ "To: ". $conf->config('signup_server-email'),
+ "Sender: $from",
+ "Reply-To: $from",
+ "Date: ". time2str("%a, %d %b %Y %X %z", time),
+ "Subject: FREESIDE NOTIFICATION: Signup Server",
+ ] );
+ my $body = [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you a customer has signed up via the signup server:\n",
+ "\n",
+ 'custnum : '. $cust_main->custnum. "\n",
+ 'Name : '. $cust_main->last. ", ". $cust_main->first. "\n",
+ 'Agent : '. $cust_main->agent->agent. "\n",
+ 'Package : '. $part_pkg->pkg. ' - '. $part_pkg->comment. "\n",
+ 'Signup Date : '. time2str('%C', time). "\n",
+ 'Username : '. $svc_acct->username. "\n",
+ #'Password : '. # config file to turn this on if noment insists
+ 'Day phone : '. $cust_main->daytime. "\n",
+ 'Night phone : '. $cust_main->night. "\n",
+ 'Address : '. $cust_main->address1. "\n",
+ ( $cust_main->address2
+ ? ' '. $cust_main->address2. "\n"
+ : '' ),
+ ' '. $cust_main->city. ', '. $cust_main->state. ' '.
+ $cust_main->zip. "\n",
+ ( $cust_main->country eq 'US'
+ ? ''
+ : ' '. $cust_main->country. "\n" ),
+ "\n",
+ ];
+ #if ( $cust_main->balance > 0 ) {
+ # push @$body,
+ # "This customer has an outstanding balance and has been suspended.\n";
+ #}
+ my $message = new Mail::Internet ( 'Header' => $header, 'Body' => $body );
+ $!=0;
+ $message->smtpsend( Host => $smtpmachine )
+ or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+ or warn "[fs_signup_server] can't send email to ".
+ $conf->config('signup_server-email').
+ " via server $smtpmachine with SMTP: $!";
+ #end-of-send mail
+ }
+
+ }
+ close $writer;
+ close $reader;
+ warn "connection to $machine lost! waiting 60 seconds...\n";
+ sleep 60;
+ warn "reconnecting...\n";
+}
+
+sub usage {
+ die "Usage:\n\n fs_signup_server user machine agentnum refnum\n";
+}
+
diff --git a/fs_signup/ieak.template b/fs_signup/ieak.template
new file mode 100755
index 0000000..52edaa9
--- /dev/null
+++ b/fs_signup/ieak.template
@@ -0,0 +1,40 @@
+[Entry]
+Entry_Name = The Internet
+[Phone]
+Dial_As_Is=no
+Phone_Number = { $exch. $loc }
+Area_Code = { $ac }
+Country_Code = 1
+Country_Id = 1
+[Server]
+Type = PPP
+SW_Compress = Yes
+PW_Encrypt = Yes
+Negotiate_TCP/IP = Yes
+Disable_LCP = No
+[TCP/IP]
+Specify_IP_Address = No
+Specity_Server_Address = No
+IP_Header_Compress = Yes
+Gateway_On_Remote = Yes
+[User]
+Name = { $username }
+Password = { $password }
+Display_Password = Yes
+[Internet_Mail]
+Email_Name = { $email_name }
+Email_Address = { $username }\@domain.tld
+POP_Server = mail.domain.tld
+POP_Server_Port_Number = 110
+POP_Login_Name = { $username }
+POP_Login_Password = { $password }
+SMTP_Server = mail.domain.tld
+SMTP_Server_Port_Number = 25
+Install_Mail = 1
+[Internet_News]
+NNTP_Server = news.domain.tld
+NNTP_Server_Port_Number = 119
+Logon_Required = No
+Install_News = 1
+[Branding]
+Window_Title = The Internet
diff --git a/fs_webdemo/register.cgi b/fs_webdemo/register.cgi
new file mode 100755
index 0000000..8255822
--- /dev/null
+++ b/fs_webdemo/register.cgi
@@ -0,0 +1,136 @@
+#!/usr/bin/perl -Tw
+#
+# $Id: register.cgi,v 1.5 2000-03-03 18:22:42 ivan Exp $
+
+use strict;
+use vars qw(
+ $datasrc $user $pass $x
+ $cgi $username $email
+ $dbh $sth
+ );
+ #$freeside_bin $freeside_test $freeside_conf
+ #@pw_set @saltset
+ #$user_pw $crypt_pw
+ #$header $msg
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+#use Mail::Internet;
+#use Mail::Header;
+#use Date::Format;
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+#$freeside_bin = '/home/freeside/bin/';
+#$freeside_test = '/home/freeside/test/';
+#$freeside_conf = '/usr/local/etc/freeside/';
+
+$datasrc = 'DBI:mysql:http_auth';
+$user = "freeside";
+$pass = "maelcolm";
+
+##my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
+##my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9' );
+#@pw_set = ( 'a'..'z', '0'..'9' );
+#@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+###
+
+$cgi = new CGI;
+
+$username = $cgi->param('username');
+$username =~ /^\s*([a-z][\w]{0,15})\s*$/i
+ or &idiot("Illegal username. Please use 1-16 alphanumeric characters, and start your username with a letter.");
+$username = lc($1);
+
+$email = $cgi->param('email');
+$email =~ /^([\w\-\.\+]+\@[\w\-\.]+)$/
+ or &idiot("Illegal email address.");
+$email = $1;
+
+###
+
+#$user_pw = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+#$crypt_pw = crypt($user_pw,$saltset[int(rand(64))].$saltset[int(rand(64))]);
+
+###
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+###
+
+$dbh = DBI->connect( $datasrc, $user, $pass, {
+ 'AutoCommit' => 'true',
+} ) or die "DBI->connect error: $DBI::errstr\n";
+$x = $DBI::errstr; #silly; to avoid "used only once" warning
+
+$sth = $dbh->prepare("INSERT INTO mysql_auth VALUES (". join(", ",
+ $dbh->quote($username),
+# $dbh->quote("X"),
+# $dbh->quote($crypt_pw),
+ $dbh->quote($email),
+ $dbh->quote('freeside'),
+ $dbh->quote('unconfigured'),
+). ")" );
+
+$sth->execute or &idiot("Username in use: ". $sth->errstr);
+
+$dbh->disconnect or die $dbh->errstr;
+
+###
+
+$|=1;
+print $cgi->header;
+print <<END;
+<HTML>
+ <HEAD>
+ <TITLE>Freeside demo registration successful</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#FFFFFF">
+ <table>
+ <tr><td>
+ <p align=center>
+ <img border=0 alt="Silicon Interactive Software Design" src="http://www.sisd.com/freeside/small-logo.gif">
+ </td><td>
+ <center><font color="#ff0000" size=7>freeside demo registration successful</font></center>
+ </td></tr>
+ </table>
+ <P>Your sample database has been setup. Your password and the URL for the
+ Freeside demo have been emailed to you.
+ </BODY>
+</HTML>
+END
+
+###
+
+sub idiot {
+ my($error)=@_;
+ print $cgi->header, <<END;
+<HTML>
+ <HEAD>
+ <TITLE>Registration error</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#FFFFFF">
+ <CENTER>
+ <H4>Registration error</H4>
+ </CENTER>
+ <P><B>$error</B>
+ <P>Hit the <I>Back</I> button in your web browser, correct this mistake,
+ and submit the form again.
+ </BODY>
+</HTML>
+END
+
+ exit;
+
+}
diff --git a/fs_webdemo/register.html b/fs_webdemo/register.html
new file mode 100644
index 0000000..acf9cff
--- /dev/null
+++ b/fs_webdemo/register.html
@@ -0,0 +1,33 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ Freeside - Billing and account administration software for ISPs
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#ffffff">
+ <table>
+ <tr><td>
+ <A HREF="http://www.sisd.com/">
+ <IMG BORDER=0 SRC="small-logo.gif" ALIGN=LEFT>
+ </A>
+ </td><td>
+ <center><font color="#ff0000" size=7 size=+4>freeside demo registration</font></center>
+ </td></tr>
+ </table>
+<P>You will need to choose a username for access to the Freeside web demo.
+
+<P><FONT SIZE=+1 COLOR="#ff0000">A password
+ and the URL for your demo will be emailed to you, so don't waste your
+ time with non-deliverable addresses.</FONT>
+We will <B>not</B> give your email address to any third party,
+ nor will we send you any unsolicited email (or in fact any email after the automatic registration).
+ <FORM ACTION="register.cgi" METHOD="POST">
+ <PRE>
+Freeside username: <INPUT TYPE="text" NAME="username" MAXLENGTH=16>
+
+Email address: <INPUT TYPE="text" NAME="email">
+</PRE>
+<BR><INPUT TYPE="Submit" VALUE="Register">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/fs_webdemo/registerd b/fs_webdemo/registerd
new file mode 100755
index 0000000..6314d0a
--- /dev/null
+++ b/fs_webdemo/registerd
@@ -0,0 +1,192 @@
+#!/usr/bin/perl -w
+#
+# $Id: registerd,v 1.8 2000-03-03 12:27:54 ivan Exp $
+
+use strict;
+use vars qw(
+ $freeside_conf
+ $mysql_data
+ $datasrc $user $pass $x
+ $dbh $sth
+ @pw_set @saltset
+ $header $msg
+ );
+ # $freeside_bin $freeside_test
+ # $cgi $username $name $email $user_pw $crypt_pw
+#use CGI;
+#use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use Mail::Internet;
+use Mail::Header;
+use Date::Format;
+
+#$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+#$ENV{'SHELL'} = '/bin/sh';
+#$ENV{'IFS'} = " \t\n";
+#$ENV{'CDPATH'} = '';
+#$ENV{'ENV'} = '';
+#$ENV{'BASH_ENV'} = '';
+
+#$freeside_bin = '/home/freeside/bin/';
+#$freeside_test = '/home/freeside/test/';
+$freeside_conf = '/usr/local/etc/freeside/';
+
+$mysql_data = "/var/lib/mysql";
+
+$datasrc = 'DBI:mysql:http_auth';
+$user = "freeside";
+$pass = "maelcolm";
+
+#my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
+#my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9' );
+@pw_set = ( 'a'..'z', '0'..'9' );
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+#die "not running as system user freeside"
+# unless $> == scalar(getpwnam('freeside'));
+die "not running as root user"
+ unless $> == 0;
+
+$dbh = DBI->connect( $datasrc, $user, $pass, {
+ 'AutoCommit' => 'true',
+} ) or die "DBI->connect error: $DBI::errstr\n";
+$x = $DBI::errstr; #silly; to avoid "used only once" warning
+
+while ( 1 ) {
+
+ $SIG{HUP} = 'IGNORE';
+ $SIG{INT} = 'IGNORE';
+ $SIG{QUIT} = 'IGNORE';
+ $SIG{TERM} = 'IGNORE';
+ $SIG{TSTP} = 'IGNORE';
+ $SIG{PIPE} = 'IGNORE';
+
+ $sth = $dbh->prepare("LOCK TABLES mysql_auth WRITE");
+ $sth->execute or die $sth->errstr;
+
+ $sth = $dbh->prepare(
+ 'SELECT * FROM mysql_auth WHERE status = "unconfigured"'
+ );
+ $sth->execute or die $sth->errstr;
+ my $pending = $sth->fetchall_arrayref( {} );
+
+ $sth = $dbh->prepare(
+ 'UPDATE mysql_auth SET status = "locked" WHERE status = "unconfigured"'
+ );
+ $sth->execute or die $sth->errstr;
+
+ $sth = $dbh->prepare("UNLOCK TABLES");
+ $sth->execute or die $sth->errstr;
+
+ #
+
+ foreach my $row ( @{$pending} ) {
+
+ my $username = $row->{'username'};
+ my $email = $row->{'passwd'};
+
+ system("/usr/bin/mysqladmin --user=$user --password=$pass ".
+ "create demo_$username >/dev/null");
+
+ system "cp -p $mysql_data/demo_template/* $mysql_data/demo_$username";
+
+ mkdir "${freeside_conf}conf.DBI:mysql:demo_$username", 0755;
+ system "cp -pr ${freeside_conf}conf.DBI:mysql:demo_template/* ".
+ "${freeside_conf}conf.DBI:mysql:demo_$username";
+
+ mkdir "${freeside_conf}counters.DBI:mysql:demo_$username", 0755;
+ system "cp -p ${freeside_conf}counters.DBI:mysql:demo_template/* ".
+ "${freeside_conf}counters.DBI:mysql:demo_$username";
+ chown scalar(getpwnam('freeside')), scalar(getgrnam('freeside')),
+ "${freeside_conf}counters.DBI:mysql:demo_$username";
+
+ system "cp -p ${freeside_conf}dbdef.DBI:mysql:demo_template ".
+ "${freeside_conf}dbdef.DBI:mysql:demo_$username";
+
+ open(INVOICE_FROM, ">${freeside_conf}conf.DBI:mysql:demo_$username/invoice_from")
+ or die "Can\'t open ${freeside_conf}conf.DBI:mysql:demo_$username/invoice_from: $!";
+ print INVOICE_FROM "$email\n";
+ close INVOICE_FROM;
+
+ open(LPR, ">${freeside_conf}conf.DBI:mysql:demo_$username/lpr")
+ or die "Can\'t open ${freeside_conf}conf.DBI:mysql:demo_$username/lpr: $!";
+ print LPR "mail $email";
+ close LPR;
+
+ open(FROM, ">${freeside_conf}conf.DBI:mysql:demo_$username/registries/internic/from")
+ or die "Can\'t open ${freeside_conf}conf.DBI:mysql:demo_$username/registries/internic/from: $!";
+ print FROM "$email\n";
+ close FROM;
+
+ open(TO, ">${freeside_conf}conf.DBI:mysql:demo_$username/registries/internic/to")
+ or die "Can\'t open ${freeside_conf}conf.DBI:mysql:demo_$username/registries/internic/to: $!";
+ print TO "$email\n";
+ close TO;
+
+ open(SECRETS, ">${freeside_conf}secrets.demo_$username")
+ or die "Can\'t open ${freeside_conf}secrets.demo_$username: $!";
+ chown scalar(getpwnam('freeside')), scalar(getgrnam('freeside')),
+ "${freeside_conf}secrets.demo_$username";
+ chmod 0600, "${freeside_conf}secrets.demo_$username";
+ print SECRETS "DBI:mysql:demo_$username\nfreeside\nmaelcolm\n";
+ close SECRETS;
+
+ open(MAPSECRETS, ">>${freeside_conf}mapsecrets")
+ or die "Can\'t open ${freeside_conf}mapsecrets: $!";
+ print MAPSECRETS "$username secrets.demo_$username\n";
+ close MAPSECRETS;
+
+ my $user_pw = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+ my $crypt_pw =
+ crypt($user_pw,$saltset[int(rand(64))].$saltset[int(rand(64))]);
+
+ $sth = $dbh->prepare(
+ qq(UPDATE mysql_auth SET passwd = "$crypt_pw", status = "done" WHERE username = "$username")
+ );
+ $sth->execute or die $sth->errstr;
+
+ $ENV{SMTPHOSTS} = "localhost";
+ $ENV{MAILADDRESS} = 'ivan-fsreg@sisd.com';
+ $ENV{TZ} = "PST8PDT";
+ $header = Mail::Header->new( [
+ 'From: ivan-fsreg@sisd.com',
+ "To: $email",
+ 'Bcc: ivan-fsreg_bcc@sisd.com',
+ 'Sender: ivan-fsreg@sisd.com',
+ 'Reply-To: ivan-fsreg@sisd.com',
+ #'Date: '. time2str("%a, %d %b %Y %X %z", time ),
+ 'Date: '. time2str("%a, %d %b %Y %X ", time ). "-0800",
+ 'Subject: Freeside demo information',
+ ] );
+ $msg = Mail::Internet->new(
+ 'Header' => $header,
+ 'Body' => [
+ "Hello,\n",
+ "\n",
+ "Your sample Freeside database has been setup.\n",
+ "\n",
+ "Point your web browswer at http://freeside.sisd.com/ and use the following\n",
+ "authentication information:\n",
+ "\n",
+ "Username: $username\n",
+ "Password: $user_pw\n",
+ "\n",
+ "-- \n",
+ "ivan\n",
+ ]
+ );
+ $msg->smtpsend or die "Can\'t send registration email!";
+
+ }
+
+ $SIG{HUP} = 'DEFAULT';
+ $SIG{INT} = 'DEFAULT';
+ $SIG{QUIT} = 'DEFAULT';
+ $SIG{TERM} = 'DEFAULT';
+ $SIG{TSTP} = 'DEFAULT';
+ $SIG{PIPE} = 'DEFAULT';
+
+ sleep 5;
+
+}
+
diff --git a/fs_webdemo/registerd.Pg b/fs_webdemo/registerd.Pg
new file mode 100755
index 0000000..f166846
--- /dev/null
+++ b/fs_webdemo/registerd.Pg
@@ -0,0 +1,221 @@
+#!/usr/bin/perl -w
+#
+# $Id: registerd.Pg,v 1.11 2001-10-24 15:29:30 ivan Exp $
+
+use strict;
+use vars qw(
+ $freeside_conf
+ $mysql_data
+ $datasrc $user $pass $x
+ $dbh $sth
+ @pw_set @saltset
+ $header $msg
+ );
+ # $freeside_bin $freeside_test
+ # $cgi $username $name $email $user_pw $crypt_pw
+#use CGI;
+#use CGI::Carp qw(fatalsToBrowser);
+use DBI;
+use Mail::Internet;
+use Mail::Header;
+use Date::Format;
+
+#$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+#$ENV{'SHELL'} = '/bin/sh';
+#$ENV{'IFS'} = " \t\n";
+#$ENV{'CDPATH'} = '';
+#$ENV{'ENV'} = '';
+#$ENV{'BASH_ENV'} = '';
+
+#$freeside_bin = '/home/freeside/bin/';
+#$freeside_test = '/home/freeside/test/';
+$freeside_conf = '/usr/local/etc/freeside/';
+
+#$mysql_data = "/var/lib/mysql";
+
+$datasrc = 'DBI:mysql:http_auth';
+$user = "freeside";
+$pass = "maelcolm";
+
+#my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' );
+#my(@pw_set)= ( 'a'..'z', 'A'..'Z', '0'..'9' );
+@pw_set = ( 'a'..'z', '0'..'9' );
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+#die "not running as system user freeside"
+# unless $> == scalar(getpwnam('freeside'));
+die "not running as root user"
+ unless $> == 0;
+
+$dbh = DBI->connect( $datasrc, $user, $pass, {
+ 'AutoCommit' => 'true',
+} ) or die "DBI->connect error: $DBI::errstr\n";
+#$x = $DBI::errstr; #silly; to avoid "used only once" warning
+
+while ( 1 ) {
+
+ $SIG{HUP} = 'IGNORE';
+ $SIG{INT} = 'IGNORE';
+ $SIG{QUIT} = 'IGNORE';
+ $SIG{TERM} = 'IGNORE';
+ $SIG{TSTP} = 'IGNORE';
+ $SIG{PIPE} = 'IGNORE';
+
+ $sth = $dbh->prepare("LOCK TABLES mysql_auth WRITE");
+ $sth->execute or die $sth->errstr;
+
+ $sth = $dbh->prepare(
+ 'SELECT * FROM mysql_auth WHERE status = "unconfigured"'
+ );
+ $sth->execute or die $sth->errstr;
+ my $pending = $sth->fetchall_arrayref( {} );
+
+ $sth = $dbh->prepare(
+ 'UPDATE mysql_auth SET status = "locked" WHERE status = "unconfigured"'
+ );
+ $sth->execute or die $sth->errstr;
+
+ $sth = $dbh->prepare("UNLOCK TABLES");
+ $sth->execute or die $sth->errstr;
+
+ #
+
+ foreach my $row ( @{$pending} ) {
+
+ my $username = $row->{'username'};
+ my $email = $row->{'passwd'};
+
+ my $pdbh = DBI->connect( 'DBI:Pg:host=localhost;dbname=demo_template', 'freeside', 'maelcolm' )
+ or do { &myerr("$username: ". $DBI::errstr); next; };
+
+ my $psth = $pdbh->prepare("CREATE DATABASE demo_$username")
+ or do { &myerr("$username: ". $pdbh->errstr); next; };
+ $psth->execute()
+ or do { &myerr("$username: ". $psth->errstr); next; };
+
+ $pdbh->disconnect
+ or do { &myerr("fatal: $DBI::errstr"); die; };
+
+ open(PSQL,"|psql -U freeside demo_$username")
+ or do { &myerr("|psql -U freeside demo_$username: $!"); next; };
+ open(PSQLDATA, "</usr/local/etc/freeside/demo_template.Pg")
+ or do { &myerr("/usr/local/etc/freeside/demo_template.Pg: $!"); next; };
+ while(<PSQLDATA>) {
+ print PSQL $_;
+ }
+ close PSQLDATA
+ or do { &myerr("/usr/local/etc/freeside/demo_template.Pg: $!"); next; };
+ close PSQL
+ or do { &myerr("|psql -U freeside demo_$username: $!"); next; };
+
+ mkdir "${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username", 0755;
+ system "cp -pr ${freeside_conf}conf.DBI:Pg:host=localhost\\;dbname=demo_template/* ".
+ "${freeside_conf}conf.DBI:Pg:host=localhost\\;dbname=demo_$username";
+
+ mkdir "${freeside_conf}counters.DBI:Pg:host=localhost;dbname=demo_$username", 0755;
+ system "cp -p ${freeside_conf}counters.DBI:Pg:host=localhost\\;dbname=demo_template/* ".
+ "${freeside_conf}counters.DBI:Pg:host=localhost\\;dbname=demo_$username";
+ chown scalar(getpwnam('freeside')), scalar(getgrnam('freeside')),
+ "${freeside_conf}counters.DBI:Pg:host=localhost;dbname=demo_$username";
+
+ system "cp -p ${freeside_conf}dbdef.DBI:Pg:host=localhost\\;dbname=demo_template ".
+ "${freeside_conf}dbdef.DBI:Pg:host=localhost\\;dbname=demo_$username";
+
+ open(INVOICE_FROM, ">${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/invoice_from")
+ or die "Can\'t open ${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/invoice_from: $!";
+ print INVOICE_FROM "$email\n";
+ close INVOICE_FROM;
+
+ open(LPR, ">${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/lpr")
+ or die "Can\'t open ${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/lpr: $!";
+ print LPR "mail $email";
+ close LPR;
+
+# open(FROM, ">${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/registries/internic/from")
+# or die "Can\'t open ${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/registries/internic/from: $!";
+# print FROM "$email\n";
+# close FROM;
+#
+# open(TO, ">${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/registries/internic/to")
+# or die "Can\'t open ${freeside_conf}conf.DBI:Pg:host=localhost;dbname=demo_$username/registries/internic/to: $!";
+# print TO "$email\n";
+# close TO;
+
+ open(SECRETS, ">${freeside_conf}secrets.demo_$username")
+ or die "Can\'t open ${freeside_conf}secrets.demo_$username: $!";
+ chown scalar(getpwnam('freeside')), scalar(getgrnam('freeside')),
+ "${freeside_conf}secrets.demo_$username";
+ chmod 0600, "${freeside_conf}secrets.demo_$username";
+ print SECRETS "DBI:Pg:host=localhost;dbname=demo_$username\nfreeside\nmaelcolm\n";
+ close SECRETS;
+
+ open(MAPSECRETS, ">>${freeside_conf}mapsecrets")
+ or die "Can\'t open ${freeside_conf}mapsecrets: $!";
+ print MAPSECRETS "$username secrets.demo_$username\n";
+ close MAPSECRETS;
+
+ my $user_pw = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+ my $crypt_pw =
+ crypt($user_pw,$saltset[int(rand(64))].$saltset[int(rand(64))]);
+
+ $sth = $dbh->prepare(
+ qq(UPDATE mysql_auth SET passwd = "$crypt_pw", status = "done" WHERE username = "$username")
+ );
+ $sth->execute or die $sth->errstr;
+
+ #$ENV{SMTPHOSTS} = "localhost";
+ $ENV{SMTPHOSTS} = "192.168.1.1";
+ $ENV{MAILADDRESS} = 'ivan-fsreg@sisd.com';
+ $ENV{TZ} = "PST8PDT";
+ $header = Mail::Header->new( [
+ 'From: ivan-fsreg@sisd.com',
+ "To: $email",
+ 'Bcc: ivan-fsreg_bcc@sisd.com',
+ 'Sender: ivan-fsreg@sisd.com',
+ 'Reply-To: ivan-fsreg@sisd.com',
+ #'Date: '. time2str("%a, %d %b %Y %X %z", time ),
+ 'Date: '. time2str("%a, %d %b %Y %X ", time ). "-0800",
+ 'Subject: Freeside demo information',
+ ] );
+ $msg = Mail::Internet->new(
+ 'Header' => $header,
+ 'Body' => [
+ "Hello,\n",
+ "\n",
+ "Your sample Freeside database has been setup.\n",
+ "\n",
+ "Your login and database will be automatically deleted in 1-2 months.\n",
+ "\n",
+ "Point your web browswer at http://freeside.sisd.com/ and use the following\n",
+ "authentication information:\n",
+ "\n",
+ "Username: $username\n",
+ "Password: $user_pw\n",
+ "\n",
+ "-- \n",
+ "ivan\n",
+ ]
+ );
+ $msg->smtpsend or die "Can\'t send registration email!";
+
+ }
+
+ $SIG{HUP} = 'DEFAULT';
+ $SIG{INT} = 'DEFAULT';
+ $SIG{QUIT} = 'DEFAULT';
+ $SIG{TERM} = 'DEFAULT';
+ $SIG{TSTP} = 'DEFAULT';
+ $SIG{PIPE} = 'DEFAULT';
+
+ sleep 5;
+
+}
+
+sub myerr {
+ my $msg = shift;
+ open(MAIL,"|mail ivan-fsdemoerr\@420.am");
+ print MAIL $msg, "\n\n";
+ print MAIL $msg, "\n\n";
+ close MAIL;
+};
+
diff --git a/htetc/global.asa b/htetc/global.asa
new file mode 100644
index 0000000..782e062
--- /dev/null
+++ b/htetc/global.asa
@@ -0,0 +1,242 @@
+BEGIN { eval "use Devel::AutoProfiler;"; } #only if installed...
+#BEGIN { package Devel::AutoProfiler; use vars qw(%caller_info); }
+#use Devel::AutoProfiler;
+
+use strict;
+use vars qw( $cgi $p );
+use Apache::ASP 2.55;
+use CGI 2.47;
+#use CGI::Carp qw(fatalsToBrowser);
+use Date::Format;
+use Date::Parse;
+use Time::Local;
+use Tie::IxHash;
+use HTML::Entities;
+use IO::Handle;
+use IO::File;
+use Net::Whois::Raw qw(whois);
+if ( $] < 5.006 ) {
+ eval "use Net::Whois::Raw 0.32 qw(whois)";
+ die $@ if $@;
+}
+use Business::CreditCard;
+use String::Approx qw(amatch);
+use Chart::LinesPoints;
+use HTML::Widgets::SelectLayers 0.03;
+use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name);
+use FS::Record qw(qsearch qsearchs fields dbdef);
+use FS::Conf;
+use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot
+ small_custview myexit http_header);
+use FS::Msgcat qw(gettext geterror);
+use FS::Misc qw( send_email );
+use FS::Report::Table::Monthly;
+
+use FS::agent;
+use FS::agent_type;
+use FS::domain_record;
+use FS::cust_bill;
+use FS::cust_bill_pay;
+use FS::cust_credit;
+use FS::cust_credit_bill;
+use FS::cust_main;
+use FS::cust_main_county;
+use FS::cust_pay;
+use FS::cust_pkg;
+use FS::cust_refund;
+use FS::cust_svc;
+use FS::nas;
+use FS::part_bill_event;
+use FS::part_pkg;
+use FS::part_referral;
+use FS::part_svc;
+use FS::part_svc_router;
+use FS::part_virtual_field;
+use FS::pkg_svc;
+use FS::port;
+use FS::queue qw(joblisting);
+use FS::raddb;
+use FS::session;
+use FS::svc_acct;
+use FS::svc_acct_pop qw(popselector);
+use FS::svc_domain;
+use FS::svc_forward;
+use FS::svc_www;
+use FS::router;
+use FS::addr_block;
+use FS::svc_broadband;
+use FS::svc_external;
+use FS::type_pkgs;
+use FS::part_export;
+use FS::part_export_option;
+use FS::export_svc;
+use FS::msgcat;
+
+sub Script_OnStart {
+ $Response->AddHeader('Cache-control' => 'no-cache');
+# $Response->AddHeader('Expires' => 0);
+ $Response->{Expires} = -36288000;
+
+ $cgi = new CGI;
+ &cgisuidsetup($cgi);
+ $p = popurl(2);
+ #print $cgi->header( '-expires' => 'now' );
+ #dbh->{'private_profile'} = {} if dbh->can('sprintProfile');
+ dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile');
+
+ #really should check for FS::Profiler or something
+ # Devel::AutoProfiler _our_ VERSION? thanks a fucking lot
+ if ( Devel::AutoProfiler->can('__recursively_fetch_subs_in_package') ) {
+ #should check to see it's my special version. well, switch to FS::Profiler
+
+ #nicked from Devel::AutoProfiler::INIT
+ my %subs = Devel::AutoProfiler::__recursively_fetch_subs_in_package('main');
+
+
+ SUB : while( my ($name, $ref) = each(%subs) )
+ {
+ #next if $name =~ /^(main::)?Apache::/;
+ next unless $name =~ /FS/;
+ foreach my $sub (@Devel::AutoProfiler::do_not_instrument_this_sub)
+ {
+ if ($name =~ /$sub/)
+ {
+ next SUB;
+ }
+ }
+ next if ($Devel::AutoProfiler::do_not_instrument_this_sub{$name});
+ #warn "INIT name is $name \n";
+ Devel::AutoProfiler::__instrument_sub($name, $ref);
+ }
+
+ }
+
+}
+
+sub Script_OnFlush {
+ my $ref = $Response->{BinaryRef};
+ #$$ref = $cgi->header( @FS::CGI::header ) . $$ref;
+ #$$ref = $cgi->header() . $$ref;
+ #warn "Script_OnFlush called with dbh ". dbh. "\n";
+ #if ( dbh->can('sprintProfile') ) {
+ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) {
+ #warn "dbh can sprintProfile\n";
+ if ( lc($Response->{ContentType}) eq 'text/html' ) { #con
+ #warn "contenttype is sprintProfile\n";
+ $$ref =~ s/<\/BODY>[\s\n]*<\/HTML>[\s\n]*$//i
+ or warn "can't remove";
+
+ #$$ref .= '<PRE>'. ("\n"x96). encode_entities(dbh->sprintProfile()). '</PRE>';
+ # wtf? konqueror...
+ $$ref .= '<PRE>'. ("\n"x4096). encode_entities(dbh->sprintProfile()).
+ "\n\n". &sprintAutoProfile(). '</PRE>';
+
+ $$ref .= '</BODY></HTML>';
+ }
+ dbh->{'private_profile'} = {};
+ }
+}
+
+#if ( defined(@DBIx::Profile::ISA) && DBIx::Profile::db->can('sprintProfile') ) {
+#if ( defined(@DBIx::Profile::ISA) && UNIVERSAL::can('DBIx::Profile::db', 'sprintProfile') ) {
+if ( defined(@DBIx::Profile::ISA) ) {
+
+ #warn "enabling profiling redirects";
+ *CGI::redirect = sub {
+ my( $self, $location) = @_;
+ my $page =
+ $cgi->header.
+ qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
+ '<BR><BR><PRE>'.
+ ( UNIVERSAL::can(dbh, 'sprintProfile')
+ ? encode_entities(dbh->sprintProfile())
+ : 'DBIx::Profile missing sprintProfile method;'.
+ 'unpatched or too old?' ).
+ "\n\n". &sprintAutoProfile(). '</PRE>'.
+ '</BODY></HTML>';
+ dbh->{'private_profile'} = {};
+ return $page;
+ };
+
+}
+
+sub by_total_time
+{
+ return $a->{total_time_in_sub} <=> $b->{total_time_in_sub};
+}
+
+sub sprintAutoProfile {
+ my %caller_info = %Devel::AutoProfiler::caller_info;
+ return unless keys %caller_info;
+
+ %Devel::AutoProfiler::caller_info = ();
+
+ my @keys = keys(%caller_info);
+
+ foreach my $key (@keys)
+ {
+ my $href = $caller_info{$key};
+
+ $href->{who_am_i} = $key;
+ }
+
+ my @subs = values(%caller_info);
+
+ #my @sorted = sort by_total_time ( @subs );
+ my @sorted = reverse sort by_total_time ( @subs );
+
+ # print Dumper \@sorted;
+
+ my @readable_info;
+
+ foreach my $sort (@sorted)
+ {
+ push(@readable_info, delete($sort->{who_am_i}));
+ push(@readable_info, $sort);
+ }
+
+ use Data::Dumper;
+ return encode_entities(Dumper(\@readable_info));
+
+}
+
+sub include {
+ ( my $file = shift ) =~ s(^/)(%%%FREESIDE_DOCUMENT_ROOT%%%/);
+ #broken in 5.005# ${$Response->TrapInclude($file, @_)};
+ my $ref = $Response->TrapInclude($file, @_);
+ $$ref;
+}
+
+if ( defined(@DBIx::Profile::ISA) ) {
+
+ #false laziness w/above
+ *redirect = sub {
+ my($location) = @_;
+
+ ${$Response->{BinaryRef}} =
+ $cgi->header.
+ qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
+ '<BR><BR><PRE>'.
+ ( UNIVERSAL::can(dbh, 'sprintProfile')
+ ? encode_entities(dbh->sprintProfile())
+ : 'DBIx::Profile missing sprintProfile method;'.
+ 'unpatched or too old?' ).
+ "\n\n". &sprintAutoProfile(). '</PRE>'.
+ '</BODY></HTML>';
+
+ dbh->{'private_profile'} = {};
+
+ $Response->End;
+
+ };
+
+} else {
+
+ *redirect = sub {
+ $Response->Redirect(@_);
+ }
+
+}
+
+1;
+
diff --git a/htetc/handler.pl b/htetc/handler.pl
new file mode 100644
index 0000000..885c216
--- /dev/null
+++ b/htetc/handler.pl
@@ -0,0 +1,305 @@
+#!/usr/bin/perl
+#
+# This is a basic, fairly fuctional Mason handler.pl.
+#
+# For something a little more involved, check out session_handler.pl
+
+package HTML::Mason;
+
+# Bring in main Mason package.
+use HTML::Mason 1.1;
+
+# Bring in ApacheHandler, necessary for mod_perl integration.
+# Uncomment the second line (and comment the first) to use
+# Apache::Request instead of CGI.pm to parse arguments.
+use HTML::Mason::ApacheHandler;
+# use HTML::Mason::ApacheHandler (args_method=>'mod_perl');
+
+# Uncomment the next line if you plan to use the Mason previewer.
+#use HTML::Mason::Preview;
+
+use strict;
+
+# List of modules that you want to use from components (see Admin
+# manual for details)
+#{ package HTML::Mason::Commands;
+# use CGI;
+#}
+
+# Create Mason objects
+#
+
+#my $parser = new HTML::Mason::Parser;
+#my $interp = new HTML::Mason::Interp (parser=>$parser,
+# comp_root=>'/var/www/masondocs',
+# data_dir=>'/usr/local/etc/freeside/masondata',
+# out_mode=>'stream',
+# );
+
+use vars qw($r);
+
+if ( %%%RT_ENABLED%%% ) {
+ eval '
+ use lib ("/opt/rt3/local/lib", "/opt/rt3/lib");
+ use RT;
+ use vars qw($Nobody $SystemUser);
+ RT::LoadConfig();
+ ';
+ die $@ if $@;
+
+
+}
+
+
+my $ah = new HTML::Mason::ApacheHandler (
+ #interp => $interp,
+ #auto_send_headers => 0,
+ comp_root=> [
+ [ 'freeside' => '%%%FREESIDE_DOCUMENT_ROOT%%%' ],
+ [ 'rt' => '%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ],
+ ],
+ data_dir=>'/usr/local/etc/freeside/masondata',
+ #out_mode=>'stream',
+
+ #RT
+ args_method => 'CGI',
+ default_escape_flags => 'h',
+ allow_globals => [qw(%session)],
+ #autoflush => 1,
+);
+
+# Activate the following if running httpd as root (the normal case).
+# Resets ownership of all files created by Mason at startup.
+#
+#chown (Apache->server->uid, Apache->server->gid, $interp->files_written);
+
+sub handler
+{
+ ($r) = @_;
+
+ # If you plan to intermix images in the same directory as
+ # components, activate the following to prevent Mason from
+ # evaluating image files as components.
+ #
+ #return -1 if $r->content_type && $r->content_type !~ m|^text/|i;
+
+ #rar
+ { package HTML::Mason::Commands;
+ use strict;
+ use vars qw( $cgi $p );
+ use vars qw( %session );
+ use CGI 2.47 qw(-private_tempfiles);
+ #use CGI::Carp qw(fatalsToBrowser);
+ use Date::Format;
+ use Date::Parse;
+ use Time::Local;
+ use Tie::IxHash;
+ use HTML::Entities;
+ use IO::Handle;
+ use IO::File;
+ use Net::Whois::Raw qw(whois);
+ if ( $] < 5.006 ) {
+ eval "use Net::Whois::Raw 0.32 qw(whois)";
+ die $@ if $@;
+ }
+ use Business::CreditCard;
+ use String::Approx qw(amatch);
+ use Chart::LinesPoints;
+ use HTML::Widgets::SelectLayers 0.03;
+ use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name);
+ use FS::Record qw(qsearch qsearchs fields dbdef);
+ use FS::Conf;
+ use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot
+ small_custview myexit http_header);
+ use FS::Msgcat qw(gettext geterror);
+ use FS::Misc qw( send_email );
+ use FS::Report::Table::Monthly;
+
+ use FS::agent;
+ use FS::agent_type;
+ use FS::domain_record;
+ use FS::cust_bill;
+ use FS::cust_bill_pay;
+ use FS::cust_credit;
+ use FS::cust_credit_bill;
+ use FS::cust_main;
+ use FS::cust_main_county;
+ use FS::cust_pay;
+ use FS::cust_pkg;
+ use FS::cust_refund;
+ use FS::cust_svc;
+ use FS::nas;
+ use FS::part_bill_event;
+ use FS::part_pkg;
+ use FS::part_referral;
+ use FS::part_svc;
+ use FS::part_svc_router;
+ use FS::part_virtual_field;
+ use FS::pkg_svc;
+ use FS::port;
+ use FS::queue qw(joblisting);
+ use FS::raddb;
+ use FS::session;
+ use FS::svc_acct;
+ use FS::svc_acct_pop qw(popselector);
+ use FS::svc_domain;
+ use FS::svc_forward;
+ use FS::svc_www;
+ use FS::router;
+ use FS::addr_block;
+ use FS::svc_broadband;
+ use FS::svc_external;
+ use FS::type_pkgs;
+ use FS::part_export;
+ use FS::part_export_option;
+ use FS::export_svc;
+ use FS::msgcat;
+
+ if ( %%%RT_ENABLED%%% ) {
+ eval '
+ use RT::Tickets;
+ use RT::Transactions;
+ use RT::Users;
+ use RT::CurrentUser;
+ use RT::Templates;
+ use RT::Queues;
+ use RT::ScripActions;
+ use RT::ScripConditions;
+ use RT::Scrips;
+ use RT::Groups;
+ use RT::GroupMembers;
+ use RT::CustomFields;
+ use RT::CustomFieldValues;
+ use RT::TicketCustomFieldValues;
+
+ use RT::Interface::Web;
+ use MIME::Entity;
+ use Text::Wrapper;
+ use CGI::Cookie;
+ use Time::ParseDate;
+ ';
+ die $@ if $@;
+ }
+
+ *CGI::redirect = sub {
+ my( $self, $location ) = @_;
+ use vars qw($m);
+
+ if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect
+
+ my $page =
+ qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
+ '<BR><BR><PRE>'.
+ ( UNIVERSAL::can(dbh, 'sprintProfile')
+ ? encode_entities(dbh->sprintProfile())
+ : 'DBIx::Profile missing sprintProfile method;'.
+ 'unpatched or too old?' ).
+ #"\n\n". &sprintAutoProfile(). '</PRE>'.
+ "\n\n". '</PRE>'.
+ '</BODY></HTML>';
+ dbh->{'private_profile'} = {};
+ return $page;
+
+ } else { #normal redirect
+
+ $m->redirect($location);
+ '';
+
+ }
+
+ };
+
+ $cgi = new CGI;
+ &cgisuidsetup($cgi);
+ #&cgisuidsetup($r);
+ $p = popurl(2);
+
+ sub include {
+ use vars qw($m);
+ $m->scomp(@_);
+ }
+
+ sub redirect {
+ my( $location ) = @_;
+ use vars qw($m);
+ $m->clear_buffer;
+ #false laziness w/above
+ if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect
+
+ $m->print(
+ qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
+ '<BR><BR><PRE>'.
+ ( UNIVERSAL::can(dbh, 'sprintProfile')
+ ? encode_entities(dbh->sprintProfile())
+ : 'DBIx::Profile missing sprintProfile method;'.
+ 'unpatched or too old?' ).
+ #"\n\n". &sprintAutoProfile(). '</PRE>'.
+ "\n\n". '</PRE>'.
+ '</BODY></HTML>'
+ );
+ dbh->{'private_profile'} = {};
+
+ $m->abort(200);
+
+ } else { #normal redirect
+
+ $m->redirect($location);
+
+ }
+
+ }
+
+ } # end package HTML::Mason::Commands;
+
+ $r->content_type('text/html');
+ #eorar
+
+ my $headers = $r->headers_out;
+ $headers->{'Cache-control'} = 'no-cache';
+ #$r->no_cache(1);
+ $headers->{'Expires'} = '0';
+
+# $r->send_http_header;
+
+ #$ah->interp->remove_escape('h');
+
+ if ( $r->filename =~ /\/rt\// ) { #RT
+ #warn "processing RT file". $r->filename. "; escaping for RT\n";
+
+ # MasonX::Request::ExtendedCompRoot
+ #$ah->interp->comp_root( '/rt'. $ah->interp->comp_root() );
+
+ $ah->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+
+ local $SIG{__WARN__};
+ local $SIG{__DIE__};
+
+ RT::Init();
+
+ # We don't need to handle non-text items
+ return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io;
+
+ } else {
+ $ah->interp->set_escape( 'h' => sub { ${$_[0]}; } );
+ }
+
+ my %session;
+ my $status;
+ eval { $status = $ah->handle_request($r); };
+#!!
+# if ( $@ ) {
+# $RT::Logger->crit($@);
+# }
+
+ undef %session;
+
+#!!
+# if ($RT::Handle->TransactionDepth) {
+# $RT::Handle->ForceRollback;
+# $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
+# }
+
+ $status;
+}
+
+1;
diff --git a/httemplate/.htaccess b/httemplate/.htaccess
new file mode 100755
index 0000000..f8c6b9c
--- /dev/null
+++ b/httemplate/.htaccess
@@ -0,0 +1,3 @@
+AuthName Freeside
+AuthType Basic
+require valid-user
diff --git a/httemplate/autohandler b/httemplate/autohandler
new file mode 100644
index 0000000..2bd3adf
--- /dev/null
+++ b/httemplate/autohandler
@@ -0,0 +1,21 @@
+% $m->call_next;
+<%init>
+ dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile');
+</%init>
+<%filter>
+
+my $profile = '';
+if ( UNIVERSAL::can(dbh, 'sprintProfile') ) {
+
+ if ( lc($r->content_type) eq 'text/html' ) {
+
+ $profile = '<PRE>'. ("\n"x4096). encode_entities(dbh->sprintProfile()).
+ #"\n\n". &sprintAutoProfile(). '</PRE>';
+ "\n\n". '</PRE>';
+ }
+
+ dbh->{'private_profile'} = {};
+}
+
+s/(<\/BODY>[\s\n]*<\/HTML>[\s\n]*)$/$profile$1/i;
+</%filter>
diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi
new file mode 100644
index 0000000..06ac556
--- /dev/null
+++ b/httemplate/browse/addr_block.cgi
@@ -0,0 +1,76 @@
+<%= header('Address Blocks', menubar('Main Menu' => $p)) %>
+<%
+
+use NetAddr::IP;
+
+my @addr_block = qsearch('addr_block', {});
+my @router = qsearch('router', {});
+my $block;
+my $p2 = popurl(2);
+my $path = $p2 . "edit/process/addr_block";
+
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<%=table()%>
+
+<% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { %>
+ <TR>
+ <TD><%=$block->NetAddr%></TD>
+ <% if (my $router = $block->router) { %>
+ <% if (scalar($block->svc_broadband) == 0) { %>
+ <TD>
+ <%=$router->routername%>
+ </TD>
+ <TD>
+ <FORM ACTION="<%=$path%>/deallocate.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <INPUT TYPE="submit" NAME="submit" VALUE="Deallocate">
+ </FORM>
+ </TD>
+ <% } else { %>
+ <TD COLSPAN="2">
+ <%=$router->routername%>
+ </TD>
+ <% } %>
+ <% } else { %>
+ <TD>
+ <FORM ACTION="<%=$path%>/allocate.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <SELECT NAME="routernum" SIZE="1">
+ <% foreach (@router) { %>
+ <OPTION VALUE="<%=$_->routernum %>"><%=$_->routername%></OPTION>
+ <% } %>
+ </SELECT>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Allocate">
+ </FORM>
+ </TD>
+ <TD>
+ <FORM ACTION="<%=$path%>/split.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <INPUT TYPE="submit" NAME="submit" VALUE="Split">
+ </FORM>
+ </TD>
+ </TR>
+<% }
+ } %>
+ <TR><TD COLSPAN="3"><BR></TD></TR>
+ <TR>
+ <FORM ACTION="<%=$path%>/add.cgi" METHOD="POST">
+ <TD>Gateway/Netmask</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2">
+ </TD>
+ <TD>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Add">
+ </TD>
+ </FORM>
+ </TR>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
new file mode 100755
index 0000000..f389342
--- /dev/null
+++ b/httemplate/browse/agent.cgi
@@ -0,0 +1,100 @@
+<!-- mason kludge -->
+
+<%
+
+ my %search;
+ if ( $cgi->param('showdisabled')
+ || !dbdef->table('agent')->column('disabled') ) {
+ %search = ();
+ } else {
+ %search = ( 'disabled' => '' );
+ }
+
+%>
+
+<%= header('Agent Listing', menubar(
+ 'Main Menu' => $p,
+ 'Agent Types' => $p. 'browse/agent_type.cgi',
+# 'Add new agent' => '../edit/agent.cgi'
+)) %>
+Agents are resellers of your service. Agents may be limited to a subset of your
+full offerings (via their type).<BR><BR>
+<A HREF="<%= $p %>edit/agent.cgi"><I>Add a new agent</I></A><BR><BR>
+
+<% if ( dbdef->table('agent')->column('disabled') ) { %>
+ <%= $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled agents</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled agents</a> )'; }
+ %>
+<% } %>
+
+<%= table() %>
+<TR>
+ <TH COLSPAN=<%= ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH>
+ <TH>Type</TH>
+ <TH>Customers</TH>
+ <TH><FONT SIZE=-1>Freq.</FONT></TH>
+ <TH><FONT SIZE=-1>Prog.</FONT></TH>
+</TR>
+<%
+# <TH><FONT SIZE=-1>Agent #</FONT></TH>
+# <TH>Agent</TH>
+
+foreach my $agent ( sort {
+ #$a->getfield('agentnum') <=> $b->getfield('agentnum')
+ $a->getfield('agent') cmp $b->getfield('agent')
+} qsearch('agent', \%search ) ) {
+
+ my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'.
+ 'agentnum='. $agent->agentnum;
+
+%>
+
+ <TR>
+ <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>">
+ <%= $agent->agentnum %></A></TD>
+<% if ( dbdef->table('agent')->column('disabled')
+ && !$cgi->param('showdisabled') ) { %>
+ <TD><%= $agent->disabled ? 'DISABLED' : '' %></TD>
+<% } %>
+
+ <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>">
+ <%= $agent->agent %></A></TD>
+ <TD><A HREF="<%=$p%>edit/agent_type.cgi?<%= $agent->typenum %>"><%= $agent->agent_type->atype %></A></TD>
+ <TD>
+
+ <B>
+ <%= my $num_prospect = $agent->num_prospect_cust_main %>
+ </B>
+ <% if ( $num_prospect ) { %>
+ <A HREF="<%= $cust_main_link %>&prospect=1"><% } %>prospects<% if ($num_prospect ) { %></A><% } %>
+
+ <BR><FONT COLOR="#00CC00"><B>
+ <%= my $num_active = $agent->num_active_cust_main %>
+ </B></FONT>
+ <% if ( $num_active ) { %>
+ <A HREF="<%= $cust_main_link %>&active=1"><% } %>active<% if ( $num_active ) { %></A><% } %>
+
+ <BR><FONT COLOR="#FF9900"><B>
+ <%= my $num_susp = $agent->num_susp_cust_main %>
+ </B></FONT>
+ <% if ( $num_susp ) { %>
+ <A HREF="<%= $cust_main_link %>&suspended=1"><% } %>suspended<% if ( $num_susp ) { %></A><% } %>
+
+ <BR><FONT COLOR="#FF0000"><B>
+ <%= my $num_cancel = $agent->num_cancel_cust_main %>
+ </B></FONT>
+ <% if ( $num_cancel ) { %>
+ <A HREF="<%= $cust_main_link %>&showcancelledcustomers=1&cancelled=1"><% } %>cancelled<% if ( $num_cancel ) { %></A><% } %>
+ </TD>
+ <TD><%= $agent->freq %></TD>
+ <TD><%= $agent->prog %></TD>
+ </TR>
+
+<% } %>
+
+ </TABLE>
+ </BODY>
+</HTML>
diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi
new file mode 100755
index 0000000..5473804
--- /dev/null
+++ b/httemplate/browse/agent_type.cgi
@@ -0,0 +1,60 @@
+<!-- mason kludge -->
+<%= header("Agent Type Listing", menubar(
+ 'Main Menu' => $p,
+ 'Agents' => $p. 'browse/agent.cgi',
+)) %>
+Agent types define groups of packages that you can then assign to particular
+agents.<BR><BR>
+<A HREF="<%= $p %>edit/agent_type.cgi"><I>Add a new agent type</I></A><BR><BR>
+
+<%= table() %>
+<TR>
+ <TH COLSPAN=2>Agent Type</TH>
+ <TH COLSPAN=2>Packages</TH>
+</TR>
+
+<%
+foreach my $agent_type ( sort {
+ $a->getfield('typenum') <=> $b->getfield('typenum')
+} qsearch('agent_type',{}) ) {
+ my $hashref = $agent_type->hashref;
+ #more efficient to do this with SQL...
+ my @type_pkgs = grep { $_->part_pkg and ! $_->part_pkg->disabled }
+ qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} });
+ my $rowspan = scalar(@type_pkgs);
+ $rowspan = int($rowspan/2+0.5) ;
+ print <<END;
+ <TR>
+ <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}">
+ $hashref->{typenum}
+ </A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}">$hashref->{atype}</A></TD>
+END
+
+ my($type_pkgs);
+ my($tdcount) = -1 ;
+ foreach $type_pkgs ( @type_pkgs ) {
+ my($pkgpart)=$type_pkgs->getfield('pkgpart');
+ my($part_pkg) = qsearchs('part_pkg',{'pkgpart'=> $pkgpart });
+ print qq!<TR>! if ($tdcount == 0) ;
+ $tdcount = 0 if ($tdcount == -1) ;
+ print qq!<TD><A HREF="${p}edit/part_pkg.cgi?$pkgpart">!,
+ $part_pkg->getfield('pkg'),"</A></TD>";
+ $tdcount ++ ;
+ if ($tdcount == 2)
+ {
+ print qq!</TR>\n! ;
+ $tdcount = 0 ;
+ }
+ }
+
+ print "</TR>";
+}
+
+print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi
new file mode 100755
index 0000000..1e0e088
--- /dev/null
+++ b/httemplate/browse/cust_main_county.cgi
@@ -0,0 +1,142 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+my $enable_taxclasses = $conf->exists('enable_taxclasses');
+
+print header("Tax Rate Listing", menubar(
+ 'Main Menu' => $p,
+ 'Edit tax rates' => $p. "edit/cust_main_county.cgi",
+)),<<END;
+ Click on <u>expand country</u> to specify a country's tax rates by state.
+ <BR>Click on <u>expand state</u> to specify a state's tax rates by county.
+END
+
+if ( $enable_taxclasses ) {
+ print '<BR>Click on <u>expand taxclasses</u> to specify tax classes';
+}
+
+print '<BR><BR>'. &table(). <<END;
+ <TR>
+ <TH><FONT SIZE=-1>Country</FONT></TH>
+ <TH><FONT SIZE=-1>State</FONT></TH>
+ <TH>County</TH>
+ <TH>Taxclass<BR><FONT SIZE=-1>(per-package classification)</FONT></TH>
+ <TH>Tax name<BR><FONT SIZE=-1>(printed on invoices)</FONT></TH>
+ <TH><FONT SIZE=-1>Tax</FONT></TH>
+ <TH><FONT SIZE=-1>Exemption</TH>
+ </TR>
+END
+
+my @regions = sort { $a->country cmp $b->country
+ or $a->state cmp $b->state
+ or $a->county cmp $b->county
+ or $a->taxclass cmp $b->taxclass
+ } qsearch('cust_main_county',{});
+
+my $sup=0;
+#foreach $cust_main_county ( @regions ) {
+for ( my $i=0; $i<@regions; $i++ ) {
+ my $cust_main_county = $regions[$i];
+ my $hashref = $cust_main_county->hashref;
+ print <<END;
+ <TR>
+ <TD BGCOLOR="#ffffff">$hashref->{country}</TD>
+END
+
+ my $j;
+ if ( $sup ) {
+ $sup--;
+ } else {
+
+ #lookahead
+ for ( $j=1; $i+$j<@regions; $j++ ) {
+ last if $hashref->{country} ne $regions[$i+$j]->country
+ || $hashref->{state} ne $regions[$i+$j]->state
+ || $hashref->{tax} != $regions[$i+$j]->tax
+ || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount
+ || $hashref->{setuptax} ne $regions[$i+$j]->setuptax
+ || $hashref->{recurtax} ne $regions[$i+$j]->recurtax;
+ }
+
+ my $newsup=0;
+ if ( $j>1 && $i+$j+1 < @regions
+ && ( $hashref->{state} ne $regions[$i+$j+1]->state
+ || $hashref->{country} ne $regions[$i+$j+1]->country
+ )
+ && ( ! $i
+ || $hashref->{state} ne $regions[$i-1]->state
+ || $hashref->{country} ne $regions[$i-1]->country
+ )
+ ) {
+ $sup = $j-1;
+ } else {
+ $j = 1;
+ }
+
+ print "<TD ROWSPAN=$j", $hashref->{state}
+ ? ' BGCOLOR="#ffffff">'. $hashref->{state}
+ : qq! BGCOLOR="#cccccc">(ALL) <FONT SIZE=-1>!.
+ qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}.
+ qq!">expand country</A></FONT>!;
+
+ print qq! <FONT SIZE=-1><A HREF="${p}edit/process/cust_main_county-collapse.cgi?!. $hashref->{taxnum}. qq!">collapse state</A></FONT>! if $j>1;
+
+ print "</TD>";
+ }
+
+# $sup=$newsup;
+
+ print "<TD";
+ if ( $hashref->{county} ) {
+ print ' BGCOLOR="#ffffff">'. $hashref->{county};
+ } else {
+ print ' BGCOLOR="#cccccc">(ALL)';
+ if ( $hashref->{state} ) {
+ print qq!<FONT SIZE=-1>!.
+ qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}.
+ qq!">expand state</A></FONT>!;
+ }
+ }
+ print "</TD>";
+
+ print "<TD";
+ if ( $hashref->{taxclass} ) {
+ print ' BGCOLOR="#ffffff">'. $hashref->{taxclass};
+ } else {
+ print ' BGCOLOR="#cccccc">(ALL)';
+ if ( $enable_taxclasses ) {
+ print qq!<FONT SIZE=-1>!.
+ qq!<A HREF="${p}edit/cust_main_county-expand.cgi?taxclass!.
+ $hashref->{taxnum}. qq!">expand taxclasses</A></FONT>!;
+ }
+
+ }
+ print "</TD>";
+
+ print "<TD";
+ if ( $hashref->{taxname} ) {
+ print ' BGCOLOR="#ffffff">'. $hashref->{taxname};
+ } else {
+ print ' BGCOLOR="#cccccc">Tax';
+ }
+ print "</TD>";
+
+ print "<TD BGCOLOR=\"#ffffff\">$hashref->{tax}%</TD>".
+ '<TD BGCOLOR="#ffffff">';
+ print '$'. sprintf("%.2f", $hashref->{exempt_amount} ).
+ '&nbsp;per&nbsp;month<BR>'
+ if $hashref->{exempt_amount} > 0;
+ print 'Setup&nbsp;fee<BR>' if $hashref->{setuptax} =~ /^Y$/i;
+ print 'Recurring&nbsp;fee<BR>' if $hashref->{recurtax} =~ /^Y$/i;
+ print '</TD></TR>';
+
+}
+
+print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi
new file mode 100755
index 0000000..3420e97
--- /dev/null
+++ b/httemplate/browse/cust_pay_batch.cgi
@@ -0,0 +1,76 @@
+<!-- mason kludge -->
+<%= header("Pending credit card batch", menubar( 'Main Menu' => $p,)) %>
+
+<FORM ACTION="<%=$p%>misc/download-batch.cgi" METHOD="POST">
+Download batch in format <SELECT NAME="format">
+<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>
+</SELECT><INPUT TYPE="submit" VALUE="Download"></FORM>
+<BR><BR>
+
+<FORM ACTION="<%=$p%>misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">
+Upload results<BR>
+Filename <INPUT TYPE="file" NAME="batch_results"><BR>
+Format <SELECT NAME="format">
+<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>
+</SELECT><BR>
+<INPUT TYPE="submit" VALUE="Upload"></FORM>
+<BR>
+
+<%
+ my $statement = "SELECT SUM(amount) from cust_pay_batch";
+ my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+
+ my $c_statement = "SELECT COUNT(*) from cust_pay_batch";
+ my $c_sth = dbh->prepare($c_statement)
+ or die dbh->errstr. "doing $c_statement";
+ $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr;
+ my $cards = $c_sth->fetchrow_arrayref->[0];
+%>
+<%= $cards %> credit card payments batched<BR>
+$<%= sprintf("%.2f", $total) %> total in pending batch<BR>
+
+<BR>
+<%= &table() %>
+ <TR>
+ <TH>#</TH>
+ <TH><font size=-1>inv#</font></TH>
+ <TH COLSPAN=2>Customer</TH>
+ <TH>Card name</TH>
+ <TH>Card</TH>
+ <TH>Exp</TH>
+ <TH>Amount</TH>
+ </TR>
+
+<%
+foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch('cust_pay_batch', {} )
+) {
+ my $cardnum = $cust_pay_batch->cardnum;
+ #$cardnum =~ s/.{4}$/xxxx/;
+ $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
+
+ $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my( $mon, $year ) = ( $2, $1 );
+ $mon = "0$mon" if $mon < 10;
+ my $exp = "$mon/$year";
+
+%>
+
+ <TR>
+ <TD><%= $cust_pay_batch->paybatchnum %></TD>
+ <TD><A HREF="../view/cust_bill.cgi?<%= $cust_pay_batch->invnum %>"><%= $cust_pay_batch->invnum %></TD>
+ <TD><A HREF="../view/cust_main.cgi?<%= $cust_pay_batch->custnum %>"><%= $cust_pay_batch->custnum %></TD>
+ <TD><%= $cust_pay_batch->get('last'). ', '. $cust_pay_batch->first %></TD>
+ <TD><%= $cust_pay_batch->payname %></TD>
+ <TD><%= $cardnum %></TD>
+ <TD><%= $exp %></TD>
+ <TD align="right">$<%= $cust_pay_batch->amount %></TD>
+ </TR>
+
+<% } %>
+
+ </TABLE>
+ </BODY>
+</HTML>
diff --git a/httemplate/browse/generic.cgi b/httemplate/browse/generic.cgi
new file mode 100644
index 0000000..9ac0f23
--- /dev/null
+++ b/httemplate/browse/generic.cgi
@@ -0,0 +1,46 @@
+<%
+
+use FS::Record qw(qsearch dbdef);
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+
+my $error;
+my $p2 = popurl(2);
+my ($table) = $cgi->keywords;
+my $dbdef = dbdef or die "Cannot fetch dbdef!";
+my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table";
+
+my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table";
+print header("Browse $table", menubar('Main Menu' => $p));
+
+my @rec = qsearch($table, {});
+my @col = $dbdef_table->columns;
+
+if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% }
+%>
+<A HREF="<%=$p2%>edit/<%=$table%>.cgi"><I>Add a new <%=$table%></I></A><BR><BR>
+
+<%=table()%>
+<TH>
+<% foreach (grep { $_ ne $pkey } @col) {
+ %><TD><%=$_%></TD>
+ <% } %>
+</TH>
+<% foreach $rec (sort {$a->getfield($pkey) cmp $b->getfield($pkey) } @rec) {
+ %>
+ <TR>
+ <TD>
+ <A HREF="<%=$p2%>edit/<%=$table%>.cgi?<%=$rec->getfield($pkey)%>">
+ <%=$rec->getfield($pkey)%></A> </TD> <%
+ foreach $col (grep { $_ ne $pkey } @col) { %>
+ <TD><%=$rec->getfield($col)%></TD> <% } %>
+ </A>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi
new file mode 100755
index 0000000..d4adf9f
--- /dev/null
+++ b/httemplate/browse/msgcat.cgi
@@ -0,0 +1,50 @@
+<!-- mason kludge -->
+<%
+
+print header("View Message catalog", menubar(
+ 'Main Menu' => $p,
+ 'Edit message catalog' => $p. "edit/msgcat.cgi",
+)), '<BR>';
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => 'en_US',
+ 'options' => { 'en_US'=>'en_US' },
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = "<BR>Messages for locale $layer<BR>". table().
+ "<TR><TH COLSPAN=2>Code</TH>".
+ "<TH>Message</TH>";
+ $html .= "<TH>en_US Message</TH>" unless $layer eq 'en_US';
+ $html .= '</TR>';
+
+ #foreach my $msgcat ( sort { $a->msgcode cmp $b->msgcode }
+ # qsearch('msgcat', { 'locale' => $layer } ) ) {
+ foreach my $msgcat ( qsearch('msgcat', { 'locale' => $layer } ) ) {
+ $html .= '<TR><TD>'. $msgcat->msgnum. '</TD>'.
+ '<TD>'. $msgcat->msgcode. '</TD>'.
+ '<TD>'. $msgcat->msg. '</TD>';
+ unless ( $layer eq 'en_US' ) {
+ my $en_msgcat = qsearchs('msgcat', {
+ 'locale' => 'en_US',
+ 'msgcode' => $msgcat->msgcode,
+ } );
+ $html .= '<TD>'. $en_msgcat->msg. '</TD>';
+ }
+ $html .= '</TR>';
+ }
+
+ $html .= '</TABLE>';
+ $html;
+ },
+
+);
+
+print $widget->html;
+
+print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi
new file mode 100755
index 0000000..9ccbfe6
--- /dev/null
+++ b/httemplate/browse/nas.cgi
@@ -0,0 +1,80 @@
+<!-- mason kludge -->
+<%
+
+print header('NAS ports', menubar(
+ 'Main Menu' => $p,
+));
+
+my $now = time;
+
+foreach my $nas ( sort { $a->nasnum <=> $b->nasnum } qsearch( 'nas', {} ) ) {
+ print $nas->nasnum. ": ". $nas->nas. " ".
+ $nas->nasfqdn. " (". $nas->nasip. ") ".
+ "as of ". time2str("%c",$nas->last).
+ " (". &pretty_interval($now - $nas->last). " ago)<br>".
+ &table(). "<TR><TH>Nas<BR>Port #</TH><TH>Global<BR>Port #</BR></TH>".
+ "<TH>IP address</TH><TH>User</TH><TH>Since</TH><TH>Duration</TH><TR>",
+ ;
+ foreach my $port ( sort {
+ $a->nasport <=> $b->nasport || $a->portnum <=> $b->portnum
+ } qsearch( 'port', { 'nasnum' => $nas->nasnum } ) ) {
+ my $session = $port->session;
+ my($user, $since, $pretty_since, $duration);
+ if ( ! $session ) {
+ $user = "(empty)";
+ $since = 0;
+ $pretty_since = "(never)";
+ $duration = '';
+ } elsif ( $session->logout ) {
+ $user = "(empty)";
+ $since = $session->logout;
+ } else {
+ my $svc_acct = $session->svc_acct;
+ $user = "<A HREF=\"$p/view/svc_acct.cgi?". $svc_acct->svcnum. "\">".
+ $svc_acct->username. "</A>";
+ $since = $session->login;
+ }
+ $pretty_since = time2str("%c", $since) if $since;
+ $duration = pretty_interval( $now - $since ). " ago"
+ unless defined($duration);
+ print "<TR><TD>". $port->nasport. "</TD><TD>". $port->portnum. "</TD><TD>".
+ $port->ip. "</TD><TD>$user</TD><TD>$pretty_since".
+ "</TD><TD>$duration</TD></TR>"
+ ;
+ }
+ print "</TABLE><BR>";
+}
+
+#Time::Duration??
+sub pretty_interval {
+ my $interval = shift;
+ my %howlong = (
+ '604800' => 'week',
+ '86400' => 'day',
+ '3600' => 'hour',
+ '60' => 'minute',
+ '1' => 'second',
+ );
+
+ my $pretty = "";
+ foreach my $key ( sort { $b <=> $a } keys %howlong ) {
+ my $value = int( $interval / $key );
+ if ( $value ) {
+ if ( $value == 1 ) {
+ $pretty .=
+ ( $howlong{$key} eq 'hour' ? 'an ' : 'a ' ). $howlong{$key}. " "
+ } else {
+ $pretty .= $value. ' '. $howlong{$key}. 's ';
+ }
+ }
+ $interval -= $value * $key;
+ }
+ $pretty =~ /^\s*(\S.*\S)\s*$/;
+ $1;
+}
+
+#print &table(), <<END;
+#<TR>
+# <TH>#</TH>
+# <TH>NAS</
+%>
diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi
new file mode 100755
index 0000000..670474d
--- /dev/null
+++ b/httemplate/browse/part_bill_event.cgi
@@ -0,0 +1,71 @@
+<!-- mason kludge -->
+<%
+
+my %search;
+if ( $cgi->param('showdisabled') ) {
+ %search = ();
+} else {
+ %search = ( 'disabled' => '' );
+}
+
+my @part_bill_event = qsearch('part_bill_event', \%search );
+my $total = scalar(@part_bill_event);
+
+%>
+<%= header('Invoice Event Listing', menubar( 'Main Menu' => $p) ) %>
+
+ Invoice events are actions taken on overdue invoices.<BR><BR>
+<A HREF="<%= $p %>edit/part_bill_event.cgi"><I>Add a new invoice event</I></A>
+<BR><BR>
+<%= $total %> events
+<%= $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled events</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled events</a> )'; }
+%>
+<%= table() %>
+ <TR>
+ <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH>
+ <TH>Payby</TH>
+ <TH>After</TH>
+ <TH>Action</TH>
+ <TH>Options</TH>
+ <TH>Code</TH>
+ </TR>
+
+<% foreach my $part_bill_event ( sort { $a->payby cmp $b->payby
+ || $a->seconds <=> $b->seconds
+ || $a->weight <=> $b->weight
+ || $a->eventpart <=> $b->eventpart
+ } @part_bill_event ) {
+ my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart;
+ use Time::Duration;
+ my $delay = duration_exact($part_bill_event->seconds);
+ my $plandata = $part_bill_event->plandata;
+ $plandata =~ s/\n/<BR>/go;
+%>
+ <TR>
+ <TD><A HREF="<%= $url %>">
+ <%= $part_bill_event->eventpart %></A></TD>
+<% unless ( $cgi->param('showdisabled') ) { %>
+ <TD>
+ <%= $part_bill_event->disabled ? 'DISABLED' : '' %></TD>
+<% } %>
+ <TD><A HREF="<%= $url %>">
+ <%= $part_bill_event->event %></A></TD>
+ <TD>
+ <%= $part_bill_event->payby %></TD>
+ <TD>
+ <%= $delay %></TD>
+ <TD>
+ <%= $part_bill_event->plan %></TD>
+ <TD>
+ <%= $plandata %></TD>
+ <TD><FONT SIZE="-1">
+ <%= $part_bill_event->eventcode %></FONT></TD>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
new file mode 100755
index 0000000..79c57ae
--- /dev/null
+++ b/httemplate/browse/part_export.cgi
@@ -0,0 +1,39 @@
+<!-- mason kludge -->
+<%= header("Export Listing", menubar( 'Main Menu' => "$p#sysadmin" )) %>
+Provisioning services to external machines, databases and APIs.<BR><BR>
+<A HREF="<%= $p %>edit/part_export.cgi"><I>Add a new export</I></A><BR><BR>
+<SCRIPT>
+function part_export_areyousure(href) {
+ if (confirm("Are you sure you want to delete this export?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+<%= table() %>
+ <TR>
+ <TH COLSPAN=2>Export</TH>
+ <TH>Options</TH>
+ </TR>
+
+<% foreach my $part_export ( sort {
+ $a->getfield('exportnum') <=> $b->getfield('exportnum')
+ } qsearch('part_export',{}) ) {
+%>
+ <TR>
+ <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exportnum %></A></TD>
+ <TD><%= $part_export->exporttype %> to <%= $part_export->machine %> (<A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>">edit</A>&nbsp;|&nbsp;<A HREF="javascript:part_export_areyousure('<%= $p %>misc/delete-part_export.cgi?<%= $part_export->exportnum %>')">delete</A>)</TD>
+ <TD>
+ <%= itable() %>
+ <% my %opt = $part_export->options;
+ foreach my $opt ( keys %opt ) { %>
+ <TR><TD><%= $opt %></TD><TD><%= encode_entities($opt{$opt}) %></TD></TR>
+ <% } %>
+ </TABLE>
+ </TD>
+ </TR>
+
+<% } %>
+
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
new file mode 100755
index 0000000..be67338
--- /dev/null
+++ b/httemplate/browse/part_pkg.cgi
@@ -0,0 +1,172 @@
+<!-- mason kludge -->
+<%
+
+my %search;
+if ( $cgi->param('showdisabled') ) {
+ %search = ();
+} else {
+ %search = ( 'disabled' => '' );
+}
+
+my @part_pkg = qsearch('part_pkg', \%search );
+my $total = scalar(@part_pkg);
+
+my $sortby;
+my %num_active_cust_pkg = ();
+my( $suspended_sth, $canceled_sth ) = ( '', '' );
+if ( $cgi->param('active') ) {
+ my $active_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND ( cancel IS NULL OR cancel = 0 )'.
+ ' AND ( susp IS NULL OR susp = 0 )'
+ ) or die dbh->errstr;
+ foreach my $part_pkg ( @part_pkg ) {
+ $active_sth->execute($part_pkg->pkgpart) or die $active_sth->errstr;
+ $num_active_cust_pkg{$part_pkg->pkgpart} =
+ $active_sth->fetchrow_arrayref->[0];
+ }
+ $sortby = sub {
+ $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart};
+ };
+
+ $suspended_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND ( cancel IS NULL OR cancel = 0 )'.
+ ' AND susp IS NOT NULL AND susp != 0'
+ ) or die dbh->errstr;
+
+ $canceled_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND cancel IS NOT NULL AND cancel != 0'
+ ) or die dbh->errstr;
+
+} else {
+ $sortby = sub { $a->pkgpart <=> $b->pkgpart; };
+}
+
+my $conf = new FS::Conf;
+my $taxclasses = $conf->exists('enable_taxclasses');
+
+%>
+<%= header("Package Definition Listing",menubar( 'Main Menu' => $p )) %>
+<% unless ( $cgi->param('active') ) { %>
+ One or more service definitions are grouped together into a package
+ definition and given pricing information. Customers purchase packages
+ rather than purchase services directly.<BR><BR>
+ <A HREF="<%= $p %>edit/part_pkg.cgi"><I>Add a new package definition</I></A>
+ <BR><BR>
+<% } %>
+
+<%= $total %> package definitions
+<% if ( $cgi->param('showdisabled') ) { $cgi->param('showdisabled', 0); %>
+ ( <a href="<%= $cgi->self_url %>">hide disabled packages</a> )
+<% } else { $cgi->param('showdisabled', 1); %>
+ ( <a href="<%= $cgi->self_url %>">show disabled packages</a> )
+<% } %>
+
+<% my $colspan = $cgi->param('showdisabled') ? 2 : 3; %>
+
+<%= &table() %>
+ <TR>
+ <TH COLSPAN=<%= $colspan %>>Package</TH>
+ <TH>Comment</TH>
+<% if ( $cgi->param('active') ) { %>
+ <TH><FONT SIZE=-1>Customer<BR>packages</FONT></TH>
+<% } %>
+ <TH><FONT SIZE=-1>Freq.</FONT></TH>
+<% if ( $taxclasses ) { %>
+ <TH><FONT SIZE=-1>Taxclass</FONT></TH>
+<% } %>
+ <TH><FONT SIZE=-1>Plan</FONT></TH>
+ <TH><FONT SIZE=-1>Data</FONT></TH>
+ <TH>Service</TH>
+ <TH><FONT SIZE=-1>Quan.</FONT></TH>
+<% if ( dbdef->table('pkg_svc')->column('primary_svc') ) { %>
+ <TH><FONT SIZE=-1>Primary</FONT></TH>
+<% } %>
+
+ </TR>
+
+<%
+foreach my $part_pkg ( sort $sortby @part_pkg ) {
+ my($hashref)=$part_pkg->hashref;
+ my(@pkg_svc)=grep $_->getfield('quantity'),
+ qsearch('pkg_svc',{'pkgpart'=> $hashref->{pkgpart} });
+ my($rowspan)=scalar(@pkg_svc);
+ my $plandata;
+ if ( $hashref->{plan} ) {
+ $plandata = $hashref->{plandata};
+ $plandata =~ s/^(\w+)=/$1&nbsp;/mg;
+ $plandata =~ s/\n/<BR>/g;
+ } else {
+ $hashref->{plan} = "(legacy)";
+ $plandata = "Setup&nbsp;". $hashref->{setup}.
+ "<BR>Recur&nbsp;". $hashref->{recur};
+ }
+%>
+ <TR>
+ <TD ROWSPAN=<%= $rowspan %>><A HREF="<%=$p%>edit/part_pkg.cgi?<%= $hashref->{pkgpart} %>"><%= $hashref->{pkgpart} %></A></TD>
+
+<% unless ( $cgi->param('showdisabled') ) { %>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <% if ( $hashref->{disabled} ) { %>
+ DISABLED
+ <% } %>
+ </TD>
+<% } %>
+
+ <TD ROWSPAN=<%= $rowspan %>><A HREF="<%=$p%>edit/part_pkg.cgi?<%= $hashref->{pkgpart} %>"><%= $hashref->{pkg} %></A></TD>
+ <TD ROWSPAN=<%= $rowspan %>><%= $hashref->{comment} %></TD>
+
+<% if ( $cgi->param('active') ) { %>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <FONT COLOR="#00CC00"><B><%= $num_active_cust_pkg{$hashref->{'pkgpart'}} %></B></FONT>&nbsp;<A HREF="<%=$p%>search/cust_pkg.cgi?magic=active;pkgpart=<%= $hashref->{pkgpart} %>">active</A><BR>
+
+ <% $suspended_sth->execute( $part_pkg->pkgpart )
+ or die $suspended_sth->errstr;
+ my $num_suspended = $suspended_sth->fetchrow_arrayref->[0];
+ %>
+ <FONT COLOR="#FF9900"><B><%= $num_suspended %></B></FONT>&nbsp;<A HREF="<%=$p%>search/cust_pkg.cgi?magic=suspended;pkgpart=<%= $hashref->{pkgpart} %>">suspended</A><BR>
+
+ <% $canceled_sth->execute( $part_pkg->pkgpart )
+ or die $canceled_sth->errstr;
+ my $num_canceled = $canceled_sth->fetchrow_arrayref->[0];
+ %>
+ <FONT COLOR="#FF0000"><B><%= $num_canceled %></B></FONT>&nbsp;<A HREF="<%=$p%>search/cust_pkg.cgi?magic=canceled;pkgpart=<%= $hashref->{pkgpart} %>">canceled</A>
+ </TD>
+<% } %>
+
+ <TD ROWSPAN=<%= $rowspan %>><%= $hashref->{freq} %></TD>
+
+<% if ( $taxclasses ) { %>
+ <TD ROWSPAN=<%= $rowspan %>><%= $hashref->{taxclass} || '&nbsp;' %></TD>
+<% } %>
+
+ <TD ROWSPAN=<%= $rowspan %>><%= $hashref->{plan} %></TD>
+ <TD ROWSPAN=<%= $rowspan %>><%= $plandata %></TD>
+
+<%
+ my($pkg_svc);
+ my($n)="";
+ foreach $pkg_svc ( @pkg_svc ) {
+ my($svcpart)=$pkg_svc->getfield('svcpart');
+ my($part_svc) = qsearchs('part_svc',{'svcpart'=> $svcpart });
+ print $n,qq!<TD><A HREF="${p}edit/part_svc.cgi?$svcpart">!,
+ $part_svc->getfield('svc'),"</A></TD><TD>",
+ $pkg_svc->getfield('quantity'),"</TD>";
+ if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ print '<TD>';
+ print 'PRIMARY' if $pkg_svc->primary_svc =~ /^Y/i;
+ print '</TD>';
+ }
+ print "</TR>\n";
+ $n="<TR>";
+ }
+%>
+
+ </TR>
+<% } %>
+
+ </TABLE>
+ </BODY>
+</HTML>
diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi
new file mode 100755
index 0000000..581e01b
--- /dev/null
+++ b/httemplate/browse/part_referral.cgi
@@ -0,0 +1,97 @@
+<!-- mason kludge -->
+<%= header("Advertising source Listing", menubar(
+ 'Main Menu' => $p,
+# 'Add new referral' => "../edit/part_referral.cgi",
+)) %>
+Where a customer heard about your service. Tracked for informational purposes.
+<BR><BR>
+<A HREF="<%= $p %>edit/part_referral.cgi"><I>Add a new advertising source</I></A>
+<BR><BR>
+
+<%
+ my $today = timelocal(0, 0, 0, (localtime(time))[3..5] );
+ my %after;
+ tie %after, 'Tie::IxHash',
+ 'Today' => 0,
+ 'Yesterday' => 86400, # 60sec * 60min * 24hrs
+ 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days
+ 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days
+ 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days
+ 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days
+ 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days
+ 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days
+ 'Total' => $today,
+ ;
+ my %before = (
+ 'Today' => 86400, # 60sec * 60min * 24hrs
+ 'Yesterday' => 0,
+ 'Past week' => 86400, # 60sec * 60min * 24hrs
+ 'Past 30 days' => 86400, # 60sec * 60min * 24hrs
+ 'Past 60 days' => 86400, # 60sec * 60min * 24hrs
+ 'Past 90 days' => 86400, # 60sec * 60min * 24hrs
+ 'Past 6 months' => 86400, # 60sec * 60min * 24hrs
+ 'Past year' => 86400, # 60sec * 60min * 24hrs
+ 'Total' => 86400, # 60sec * 60min * 24hrs
+ );
+
+ my $statement = "SELECT COUNT(*) FROM h_cust_main
+ WHERE history_action = 'insert'
+ AND refnum = ?
+ AND history_date >= ?
+ AND history_date < ?
+ ";
+ my $sth = dbh->prepare($statement)
+ or die dbh->errstr;
+%>
+
+<%= table() %>
+<TR>
+ <TH COLSPAN=2 ROWSPAN=2>Advertising source</TH>
+ <TH COLSPAN=<%= scalar(keys %after) %>>Customers</TH>
+</TR>
+<% for my $period ( keys %after ) { %>
+ <TH><FONT SIZE=-1><%= $period %></FONT></TH>
+<% } %>
+</TR>
+
+<%
+foreach my $part_referral ( sort {
+ $a->getfield('refnum') <=> $b->getfield('refnum')
+} qsearch('part_referral',{}) ) {
+%>
+ <TR>
+ <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>">
+ <%= $part_referral->refnum %></A></TD>
+ <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>">
+ <%= $part_referral->referral %></A></TD>
+ <% for my $period ( keys %after ) {
+ $sth->execute( $part_referral->refnum,
+ $today-$after{$period},
+ $today+$before{$period},
+ ) or die $sth->errstr;
+ my $number = $sth->fetchrow_arrayref->[0];
+ %>
+ <TD ALIGN="right"><%= $number %></TD>
+ <% } %>
+ </TR>
+<% } %>
+
+<%
+ $statement =~ s/AND refnum = \?//;
+ $sth = dbh->prepare($statement)
+ or die dbh->errstr;
+%>
+ <TR>
+ <TH COLSPAN=2>Total</TH>
+ <% for my $period ( keys %after ) {
+ $sth->execute( $today-$after{$period},
+ $today+$before{$period},
+ ) or die $sth->errstr;
+ my $number = $sth->fetchrow_arrayref->[0];
+ %>
+ <TD ALIGN="right"><%= $number %></TD>
+ <% } %>
+ </TR>
+ </TABLE>
+ </BODY>
+</HTML>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
new file mode 100755
index 0000000..ef0de13
--- /dev/null
+++ b/httemplate/browse/part_svc.cgi
@@ -0,0 +1,133 @@
+<!-- mason kludge -->
+<%
+
+my %search;
+if ( $cgi->param('showdisabled') ) {
+ %search = ();
+} else {
+ %search = ( 'disabled' => '' );
+}
+
+my @part_svc =
+ sort { $a->getfield('svcpart') <=> $b->getfield('svcpart') }
+ qsearch('part_svc', \%search );
+my $total = scalar(@part_svc);
+
+my %num_active_cust_svc = ();
+if ( $cgi->param('active') ) {
+ my $active_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_svc WHERE svcpart = ?'
+ ) or die dbh->errstr;
+ foreach my $part_svc ( @part_svc ) {
+ $active_sth->execute($part_svc->svcpart) or die $active_sth->errstr;
+ $num_active_cust_svc{$part_svc->svcpart} =
+ $active_sth->fetchrow_arrayref->[0];
+ }
+ @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=>
+ $num_active_cust_svc{$a->svcpart} } @part_svc;
+}
+
+%>
+<%= header('Service Definition Listing', menubar( 'Main Menu' => $p) ) %>
+
+<SCRIPT>
+function part_export_areyousure(href) {
+ if (confirm("Are you sure you want to delete this export?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+ Service definitions are the templates for items you offer to your customers.<BR><BR>
+
+<FORM METHOD="POST" ACTION="<%= $p %>edit/part_svc.cgi">
+<A HREF="<%= $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A><% if ( @part_svc ) { %>&nbsp;or&nbsp;<SELECT NAME="clone"><OPTION></OPTION>
+<% foreach my $part_svc ( @part_svc ) { %>
+ <OPTION VALUE="<%= $part_svc->svcpart %>"><%= $part_svc->svc %></OPTION>
+<% } %>
+</SELECT><INPUT TYPE="submit" VALUE="Clone existing service">
+<% } %>
+</FORM><BR>
+
+<%= $total %> service definitions
+<%= $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled services</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled services</a> )'; }
+%>
+<%= table() %>
+ <TR>
+ <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Service</TH>
+ <TH>Table</TH>
+<% if ( $cgi->param('active') ) { %>
+ <TH><FONT SIZE=-1>Customer<BR>Services</FONT></TH>
+<% } %>
+ <TH>Export</TH>
+ <TH>Field</TH>
+ <TH COLSPAN=2>Modifier</TH>
+ </TR>
+
+<% foreach my $part_svc ( @part_svc ) {
+ my $hashref = $part_svc->hashref;
+ my $svcdb = $hashref->{svcdb};
+ my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } );
+ my @dfields = $svc_x->fields;
+ push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
+ my @fields =
+ grep { $svc_x->pvf($_)
+ or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
+ @dfields ;
+ my $rowspan = scalar(@fields) || 1;
+ my $url = "${p}edit/part_svc.cgi?$hashref->{svcpart}";
+%>
+
+ <TR>
+ <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>">
+ <%= $hashref->{svcpart} %></A></TD>
+<% unless ( $cgi->param('showdisabled') ) { %>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <%= $hashref->{disabled} ? 'DISABLED' : '' %></TD>
+<% } %>
+ <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>">
+ <%= $hashref->{svc} %></A></TD>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <%= $hashref->{svcdb} %></TD>
+<% if ( $cgi->param('active') ) { %>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <FONT COLOR="#00CC00"><B><%= $num_active_cust_svc{$hashref->{svcpart}} %></B></FONT>&nbsp;<A HREF="<%=$p%>search/<%= $hashref->{svcdb} %>.cgi?svcpart=<%= $hashref->{svcpart} %>">active</A>
+ </TD>
+<% } %>
+ <TD ROWSPAN=<%= $rowspan %>><%= itable() %>
+<%
+# my @part_export =
+map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ;
+ foreach my $part_export (
+ map { qsearchs('part_export', { exportnum => $_->exportnum } ) }
+ qsearch('export_svc', { svcpart => $part_svc->svcpart } )
+ ) {
+%>
+ <TR>
+ <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exportnum %>:&nbsp;<%= $part_export->exporttype %>&nbsp;to&nbsp;<%= $part_export->machine %></A></TD></TR>
+<% } %>
+ </TABLE></TD>
+
+<% my($n1)='';
+ foreach my $field ( @fields ) {
+ my $flag = $part_svc->part_svc_column($field)->columnflag;
+%>
+ <%= $n1 %><TD><%= $field %></TD><TD>
+
+<% if ( $flag eq "D" ) { print "Default"; }
+ elsif ( $flag eq "F" ) { print "Fixed"; }
+ elsif ( not $flag ) { }
+ else { print "(Unknown!)"; }
+%>
+ </TD><TD><%= $part_svc->part_svc_column($field)->columnvalue%></TD>
+<% $n1="</TR><TR>";
+ }
+%>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi
new file mode 100644
index 0000000..a0009da
--- /dev/null
+++ b/httemplate/browse/part_virtual_field.cgi
@@ -0,0 +1,39 @@
+<%= header('Virtual field definitions', menubar('Main Menu' => $p)) %>
+<%
+
+my %pvfs;
+my $block;
+my $p2 = popurl(2);
+my $dbtable;
+
+foreach (qsearch('part_virtual_field', {})) {
+ push @{ $pvfs{$_->dbtable} }, $_;
+}
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<A HREF="<%=$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR>
+
+<% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { %>
+<H3><%=$dbtable%></H3>
+
+<%=table()%>
+<TH><TD>Field name</TD><TD>Description</TD></TH>
+<% foreach my $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { %>
+ <TR>
+ <TD></TD>
+ <TD>
+ <A HREF="<%=$p2%>edit/part_virtual_field.cgi?<%=$pvf->vfieldpart%>">
+ <%=$pvf->name%></A></TD>
+ <TD><%=$pvf->label%></TD>
+ </TR>
+<% } %>
+</TABLE>
+<% } %>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/queue.cgi b/httemplate/browse/queue.cgi
new file mode 100755
index 0000000..b53c140
--- /dev/null
+++ b/httemplate/browse/queue.cgi
@@ -0,0 +1,7 @@
+<!-- mason kludge -->
+<%
+
+print header("Job Queue", menubar( 'Main Menu' => $p, )).
+ joblisting({}). '</BODY></HTML>';
+
+%>
diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi
new file mode 100644
index 0000000..149db49
--- /dev/null
+++ b/httemplate/browse/router.cgi
@@ -0,0 +1,57 @@
+<%= header('Routers', menubar('Main Menu' => $p)) %>
+<%
+
+my @router = qsearch('router', {});
+my $p2 = popurl(2);
+
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<%
+my $hidecustomerrouters = 0;
+my $hideurl = '';
+if ($cgi->param('hidecustomerrouters') eq '1') {
+ $hidecustomerrouters = 1;
+ $cgi->param('hidecustomerrouters', 0);
+ $hideurl = '<A HREF="' . $cgi->self_url() . '">Show customer routers</A>';
+} else {
+ $hidecustomerrouters = 0;
+ $cgi->param('hidecustomerrouters', 1);
+ $hideurl = '<A HREF="' . $cgi->self_url() . '">Hide customer routers</A>';
+}
+%>
+
+<A HREF="<%=$p2%>edit/router.cgi">Add a new router</A>&nbsp;|&nbsp;<%=$hideurl%>
+
+<%=table()%>
+ <TR>
+ <TD><B>Router name</B></TD>
+ <TD><B>Address block(s)</B></TD>
+ </TR>
+<% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) {
+ next if $hidecustomerrouters && $router->svcnum;
+ my @addr_block = $router->addr_block;
+ if (scalar(@addr_block) == 0) {
+ push @addr_block, '&nbsp;';
+ }
+%>
+ <TR>
+ <TD ROWSPAN="<%=scalar(@addr_block)+1%>">
+ <A HREF="<%=$p2%>edit/router.cgi?<%=$router->routernum%>"><%=$router->routername%></A>
+ </TD>
+ </TR>
+ <% foreach my $block ( @addr_block ) { %>
+ <TR>
+ <TD><%=UNIVERSAL::isa($block, 'FS::addr_block') ? $block->NetAddr : '&nbsp;'%></TD>
+ </TR>
+ <% } %>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi
new file mode 100755
index 0000000..44cda81
--- /dev/null
+++ b/httemplate/browse/svc_acct_pop.cgi
@@ -0,0 +1,63 @@
+<!-- mason kludge -->
+<%
+ my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct
+ WHERE popnum = ? ")
+ or die dbh->errstr;
+%>
+<%= header('Access Number Listing', menubar( 'Main Menu' => $p )) %>
+Points of Presence<BR><BR>
+<A HREF="<%= $p %>edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A><BR><BR>
+<%= table() %>
+ <TR>
+ <TH></TH>
+ <TH>City</TH>
+ <TH>State</TH>
+ <TH>Area code</TH>
+ <TH>Exchange</TH>
+ <TH>Local</TH>
+ <TH>Accounts</TH>
+ </TR>
+
+<%
+foreach my $svc_acct_pop ( sort {
+ #$a->getfield('popnum') <=> $b->getfield('popnum')
+ $a->state cmp $b->state || $a->city cmp $b->city
+ || $a->ac <=> $b->ac || $a->exch <=> $b->exch || $a->loc <=> $b->loc
+} qsearch('svc_acct_pop',{}) ) {
+
+ my $svc_acct_pop_link = $p . 'edit/svc_acct_pop.cgi?'. $svc_acct_pop->popnum;
+
+ $accounts_sth->execute($svc_acct_pop->popnum) or die $accounts_sth->errstr;
+ my $num_accounts = $accounts_sth->fetchrow_arrayref->[0];
+
+ my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='. $svc_acct_pop->popnum;
+
+%>
+ <TR>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->popnum %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->city %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->state %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->ac %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->exch %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->loc %></A></TD>
+ <TD>
+ <FONT COLOR="#00CC00"><B><%= $num_accounts %></B></FONT>
+ <% if ( $num_accounts ) { %><A HREF="<%= $svc_acct_link %>"><% } %>
+ active
+ <% if ( $num_accounts ) { %></A><% } %>
+ </TD>
+ </TR>
+<% } %>
+
+ <TR>
+ </TR>
+ </TABLE>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi
new file mode 100644
index 0000000..2597132
--- /dev/null
+++ b/httemplate/config/config-process.cgi
@@ -0,0 +1,51 @@
+<%
+ my $conf = new FS::Conf;
+ $FS::Conf::DEBUG = 1;
+ my @config_items = $conf->config_items;
+
+ foreach my $i ( @config_items ) {
+ my @touch = ();
+ my @delete = ();
+ my $n = 0;
+ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
+ if ( $type eq '' ) {
+ } elsif ( $type eq 'textarea' ) {
+ if ( $cgi->param($i->key. $n) ne '' ) {
+ my $value = $cgi->param($i->key. $n);
+ $value =~ s/\r\n/\n/g; #browsers?
+ $conf->set($i->key, $value);
+ } else {
+ $conf->delete($i->key);
+ }
+ } elsif ( $type eq 'checkbox' ) {
+# if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) {
+ if ( defined $cgi->param($i->key. $n) ) {
+ #$conf->touch($i->key);
+ push @touch, $i->key;
+ } else {
+ #$conf->delete($i->key);
+ push @delete, $i->key;
+ }
+ } elsif ( $type eq 'text' || $type eq 'select' ) {
+ if ( $cgi->param($i->key. $n) ne '' ) {
+ $conf->set($i->key, $cgi->param($i->key. $n));
+ } else {
+ $conf->delete($i->key);
+ }
+ } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' ) {
+ if ( scalar(@{[ $cgi->param($i->key. $n) ]}) ) {
+ $conf->set($i->key, join("\n", @{[ $cgi->param($i->key. $n) ]} ));
+ } else {
+ $conf->delete($i->key);
+ }
+ } else {
+ }
+ $n++;
+ }
+ # warn @touch;
+ $conf->touch($_) foreach @touch;
+ $conf->delete($_) foreach @delete;
+ }
+
+%>
+<%= $cgi->redirect("config-view.cgi") %>
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
new file mode 100644
index 0000000..9a00067
--- /dev/null
+++ b/httemplate/config/config-view.cgi
@@ -0,0 +1,64 @@
+<!-- mason kludge -->
+<%= header('View Configuration', menubar( 'Main Menu' => $p,
+ 'Edit Configuration' => 'config.cgi' ) ) %>
+
+<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %>
+
+<% foreach my $section ( qw(required billing username password UI session
+ shell BIND
+ ),
+ '', 'deprecated') { %>
+ <A NAME="<%= $section || 'unclassified' %>"></A>
+ <FONT SIZE="-2">
+ <% foreach my $nav_section ( qw(required billing username password UI session
+ shell BIND
+ ),
+ '', 'deprecated') { %>
+ <% if ( $section eq $nav_section ) { %>
+ [<A NAME="not<%= $nav_section || 'unclassified' %>" style="background-color: #cccccc"><%= ucfirst($nav_section || 'unclassified') %></A>]
+ <% } else { %>
+ [<A HREF="#<%= $nav_section || 'unclassified' %>"><%= ucfirst($nav_section || 'unclassified') %></A>]
+ <% } %>
+ <% } %>
+ </FONT><BR>
+ <%= table("#cccccc", 2) %>
+ <tr>
+ <th colspan="2" bgcolor="#dcdcdc">
+ <%= ucfirst($section || 'unclassified') %> configuration options
+ </th>
+ </tr>
+ <% foreach my $i (grep $_->section eq $section, @config_items) { %>
+ <tr>
+ <td><a name="<%= $i->key %>">
+ <b><%= $i->key %></b>&nbsp;-&nbsp;<%= $i->description %>
+ </a></td>
+ <td><table border=0>
+ <% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
+ my $n = 0; %>
+ <% if ( $type eq '' ) { %>
+ <tr><td><font color="#ff0000">no type</font></td></tr>
+ <% } elsif ( $type eq 'textarea'
+ || $type eq 'editlist'
+ || $type eq 'selectmultiple' ) { %>
+ <tr><td bgcolor="#ffffff">
+<pre>
+<%= encode_entities(join("\n", $conf->config($i->key) ) ) %>
+</pre>
+ </td></tr>
+ <% } elsif ( $type eq 'checkbox' ) { %>
+ <tr><td bgcolor="#<%= $conf->exists($i->key) ? '00ff00">YES' : 'ff0000">NO' %></td></tr>
+ <% } elsif ( $type eq 'text' || $type eq 'select' ) { %>
+ <tr><td bgcolor="#ffffff"><%= $conf->exists($i->key) ? $conf->config($i->key) : '' %></td></tr>
+ <% } else { %>
+ <tr><td>
+ <font color="#ff0000">unknown type <%= $type %></font>
+ </td></tr>
+ <% } %>
+ <% $n++; } %>
+ </table></td>
+ </tr>
+ <% } %>
+ </table><br><br>
+<% } %>
+
+</body></html>
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
new file mode 100644
index 0000000..409869e
--- /dev/null
+++ b/httemplate/config/config.cgi
@@ -0,0 +1,176 @@
+<!-- mason kludge -->
+<%= header('Edit Configuration', menubar( 'Main Menu' => $p ) ) %>
+<SCRIPT>
+var gSafeOnload = new Array();
+var gSafeOnsubmit = new Array();
+window.onload = SafeOnload;
+function SafeAddOnLoad(f) {
+ gSafeOnload[gSafeOnload.length] = f;
+}
+function SafeOnload() {
+ for (var i=0;i<gSafeOnload.length;i++)
+ gSafeOnload[i]();
+}
+function SafeAddOnSubmit(f) {
+ gSafeOnsubmit[gSafeOnsubmit.length] = f;
+}
+function SafeOnsubmit() {
+ for (var i=0;i<gSafeOnsubmit.length;i++)
+ gSafeOnsubmit[i]();
+}
+</SCRIPT>
+
+<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %>
+
+<form name="OneTrueForm" action="config-process.cgi" METHOD="POST" onSubmit="SafeOnsubmit()">
+
+<% foreach my $section ( qw(required billing username password UI session
+ shell BIND
+ ),
+ '', 'deprecated') { %>
+ <A NAME="<%= $section || 'unclassified' %>"></A>
+ <FONT SIZE="-2">
+ <% foreach my $nav_section ( qw(required billing username password UI session
+ shell BIND
+ ),
+ '', 'deprecated') { %>
+ <% if ( $section eq $nav_section ) { %>
+ [<A NAME="not<%= $nav_section || 'unclassified' %>" style="background-color: #cccccc"><%= ucfirst($nav_section || 'unclassified') %></A>]
+ <% } else { %>
+ [<A HREF="#<%= $nav_section || 'unclassified' %>"><%= ucfirst($nav_section || 'unclassified') %></A>]
+ <% } %>
+ <% } %>
+ </FONT><BR>
+ <%= table("#cccccc", 2) %>
+ <tr>
+ <th colspan="2" bgcolor="#dcdcdc">
+ <%= ucfirst($section || 'unclassified') %> configuration options
+ </th>
+ </tr>
+ <% foreach my $i (grep $_->section eq $section, @config_items) { %>
+ <tr>
+ <td>
+ <% my $n = 0;
+ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
+ #warn $i->key unless defined($type);
+ %>
+ <% if ( $type eq '' ) { %>
+ <font color="#ff0000">no type</font>
+ <% } elsif ( $type eq 'textarea' ) { %>
+ <textarea name="<%= $i->key. $n %>" rows=5><%= "\n". join("\n", $conf->config($i->key) ) %></textarea>
+ <% } elsif ( $type eq 'checkbox' ) { %>
+ <input name="<%= $i->key. $n %>" type="checkbox" value="1"<%= $conf->exists($i->key) ? ' CHECKED' : '' %>>
+ <% } elsif ( $type eq 'text' ) { %>
+ <input name="<%= $i->key. $n %>" type="<%= $type %>" value="<%= $conf->exists($i->key) ? $conf->config($i->key) : '' %>">
+ <% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { %>
+ <select name="<%= $i->key. $n %>" <%= $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>>
+ <% my %saw;
+ foreach my $value ( "", @{$i->select_enum} ) {
+ local($^W)=0; next if $saw{$value}++; %>
+ <option value="<%= $value %>"<%= $value eq $conf->config($i->key) || ( $type eq 'selectmultiple' && grep { $_ eq $value } $conf->config($i->key) ) ? ' SELECTED' : '' %>><%= $value %>
+ <% } %>
+ <% if ( $conf->exists($i->key) && $conf->config($i->key) && ! grep { $conf->config($i->key) eq $_ } @{$i->select_enum}) { %>
+ <option value=<%= $conf->config($i->key) %> SELECTED><%= $conf->config($i->key) %>
+ <% } %>
+ </select>
+ <% } elsif ( $type eq 'editlist' ) { %>
+ <script>
+ function doremove<%= $i->key. $n %>() {
+ fromObject = document.OneTrueForm.<%= $i->key. $n %>;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ if (fromObject.options[i].selected)
+ deleteOption<%= $i->key. $n %>(fromObject,i);
+ }
+ }
+ function deleteOption<%= $i->key. $n %>(object,index) {
+ object.options[index] = null;
+ }
+ function selectall<%= $i->key. $n %>() {
+ fromObject = document.OneTrueForm.<%= $i->key. $n %>;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ fromObject.options[i].selected = true;
+ }
+ }
+ function doadd<%= $i->key. $n %>(object) {
+ var myvalue = "";
+ <% if ( defined($i->editlist_parts) ) { %>
+
+ <% foreach my $pnum ( 0 .. scalar(@{$i->editlist_parts})-1 ) { %>
+
+ if ( myvalue != "" ) { myvalue = myvalue + " "; }
+ <% if ( $i->editlist_parts->[$pnum]{type} eq 'select' ) { %>
+ myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.options[object.add<%= $i->key. $n . "_$pnum" %>.selectedIndex].value;
+ <!-- #RESET SELECT?? maybe not... -->
+ <% } elsif ( $i->editlist_parts->[$pnum]{type} eq 'immutable' ) { %>
+ myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.value;
+ <% } else { %>
+ myvalue = myvalue + object.add<%= $i->key. $n . "_$pnum" %>.value;
+ object.add<%= $i->key. $n. "_$pnum" %>.value = "";
+ <% } %>
+
+
+ <% } %>
+ <% } else { %>
+ myvalue = object.add<%= $i->key. $n. "_1" %>.value;
+ <% } %>
+ var optionName = new Option(myvalue, myvalue);
+ var length = object.<%= $i->key. $n %>.length;
+ object.<%= $i->key. $n %>.options[length] = optionName;
+ }
+ </script>
+ <select multiple size=5 name="<%= $i->key. $n %>">
+ <option selected>----------------------------------------------------------------</option>
+ <% foreach my $line ( $conf->config($i->key) ) { %>
+ <option value="<%= $line %>"><%= $line %></option>
+ <% } %>
+ </select><br>
+ <input type="button" value="remove selected" onClick="doremove<%= $i->key. $n %>()">
+ <script>SafeAddOnLoad(doremove<%= $i->key. $n %>);
+ SafeAddOnSubmit(selectall<%= $i->key. $n %>);</script>
+ <br>
+ <%= itable() %><tr>
+ <% if ( defined $i->editlist_parts ) { %>
+ <% my $pnum=0; foreach my $part ( @{$i->editlist_parts} ) { %>
+ <td>
+ <% if ( $part->{type} eq 'text' ) { %>
+ <input type="text" name="add<%= $i->key. $n."_$pnum" %>">
+ <% } elsif ( $part->{type} eq 'immutable' ) { %>
+ <%= $part->{value} %><input type="hidden" name="add<%= $i->key. $n. "_$pnum" %>" value="<%= $part->{value} %>">
+ <% } elsif ( $part->{type} eq 'select' ) { %>
+ <select name="add<%= $i->key. $n. "_$pnum" %>">
+ <% foreach my $key ( keys %{$part->{select_enum}} ) { %>
+ <option value="<%= $key %>"><%= $part->{select_enum}{$key} %></option>
+ <% } %>
+ </select>
+ <% } else { %>
+ <font color="#ff0000">unknown type <%= $part->type %></font>
+ <% } %>
+ </td>
+ <% $pnum++; } %>
+ <% } else { %>
+ <td><input type="text" name="add<%= $i->key. $n %>_0"></td>
+ <% } %>
+ <td><input type="button" value="add" onClick="doadd<%= $i->key. $n %>(this.form)"></td>
+ </tr></table>
+ <% } else { %>
+ <font color="#ff0000">unknown type <%= $type %></font>
+ <% } %>
+ <% $n++; } %>
+ </td>
+ <td><a name="<%= $i->key %>">
+ <b><%= $i->key %></b> - <%= $i->description %>
+ </a></td>
+ </tr>
+ <% } %>
+ </table><br>
+
+ You may need to restart Apache and/or freeside-queued for configuration
+ changes to take effect.<br>
+
+ <input type="submit" value="Apply changes"><br><br>
+
+<% } %>
+
+</form>
+
+</body></html>
diff --git a/httemplate/docs/ach.html b/httemplate/docs/ach.html
new file mode 100644
index 0000000..b79df78
--- /dev/null
+++ b/httemplate/docs/ach.html
@@ -0,0 +1,12 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ Electronic check (ACH) information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <IMG BORDER=0 SRC="../images/ach.png">
+ <BR>
+ <CENTER><A HREF="javascript:close()">(close window)</A></CENTER>
+ </BODY>
+</HTML>
diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html
new file mode 100755
index 0000000..50beafe
--- /dev/null
+++ b/httemplate/docs/admin.html
@@ -0,0 +1,81 @@
+<head>
+ <title>Administration</title>
+</head>
+<body>
+ <h1>Administration</h1>
+</body>
+<ul>
+ <li>Open up the root of the Freeside document tree in your web
+ browser. For example, if you created the Freeside document tree in
+ /home/httpd/html/freeside, and your web browser's DocumentRoot is
+ /home/httpd/html, open https://your_host/freeside/. Replace
+ "your_host" with the name or network address of your web server.
+ <li>Select <u>Configuration</u> from the main menu and update your configuration values.
+ <li>Next you must create a service definition. An example of a service
+ definition would be a dial-up account or a domain. First, it is
+ necessary to create a domain definition. Click on <u>View/Edit service
+ definitions</u> and <u>Add a new service definition</u> with <i>Table</i>
+ <b>svc_domain</b> (and no modifiers).
+
+ <li>Now that you have created your first service, you must create a package
+ including this service which you can sell to customers. Zero, one, or many
+ services are bundled into a package. Click on <u>View/Edit package
+ definitions</u> and <u>Add a new package definition</u> which includes
+ quantity <b>1</b> of the svc_domain service you created above.
+
+ <li>After you create your first package, then you must define who is
+ able to sell that package by creating an agent type. An example of
+ an agent type would be an internal sales representitive which sells
+ regular and promotional packages, as opposed to an external sales
+ representitive which would only sell regular packages of services. Click on
+ <u>View/Edit agent types</u> and <u>Add a new agent type</u>. Allow this
+ agent type to sell the package you created above.
+
+ <li>After creating a new agent type, you must create an agent. Click on
+ <u>View/Edit agents</u> and <u>Add a new agent</u>.
+
+ <li>Set up at least one Advertising source. Advertising sources will help
+ you keep track of how effective your advertising is, tracking where customers
+ heard of your service offerings. You must create at least one advertising
+ source. If you do not wish to use the referral functionality, simply create
+ a single advertising source only. Click on <u>View/Edit advertising
+ sources</u> and <u>Add a new advertising source</u>.
+
+ <li>Click on <u>New Customer</u> and create a new customer for your system
+ accounts with billing type <b>Complimentary</b>.
+
+ <li>From the Customer View screen of the newly created customer, order the
+ package you defined above.
+
+ <li>From the Package View screen of the newly created package, choose
+ <u>(Provision)</u> to add the customer's service for this new package.
+
+ <li>Add your own domain.
+
+ <li>Go back to <u>View/Edit service definitions</u> on the main menu, and
+ <u>Add a new service definition</u> with <i>Table</i> <b>svc_acct</b>.
+ Select your domain in the <b>domsvc</b> Modifier. Set <b>Fixed</b> to define
+ a service locked-in to this domain, or <b>Default</b> to define a service
+ which may select from among this domain and the customer's domains.
+
+ <li><table><tr>
+ <td> Create at least POP (Point of Presence) by selecting
+ <u>View/Edit POPs</u> from the main menu.</td>
+ <th align="left"> OR </th>
+ <td>If you are not doing dialup, set slipip to fixed and blank for all your
+ Service Definitions which have Table <b>svc_acct</b>.</td>
+ </tr></table>
+
+ <li>If you are using Freeside to keep track of sales taxes, define tax
+ information for your locales by clicking on the <u>View/Edit locales and tax
+ rates</b> on the main menu.
+
+ <li>If you would like Freeside to notify your customers when their credit
+ cards and other billing arrangements are about to expire, arrange for
+ <b>freeside-expiration-alerter</b> to be run daily by cron or similar
+ facility. The message it sends can be configured from the
+ <u>Configuration</u> choice of the main menu as <u>alerter_template</u>.
+
+</ul>
+</body>
+</html>
diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html
new file mode 100644
index 0000000..1d6f8c4
--- /dev/null
+++ b/httemplate/docs/billing.html
@@ -0,0 +1,54 @@
+<head>
+ <title>Billing</title>
+</head>
+<body>
+ <h1>Billing</h1>
+ <ul>
+ <li>You can bill individual customers by clicking on the <i>Bill now</i> link on the main customer view.
+ <li>The <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> script should be run daily to bill customers and run invoice collection events.
+ <li>Real-time credit card processing: Install the <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> module for your processor. Configure the <a href="../config/config-view.cgi#business-onlinepayment">business-onlinepayment</a> configuration option. Disable the default <b>Batch card</b> <a href="../browse/part_bill_event.cgi">invoice event</a> and add one for Business::OnlinePayment.
+ <li>Optional: Credit card expiration alerts: Customize <a href="../config/config.cgi#alerter_template">alerter_template</a> configuration option and run <a href="man/bin/freeside-expiration-alerter.html">freeside-expiration-alerter</a> daily.
+ <li>Credit card decline alerts: Customize the <a href="../config/config.cgi#declinetemplate">declinetemplate</a> configuration option and set the <a href="../config/config.cgi#emaildecline">emaildecline</a> configuration option.
+ <li>Optional: Invoice template customization
+ <ul>
+ <li>See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the substitution language.
+ <li>You <b>must</b> call the invoice_lines() function at least once - pass it a number of lines, and it returns a list of array references, each of two elements: a service description column, and a price column. Alternatively, call invoice_lines() with no arguments, and pagination will be disabled - all invoice line items will print on one page, with no padding (recommended for email invoices).
+ <li>In addition, the following variables are available:
+ <ul>
+ <li>$invnum - invoice number
+ <li>$date - as a UNIX timestamp (see <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Format.pm">Date::Format</a> for conversion functions).
+ <li>$page - current page
+ <li>$total_pages - total pages
+ <li>@address - A six-element array containing the customer name, company, and address.
+<!-- <li>$overdue - true if this invoice is overdue -->
+ </ul>
+ </ul>
+ <li>Batch credit card processing
+ <ul>
+ <li>After <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table. Export this table to your credit card batching.
+ <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table. Example code to add payments is:
+ <pre>use FS::cust_pay;
+
+# loop over all records in batch
+
+my $payment=create FS::cust_pay (
+ 'invnum' => $invnum,
+ 'paid' => $paid,
+ '_date' => $_date,
+ 'payby' => $payby,
+ 'payinfo' => $payinfo,
+ 'paybatch' => $paybatch,
+);
+
+my $error=$payment->insert;
+if ( $error ) {
+ #process error
+}
+
+# end loop
+</pre>
+All fields except paybatch are contained in the cust_pay_batch table. You can use paybatch field to track particular batches and/or particular transactions within a batch.
+ </ul>
+<!-- <li>The <a href="man/bin/freeside-print-batch.html"><b>freeside-print-batch</b></a> script can print or email pending credit card batches for manual entry. -->
+ </ul>
+</body>
diff --git a/httemplate/docs/config.html b/httemplate/docs/config.html
new file mode 100644
index 0000000..9caf3bb
--- /dev/null
+++ b/httemplate/docs/config.html
@@ -0,0 +1,36 @@
+<head>
+ <title>Configuration files</title>
+</head>
+<body>
+ <h1>Configuration files</h1>
+<font size="+1" color="#ff0000">Configuration is now done by the top-level Makefile and web interface. The instructions below are no longer necessary.</font>
+<ul>
+ <li>Create the <b>/usr/local/etc/freeside</b> directory to hold your configuration.
+ <li>Setting up <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication">Apache user authetication</a> is mandatory.
+ <li>Create the <b>/usr/local/etc/freeside/mapsecrets</b> file, which maps Apache users to a secrets file which contains a DBI data source, username and password. Every
+line in <b>/usr/local/etc/freeside/mapsecrets</b> should contain a username and
+filename, separated by whitespace. Note that these are not local usernames -
+they are passed from Apache. <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication">
+Apache user authetication</a> is mandatory. For example, if you had the Apache users admin,
+john, and sam,
+you mapsecrets file might look like:
+<pre>
+admin secretfile
+john secretfile
+sam secretfile
+</pre>
+ <li>Next, the filename(s) referenced in <b>/usr/local/etc/freeside/mapsecrets</b> file should be created in the <b>/usr/local/etc/freeside/</b> directory. Each file contains three lines: <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI data source</a> (for example,
+ <tt>DBI:mysql:freeside</tt> or <tt>DBI:Pg:host=localhost;dbname=freeside</tt>), database username, and database password.
+ These files should not be world readable. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD">manpage for your DBD</a> for the exact syntax of a DBI data source. In a normal installation such as the example above, a single file <b>/usr/local/etc/freeside/secretfile</b> would be created - for example:
+<pre>
+DBI:Pg:host=localhost;dbname=freeside
+dbusername
+dbpassword
+</pre>
+<li>Create the <b>/usr/local/etc/freeside/conf.<i>datasource</i></b> directory, for example, <b>/usr/local/etc/freeside/conf.DBI:Pg:host=localhost;dbname=freeside</b> (remember to backslash-escape the ; character when creating directories in the shell:
+<pre>mkdir&nbsp;/usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=freeside
+</pre>
+<li>The rest of the configuration can be done with the web interface. Select <u>Configuration</u> from the main menu and update your configuration values.
+</ul>
+</body>
+</html>
diff --git a/httemplate/docs/cvv2.html b/httemplate/docs/cvv2.html
new file mode 100644
index 0000000..fe8a17f
--- /dev/null
+++ b/httemplate/docs/cvv2.html
@@ -0,0 +1,25 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ CVV2 information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ The CVV2 number (also called CVC2 or CID) is a three- or four-digit
+ security code used to reduce credit card fraud.<BR><BR>
+ <TABLE BORDER=0 CELLSPACING=4>
+ <TR>
+ <TH>Visa / MasterCard / Discover</TH>
+ <TH>American Express</TH>
+ </TR>
+ <TR>
+ <TD>
+ <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="../images/cvv2.png">
+ </TD>
+ <TD>
+ <IMG BORDER=0 ALT="American Express" SRC="../images/cvv2_amex.png">
+ </TD>
+ </TABLE>
+ <CENTER><A HREF="javascript:close()">(close window)</A></CENTER>
+ </BODY>
+</HTML>
diff --git a/httemplate/docs/export.html b/httemplate/docs/export.html
new file mode 100755
index 0000000..71e3acf
--- /dev/null
+++ b/httemplate/docs/export.html
@@ -0,0 +1,55 @@
+<head>
+ <title>File exporting</title>
+</head>
+<body>
+ <h1>File exporting</h1>
+ <font size="+2">NOTE: This file is OUT OF DATE with the landing of the new export code and is only here for reference. DO NOT follow these instructions. Instead use the new exports in the web interface.</font>
+ <ul>
+ <li>bin/svc_acct.export will create UNIX <b>passwd</b>, <b>shadow</b> and <b>master.passwd</b> files, ERPCD <b>acp_passwd</b> and <b>acp_dialup</b> files and a RADIUS <b>users</b> file in the <b>/usr/local/etc/freeside/export.<i>datasrc</i></b> directory. Some RADIUS servers (such as <a href="http://www.open.com.au/radiator/">Radiator</a>, <a href="ftp://ftp.cheapnet.net/pub/icradius/">ICRADIUS</a> and <a href="http://www.freeradius.org/">FreeRADIUS</a>) will authenticate directly out of an SQL database. In these cases,
+it is reccommended that you replicate (<a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">Replication in MySQL</a>) the data to an external RADIUS machine or point icradius_secrets to the external machine rather than running the RADIUS server on your Freeside machine. Using the appropriate <a href="../config/config-view.cgi">configuration settings</a>, you can export these files to your remote machines unattended:
+ <ul>
+ <li>shellmachines - <b>passwd</b> and <b>shadow</b> are copied to the remote machine as <b>/etc/passwd.new</b> and <b>/etc/shadow.new</b> and then moved to <b>/etc/passwd</b> and <b>/etc/shadow</b> if no errors occur.
+ <li>bsdshellmachines - <b>passwd</b> and <b>master.passwd</b> are copied to the remote machine as <b>/etc/passwd.new</b> and <b>/etc/master.passwd.new</b> and moved to <b>/etc/passwd</b> and <b>/etc/master.passwd</b> if no errors occur.
+ <li>nismachines - <b>passwd</b> and <b>shadow</b> are copied to the <b>/etc/global</b> directory on the remote machine. If no errors occur, the command <b>( cd /var/yp; make; )</b> is executed on the remote machine.
+ <li>erpcdmachines - <b>acp_passwd</b> and <b>acp_dialup</b> are copied to the <b>/usr/annex</b> directory on the remote machine. If no errors occur, the command <b>( kill -USR1 `cat /usr/annex/erpcd.pid` )</b> is executed on the remote machine.
+ <li>radiusmachines - <b>users</b> is copied to the <b>/etc/raddb</b> directory on the remote machine. If no errors occur, the command <b>( builddbm )</b> is executed on the remote machine.
+ <li>icradiusmachines - Turn this option on to enable radcheck table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck table needs to be created manually). You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option. <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line. Machines listed in this file will have the radcheck table exported to them. Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password. For example: <CODE>"radius.isp.tld&nbsp;radius_db&nbsp;radius_user&nbsp;passw0rd"</CODE></blockquote>
+ </ul>
+ <li>svc_acct.pm - If a shellmachine is defined, users can be created, modified and deleted remotely; see below.
+ <ul>
+ <li>Account creation - If the <b>username</b>, <b>uid</b> and <b>dir</b> fields are defined for a new user, the command(s) specified in the <a href="../config/config-view.cgi#shellmachine-useradd">shellmachine-useradd</a> configuration file are executed on shellmachine via ssh. If this file does not exist, <code>useradd -d $dir -m -s $shell -u $uid $username</code> is the default. If the file exists but is empty, <code>cp -pr /etc/skel $dir; chown -R $uid.$gid $dir</code> is the default instead. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$username</code>, <code>$uid</code>, <code>$gid</code>, <code>$dir</code>, and <code>$shell</code>.
+ <li>Account deletion - The command(s) specified in the <a href="../config/config-view.cgi#shellmachine-userdel">shellmachine-userdel</a> configuration file are executed on shellmachine via ssh. If this file does not exist, <code>userdel $username</code> is the default. If the file exists but is empty, <code>rm -rf $dir</code> is the default instead. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$username</code> and <code>$dir</code>.
+ <li>Account modification - If a user's home directory changes, the command(s) specified in the <a href="../config/config-view.cgi#shellmachine-usermod">shellmachine-usermod</a> configuration file are execute on shellmachine via ssh. If this file does not exist or is empty, <code>[ -d $old_dir ] &amp;&amp; mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $uid.$gid $new_dir; rm -rf $old_dir )</code> is the default. Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$old_dir</code>, <code>$new_dir</code>, <code>$uid</code> and <code>$gid</code>.
+ </ul>
+ <li>svc_acct.pm - <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a> integration, enabled by the <a href="../config/config-view.cgi#cyrus">cyrus configuration file</a>
+ <ul>
+ <li>Account creation - (Cyrus::IMAP::Admin should be installed locally)
+ <li>Account deletion - (Cyrus::IMAP::Admin should be installed locally)
+ <li>Account modification - (not yet implemented)
+ </ul>
+ <li>bin/svc_acct_sm.export will create <a href="http://www.qmail.org">Qmail</a> <b>rcpthosts</b>, <b>recipientmap</b> and <b>virtualdomains</b> files and <a href="http://www.sendmail.org">Sendmail</a> <b>virtusertable</b> and <b>sendmail.cw</b> files in the <b>/usr/local/etc/freeside/export.<i>datasrc</i></b> directory. Using the appropriate <a href="../config/config-view.cgi">configuration files</a>, you can export these files to your remote machines unattemded:
+ <ul>
+ <li>qmailmachines - <b>recipientmap</b>, <b>virtualdomains</b> and <b>rcpthosts</b> are copied to the <b>/var/qmail/control</b> directory on the remote machine. Note: If you <a href="legacy.html#svc_acct_sm">imported</a> qmail configuration files, run the generated <b>/usr/local/etc/freeside/export.<i>datasrc</i>/virtualdomains.FIX</b> on a machine with your user home directories before exporting qmail configuration files.
+ <li>shellmachine - The command <b>[ -e <i>homedir</i>/.qmail-default ] || { touch <i>homedir</i>/.qmail-default; chown <i>uid</i>.<i>gid</i> <i>homedir</i>/.qmail-default; }</b> will be run on this machine for users in the virtualdomains file.
+ <li>sendmailmachines - <b>sendmail.cw</b> and <b>virtusertable</b> are copied to the remote machine as <b>/etc/sendmail.cw.new</b> and <b>/etc/virtusertable.new</b>. If no errors occur, they are moved to <b>/etc/sendmail.cw</b> and <b>/etc/virtusertable</b> and the command specified in the <a href="../config/config-view.cgi#sendmailrestart">sendmailrestart</a> configuration file is executed. (The path can be changed from the default <b>/etc</b> with the <a href="../config/config-view.cgi#sendmailconfigpath">sendmailconfigpath</a> configuration file.)
+ </ul>
+ <li>svc_domain.pm - If the qmailmachines configuration file exists and a shellmachine is defined, user <b>.qmail-</b> files can be updated for catchall mailboxes.
+ <ul>
+ <li>The command <pre>[ -e <i>homedir</i>/.qmail-<i>domain</i>-default ] || {
+ touch <i>homedir</i>/.qmail-<i>domain</i>-default;
+ chown <i>uid</i>.<i>gid</i> <i>homedir</i>/.qmail-<i>domain</i>-default;
+}</pre> is run.
+ </ul>
+ <li>svc_forward.pm - Not yet documented; see manpage.
+ <li>svc_www.pm - Not yet documented; see manpage.
+ </ul>
+ <br><a name=ssh>Unattended remote login</a> - Freeside can login to remote machines unattended using SSH. This can pose a security risk if not configured correctly, and will allow an intruder who breaks into your freeside machine full access to your remote machines. <b>Do not use this feature unless you understand what you are doing!</b>
+ <ul>
+ <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase.
+ <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> on the remote machine(s).
+ <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> on the remote machine(s).
+ <li>You may need to set <code>PermitRootLogin without-password</code> (meaning with keys only) in your <code>sshd_config</code> file on the remote machine(s).
+ </ul>
+
+</body>
+
diff --git a/httemplate/docs/ieak.html b/httemplate/docs/ieak.html
new file mode 100644
index 0000000..00c5342
--- /dev/null
+++ b/httemplate/docs/ieak.html
@@ -0,0 +1,75 @@
+<pre>
+this is incomplete
+mostly it should be merged into signup.html and fs_signup/ieak.template
+
+- download and install the IEAK from
+ http://www.microsoft.com/windows/ieak/default.asp
+
+- Good examples may be found in
+ C:\Program Files\IEAK\toolkit\isp\server\ICW\signup\perl\signup08.pl
+ C:\Program Files\IEAK\toolkit\isp\server\ICW\reconfig\perl\reconfig04.pl
+ C:\Program Files\IEAK6\toolkit\isp\servless\basic\sample.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4567.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4568.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7890.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7891.ins
+
+- Full documentation on all the settings available in .INS files is
+ avaialble under Program Files | Microsoft IEAK 6 | IEAK Help
+ | Reference | Internet Settings (.ins) Files
+
+- Freeside will make the following substitutions before sending the file
+ to the user:
+
+ { $ac } - area code of selected POP
+ { $exch } - exchange of selected POP
+ { $loc } - local part of selected POP
+ { $username }
+ { $password }
+ { $email_name } - first and last name
+ { $pkg } - package name
+
+- Simple example follows:
+
+[Entry]
+Entry Name = IEAK Sample
+[Phone]
+Dial_As_Is = No
+Phone_Number = { $exch }{ $loc }
+Area_Code = { $ac }
+Country_Code = 1
+Country_Id = 1
+[Server]
+Type = PPP
+SW_Compress = Yes
+PW_Encrypt = Yes
+Negotiate_TCP/IP = Yes
+Disable_LCP = No
+[TCP/IP]
+Specity_IP_Address = No
+Specity_Server_Address = No
+IP_Header_Compress = Yes
+Gateway_On_Remote = Yes
+[User]
+Name = { $username }
+Passowrd = { $password }
+Display_Password = Yes
+[Internet_Mail]
+Email_Name = { $email_name }
+Email_Address = { $username }@example.com
+POP_Server = mail.example.com
+POP_Server_Port_Number = 110
+POP_Logon_Password = { $password }
+SMTP_Server = mail.example.com
+SMTP_Server_Port_Number = 25
+Install_Mail = 1
+[URL]
+Help_Page = http://www.ieaksample.net/helpdesk
+Home_Page = http://www.ieaksample.net
+Search_Page = http://www.ieaksample,net/search
+[Favorites]
+IEAK Sample \\ IEAK Sample Home Page.url = http://acme.ieaksample.net/
+[Branding]
+Window_Title = Internet Explorer from Acme Internet Services
+
+</pre>
diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html
new file mode 100644
index 0000000..648cb98
--- /dev/null
+++ b/httemplate/docs/index.html
@@ -0,0 +1,30 @@
+<head>
+ <title>Documentation</title>
+</head>
+<body bgcolor="#ffffff">
+ <h1>Documentation</h1>
+<img src="overview.png">
+<ul>
+ <li><a href="install.html">New Installation</a>
+ <li><a href="upgrade7.html">Upgrading from 1.3.0 to 1.3.1</a>
+ <li><a href="upgrade8.html">Upgrading from 1.3.1 to 1.4.0</a>
+ <li><a href="upgrade9.html">Upgrading from 1.4.0 to 1.4.1</a>
+ <li><a href="upgrade-1.4.2.html">Upgrading from 1.4.1 to 1.4.2</a>
+ <li><a href="upgrade10.html">Upgrading from 1.4.1 (or 1.4.2?) to 1.5.0</a>
+<!--
+ <li><a href="config.html">Configuration files</a>
+!-->
+ <li><a href="admin.html">Administration</a>
+<!--
+ <li><a href="../index.html#admin">Administration</a>
+!-->
+ <li><a href="legacy.html">Importing legacy data</a>
+ <li><a href="export.html">File exporting and remote setup</a>
+ <li><a href="passwd.html">fs_passwd</a>
+ <li><a href="signup.html">Signup server</a>
+ <li><a href="session.html">Session monitor</a>
+ <li><a href="billing.html">Billing</a>
+ <li><a href="schema.html">Schema reference</a>
+ <li><a href="man/FS.html">Perl API</a>
+</ul>
+</body>
diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html
new file mode 100644
index 0000000..d4507a2
--- /dev/null
+++ b/httemplate/docs/install.html
@@ -0,0 +1,212 @@
+<head>
+ <title>Installation</title>
+</head>
+<body>
+<h1>Installation</h1>
+<i>Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.</i><br><br>
+Before installing, you need:
+<ul>
+ <li><a href="http://www.perl.com/">Perl</a>
+ <li><a href="http://www.apache.org">Apache</a> (<a href="http://www.modssl.org/">mod_ssl</a> or <a href="http://www.apache-ssl.org">Apache-SSL</a> highly recommended)
+ <li><a href="http://perl.apache.org/">mod_perl</a> (if compiling your own mod_perl, make sure you set the <a href="http://perl.apache.org/guide/install.html#EVERYTHING">EVERYTHING</a>=1 compile-time option)
+ <li><a href="http://www.openssh.com/">SSH</a> (<a href="http://www.openssh.com//">OpenSSH</a> is recommended. SSH Communications Security <a href="http://www.ssh.com/products/ssh/download.cfm">commercial SSH version 3</a> has been reported incompatible with Freeside.)
+ <li><a href="http://rsync.samba.org/">rsync</a>
+ <li>A <b>transactional</b> database engine <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">supported</a> by Perl's <a href="http://dbi.perl.org">DBI</a>.
+ <ul>
+ <li><a href="http://www.postgresql.org/">PostgreSQL</a> is recommended (v7or later).
+ <li><a href="http://www.mysql.com/">MySQL</a> <b>MINIMUM VERSION 4.1</b> is untested but may work. Versions before 4.1 do not support standard SQL subqueries and are <b>NOT SUPPORTED</b>. If you are a developer who wishes to contribute MySQL 3.x/4.0 support, see <a href="http://pouncequick.420.am/rt/Ticket/Display.html?id=438">ticket #438</a> in the bug-tracking system and ask on the -devel mailing list.
+<!-- <li>MySQL has been reported to work. -->
+ <b>MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are not supported</b>. If you want to use MySQL, you <b>must</b> use one of the new <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/B/D/BDB.html">BDB</a> or <a href="http://www.mysql.com/doc/I/n/InnoDB.html">InnoDB</a>, and set it as the default table type using the <code>--default-table-type=BDB</code> or <code>--default-table-type=InnoDB</code> <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Command-line_options">mysqld command-line option</a> or by setting <code>default-table-type=BDB</code> or <code>default-table-type=InnoDB</code> in the <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Option_files">my.cnf option file</a>.
+ </ul>
+ <li>Perl modules (<a href="http://search.cpan.org/~andk/CPAN/lib/CPAN.pm">CPAN</a> will query, download and build perl modules automatically)
+ <ul>
+<!-- <li><a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a>
+ <li><a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) -->
+ <li><a href="http://search.cpan.org/search?dist=MIME-Base64">MIME-Base64</a>
+ <li><a href="http://search.cpan.org/search?dist=Digest-MD5">Digest-MD5</a>
+<!-- <li><a href="http://search.cpan.org/search?dist=MD5">MD5</a> -->
+ <li><a href="http://search.cpan.org/search?dist=URI">URI</a>
+ <li><a href="http://search.cpan.org/search?dist=HTML-Tagset">HTML-Tagset</a>
+ <li><a href="http://search.cpan.org/search?dist=HTML-Parser">HTML-Parser</a>
+ <li><a href="http://search.cpan.org/search?dist=libnet">libnet</a>
+ <li><a href="http://search.cpan.org/search?dist=Locale-Codes">Locale-Codes</a>
+ <li><a href="http://search.cpan.org/search?dist=Net-Whois-Raw">Net-Whois-Raw</a>
+ <li><a href="http://search.cpan.org/search?dist=libwww-perl">libwww-perl</a>
+ <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a>
+<!-- <li><a href="http://search.cpan.org/search?dist=Data-ShowTable">Data-ShowTable</a> -->
+ <li><a href="http://search.cpan.org/search?dist=MailTools">MailTools</a>
+ <li><a href="http://search.cpan.org/search?dist=TimeDate">TimeDate</a>
+ <li><a href="http://search.cpan.org/search?dist=DateManip">DateManip</a>
+ <li><a href="http://search.cpan.org/search?dist=File-CounterFile">File-CounterFile</a>
+ <li><a href="http://search.cpan.org/search?dist=FreezeThaw">FreezeThaw</a>
+ <li><a href="http://search.cpan.org/search?dist=String-Approx">String-Approx</a>
+ <li><a href="http://search.cpan.org/search?dist=Text-Template">Text-Template</a>
+ <li><a href="http://search.cpan.org/search?dist=DBI">DBI</a>
+ <li><a href="http://search.cpan.org/search?mode=module&query=DBD">DBD for your database engine</a> (<a href="http://search.cpan.org/search?dist=DBD-Pg">DBD::Pg</a> for PostgreSQL, <a href="http://search.cpan.org/search?dist=DBD-mysql">DBD::mysql</a> for MySQL)
+ <li><a href="http://search.cpan.org/search?dist=DBIx-DataSource">DBIx-DataSource</a>
+ <li><a href="http://search.cpan.org/search?dist=DBIx-DBSchema">DBIx-DBSchema</a>
+ <li><a href="http://search.cpan.org/search?dist=Net-SSH">Net-SSH</a>
+ <li><a href="http://search.cpan.org/search?dist=String-ShellQuote">String-ShellQuote</a>
+ <li><a href="http://search.cpan.org/search?dist=Net-SCP">Net-SCP</a>
+ <li><a href="http://www.masonhq.com/">HTML::Mason</a> (recommended, enables full functionality) or <a href="http://www.apache-asp.org/">Apache::ASP</a> (deprecated, integrated RT ticketing will not be available)
+ <li><a href="http://search.cpan.org/search?dist=Tie-IxHash">Tie-IxHash</a>
+ <li><a href="http://search.cpan.org/search?dist=Time-Duration">Time-Duration</a>
+ <li><a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML-Widgets-SelectLayers</a>
+ <li><a href="http://search.cpan.org/search?dist=Storable">Storable</a>
+<!-- MyAccounts, maybe only for dev <li><a href="http://search.cpan.org/search?dist=Cache-Cache">Cache::Cache</a> -->
+ <li><a href="http://search.cpan.org/search?dist=NetAddr-IP">NetAddr-IP</a>
+ <li><a href="http://search.cpan.org/search?dist=Chart">Chart</a>
+ <li><a href="http://search.cpan.org/search?dist=Crypt-PasswdMD5">Crypt::PasswdMD5</a>
+ <li><a href="http://search.cpan.org/search?dist=ApacheDBI">Apache::DBI</a> <i>(optional but recommended for better webinterface performance)</i>
+ </ul>
+</ul>
+Install the Freeside distribution:
+<ul>
+ <li>Add the user and group `freeside' to your system.
+ <li>Allow the freeside user full access to the freeside database.
+ <ul>
+ <li> with <a href="http://www.postgresql.org/users-lounge/docs/7.1/postgres/user-manag.html#DATABASE-USERS">PostgreSQL</a>:
+ <pre>
+$ su postgres (pgsql on some distributions)
+$ createuser -P freeside
+Enter password for user "freeside":
+Enter it again:
+Shall the new user be allowed to create databases? (y/n) y
+Shall the new user be allowed to create more new users? (y/n) n
+CREATE USER</pre>
+ <li> with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#User_Account_Management">MySQL</a>:
+ <pre>
+$ mysqladmin -u root password '<i>set_a_root_database_password</i>'
+$ mysql -u root -p
+mysql> GRANT SELECT,INSERT,UPDATE,DELETE,INDEX,ALTER,CREATE,DROP on freeside.* TO freeside@localhost IDENTIFIED BY '<i>set_a_freeside_database_password</i>';</pre>
+ </ul>
+<!-- <li>Unpack the tarball: <pre>gunzip -c fs-x.y.z.tar.gz | tar xvf -</pre>-->
+ <li>Edit the top-level Makefile:
+ <ul>
+ <li>Set <tt>DATASOURCE</tt> to your <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI data source</a>, for example, <tt>DBI:Pg:dbname=freeside</tt> for PostgresSQL or <tt>DBI:mysql:freeside</tt> for MySQL. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">manpage for your DBD</a> for the exact syntax of your DBI data source.
+ <li>Set <tt>DB_PASSWORD</tt> to the freeside database user's password.
+ </ul>
+ <li>Add the freeside database to your database engine:
+ <pre>
+$ su
+# make create-database</pre>
+ (or manually, with Postgres:)
+ <pre>
+$ su freeside
+$ createdb freeside</pre>
+ (with MySQL:)
+ <pre>
+$ mysqladmin -u freeside -p create freeside </pre>
+ <li>Build and install the Perl modules:
+ <pre>
+$ make perl-modules
+$ su
+# make install-perl-modules</pre>
+ <li>Create the necessary configuration files:<pre>
+$ su
+# make create-config
+</pre>
+ <li>Run a <b>separate</b> iteration of Apache[-SSL] with mod_perl enabled <b>as the freeside user</b>.
+</ul>
+<table>
+ <tr>
+ <th>Apache::ASP</th><th>Mason</th>
+ </tr>
+ <tr>
+ <td><ul>
+ <li>Run <tt>make aspdocs</tt>
+ <li>Copy <tt>aspdocs/</tt> to your web server's document space:
+<font size="-1"><pre>
+cp&nbsp;aspdocs&nbsp;/usr/local/apache/htdocs/freeside-asp
+</pre></font>
+ <li>Create a <a href="http://www.apache-asp.org/config.html#Global">Global</a> directory, such as <tt>/usr/local/etc/freeside/asp-global/</tt>:
+<font size="-1"><pre>
+mkdir&nbsp;/usr/local/etc/freeside/asp-global/
+chown&nbsp;freeside&nbsp;/usr/local/etc/freeside/asp-global/
+</pre></font>
+ <li>Copy <tt>htetc/global.asa</tt> to the Global directory:
+<font size="-1"><pre>
+cp&nbsp;htetc/global.asa&nbsp;/usr/local/etc/freeside/asp-global/global.asa
+</pre></font>
+ <li>Configure Apache for the Global directory and to execute .cgi files using Apache::ASP. For example, add something like the following to your Apache httpd.conf file, adjusting for your actual paths:
+<font size="-1"><pre>
+PerlModule Apache::ASP
+# your freeside document root
+&lt;Directory&nbsp;/usr/local/apache/htdocs/freeside-asp&gt;
+&lt;Files ~ (\.cgi|\.html)&gt;
+AddHandler perl-script .cgi .html
+PerlHandler Apache::ASP
+&lt;/Files&gt;
+&lt;Perl&gt;
+$MLDBM::RemoveTaint = 1;
+&lt;/Perl&gt;
+PerlSetVar&nbsp;Global&nbsp;/usr/local/etc/freeside/asp-global/
+PerlSetVar&nbsp;Debug&nbsp;2
+PerlSetVar&nbsp;RequestBinaryRead&nbsp;Off
+# your freeside document root
+PerlSetVar&nbsp;IncludesDir&nbsp;/usr/local/apache/htdocs/freeside-asp
+&lt;/Directory&gt;
+</pre></font>
+ </ul></td>
+ <td><ul>
+ <li>Run <tt>make masondocs</tt>
+ <li>Copy <tt>masondocs/</tt> to your web server's document space. (For example: <tt>/usr/local/apache/htdocs/freeside-mason</tt>)
+ <li>Copy <tt>htetc/handler.pl</tt> to <tt>/usr/local/etc/freeside</tt>
+ <li>Edit <tt>handler.pl</tt> and:
+ <ul>
+ <li> set an appropriate <tt>comp_root</tt>, such as <tt>/usr/local/apache/htdocs/freeside-mason</tt>
+ <li> set an appropriate <tt>data_dir</tt>, such as <tt>/usr/local/etc/freeside/masondata</tt>
+ </ul>
+
+ <li>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason. For example, add something like the following to your Apache httpd.conf file, adjusting for your actual paths:
+<font size="-1"><pre>
+PerlModule HTML::Mason
+&lt;Directory&nbsp;/usr/local/apache/htdocs/freeside-mason&gt;
+&lt;Files ~ (\.cgi|\.html)&gt;
+AddHandler perl-script .cgi .html
+PerlHandler HTML::Mason
+&lt;/Files&gt;
+&lt;Perl&gt;
+require&nbsp;"/usr/local/etc/freeside/handler.pl";
+&lt;/Perl&gt;
+&lt;/Directory&gt;
+</pre></font>
+ </ul></td>
+ </tr>
+</table>
+<ul>
+<li>Restrict access to this web interface - see the <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache documentation on user authentication</a>. For example, to configure user authentication with <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files), add something like the following to your Apache httpd.conf file, adjusting for your actual paths:
+<pre>
+&lt;Directory /usr/local/apache/htdocs/freeside-asp&gt;
+AuthName Freeside
+AuthType Basic
+AuthUserFile /usr/local/etc/freeside/htpasswd
+require valid-user
+&lt;/Directory&gt;
+</pre>
+ <li>Create one or more Freeside users (your internal sales/tech folks, not customer accounts). These users are setup using using Apache authentication, not UNIX user accounts. For example, using <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files):
+ <ul>
+ <li>First user:<font size="-1">
+<pre>$ su
+$ <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -c -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font>
+ <li>Additional users:<font size="-1">
+<pre>$ su
+$ <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font>
+ </ul>
+ <i>(using other auth types, add each user to your <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache authentication</a> and then run: <tt>freeside-adduser <b>username</b></tt></i>
+ <li>As the freeside UNIX user, run <tt>freeside-setup <b>username</b></tt> to create the database tables, passing the username of a Freeside user you created above:
+<pre>
+$ su freeside
+$ freeside-setup <b>username</b>
+</pre>
+ Alternately, use the -s option to enable shipping addresses: <tt>freeside-setup -s <b>username</b></tt>
+ <li>As the freeside UNIX user, run <tt>bin/populate-msgcat <b>username</b></tt> (in the untar'ed freeside directory) to populate the message catalog, passing the username of a Freeside user you created above:
+<pre>
+$ su freeside
+$ cd <b>/path/to/freeside/</b>
+$ bin/populate-msgcat <b>username</b>
+</pre>
+ <li><tt>freeside-queued</tt> was installed with the Perl modules. Start it now and ensure that is run upon system startup (Do this manually, or edit the top-level Makefile, replacing INIT_FILE with the appropriate location on your systemand QUEUED_USER with the username of a Freeside user you created above, and run <tt>make install-init</tt>)
+ <li>Now proceed to the initial <a href="admin.html">administration</a> of your installation.
+</ul>
+</body>
diff --git a/httemplate/docs/legacy.html b/httemplate/docs/legacy.html
new file mode 100755
index 0000000..94efe53
--- /dev/null
+++ b/httemplate/docs/legacy.html
@@ -0,0 +1,39 @@
+<head>
+ <title>Importing legacy data</title>
+</head>
+<body>
+ <h1>Importing legacy data</h1>
+<font size="+2">In almost all cases, legacy data import will require writing custom code to deal with your particular legacy data. The example scripts here will probably <b>not</b> work "out-of-the-box", and are provided <b>as a starting point only</b>.</font>
+<br><br><i>Some import scripts may require installation of the <a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a> and <a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) modules.</i><br>
+<ul>
+ <li><a name="bind">bin/bind.import</a> - Import domain information from BIND named
+ <li><a name="passwd">bin/passwd.import</a> - Just import `passwd' and `shadow' or `master.passwd', no RADIUS import.
+ <li><a name="svc_acct">bin/svc_acct.import</a> - Import `passwd', ( `shadow' or `master.passwd' ) and RADIUS `users'. Before running bin/svc_acct.import, you need <a href="../browse/part_svc.cgi">services</a> (with table svc_acct) as follows:
+ <ul>
+ <li>Most accounts probably have entries in passwd and users (with Port-Limit nonexistant or 1)
+ <li>Some accounts have entries in passwd and users, but with Port-Limit 2 (or more)
+ <li>Some accounts might have entries in users only (Port-Limit 1)
+ <li>Some accounts might have entries in users only (Port-Limit >= 2)
+ <li>POP mail accounts have entries in passwd only, and have a particular shell.
+ <li>Everything else in passwd is a shell account.
+ </ul>
+<!-- <li><a name="svc_acct_sm">bin/svc_acct_sm.import</a> - Import qmail ( `virtualdomains' and `rcpthosts' ), or sendmail ( `virtusertable' and `sendmail.cw' ) files. Before running bin/svc_acct_sm.import, you need <a href="../browse/part_svc.cgi">services</a> as follows:
+ <ul>
+ <li>Domain (table svc_acct)
+ <li>Mail alias (table svc_acct_sm)
+ </ul>
+-->
+ <li><a name="cust_main">Importing customer data</a>
+ <ul>
+ <li>Manually
+ <ul>
+ <li>Add a <a href="../edit/cust_main.cgi">new customer</a>
+ <li>Add one or more packages for this customer
+ <li>Enter a package by clicking on the package number
+ <li>Pick the `Link to existing' option
+ </ul>
+ <li>Batch - You will need to write a script to import your particular legacy data. You can use eg/TEMPLATE_cust_main.import as a starting point.
+ </ul>
+</ul>
+</body>
+
diff --git a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack
diff --git a/httemplate/docs/overview.dia b/httemplate/docs/overview.dia
new file mode 100644
index 0000000..a0e34c3
--- /dev/null
+++ b/httemplate/docs/overview.dia
Binary files differ
diff --git a/httemplate/docs/overview.png b/httemplate/docs/overview.png
new file mode 100644
index 0000000..bf2dbc2
--- /dev/null
+++ b/httemplate/docs/overview.png
Binary files differ
diff --git a/httemplate/docs/passwd.html b/httemplate/docs/passwd.html
new file mode 100755
index 0000000..fc1dde9
--- /dev/null
+++ b/httemplate/docs/passwd.html
@@ -0,0 +1,23 @@
+<head>
+ <title>fs_passwd</title>
+</head>
+<body>
+ <h1>fs_passwd</h1>
+You may use fs_passwd/fs_passwd as a "passwd", "chfn" and "chsh" replacement on your shell machine(s) to cause password, gecos and shell changes to update your freeside machine. You can also use the fs_passwd/fs_passwd.html and fs_passwd/fs_passwd.cgi to run a public password change CGI on a public web server. This can pose a security risk if not configured correctly. <b>Do not use this feature unless you understand what you are doing!</b>
+<br><br>Currently it is assumed that the the crypt(3) function in the C library is the same on the Freeside machine as on the target machine.
+<ul>
+ <li>Create a freeside account on the shell or web machine(s).
+ <li>Setup SSH keys:
+ <ul>
+ <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase.
+ <li>Append the newly-created <code>identity.pub</code> file to <code>~freeside
+/.ssh/authorized_keys</code> on the shell or web machine(s).
+ <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~freeside/.ssh/authorized_keys2</code> on the remote machine(s).
+ </ul>
+ <li>Copy fs_passwd/fs_passwdd to /usr/local/sbin on the shell or web machine(s). (chown freeside, chmod 500)
+ <li>Create /usr/local/freeside on the shell or web machine(s). (chown freeside, chmod 700)
+ <li>Run an iteration of "fs_passwd/fs_passwd_server <i>user</i> shell.machine" as the freeside user for each shell or web machine (this is a daemon process). <i>user</i> refers to a freeside user added by <a href="man/bin/freeside-adduser.html">freeside-adduser</a>.
+ <li>Copy fs_passwd/fs_passwd to /usr/local/bin on the shell machine(s). (chown freeside, chmod 4755). You may link it to passwd, chfn and chsh as well.
+ <li>Copy fs_passwd/fs_passwd.cgi to the cgi-bin directory on your web machine(s). Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perldoc.com/perl5.6.1/pod/perlsec.html">suidperl</a> to run fs_passwd.cgi as the freeside user.
+</ul>
+</body>
diff --git a/httemplate/docs/schema.dia b/httemplate/docs/schema.dia
new file mode 100644
index 0000000..7465615
--- /dev/null
+++ b/httemplate/docs/schema.dia
Binary files differ
diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html
new file mode 100644
index 0000000..8dee8ad
--- /dev/null
+++ b/httemplate/docs/schema.html
@@ -0,0 +1,457 @@
+<head>
+ <title>Schema reference</title>
+</head>
+<body>
+ <h1>Schema reference</h1>
+ Schema diagram: <a href="schema.png">as a giant .png</a> or <a href="schema.dia">dia source</a> (<a href="http://www.lysator.liu.se/~alla/dia/">dia homepage</a>).
+ <ul>
+ <li><a name="agent" href="man/FS/agent.html">agent</a> - Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their agent type).
+ <ul>
+ <li>agentnum - primary key
+ <li>agent - name of this agent
+ <li>typenum - <a href="#agent_type">agent type</a>
+ <li>prog - (unimplemented)
+ <li>freq - (unimplemented)
+ <li>disabled - Disabled flag, empty or 'Y'
+ <li>username - Username for the Agent interface
+ <li>_password - Password for the Agent interface
+ </ul>
+ <li><a name="agent_type" href="man/FS/agent_type.html">agent_type</a> - Agent types define groups of packages that you can then assign to particular agents.
+ <ul>
+ <li>typenum - primary key
+ <li>atype - name of this agent type
+ </ul>
+ <li><a name="cust_bill" href="man/FS/cust_bill.html">cust_bill</a> - Invoices. Declarations that a customer owes you money. The specific charges are itemized in <a href="#cust_bill_pkg">cust_bill_pkg</a>.
+ <ul>
+ <li>invnum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>_date
+ <li>charged - amount of this invoice
+ <li>printed - how many times this invoice has been printed automatically
+ <li>closed - books closed flag, empty or `Y'
+ </ul>
+ <li><a name="cust_bill_event" href="man/FS/cust_bill_event.html">cust_bill_event</a> - Invoice event history
+ <ul>
+ <li>eventnum - primary key
+ <li>invnum - <a href="#cust_bill">invoice</a>
+ <li>eventpart - <a href="#part_bill_event">event definition</a>
+ <li>_date
+ <li>status
+ <li>statustext
+ </ul>
+ <li><a name="part_bill_event" href="man/FS/part_bill_event.html">part_bill_event</a> - Invoice event definitions
+ <ul>
+ <li>eventpart - primary key
+ <li>payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, or COMP
+ <li>event - event name
+ <li>eventcode - event action
+ <li>seconds - how long after the invoice date (<a href="#cust_bill">cust_bill</a>._date) events of this type are triggered
+ <li>weight - ordering for events with identical seconds
+ <li>plan - eventcode plan
+ <li>plandata - additional plan data
+ <li>disabled - Disabled flag, empty or `Y'
+ <li>taxclass - Texas tax class flag, empty or "none", "access", or "hosting"
+ </ul>
+ <li><a name="cust_bill_pkg" href="man/FS/cust_bill_pkg.html">cust_bill_pkg</a> - Invoice line items
+ <ul>
+ <li>invnum - (multiple) key
+ <li>pkgnum - <a href="#cust_pkg">package</a> or 0 for the special virtual sales tax package
+ <li>setup - setup fee
+ <li>recur - recurring fee
+ <li>sdate - starting date
+ <li>edate - ending date
+ <li>itemdesc - Line item description (currently used only when pkgnum is 0)
+ </ul>
+ <li><a name="cust_bill_pkg_detail" href="man/FS/cust_bill_pkg_detail.html">cust_bill_pkg_detail</a> - Invoice line items detail
+ <ul>
+ <li>detailnum - primary key
+ <li>pkgnum -
+ <li>invnum -
+ <li>detail - Detail description
+ </ul>
+ <li><a name="cust_credit" href="man/FS/cust_credit.html">cust_credit</a> - Credits. The equivalent of a negative <a href="#cust_bill">cust_bill</a> record.
+ <ul>
+ <li>crednum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>amount - amount credited
+ <li>_date
+ <li>otaker - order taker
+ <li>reason
+ <li>closed - books closed flag, empty or `Y'
+ </ul>
+ <li><a name="cust_credit_bill" href="man/FS/cust_credit_bill.html">cust_credit_bill</a> - Credit invoice application. Links a credit to an invoice.
+ <ul>
+ <li>creditbillnum - primary key
+ <li>crednum - <a href="#cust_credit">credit</a> being applied
+ <li>invnum - <a href="#cust_bill">invoice</a> to which credit is applied
+ <li>amount - amount applied
+ <li>_date
+ </ul>
+ <li><a name="cust_pay_refund" href="man/FS/cust_pay_refund.html">cust_credit_bill</a> - Refund payment application. Links a refund to a payment.
+ <ul>
+ <li>payrefundnum - primary key
+ <li>paynum - <a href="#cust_pay">payment</a>
+ <li>refundnum - <a href="#cust_refund">refund</a>
+ <li>amount - amount applied
+ <li>_date
+ </ul>
+ <li><a name="cust_main" href="man/FS/cust_main.html">cust_main</a> - Customers
+ <ul>
+ <li>custnum - primary key
+ <li>agentnum - <a href="#agent">agent</a>
+ <li>refnum - <a href="#part_referral">referral</a>
+ <li>first - name
+ <li>last - name
+ <li>ss - social security number
+ <li>company
+ <li>address1
+ <li>address2
+ <li>city
+ <li>county
+ <li>state
+ <li>zip
+ <li>country
+ <li>daytime - phone
+ <li>night - phone
+ <li>fax - phone
+ <li><i>ship_first</i>
+ <li><i>ship_last</i>
+ <li><i>ship_company</i>
+ <li><i>ship_address1</i>
+ <li><i>ship_address2</i>
+ <li><i>ship_city</i>
+ <li><i>ship_county</i>
+ <li><i>ship_state</i>
+ <li><i>ship_zip</i>
+ <li><i>ship_country</i>
+ <li><i>ship_daytime</i>
+ <li><i>ship_night</i>
+ <li><i>ship_fax</i>
+ <li>payby - CARD, DCHK, CHEK, DCHK, LECB, BILL, or COMP
+ <li>payinfo - card number, P.O.#, or comp issuer
+ <li>paycvv - Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
+ <li>paydate - expiration date
+ <li>payname - billing name (name on card)
+ <li>tax - tax exempt, Y or null
+ <li>otaker - order taker
+ <li>referral_custnum
+ <li>comments
+ </ul>
+ (columns in <i>italics</i> are optional)
+ <li><a name="cust_main_invoice" href="man/FS/cust_main_invoice.html">cust_main_invoice</a> - Invoice destinations for email invoices. Note that a customer can have many email destinations for their invoice (either literal or via svcnum), but only one postal destination.
+ <ul>
+ <li>destnum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>dest - Invoice destination. Freeside supports three types of invoice delivery: send directly to a service defined in Freeside, send to an arbitrary email address, or print the invoice to a printer and have someone send it out via snail mail. Freeside determines which method to use based on the contents of the dest field. If the contents are numeric, a <a href="#svc_acct">svcnum</a> pointing to a valid service is expected in the field. If the contents are a string, a literal email address is expected to be in the field. If the special keyword `POST' is present, the snail mail method is used (which is the default if no cust_main_invoice records exist). Snail mail invoices get their address information from <A name="#cust_main">cust_main</A> and are printed with the printer defined in the configuration files.
+ </ul>
+ <li><a name="cust_main_county" href="man/FS/cust_main_county.html">cust_main_county</a> - Tax rates
+ <ul>
+ <li>taxnum - primary key
+ <li>state
+ <li>county
+ <li>country
+ <li>tax - % rate
+ <li>taxclass
+ <li>exempt_amount
+ <li>taxname - if defined, printed on invoices instead of "Tax"
+ <li>setuptax - if 'Y', this tax does not apply to setup fees
+ <li>recurtax - if 'Y', this tax does not apply to recurring fees
+ </ul>
+ <li><a name="cust_tax_exempt" href="man/FS/cust_tax_exempt.html">cust_tax_exempt</a> - Tax exemption record
+ <ul>
+ <li>exemptnum - primary key
+ <li>taxnum - <a href="#cust_main_county">tax rate</a>
+ <li>year
+ <li>month
+ <li>amount
+ </ul>
+ <li><a name="cust_pay" href="man/FS/cust_pay.html">cust_pay</a> - Payments. Money being transferred from a customer.
+ <ul>
+ <li>paynum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>paid - amount
+ <li>_date
+ <li>payby - CARD, CHEK, LECB, BILL, or COMP
+ <li>payinfo - card number, P.O.#, or comp issuer
+ <li>paybatch - text field for tracking card processor batches
+ <li>closed - books closed flag, empty or `Y'
+ </ul>
+ <li><a name="cust_pay-void" href="man/FS/cust_pay_void.html">cust_pay_void</a> - Voided payments.
+ <ul>
+ <li>paynum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>paid - amount
+ <li>_date
+ <li>payby - CARD, CHEK, LECB, BILL, or COMP
+ <li>payinfo - card number, P.O.#, or comp issuer
+ <li>paybatch - text field for tracking card processor batches
+ <li>closed - books closed flag, empty or `Y'
+ <li>void_date
+ <li>reason
+ <li>otaker - order taker
+ </ul>
+ <li><a name="cust_bill_pay" href="man/FS/cust_bill_pay.html">cust_bill_pay</a> - Applicaton of a payment to a specific invoice.
+ <ul>
+ <li>billpaynum
+ <li>invnum - <a href="#cust_bill">invoice</a>
+ <li>paynum - <a href="#cust_pay">payment</a>
+ <li>amount
+ <li>_date
+ </ul>
+ <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch
+ <ul>
+ <li>paybatchnum
+ <li>cardnum
+ <li>exp - card expiration
+ <li>amount
+ <li>invnum - <a href="#cust_bill">invoice</a>
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>payname - name on card
+ <li>first - name
+ <li>last - name
+ <li>address1
+ <li>address2
+ <li>city
+ <li>state
+ <li>zip
+ <li>country
+ </ul>
+ <li><a name="cust_pkg" href="man/FS/cust_pkg.html">cust_pkg</a> - Customer billing items
+ <ul>
+ <li>pkgnum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>pkgpart - <a href="#part_pkg">Package definition</a>
+ <li>setup - date
+ <li>bill - next bill date
+ <li>last_bill - last bill date
+ <li>susp - (past) suspension date
+ <li>expire - (future) cancellation date
+ <li>cancel - (past) cancellation date
+ <li>otaker - order taker
+ <li>manual_flag - If this field is set to 1, disables the automatic unsuspensiond of this package when using the <a href="config.html#unsuspendauto">unsuspendauto</a> config file.
+ </ul>
+ <li><a name="cust_refund" href="man/FS/cust_refund.html">cust_refund</a> - Refunds. The transfer of money to a customer; equivalent to a negative <a href="#cust_pay">cust_pay</a> record.
+ <ul>
+ <li>refundnum - primary key
+ <li>custnum - <a href="#cust_main">customer</a>
+ <li>refund - amount
+ <li>_date
+ <li>payby - CARD, CHEK, LECB, BILL or COMP
+ <li>payinfo - card number, P.O.#, or comp issuer
+ <li>otaker - order taker
+ <li>closed - books closed flag, empty or `Y'
+ </ul>
+ <li><a name="cust_credit_refund" href="man/FS/cust_credit_refund.html">cust_credit_refund</a> - Applicaton of a refund to a specific credit.
+ <ul>
+ <li>creditrefundnum - primary key
+ <li>crednum - <a href="#cust_credit">credit</a>
+ <li>refundnum - <a href="#cust_refund">refund</a>
+ <li>amount
+ <li>_date
+ </ul>
+ <li><a name="cust_svc" href="man/FS/cust_svc.html">cust_svc</a> - Customer services
+ <ul>
+ <li>svcnum - primary key
+ <li>pkgnum - <a href="#cust_pkg">package</a>
+ <li>svcpart - <a href="#part_svc">Service definition</a>
+ </ul>
+ <li><a name="nas" href="man/FS/nas.html">nas</a> - Network Access Server (terminal server)
+ <ul>
+ <li>nasnum - primary key
+ <li>nas - NAS name
+ <li>nasip - NAS ip address
+ <li>nasfqdn - NAS fully-qualified domain name
+ <li>last - timestamp indicating the last instant the NAS was in a known state (used by the session monitoring).
+ </ul>
+ <li><a name="part_pkg" href="man/FS/part_pkg.html">part_pkg</a> - Package definitions
+ <ul>
+ <li>pkgpart - primary key
+ <li>pkg - package name
+ <li>comment - non-customer visable package comment
+ <li>setup - setup fee expression
+ <li>freq - recurring frequency (months)
+ <li>recur - recurring fee expression
+ <li>setuptax - Setup fee tax exempt flag, empty or `Y'
+ <li>recurtax - Recurring fee tax exempt flag, empty or `Y'
+ <li>plan - price plan
+ <li>plandata - additional price plan data
+ <li>disabled - Disabled flag, empty or `Y'
+ </ul>
+ <li><a name="part_referral" href="man/FS/part_referral.html">part_referral</a> - Referral listing
+ <ul>
+ <li>refnum - primary key
+ <li>referral - referral
+ </ul>
+ <li><a name="part_svc" href="man/FS/part_svc.html">part_svc</a> - Service definitions
+ <ul>
+ <li>svcpart - primary key
+ <li>svc - name of this service
+ <li>svcdb - table used for this service: svc_acct, svc_forward, svc_domain, svc_charge or svc_wo
+ <li>disabled - Disabled flag, empty or `Y'
+<!-- <li><i>table</i>__<i>field</i> - Default or fixed value for <i>field</i> in <i>table</i>
+ <li><i>table</i>__<i>field</i>_flag - null, D or F
+-->
+ </ul>
+ <li><a name="part_svc_column" href="man/FS/part_svc_column.html">part_svc_column</a>
+ <ul>
+ <li>columnnum - primary key
+ <li>svcpart - <a href="#part_svc">Service definition</a>
+ <li>columnname - column name in part_svc.svcdb table
+ <li>columnvalue - default or fixed value for the column
+ <li>columnflag - null, D or F
+ </ul>
+ <li><a name="pkg_svc" href="man/FS/pkg_svc.html">pkg_svc</a>
+ <ul>
+ <li>pkgpart - <a href="#part_pkg">Package definition</a>
+ <li>svcpart - <a href="#part_svc">Service definition</a>
+ <li>quantity - quantity of this service that this package includes
+ <li>primary_svc - blank or Y: primary service
+ </ul>
+ <li><a name="export_svc" href="man/FS/export_svc.html">export_svc</a>
+ <ul>
+ <li>exportsvcnum - primary key
+ <li>svcpart - <a href="#part_svc">Service definition</a>
+ <li>exportnum - <a href="#exportnum">Export</a>
+ </ul>
+ <li><a name="part_export" href="man/FS/part_export.html">part_export</a> - Export to external provisioning
+ <ul>
+ <li>exportnum - primary key
+ <li>machine - Machine name
+ <li>exporttype - Export type
+ <li>nodomain - blank or Y: usernames are exported to this service with no domain
+ </ul>
+ <li><a name="part_export_option" href="man/FS/part_export_option.html">part_export_option</a> - provisioning options
+ <ul>
+ <li>optionnum - primary key
+ <li>exportnum - <a href="#part_export">Export</a>
+ <li>optionname - option name
+ <li>optionvalue - option value
+ </ul>
+ <li><a name="port" href="man/FS/port.html">port</a> - individual port on a <a href="#nas">nas</a>
+ <ul>
+ <li>portnum - primary key
+ <li>ip - IP address of this port
+ <li>nasport - port number on the NAS
+ <li>nasnum - <a href="#nas">NAS</a>
+ </ul>
+ <li><a name="prepay_credit" href="man/FS/prepay_credit.html">prepay_credit</a>
+ <ul>
+ <li>prepaynum - primary key
+ <li>identifier - text or numeric string used to receive this credit
+ <li>amount - amount of credit
+ </ul>
+ <li><a name="session" href="man/FS/session.html">session</a>
+ <ul>
+ <li>sessionnum - primary key
+ <li>portnum - <a href="#port">Port</a>
+ <li>svcnum - <a href="#svc_acct">Account</a>
+ <li>login - timestamp indicating the beginning of this user session.
+ <li>logout - timestamp indicating the end of this user session. May be null, which indicates a currently open session.
+ </ul>
+
+ <li><a name="svc_acct" href="man/FS/svc_acct.html">svc_acct</a> - Accounts
+ <ul>
+ <li>svcnum - <a href="#cust_svc">primary key</a>
+ <li>username
+ <li>_password
+ <li>sec_phrase - security phrase
+ <li>popnum - <a href="#svc_acct_pop">Point of Presence</a>
+ <li>uid
+ <li>gid
+ <li>finger - GECOS
+ <li>dir
+ <li>shell
+ <li>quota - (unimplementd)
+ <li>slipip - IP address
+ <li>seconds
+ <li>domsvc
+ <li>radius_<i>Radius_Reply_Attribute</i> - Radius-Reply-Attribute
+ <li>rc_<i>Radius_Check_Attribute</i> - Radius-Check-Attribute
+ </ul>
+ <li><a name="svc_acct_pop" href="man/FS/svc_acct_pop.html">svc_acct_pop</a> - Points of Presence
+ <ul>
+ <li>popnum - primary key
+ <li>city
+ <li>state
+ <li>ac - area code
+ <li>exch - exchange
+ <li>loc - rest of number
+ </ul>
+ <li><a name="part_pop_local" href="man/FS/part_pop_local.html">part_pop_local</a> - Local calling areas
+ <ul>
+ <li>localnum - primary key
+ <li>popnum - primary key
+ <li>city
+ <li>state
+ <li>npa - area code
+ <li>nxx - exchange
+ </ul>
+ <li><a name="svc_domain" href="man/FS/svc_domain.html">svc_domain</a> - Domains
+ <ul>
+ <li>svcnum - <a href="#cust_svc">primary key</a>
+ <li>domain
+ </ul>
+ <li><a name="svc_forward" href="man/FS/svc_forward.html">svc_forward</a> - Mail forwarding aliases
+ <ul>
+ <li>svcnum - <a href="#cust_svc">primary key</a>
+ <li>srcsvc - <a href="#svc_acct">svcnum of the source of this forward</a>
+ <li>src - literal source (username or full email address)
+ <li>dstsvc - <a href="#svc_acct">svcnum of the destination of this forward</a>
+ <li>dst - literal destination (username or full email address)
+ </ul>
+ <li><a name="domain_record" href="man/FS/domain_record.html">domain_record</a> - Domain zone detail
+ <ul>
+ <li>recnum - primary key
+ <li>svcnum - <a href="#svc_domain">Domain</a> (by svcnum)
+ <li>reczone - zone for this line
+ <li>recaf - address family, usually <b>IN</b>
+ <li>rectype - type for this record (<b>A</b>, <b>MX</b>, etc.)
+ <li>recdata - data for this record
+ </ul>
+ <li><a name="svc_www" href="man/FS/svc_www.html">svc_www</a>
+ <ul>
+ <li>svcnum - <a href="#cust-svc">primary key</a>
+ <li>recnum - <a href="#domain_record">host</a>
+ <li>usersvc - <a href="#svc_acct">account</a>
+ </ul>
+ <li><a name="type_pkgs" href="man/FS/type_pkgs.html">type_pkgs</a>
+ <ul>
+ <li>typenum - <a href="#agent_type">agent type</a>
+ <li>pkgpart - <a href="#part_pkg">Package definition</a>
+ </ul>
+ <li><a name="queue" href="man/FS/queue.html">queue</a> - job queue
+ <ul>
+ <li>jobnum - primary key
+ <li>job
+ <li>_date
+ <li>status
+ <li>statustext
+ <li>svcnum
+ </ul>
+ <li><a name="queue_arg" href="man/FS/queue_arg.html">queue_arg</a> - job arguments
+ <ul>
+ <li>argnum - primary key
+ <li>jobnum - <a href="#queue">job</a>
+ <li>arg - argument
+ </ul>
+ <li><a name="queue_depend" href="man/FS/queue_depend.html">queue_depend</a> - job dependancies
+ <ul>
+ <li>dependnum - primary key
+ <li>jobnum - source jobnum
+ <li>depend_jobnum - dependancy jobnum
+ </ul>
+ <li><a name="radius_usergroup" href="man/FS/radius_usergroup.html">radius_usergroup</a> - Link users to RADIUS groups.
+ <ul>
+ <li>usergroupnum - primary key
+ <li>svcnum - <a href="#svc_acct">account</a>
+ <li>groupname
+ </ul>
+ <li><a name="msgcat" href="man/FS/msgcat.html">msgcat</a> - i18n message catalog
+ <ul>
+ <li>msgnum - primary key
+ <li>msgcode - message code
+ <li>locale - locale
+ <li>msg - Message text
+ </ul>
+ </ul>
+</body>
diff --git a/httemplate/docs/schema.png b/httemplate/docs/schema.png
new file mode 100644
index 0000000..d0392e7
--- /dev/null
+++ b/httemplate/docs/schema.png
Binary files differ
diff --git a/httemplate/docs/session.html b/httemplate/docs/session.html
new file mode 100644
index 0000000..72e1642
--- /dev/null
+++ b/httemplate/docs/session.html
@@ -0,0 +1,59 @@
+<head>
+ <title>Session monitor</title>
+</head>
+<body>
+<h1>Session monitor</h1>
+<h2>Installation</h2>
+For security reasons, the client portion of the session montior may run on one
+or more external public machine(s). On these machines, install:
+<ul>
+ <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at l
+east 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series. Don't enable experimental features like threads or the PerlIO abstraction layer.)
+ <li><a href="man/FS/SessionClient.html">FS::SessionClient</a> (copy the fs_session/FS-SessionClient directory to the external machine, then: perl Makefile.PL; make; make install)
+</ul>
+Then:
+<ul>
+ <li>Add the user `freeside' to the the external machine.
+ <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user).
+ <li>touch /usr/local/freeside/fs_sessiond_socket; chown freeside /usr/local/freeside/fs_sessiond_socket; chmod 600 /usr/local/freeside/fs_sessiond_socket
+ <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s).
+ <li>Run <pre>fs_session_server <i>user</i> <i>machine</i></pre> on the Freeside machine.
+ <ul>
+ <li><i>user</i> is a user from the mapsecrets file.
+ <li><i>machine</i> is the name of the external machine.
+ </ul>
+</ul>
+<h2>Usage</h2>
+<ul>
+ <li>Web
+ <ul>
+ <li>Copy FS-SessionClient/cgi/login.cgi and logout.cgi to your web
+ server's document space.
+ <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run login.cgi and logout.cgi as the freeside user.
+ </ul>
+ <li>Command-line
+ <br><pre>freeside-login username ( portnum | ip | nasnum nasport )
+freeside-logout username ( portnum | ip | nasnum nasport )</pre>
+ <ul>
+ <li><i>username</i> is a customer username from the svc_acct table
+ <li><i>portnum</i>, <i>ip</i> or <i>nasport</i> and <i>nasnum</i> uniquely identify a port in the <a href="schema.html#port">port</a> database table.
+ </ul>
+ <li>RADIUS - One of:
+ <ul>
+ <li>Run the <b>freeside-sqlradius-radacctd</b> daemon to import radacct
+ records from all configured sqlradius exports:
+ <tt>freeside-sqlradius-radacctd username</tt>
+ <li>Configure your RADIUS server's login and logout callbacks to use the command-line <tt>freeside-login</tt> and <tt>freeside-logout</tt> utilites.
+ <li> <i>(incomplete)</i>Use the <b>fs_radlog/fs_radlogd</b> tool to
+ import records from a text radacct file.
+ </ul>
+</ul>
+<h2>Callbacks</h2>
+<ul>
+ <li>Sesstion start - The command(s) specified in the <a href="config.html#session-start">session-start</a> configuration file are executed on the Freeside machine. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.
+ <li>Session end - The command(s) specified in the <a href="config.html#session-stop">session-stop</a> configuration file are executed on the Freeside machine. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.
+</ul>
+<h2>Dropping expired users</h2>
+Run <pre>bin/freeside-session-kill username</pre> periodically from cron.
+</body>
+</html>
diff --git a/httemplate/docs/signup.html b/httemplate/docs/signup.html
new file mode 100644
index 0000000..97d7aa7
--- /dev/null
+++ b/httemplate/docs/signup.html
@@ -0,0 +1,54 @@
+<head>
+ <title>Signup server</title>
+</head>
+<body>
+ <h1>Signup server</h1>
+For security reasons, the signup server should run on an external public
+webserver. On this machine, install:
+<ul>
+ <li>A web server, such as <a href="http://www.apache-ssl.org">Apache-SSL</a> or <a href="http://www.apache.org">Apache</a>
+ <li><a href="ftp://ftp.cs.hut.fi/pub/ssh/">SSH</a>
+ <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at least 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series. Don't enable experimental features like threads or the PerlIO abstraction layer.)
+ <li><a href="http://search.cpan.org/search?dist=Text-Template">Text::Template</a>
+ <li><a href="http://search.cpan.org/search?dist=Storable">Storable</a>
+ <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a>
+ <li><a href="http://search.cpan.org/search?dist=HTTP-BrowserDetect">HTTP::BrowserDetect</a>
+
+ <li><a href="man/FS/SignupClient.html">FS::SignupClient</a> (copy the fs_signup/FS-SignupClient directory to the external machine, then: perl Makefile.PL; make; make install)
+</ul>
+Then:
+<ul>
+ <li>Add the user `freeside' to the the external machine.
+ <li>Copy or symlink fs_signup/FS-SignupClient/cgi/signup.cgi into the web server's document space.
+ <li>When linking to signup.cgi, you can include a referring custnum in the URL as follows: <code>http://public.web.server/path/signup.cgi?ref=1542</code>
+ <li>Enable CGI execution for files with the `.cgi' extension. (with <a href="http://www.apache.org/docs/mod/mod_mime.html#addhandler">Apache</a>)
+ <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user).
+ <li>touch /usr/local/freeside/fs_signupd_socket; chown freeside /usr/local/freeside/fs_signupd_socket; chmod 600 /usr/local/freeside/fs_signupd_socket
+ <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run signup.cgi as the freeside user.
+ <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s).
+ <li>Run <pre>fs_signup_server <i>user</i> <i>machine</i> <i>agentnum</i> <i>refnum</i></pre> on the Freeside machine.
+ <ul>
+ <li><i>user</i> is a user from the mapsecrets file.
+ <li><i>machine</i> is the name of the external machine.
+ <li><i>agentnum</i> and <i>refnum</i> are the <a href="schema.html#agent">agent</a> and <a href="schema.html#part_referral">referral</a>, respectively, to use for customers who sign up via this signup server.
+ </ul>
+</ul>
+Optional:
+<ul>
+ <li>If you create a <b>/usr/local/freeside/ieak.template</b> file on the external machine, it will be sent to IE users with MIME type <i>application/x-Internet-signup</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available.
+ (an example file is included as <b>fs_signup/ieak.template</b>) See the section on <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/INS.HTM">internet settings files</a> in the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information.
+ <li>If you create a <b>/usr/local/freeside/success.html</b> file on the external machine, it will be used as the success HTML page. Although template substiutions are available, a regular HTML file will work fine here, unlike signup.html. An example file is included as <b>fs_signup/FS-SignupClient/cgi/success.html</b>
+ <li>Variable substitutions available in <b>ieak.template</b>, <b>cck.template</b> and <b>success.html</b>:
+ <ul>
+ <li>$ac - area code of selected POP
+ <li>$exch - exchange of selected POP
+ <li>$loc - local part of selected POP
+ <li>$username
+ <li>$password
+ <li>$email_name - first and last name
+ <li>$pkg - package name
+ </ul>
+ <li>If you create a <b>/usr/local/freeside/signup.html</b> file on the external machine, it will be used as a template for the form HTML. This requires the template to be constructed appropriately; probably best to start with the example file included as <b>fs_signup/FS-SignupClient/cgi/signup.html</b>.
+ <li>If there are any entries in the <i>prepay_credit</i> table, a user can enter a string matching the <b>identifier</i> column to receive the credit specified in the <b>amount</b> column, and/or the time specified in the <b>seconds</b> column (for use with the <a href="session.html">session monitor</a>), after which that <b>identifier</b> is no longer valid. This can be used to implement pre-paid "calling card" type signups. The <i>bin/generate-prepay</i> script can be used to populate the <i>prepay_credit</i> table.
+</ul>
+</body>
diff --git a/httemplate/docs/ssh.html b/httemplate/docs/ssh.html
new file mode 100755
index 0000000..d2c501e
--- /dev/null
+++ b/httemplate/docs/ssh.html
@@ -0,0 +1,16 @@
+<head>
+ <title>Unattended SSH</title>
+</head>
+<body>
+ <h1>Unattended SSH</h1>
+ <br><a name=ssh>Unattended remote login</a> - Freeside can login to remote machines unattended using SSH. This can pose a security risk if not configured correctly, and will allow an intruder who breaks into your freeside machine full access to your remote machines. <b>Do not use this feature unless you understand what you are doing!</b>
+ <ul>
+ <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase.
+ <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
+ <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
+ <li>You may need to set <code>PermitRootLogin without-password</code> (meaning with keys only) in your <code>sshd_config</code> file on the remote machine(s).
+ <li>You may want to set <code>ForwardX11 = no</code> in <code>~root/.ssh/config</code> to prevent spurious errors if your distribution turns on X11 forwarding by default.
+ </ul>
+
+</body>
+
diff --git a/httemplate/docs/trouble.html b/httemplate/docs/trouble.html
new file mode 100755
index 0000000..fce7439
--- /dev/null
+++ b/httemplate/docs/trouble.html
@@ -0,0 +1,26 @@
+<head>
+ <title>Troubleshooting</title>
+</head>
+<body>
+ <h1>Troubleshooting</h1>
+ <ul>
+ <li>When troubleshooting the web interface, helpful information is often in your web server's error log.
+ <li>If bin/svc_acct.import fails with an "Out of memory!" error using MySQL, upgrede MySQL and recompile the Perl DBD. There was a memory leak in some older versions of MySQL.
+ <li>If you get tons of errors in your web server's error log like this:
+<pre>
+Ambiguous use of value => resolved to "value" =>
+at /usr/lib/perl5/site_perl/File/CounterFile.pm line 132.
+</pre>
+ This clutters up your log files but is otherwise harmless. Upgrade to the latest File::CounterFile.
+ <li>If you get errors like this:
+<pre>
+UID.pm: Can't open /var/spool/freeside/conf/secrets: Permission denied
+at <i>/your/path</i>/site_perl/FS/UID.pm line 26.
+BEGIN failed--compilation aborted at
+<i>/your/path</i>/edit/process/part_svc.cgi line 15.
+</pre>
+ Then the scripts are not running as the freeside freeside user. See
+the <a href="install.html">New Installation</a> section of the documentation.
+ <li>If you receive `can not connect to server' errors using MySQL on a system that doesn't support native threading, you may need to specify the full hostname in your DBI datasource. See the <a href="http://www.mysql.com/Manual_chapter/manual_Problems.html#Can_not_connect_to_server">MySQL documentation</a>, DBI manpage and the DBD::mysql manpage for details.
+ </ul>
+</body>
diff --git a/httemplate/docs/upgrade-1.4.2.html b/httemplate/docs/upgrade-1.4.2.html
new file mode 100644
index 0000000..a246611
--- /dev/null
+++ b/httemplate/docs/upgrade-1.4.2.html
@@ -0,0 +1,27 @@
+<head>
+ <title>Upgrading to 1.4.2</title>
+</head>
+<body>
+<h1>Upgrading to 1.4.2 from 1.4.1</h1>
+<ul>
+ <li>If migrating from less than 1.4.1, see these <a href="upgrade9.html">instructions</a> first.
+ <li>Back up your data and current Freeside installation.
+ <li>Install <a href="http://search.cpan.org/search?dist=Locale-SubCountry">Locale::SubCountry</a>
+ <li>Install <a href="http://search.cpan.org/search?dist=IPC-ShareLite">IPC::ShareLite</a>
+ <li>Install <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML::Widgets::SelectLayers</a> 0.04.
+ <li>Install <a href="http://search.cpan.org/search?dist=DBIx-DBSchema">DBIx::DBSchema</a> 0.23.
+ <li>Install <a href="http://search.cpan.org/search?dist=DBD-Pg">DBD::Pg</a> 1.32.
+ <li>Install <a href="http://search.cpan.org/search?dist=Cache-Cache">Cache::Cache</a>.
+ <li>Install <a href="http://search.cpan.org/search?dist=Net-SSH">Net::SSH</a> 0.08.
+ <li>Install <a href="http://search.cpan.org/search?dist=Crypt-PasswdMD5">Crypt::PasswdMD5</a>
+ <li>Install <a href="http://search.cpan.org/search?dist=Net-Whois-Raw">Net::Whois::Raw</a>
+ <li>CGI.pm minimum version 2.47 is required. You will probably need to install a current CGI.pm from CPAN if you are using Perl 5.005 or earlier.
+ <li>File::Temp minimum version 0.14 is required. You will probably need to install a currrent File::Temp from CPAN if you are using Perl 5.6 or earlier.
+ <li>If using Apache::ASP, add <code>PerlSetVar RequestBinaryRead Off</code> to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55.
+ <li>Run <code>make aspdocs</code> or <code>make masondocs</code>.
+ <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space.
+ <li>Run <code>make install-perl-modules</code>.
+ <li>The signup server and password server are deprecated in 1.4.2. Their functionality has been incorperated into the self-service server. Edit or reinstall your init script, and set the "signup_server-default_agentnum" and "signup_server-default_refnum" configuration options. The FS::SignupClient interface is still available as a compatibility wrapper, so you should be able to continue to use your current signup.cgi.
+ <li>Optional: To use typeset invoices, install tetex and ghostscript, and copy conf/invoice_latex, conf/invoice_latexnotes, and conf/invoice_latexfooter to /usr/local/etc/freeside/conf.<datasrc>/
+ <li>Restart Apache and freeside-queued.
+</body>
diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html
new file mode 100644
index 0000000..dc60865
--- /dev/null
+++ b/httemplate/docs/upgrade10.html
@@ -0,0 +1,255 @@
+<pre>
+this is incomplete
+
+install DBD::Pg 1.32 (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with <a href="http://420.am/~ivan/DBD-Pg-1.22-fixvercmp.patch">this patch</a> and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm)
+install DBIx::DBSchema 0.23
+install Net::SSH 0.08
+- If using Apache::ASP, add PerlSetVar RequestBinaryRead Off and PerlSetVar IncludesDir /your/freeside/document/root/ to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55.
+- In httpd.conf, change &lt;Files ~ \.cgi&gt; to &lt;Files ~ (\.cgi|\.html)&gt;
+- In httpd.conf, change <b>AddHandler perl-script .cgi</b> or <b>SetHandler perl-script</b> to <b>AddHandler perl-script .cgi .html</b>
+
+install NetAddr::IP, Chart::Base, IPC::ShareLite and Locale::SubCountry
+
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' );
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' );
+
+CREATE TABLE cust_bill_pkg_detail (
+ detailnum serial,
+ pkgnum int NOT NULL,
+ invnum int NOT NULL,
+ detail varchar(80),
+ PRIMARY KEY (detailnum)
+);
+CREATE INDEX cust_bill_pkg_detail1 ON cust_bill_pkg_detail ( pkgnum, invnum );
+
+CREATE TABLE part_virtual_field (
+ vfieldpart int NOT NULL,
+ dbtable varchar(32) NOT NULL,
+ name varchar(32) NOT NULL,
+ check_block text,
+ list_source text,
+ length integer,
+ label varchar(80),
+ PRIMARY KEY (vfieldpart)
+);
+
+CREATE TABLE virtual_field (
+ recnum integer NOT NULL,
+ vfieldpart integer NOT NULL,
+ value varchar(128) NOT NULL,
+ PRIMARY KEY (vfieldpart, recnum)
+);
+
+CREATE TABLE router (
+ routernum serial,
+ routername varchar(80),
+ svcnum int,
+ PRIMARY KEY (routernum)
+);
+
+CREATE TABLE part_svc_router (
+ svcpart int NOT NULL,
+ routernum int NOT NULL
+);
+
+CREATE TABLE addr_block (
+ blocknum serial,
+ routernum int NOT NULL,
+ ip_gateway varchar(15) NOT NULL,
+ ip_netmask int NOT NULL,
+ PRIMARY KEY (blocknum)
+);
+CREATE UNIQUE INDEX addr_block1 ON addr_block ( blocknum, routernum );
+
+CREATE TABLE svc_broadband (
+ svcnum int NOT NULL,
+ blocknum int NOT NULL,
+ speed_up int NOT NULL,
+ speed_down int NOT NULL,
+ ip_addr varchar(15),
+ PRIMARY KEY (svcnum)
+);
+
+CREATE TABLE acct_snarf (
+ snarfnum serial,
+ svcnum int NOT NULL,
+ machine varchar(255) NULL,
+ protocol varchar(80) NULL,
+ username varchar(80) NULL,
+ _password varchar(80) NULL,
+ PRIMARY KEY (snarfnum)
+);
+CREATE INDEX acct_snarf1 ON acct_snarf ( svcnum );
+
+CREATE TABLE svc_external (
+ svcnum int NOT NULL,
+ id int,
+ title varchar(80),
+ PRIMARY KEY (svcnum)
+);
+
+CREATE TABLE part_pkg_temp (
+ pkgpart serial NOT NULL,
+ pkg varchar(80) NOT NULL,
+ "comment" varchar(80) NOT NULL,
+ setup text NULL,
+ freq varchar(80) NOT NULL,
+ recur text NULL,
+ setuptax char(1) NULL,
+ recurtax char(1) NULL,
+ plan varchar(80) NULL,
+ plandata text NULL,
+ disabled char(1) NULL,
+ taxclass varchar(80) NULL,
+ PRIMARY KEY (pkgpart)
+);
+INSERT INTO part_pkg_temp SELECT * from part_pkg;
+DROP TABLE part_pkg;
+ALTER TABLE part_pkg_temp RENAME TO part_pkg;
+CREATE INDEX part_pkg1 ON part_pkg(disabled);
+
+On modern Pg:
+ALTER TABLE part_pkg DROP CONSTRAINT part_pkg_temp_pkey;
+ALTER TABLE part_pkg ADD PRIMARY KEY (pkgpart);
+select setval('public.part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
+
+Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far):
+DROP INDEX part_pkg_temp_pkey;
+CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg (pkgpart);
+probably this one?: select setval('part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
+probably not this one?: select setval('part_pkg_pkgpart_seq', ( select max(pkgpart) from part_pkg) );
+
+CREATE TABLE h_part_pkg_temp (
+ historynum serial NOT NULL,
+ history_date int,
+ history_user varchar(80) NOT NULL,
+ history_action varchar(80) NOT NULL,
+ pkgpart int NOT NULL,
+ pkg varchar(80) NOT NULL,
+ "comment" varchar(80) NOT NULL,
+ setup text NULL,
+ freq varchar(80) NOT NULL,
+ recur text NULL,
+ setuptax char(1) NULL,
+ recurtax char(1) NULL,
+ plan varchar(80) NULL,
+ plandata text NULL,
+ disabled char(1) NULL,
+ taxclass varchar(80) NULL,
+ PRIMARY KEY (historynum)
+);
+INSERT INTO h_part_pkg_temp SELECT * from h_part_pkg;
+DROP TABLE h_part_pkg;
+ALTER TABLE h_part_pkg_temp RENAME TO h_part_pkg;
+CREATE INDEX h_part_pkg1 ON h_part_pkg(disabled);
+
+On modern Pg:
+ALTER TABLE h_part_pkg DROP CONSTRAINT h_part_pkg_temp_pkey;
+ALTER TABLE h_part_pkg ADD PRIMARY KEY (historynum);
+select setval('public.h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) );
+
+Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far):
+DROP INDEX h_part_pkg_temp_pkey;
+CREATE UNIQUE INDEX h_part_pkg_pkey ON h_part_pkg (historynum);
+probably this one?: select setval('h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) );
+probably not this one?: select setval('h_part_pkg_historynum_seq', ( select max(historynum) from h_part_pkg) );
+
+CREATE TABLE cust_pay_refund (
+ payrefundnum serial NOT NULL,
+ paynum int NOT NULL,
+ refundnum int NOT NULL,
+ _date int NOT NULL,
+ amount decimal(10,2) NOT NULL,
+ PRIMARY KEY (payrefundnum)
+);
+CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum);
+CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum);
+
+CREATE TABLE cust_pay_void (
+ paynum int NOT NULL,
+ custnum int NOT NULL,
+ paid decimal(10,2) NOT NULL,
+ _date int,
+ payby char(4) NOT NULL,
+ payinfo varchar(80),
+ paybatch varchar(80),
+ closed char(1),
+ void_date int,
+ reason varchar(80),
+ otaker varchar(32) NOT NULL,
+ PRIMARY KEY (paynum)
+);
+CREATE INDEX cust_pay_void1 ON cust_pay_void(custnum);
+
+DROP INDEX cust_bill_pkg1;
+
+ALTER TABLE cust_bill_pkg ADD itemdesc varchar(80) NULL;
+ALTER TABLE h_cust_bill_pkg ADD itemdesc varchar(80) NULL;
+ALTER TABLE cust_main_county ADD taxname varchar(80) NULL;
+ALTER TABLE h_cust_main_county ADD taxname varchar(80) NULL;
+ALTER TABLE cust_main_county ADD setuptax char(1) NULL;
+ALTER TABLE h_cust_main_county ADD setuptax char(1) NULL;
+ALTER TABLE cust_main_county ADD recurtax char(1) NULL;
+ALTER TABLE h_cust_main_county ADD recurtax char(1) NULL;
+ALTER TABLE cust_pkg ADD last_bill int NULL;
+ALTER TABLE h_cust_pkg ADD last_bill int NULL;
+ALTER TABLE agent ADD disabled char(1) NULL;
+ALTER TABLE h_agent ADD disabled char(1) NULL;
+ALTER TABLE agent ADD username varchar(80) NULL;
+ALTER TABLE h_agent ADD username varchar(80) NULL;
+ALTER TABLE agent ADD _password varchar(80) NULL;
+ALTER TABLE h_agent ADD _password varchar(80) NULL;
+ALTER TABLE cust_main ADD paycvv varchar(4) NULL;
+ALTER TABLE h_cust_main ADD paycvv varchar(4) NULL;
+ALTER TABLE part_referral ADD disabled char(1) NULL;
+ALTER TABLE h_part_referral ADD disabled char(1) NULL;
+CREATE INDEX part_referral1 ON part_referral ( disabled );
+ALTER TABLE pkg_svc ADD primary_svc char(1) NULL;
+ALTER TABLE h_pkg_svc ADD primary_svc char(1) NULL;
+ALTER TABLE svc_forward ADD src varchar(255) NULL;
+ALTER TABLE h_svc_forward ADD src varchar(255) NULL;
+
+On recent Pg versions:
+
+ALTER TABLE svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
+ALTER TABLE h_svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
+ALTER TABLE svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
+ALTER TABLE h_svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
+
+Or on Pg versions that don't support DROP NOT NULL (tested on 7.1 and 7.2 so far):
+UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'srcsvc' OR attname = 'dstsvc' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'svc_forward' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_svc_forward' ) );
+
+If you created your database with a version before 1.4.2, dump database, edit:
+- cust_main and h_cust_main: increase otaker from 8 to 32
+- cust_main and h_cust_main: change ss from char(11) to varchar(11) ( "character(11)" to "character varying(11)" )
+- cust_credit and h_cust_credit: increase otaker from 8 to 32
+- cust_pkg and h_cust_pkg: increase otaker from 8 to 32
+- cust_refund and h_cust_refund: increase otaker from 8 to 32
+- domain_record and h_domain_record: increase reczone from 80 to 255
+- domain_record and h_domain_record: change rectype from char to varchar ( "character(5)" to "character varying(5)" )
+- domain_record and h_domain_record: increase recdata from 80 to 255
+then reload
+
+optionally:
+
+ CREATE INDEX cust_main6 ON cust_main ( daytime );
+ CREATE INDEX cust_main7 ON cust_main ( night );
+ CREATE INDEX cust_main8 ON cust_main ( fax );
+ CREATE INDEX cust_main9 ON cust_main ( ship_daytime );
+ CREATE INDEX cust_main10 ON cust_main ( ship_night );
+ CREATE INDEX cust_main11 ON cust_main ( ship_fax );
+ CREATE INDEX agent2 ON agent ( disabled );
+ CREATE INDEX part_bill_event2 ON part_bill_event ( disabled );
+ CREATE INDEX cust_pay4 ON cust_pay (_date);
+
+ serial columns
+
+mandatory again:
+
+dbdef-create username
+create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband acct_snarf svc_external cust_pay_refund cust_pay_void
+dbdef-create username
+
+apache - fix <Files> sections to include .html also
+
+</pre>
diff --git a/httemplate/docs/upgrade7.html b/httemplate/docs/upgrade7.html
new file mode 100644
index 0000000..d9dcfe2
--- /dev/null
+++ b/httemplate/docs/upgrade7.html
@@ -0,0 +1,24 @@
+<head>
+ <title>Upgrading to 1.3.1</title>
+</head>
+<body>
+<h1>Upgrading to 1.3.1 from 1.3.0</h1>
+<ul>
+ <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first.
+ <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first.
+ <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first.
+ <li>If migrating from less than 1.2.2, see these <a href="upgrade4.html">instructions</a> first.
+ <li>If migrating from less than 1.2.3, see these <a href="upgrade5.html">instructions</a> first.
+ <li>If migrating from less than 1.3.0, see these <a href="upgrade6.html">instructions</a> first.
+ <li>Back up your data and current Freeside installation.
+ <li>Copy or symlink htdocs to the new copy.
+ <li>Change to the FS directory in the new tarball, and build and install the
+ Perl modules:
+ <pre>
+$ cd FS/
+$ perl Makefile.PL
+$ make
+$ su
+# make install UNINST=1</pre>
+ <li>Run bin/dbdef-create.
+</body>
diff --git a/httemplate/docs/upgrade8.html b/httemplate/docs/upgrade8.html
new file mode 100644
index 0000000..cf60a85
--- /dev/null
+++ b/httemplate/docs/upgrade8.html
@@ -0,0 +1,392 @@
+<head>
+ <title>Upgrading to 1.4.0</title>
+</head>
+<body>
+<h1>Upgrading to 1.4.0 from 1.3.1</h1>
+<ul>
+ <li>If migrating from less than 1.3.1, see these <a href="upgrade7.html">instructions</a> first.
+ <li><font size="+2" color="#ff0000">Backup your database and current Freeside installation.</font> (with&nbsp;<a href="http://www.ca.postgresql.org/devel-corner/docs/postgres/backup.html">PostgreSQL</a>) (with&nbsp;<a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Backup">MySQL</a>)
+ <li><a href="http://perl.apache.org/">mod_perl</a> is now required.
+ <li>Install <a href="http://search.cpan.org/search?dist=Time-Duration">Time-Duration</a>, <a href="http://search.cpan.org/search?dist=Tie-IxHash">Tie-IxHash</a> and <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML-Widgets-SelectLayers</a> (minimum version 0.02).
+ <li>Install <a href="http://www.apache-asp.org/">Apache::ASP</a> or <a href="http://www.masonhq.com/">HTML::Mason</a> (use version 1.0x - Freeside is not yet compatible with version 1.1x).
+ <li>Install <a href="http://rsync.samba.org/">rsync</a>
+</ul>
+<table>
+ <tr>
+ <th>Apache::ASP</th><th>Mason</th>
+ </tr>
+ <tr>
+ <td><ul>
+ <li>Run <tt>make aspdocs</tt>
+ <li>Copy <tt>aspdocs/</tt> to your web server's document space.
+ <li>Create a <a href="http://www.apache-asp.org/config.html#Global">Global</a> directory, such as <tt>/usr/local/etc/freeside/asp-global/</tt>
+ <li>Copy <tt>htetc/global.asa</tt> to the Global directory.
+ <li>Configure Apache for the Global directory and to execute .cgi files using Apache::ASP. For example:
+<font size="-1"><pre>
+&lt;Directory /usr/local/apache/htdocs/freeside-asp&gt;
+&lt;Files ~ (\.cgi)&gt;
+AddHandler perl-script .cgi
+PerlHandler Apache::ASP
+&lt;/Files&gt;
+&lt;Perl&gt;
+$MLDBM::RemoveTaint = 1;
+&lt;/Perl&gt;
+PerlSetVar Global /usr/local/etc/freeside/asp-global/
+&lt;/Directory&gt;
+</pre></font>
+ </ul></td>
+ <td><ul>
+ <li>(use version 1.0x - Freeside is not yet compatible with version 1.1x)
+ <li>Run <tt>make masondocs</tt>
+ <li>Copy <tt>masondocs/</tt> to your web server's document space.
+ <li>Copy <tt>htetc/handler.pl</tt> to your web server's configuration directory.
+ <li>Edit <tt>handler.pl</tt> and set an appropriate <tt>data_dir</tt>, such as <tt>/usr/local/etc/freeside/mason-data</tt>
+ <li>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason. For example:
+<font size="-1"><pre>
+&lt;Directory /usr/local/apache/htdocs/freeside-mason&gt;
+&lt;Files ~ (\.cgi)&gt;
+AddHandler perl-script .cgi
+PerlHandler HTML::Mason
+&lt;/Files&gt;
+&lt;Perl&gt;
+require "/usr/local/apache/conf/handler.pl";
+&lt;/Perl&gt;
+&lt;/Directory&gt;
+</pre></font>
+ </ul></td>
+ </tr>
+</table>
+<ul>
+ <li>Build and install the Perl modules:
+ <pre>
+$ su
+# make install-perl-modules</pre>
+ <li>Apply the following changes to your database:
+<pre>
+CREATE TABLE svc_forward (
+ svcnum int NOT NULL,
+ srcsvc int NOT NULL,
+ dstsvc int NOT NULL,
+ dst varchar(80),
+ PRIMARY KEY (svcnum)
+);
+ALTER TABLE part_svc ADD svc_forward__srcsvc varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_forward__srcsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_forward__dstsvc_flag char(1) NULL;
+ALTER TABLE part_svc ADD svc_forward__dst varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_forward__dst_flag char(1) NULL;
+
+CREATE TABLE cust_credit_bill (
+ creditbillnum int primary key,
+ crednum int not null,
+ invnum int not null,
+ _date int not null,
+ amount decimal(10,2) not null
+);
+
+CREATE TABLE cust_bill_pay (
+ billpaynum int primary key,
+ invnum int not null,
+ paynum int not null,
+ _date int not null,
+ amount decimal(10,2) not null
+);
+
+CREATE TABLE cust_credit_refund (
+ creditrefundnum int primary key,
+ crednum int not null,
+ refundnum int not null,
+ _date int not null,
+ amount decimal(10,2) not null
+);
+
+CREATE TABLE part_svc_column (
+ columnnum int primary key,
+ svcpart int not null,
+ columnname varchar(64) not null,
+ columnvalue varchar(80) null,
+ columnflag char(1) null
+);
+
+CREATE TABLE queue (
+ jobnum int primary key,
+ job text not null,
+ _date int not null,
+ status varchar(80) not null,
+ statustext text null,
+ svcnum int null
+);
+CREATE INDEX queue1 ON queue ( svcnum );
+CREATE INDEX queue2 ON queue ( status );
+
+CREATE TABLE queue_arg (
+ argnum int primary key,
+ jobnum int not null,
+ arg text null
+);
+CREATE INDEX queue_arg1 ON queue_arg ( jobnum );
+
+CREATE TABLE queue_depend (
+ dependnum int primary key,
+ jobnum int not null,
+ depend_jobnum int not null
+);
+CREATE INDEX queue_depend1 ON queue_depend ( jobnum );
+CREATE INDEX queue_depend2 ON queue_depend ( depend_jobnum );
+
+CREATE TABLE part_pop_local (
+ localnum int primary key,
+ popnum int not null,
+ city varchar(80) null,
+ state char(2) null,
+ npa char(3) not null,
+ nxx char(3) not null
+);
+CREATE UNIQUE INDEX part_pop_local1 ON part_pop_local ( npa, nxx );
+
+CREATE TABLE cust_bill_event (
+ eventnum int primary key,
+ invnum int not null,
+ eventpart int not null,
+ _date int not null
+);
+CREATE UNIQUE INDEX cust_bill_event1 ON cust_bill_event ( eventpart, invnum );
+CREATE INDEX cust_bill_event2 ON cust_bill_event ( invnum );
+
+CREATE TABLE part_bill_event (
+ eventpart int primary key,
+ payby char(4) not null,
+ event varchar(80) not null,
+ eventcode text null,
+ seconds int null,
+ weight int not null,
+ plan varchar(80) null,
+ plandata text null,
+ disabled char(1) null
+);
+CREATE INDEX part_bill_event1 ON part_bill_event ( payby );
+
+CREATE TABLE export_svc (
+ exportsvcnum int primary key,
+ exportnum int not null,
+ svcpart int not null
+);
+CREATE UNIQUE INDEX export_svc1 ON export_svc ( exportnum, svcpart );
+CREATE INDEX export_svc2 ON export_svc ( exportnum );
+CREATE INDEX export_svc3 ON export_svc ( svcpart );
+
+CREATE TABLE part_export (
+ exportnum int primary key,
+ machine varchar(80) not null,
+ exporttype varchar(80) not null,
+ nodomain char(1) NULL
+);
+CREATE INDEX part_export1 ON part_export ( machine );
+CREATE INDEX part_export2 ON part_export ( exporttype );
+
+CREATE TABLE part_export_option (
+ optionnum int primary key,
+ exportnum int not null,
+ optionname varchar(80) not null,
+ optionvalue text NULL
+);
+CREATE INDEX part_export_option1 ON part_export_option ( exportnum );
+CREATE INDEX part_export_option2 ON part_export_option ( optionname );
+
+CREATE TABLE radius_usergroup (
+ usergroupnum int primary key,
+ svcnum int not null,
+ groupname varchar(80) not null
+);
+CREATE INDEX radius_usergroup1 ON radius_usergroup ( svcnum );
+CREATE INDEX radius_usergroup2 ON radius_usergroup ( groupname );
+
+CREATE TABLE msgcat (
+ msgnum int primary key,
+ msgcode varchar(80) not null,
+ locale varchar(16) not null,
+ msg text not null
+);
+CREATE INDEX msgcat1 ON msgcat ( msgcode, locale );
+
+CREATE TABLE cust_tax_exempt (
+ exemptnum int primary key,
+ custnum int not null,
+ taxnum int not null,
+ year int not null,
+ month int not null,
+ amount decimal(10,2)
+);
+CREATE UNIQUE INDEX cust_tax_exempt1 ON cust_tax_exempt ( taxnum, year, month );
+
+ALTER TABLE svc_acct ADD domsvc integer NULL;
+ALTER TABLE part_svc ADD svc_acct__domsvc varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_acct__domsvc_flag char(1) NULL;
+ALTER TABLE svc_domain ADD catchall integer NULL;
+ALTER TABLE cust_main ADD referral_custnum integer NULL;
+ALTER TABLE cust_main ADD comments text NULL;
+ALTER TABLE cust_pay ADD custnum integer;
+ALTER TABLE cust_pay_batch ADD paybatchnum integer;
+ALTER TABLE cust_refund ADD custnum integer;
+ALTER TABLE cust_pkg ADD manual_flag char(1) NULL;
+ALTER TABLE part_pkg ADD plan varchar(80) NULL;
+ALTER TABLE part_pkg ADD plandata text NULL;
+ALTER TABLE part_pkg ADD setuptax char(1) NULL;
+ALTER TABLE part_pkg ADD recurtax char(1) NULL;
+ALTER TABLE part_pkg ADD disabled char(1) NULL;
+ALTER TABLE part_svc ADD disabled char(1) NULL;
+ALTER TABLE cust_bill ADD closed char(1) NULL;
+ALTER TABLE cust_pay ADD closed char(1) NULL;
+ALTER TABLE cust_credit ADD closed char(1) NULL;
+ALTER TABLE cust_refund ADD closed char(1) NULL;
+ALTER TABLE cust_bill_event ADD status varchar(80);
+ALTER TABLE cust_bill_event ADD statustext text NULL;
+ALTER TABLE svc_acct ADD sec_phrase varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_acct__sec_phrase varchar(80) NULL;
+ALTER TABLE part_svc ADD svc_acct__sec_phrase_flag char(1) NULL;
+ALTER TABLE part_pkg ADD taxclass varchar(80) NULL;
+ALTER TABLE cust_main_county ADD taxclass varchar(80) NULL;
+ALTER TABLE cust_main_county ADD exempt_amount decimal(10,2);
+CREATE INDEX cust_main3 ON cust_main ( referral_custnum );
+CREATE INDEX cust_credit_bill1 ON cust_credit_bill ( crednum );
+CREATE INDEX cust_credit_bill2 ON cust_credit_bill ( invnum );
+CREATE INDEX cust_bill_pay1 ON cust_bill_pay ( invnum );
+CREATE INDEX cust_bill_pay2 ON cust_bill_pay ( paynum );
+CREATE INDEX cust_credit_refund1 ON cust_credit_refund ( crednum );
+CREATE INDEX cust_credit_refund2 ON cust_credit_refund ( refundnum );
+CREATE UNIQUE INDEX cust_pay_batch_pkey ON cust_pay_batch ( paybatchnum );
+CREATE UNIQUE INDEX part_svc_column1 ON part_svc_column ( svcpart, columnname );
+CREATE INDEX cust_pay2 ON cust_pay ( paynum );
+CREATE INDEX cust_pay3 ON cust_pay ( custnum );
+CREATE INDEX cust_pay4 ON cust_pay ( paybatch );
+</pre>
+
+ <li>If you are using PostgreSQL, apply the following changes to your database:
+<pre>
+CREATE UNIQUE INDEX agent_pkey ON agent ( agentnum );
+CREATE UNIQUE INDEX agent_type_pkey ON agent_type ( typenum );
+CREATE UNIQUE INDEX cust_bill_pkey ON cust_bill ( invnum );
+CREATE UNIQUE INDEX cust_credit_pkey ON cust_credit ( crednum );
+CREATE UNIQUE INDEX cust_main_pkey ON cust_main ( custnum );
+CREATE UNIQUE INDEX cust_main_county_pkey ON cust_main_county ( taxnum );
+CREATE UNIQUE INDEX cust_main_invoice_pkey ON cust_main_invoice ( destnum );
+CREATE UNIQUE INDEX cust_pay_pkey ON cust_pay ( paynum );
+CREATE UNIQUE INDEX cust_pkg_pkey ON cust_pkg ( pkgnum );
+CREATE UNIQUE INDEX cust_refund_pkey ON cust_refund ( refundnum );
+CREATE UNIQUE INDEX cust_svc_pkey ON cust_svc ( svcnum );
+CREATE UNIQUE INDEX domain_record_pkey ON domain_record ( recnum );
+CREATE UNIQUE INDEX nas_pkey ON nas ( nasnum );
+CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg ( pkgpart );
+CREATE UNIQUE INDEX part_referral_pkey ON part_referral ( refnum );
+CREATE UNIQUE INDEX part_svc_pkey ON part_svc ( svcpart );
+CREATE UNIQUE INDEX port_pkey ON port ( portnum );
+CREATE UNIQUE INDEX prepay_credit_pkey ON prepay_credit ( prepaynum );
+CREATE UNIQUE INDEX session_pkey ON session ( sessionnum );
+CREATE UNIQUE INDEX svc_acct_pkey ON svc_acct ( svcnum );
+CREATE UNIQUE INDEX svc_acct_pop_pkey ON svc_acct_pop ( popnum );
+CREATE UNIQUE INDEX svc_acct_sm_pkey ON svc_acct_sm ( svcnum );
+CREATE UNIQUE INDEX svc_domain_pkey ON svc_domain ( svcnum );
+CREATE UNIQUE INDEX svc_www_pkey ON svc_www ( svcnum );
+</pre>
+ <li>If you wish to enable service/shipping addresses, apply the following
+ changes to your database:
+<pre>
+ALTER TABLE cust_main ADD COLUMN ship_last varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_first varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_company varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_address1 varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_address2 varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_city varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_county varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_state varchar(80) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_zip varchar(10) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_country char(2) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_daytime varchar(20) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_night varchar(20) NULL;
+ALTER TABLE cust_main ADD COLUMN ship_fax varchar(12) NULL;
+CREATE INDEX cust_main4 ON cust_main ( ship_last );
+CREATE INDEX cust_main5 ON cust_main ( ship_company );
+</pre>
+ <li>If you are using the signup server, reinstall it according to the <a href="signup.html">instructions</a>. The 1.3.x signup server is not compatible with 1.4.x.
+ <li>Run <tt>bin/dbdef-create <i>username</i></tt>
+ <li>If you have svc_acct_sm records or service definitions:
+ <ul>
+ <li>Create a service definition with table svc_forward
+ <li>Run <tt>bin/fs-migrate-svc_acct_sm <i>username</i></tt>
+ </ul>
+ <li>Or if you just have svc_acct records:
+ <ul>
+ <li>Order and provision a package for your default domain and note down the <b>Service #</b> or <i>svcnum</i>.
+ <li><tt>UPDATE svc_acct SET domsvc = </tt><i>svcnum</i>
+ <li>Update your service definitions to have default (or fixed) <b>domsvc</b>.
+ </ul>
+ <li>Run <tt>bin/fs-migrate-payref<i>username</i></tt>
+ <li>Run <tt>bin/fs-migrate-part_svc<i>username</i></tt>
+ <li><b>After running bin/fs-migrate-payref</b>, apply the following changes to your database:
+ <table border><tr><th>PostgreSQL</th><th>MySQL, others</th></tr>
+<tr><td>
+<font size=-1><pre>
+CREATE TABLE cust_pay_temp (
+ paynum int primary key,
+ custnum int not null,
+ paid decimal(10,2) not null,
+ _date int null,
+ payby char(4) not null,
+ payinfo varchar(16) null,
+ paybatch varchar(80) null,
+ closed char(1) null
+);
+INSERT INTO cust_pay_temp SELECT paynum, custnum, paid, _date, payby, payinfo, paybatch, closed FROM cust_pay;
+DROP TABLE cust_pay;
+ALTER TABLE cust_pay_temp RENAME TO cust_pay;
+CREATE UNIQUE INDEX cust_pay1 ON cust_pay (paynum);
+CREATE TABLE cust_refund_temp (
+ refundnum int primary key,
+ custnum int not null,
+ _date int null,
+ refund decimal(10,2) not null,
+ otaker varchar(8) not null,
+ reason varchar(80) not null,
+ payby char(4) not null,
+ payinfo varchar(16) null,
+ paybatch varchar(80) null,
+ closed char(1) null
+);
+INSERT INTO cust_refund_temp SELECT refundnum, custnum, _date, refund, otaker, reason, payby, payinfo, '', closed from cust_refund;
+DROP TABLE cust_refund;
+ALTER TABLE cust_refund_temp RENAME TO cust_refund;
+CREATE UNIQUE INDEX cust_refund1 ON cust_refund (refundnum);
+</pre></font>
+</td><td>
+<font size=-1><pre>
+ALTER TABLE cust_pay DROP COLUMN invnum;
+ALTER TABLE cust_refund DROP COLUMN crednum;
+</pre></font>
+</td></tr></table>
+ <li><b>IMPORTANT: After applying the second set of database changes</b>, run <tt>bin/dbdef-create <i>username</i></tt> again.
+ <li><b>IMPORTANT</b>: run <tt>bin/create-history-tables <i>username</i></tt>
+ <li><b>IMPORTANT: After running bin/create-history-tables</b>, run <tt>bin/dbdef-create <i>username</i></tt> again.
+ <li>As the freeside UNIX user, run <tt>bin/populate-msgcat <i>username</i></tt
+> to populate the message catalog
+<!-- <li>set the <a href="../config/config.cgi#username_policy">user_policy configuration value</a> as appropriate for your site. -->
+ <li>set the <a href="../config/config.cgi#locale">locale configuration value</a> to en_US.
+ <li>the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated. Set the <a href="../config/config.cgi#defaultrecords">defaultrecords configuration value</a> instead.
+ <li>Create the `/usr/local/etc/freeside/cache.<i>datasrc</i>' directory
+ (owned by the freeside user).
+ <li>freeside-queued was installed with the Perl modules. Start it now and ensure that is run upon system startup.
+ <li>Set appropriate <a href="../browse/part_bill_event.cgi">invoice events</a> for your site. At the very least, you'll want to set some invoice events "<i>After 0 days</i>": a <i>BILL</i> invoice event to print invoices, a <i>CARD</i> invoice event to batch or run cards real-time, and a <i>COMP</i> invoice event to "pay" complimentary customers. If you were using the <i>-i</i> option to <a href="man/bin/freeside-bill.html">freeside-bill</a> it should be removed.
+ <li>Use <a href="man/bin/freeside-daily.html">freeside-daily</a> instead of <a href="man/bin/freeside-bill.html">freeside-bill</a>.
+ <li>If you would like Freeside to notify your customers when their credit
+ cards and other billing arrangements are about to expire, arrange for
+ <b>freeside-expiration-alerter</b> to be run daily by cron or similar
+ facility. The message it sends can be configured from the
+ <u>Configuration</u> choice of the main menu as <u>alerter_template</u>.
+ <li>Export has been rewritten. If you were using the icradiusmachines,
+ icradius_mysqldest, icradius_mysqlsource, or icradius_secrets files, add
+ an appropriate "sqlradius" export to all relevant Service Definitions
+ instead. Use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or
+ point the "sqlradius" export directly at your external ICRADIUS or FreeRADIUS
+ database (or through an SSL-necrypting proxy...)
+</ul>
+</body>
diff --git a/httemplate/docs/upgrade9.html b/httemplate/docs/upgrade9.html
new file mode 100644
index 0000000..6a8fd96
--- /dev/null
+++ b/httemplate/docs/upgrade9.html
@@ -0,0 +1,28 @@
+<head>
+ <title>Upgrading to 1.4.1</title>
+</head>
+<body>
+<h1>Upgrading to 1.4.1 from 1.4.0</h1>
+<ul>
+ <li>If migrating from less than 1.4.0, see these <a href="upgrade8.html">instructions</a> first.
+ <li>Back up your data and current Freeside installation.
+ <li>Run <code>make aspdocs</code> or <code>make masondocs</code>.
+ <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space.
+ <li>Run <code>make install-perl-modules</code>.
+ <li>Install <a href="http://search.cpan.org/search?dist=Net-SSH">Net::SSH</a> minimum version 0.07
+ <li>Apply the following changes to your database:
+<pre>
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 18, 'daytime', 'en_US', 'Day Phone' );
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 19, 'night', 'en_US', 'Night Phone' );
+</pre>
+ <li>Optionally, apply the following changes to your database (performance improvements):
+<pre>
+CREATE INDEX part_pkg1 ON part_pkg ( disabled );
+CREATE INDEX part_svc1 ON part_svc ( disabled );
+CREATE INDEX cust_bill2 ON cust_bill ( _date );
+</pre>
+ <li>If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump.
+ <li>If you will be doing bind exports you should make additional changes to your database. Follow the directions above to dump the database and change the reczone and recdata fields in the domain_record and h_domain_record tables from varchar(80) to varchar(255).
+ <li>If you made changes to your db schema from a dump as listed above run dbdef-create.
+ <li>Restart Apache and freeside-queued.
+</body>
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
new file mode 100755
index 0000000..d9b7579
--- /dev/null
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -0,0 +1,131 @@
+<!-- mason kludge -->
+<%
+# <!-- $Id: REAL_cust_pkg.cgi,v 1.7 2003-11-19 12:21:09 ivan Exp $ -->
+
+my $error ='';
+my $pkgnum = '';
+if ( $cgi->param('error') ) {
+ $error = $cgi->param('error');
+ $pkgnum = $cgi->param('pkgnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "no pkgnum";
+ $pkgnum = $1;
+}
+
+#get package record
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+die "No package!" unless $cust_pkg;
+my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')});
+
+if ( $error ) {
+ #$cust_pkg->$_(str2time($cgi->param($_)) foreach qw(setup bill);
+ $cust_pkg->setup(str2time($cgi->param('setup')));
+ $cust_pkg->bill(str2time($cgi->param('bill')));
+}
+
+#my $custnum = $cust_pkg->getfield('custnum');
+print header('Package Edit'); #, menubar(
+# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum",
+# 'Main Menu' => popurl(2)
+#));
+
+%>
+
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
+<%
+
+#print info
+my($susp,$cancel,$expire)=(
+ $cust_pkg->getfield('susp'),
+ $cust_pkg->getfield('cancel'),
+ $cust_pkg->getfield('expire'),
+);
+my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment'));
+my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill'));
+my $otaker = $cust_pkg->getfield('otaker');
+
+print '<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">', qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!
+ if $error;
+
+#my $format = "%c %z (%Z)";
+my $format = "%m/%d/%Y %T %z (%Z)";
+
+print ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">',
+ $pkgnum, '</TD></TR>',
+ '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">',
+ $pkg, '</TD></TR>',
+ '<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">',
+ $comment, '</TD></TR>',
+ '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
+ $otaker, '</TD></TR>',
+ '<TR><TD ALIGN="right">Setup date</TD><TD>'.
+ '<INPUT TYPE="text" NAME="setup" SIZE=32 ID="setup_text" VALUE="',
+ ( $setup ? time2str($format, $setup) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="setup_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Last bill date</TD><TD>',
+ '<INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="',
+ ( $cust_pkg->last_bill
+ ? time2str($format, $cust_pkg->last_bill)
+ : "" ),
+ '">'.
+ ' <IMG SRC="../images/calendar.png" ID="last_bill_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>'
+ if $cust_pkg->dbdef_table->column('last_bill');
+
+print '<TR><TD ALIGN="right">Next bill date</TD><TD>',
+ '<INPUT TYPE="text" NAME="bill" SIZE=32 ID="bill_text" VALUE="',
+ ( $bill ? time2str($format, $bill) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="bill_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">',
+ time2str($format, $susp), '</TD></TR>'
+ if $susp;
+
+#print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">',
+# time2str("%D",$expire), '</TD></TR>'
+# if $expire;
+print '<TR><TD ALIGN="right">Expiration date'.
+ '</TD><TD>',
+ '<INPUT TYPE="text" NAME="expire" SIZE=32 ID="expire_text" VALUE="',
+ ( $expire ? time2str($format, $expire) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="expire_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '<BR><FONT SIZE=-1>(will <b>cancel</b> this package'.
+ ' when the date is reached)</FONT>'.
+ '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">',
+ time2str($format, $cancel), '</TD></TR>'
+ if $cancel;
+
+%>
+</TABLE>
+<SCRIPT TYPE="text/javascript">
+<%
+ my @cal = qw( setup bill expire );
+ push @cal, 'last_bill'
+ if $cust_pkg->dbdef_table->column('last_bill');
+ foreach my $cal (@cal) {
+%>
+ Calendar.setup({
+ inputField: "<%= $cal %>_text",
+ ifFormat: "%m/%d/%Y",
+ button: "<%= $cal %>_button",
+ align: "BR"
+ });
+<% } %>
+</SCRIPT>
+<BR><INPUT TYPE="submit" VALUE="Apply Changes">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
new file mode 100755
index 0000000..8a1cb2a
--- /dev/null
+++ b/httemplate/edit/agent.cgi
@@ -0,0 +1,79 @@
+<!-- mason kludge -->
+<%
+
+my $agent;
+if ( $cgi->param('error') ) {
+ $agent = new FS::agent ( {
+ map { $_, scalar($cgi->param($_)) } fields('agent')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $agent = qsearchs( 'agent', { 'agentnum' => $1 } );
+} else { #adding
+ $agent = new FS::agent {};
+}
+my $action = $agent->agentnum ? 'Edit' : 'Add';
+my $hashref = $agent->hashref;
+
+%>
+
+<%= header("$action Agent", menubar(
+ 'Main Menu' => $p,
+ 'View all agents' => $p. 'browse/agent.cgi',
+)) %>
+
+<% if ( $cgi->param('error') ) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+<% } %>
+
+<FORM ACTION="<%=popurl(1)%>process/agent.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $hashref->{agentnum} %>">
+Agent #<%= $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %>
+
+<%= &ntable("#cccccc", 2, '') %>
+<TR>
+ <TH ALIGN="right">Agent</TH>
+ <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<%= $hashref->{agent} %>"></TD>
+</TR>
+<TR>
+ <TH ALIGN="right">Agent type</TH>
+ <TD><SELECT NAME="typenum" SIZE=1>
+
+<% foreach my $agent_type (qsearch('agent_type',{})) { %>
+ <OPTION VALUE="<%= $agent_type->typenum %>"<%= ( $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ) ) ? ' SELECTED' : '' %>>
+ <%= $agent_type->getfield('typenum') %>: <%= $agent_type->getfield('atype') %>
+<% } %>
+
+</SELECT></TD>
+</TR>
+<% if ( dbdef->table('agent')->column('disabled') ) { %>
+ <TR>
+ <TD ALIGN="right">Disable</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD>
+ </TR>
+<% } %>
+<TR>
+ <TD ALIGN="right"><!--Frequency--></TD>
+ <TD><INPUT TYPE="hidden" NAME="freq" VALUE="<%= $hashref->{freq} %>"></TD>
+</TR>
+<TR>
+ <TD ALIGN="right"><!--Program--></TD>
+ <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<%= $hashref->{prog} %>"></TD>
+</TR>
+<% if ( dbdef->table('agent')->column('username') ) { %>
+ <TR>
+ <TD ALIGN="right">Agent interface username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $hashref->{username} %>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Agent interface password</TD>
+ <TD><INPUT TYPE="text" NAME="_password" VALUE="<%= $hashref->{_password} %>"></TD>
+ </TR>
+<% } %>
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="<%= $hashref->{agentnum} ? "Apply changes" : "Add agent" %>">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi
new file mode 100755
index 0000000..637c710
--- /dev/null
+++ b/httemplate/edit/agent_type.cgi
@@ -0,0 +1,63 @@
+<!-- mason kludge -->
+<%
+
+my($agent_type);
+if ( $cgi->param('error') ) {
+ $agent_type = new FS::agent_type ( {
+ map { $_, scalar($cgi->param($_)) } fields('agent')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my( $query ) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $agent_type=qsearchs('agent_type',{'typenum'=>$1});
+} else { #adding
+ $agent_type = new FS::agent_type {};
+}
+my $action = $agent_type->typenum ? 'Edit' : 'Add';
+my $hashref = $agent_type->hashref;
+
+print header("$action Agent Type", menubar(
+ 'Main Menu' => "$p",
+ 'View all agent types' => "${p}browse/agent_type.cgi",
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print '<FORM ACTION="', popurl(1), 'process/agent_type.cgi" METHOD=POST>',
+ qq!<INPUT TYPE="hidden" NAME="typenum" VALUE="$hashref->{typenum}">!,
+ "Agent Type #", $hashref->{typenum} ? $hashref->{typenum} : "(NEW)";
+
+print <<END;
+<BR><BR>Agent Type <INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="$hashref->{atype}">
+<BR><BR>Select which packages agents of this type may sell to customers<BR>
+END
+
+foreach my $part_pkg ( qsearch('part_pkg',{ 'disabled' => '' }) ) {
+ print qq!<BR><INPUT TYPE="checkbox" NAME="pkgpart!,
+ $part_pkg->getfield('pkgpart'), qq!" !,
+ # ( 'CHECKED 'x scalar(
+ qsearchs('type_pkgs',{
+ 'typenum' => $agent_type->getfield('typenum'),
+ 'pkgpart' => $part_pkg->getfield('pkgpart'),
+ })
+ ? 'CHECKED '
+ : '',
+ qq!VALUE="ON"> !,
+ qq!<A HREF="${p}edit/part_pkg.cgi?!, $part_pkg->pkgpart,
+ '">', $part_pkg->pkgpart. ": ". $part_pkg->getfield('pkg'), '</A>',
+ ;
+}
+
+print qq!<BR><BR><INPUT TYPE="submit" VALUE="!,
+ $hashref->{typenum} ? "Apply changes" : "Add agent type",
+ qq!">!;
+
+print <<END;
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi
new file mode 100755
index 0000000..24bce30
--- /dev/null
+++ b/httemplate/edit/cust_bill_pay.cgi
@@ -0,0 +1,95 @@
+<!-- mason kludge -->
+<%
+
+my($paynum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+ $paynum = $cgi->param('paynum');
+ $amount = $cgi->param('amount');
+ $invnum = $cgi->param('invnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $paynum = $1;
+ $amount = '';
+ $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+print header("Apply Payment", '');
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+print <<END;
+ <FORM ACTION="${p1}process/cust_bill_pay.cgi" METHOD=POST>
+END
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+die "payment $paynum not found!" unless $cust_pay;
+
+my $unapplied = $cust_pay->unapplied;
+
+print "Payment # <B>$paynum</B>".
+ qq!<INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum">!.
+ '<BR>Date: <B>'. time2str("%D", $cust_pay->_date). '</B>'.
+ '<BR>Amount: $<B>'. $cust_pay->paid. '</B>'.
+ "<BR>Unapplied amount: \$<B>$unapplied</B>"
+ ;
+
+my @cust_bill = grep $_->owed != 0,
+ qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } );
+
+print <<END;
+<SCRIPT>
+function changed(what) {
+ cust_bill = what.options[what.selectedIndex].value;
+END
+
+foreach my $cust_bill ( @cust_bill ) {
+ my $invnum = $cust_bill->invnum;
+ my $changeto = $cust_bill->owed < $unapplied
+ ? $cust_bill->owed
+ : $unapplied;
+ print <<END;
+ if ( cust_bill == $invnum ) {
+ what.form.amount.value = "$changeto";
+ }
+END
+}
+
+print <<END;
+ if ( cust_bill == "Refund" ) {
+ what.form.amount.value = "$unapplied";
+ }
+}
+</SCRIPT>
+END
+
+print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!,
+ '<OPTION VALUE="">';
+foreach my $cust_bill ( @cust_bill ) {
+ print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ).
+ ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum.
+ ' - '. time2str("%D",$cust_bill->_date).
+ ' - $'. $cust_bill->owed;
+}
+print qq!<OPTION VALUE="Refund">Refund!;
+print "</SELECT>";
+
+print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!;
+
+print <<END;
+<BR>
+<INPUT TYPE="submit" VALUE="Apply">
+END
+
+print <<END;
+
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
new file mode 100755
index 0000000..aae0df2
--- /dev/null
+++ b/httemplate/edit/cust_credit.cgi
@@ -0,0 +1,63 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+my($custnum, $amount, $reason);
+if ( $cgi->param('error') ) {
+ #$cust_credit = new FS::cust_credit ( {
+ # map { $_, scalar($cgi->param($_)) } fields('cust_credit')
+ #} );
+ $custnum = $cgi->param('custnum');
+ $amount = $cgi->param('amount');
+ #$refund = $cgi->param('refund');
+ $reason = $cgi->param('reason');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+ $amount = '';
+ #$refund = 'yes';
+ $reason = '';
+}
+my $_date = time;
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+print header("Post Credit", '');
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+print <<END, small_custview($custnum, $conf->config('countrydefault'));
+ <FORM ACTION="${p1}process/cust_credit.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="crednum" VALUE="">
+ <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">
+ <INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+ <INPUT TYPE="hidden" NAME="_date" VALUE="$_date">
+ <INPUT TYPE="hidden" NAME="credited" VALUE="">
+ <INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker">
+END
+
+print '<BR><BR>Credit'. ntable("#cccccc", 2).
+ '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'.
+ time2str("%D",$_date). '</TD></TR>';
+
+print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8></TD></TR>!;
+
+#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
+
+print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!;
+
+print qq!<TR><TD ALIGN="right">Auto-apply<BR>to invoices</TD><TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>!;
+
+print <<END;
+</TABLE>
+<BR>
+<INPUT TYPE="submit" VALUE="Post credit">
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi
new file mode 100755
index 0000000..1a97e13
--- /dev/null
+++ b/httemplate/edit/cust_credit_bill.cgi
@@ -0,0 +1,101 @@
+<!-- mason kludge -->
+<%
+
+my($crednum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+ #$cust_credit_bill = new FS::cust_credit_bill ( {
+ # map { $_, scalar($cgi->param($_)) } fields('cust_credit_bill')
+ #} );
+ $crednum = $cgi->param('crednum');
+ $amount = $cgi->param('amount');
+ #$refund = $cgi->param('refund');
+ $invnum = $cgi->param('invnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $crednum = $1;
+ $amount = '';
+ #$refund = 'yes';
+ $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+print header("Apply Credit", '');
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+print <<END;
+ <FORM ACTION="${p1}process/cust_credit_bill.cgi" METHOD=POST>
+END
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } );
+die "credit $crednum not found!" unless $cust_credit;
+
+my $credited = $cust_credit->credited;
+
+print "Credit # <B>$crednum</B>".
+ qq!<INPUT TYPE="hidden" NAME="crednum" VALUE="$crednum">!.
+ '<BR>Date: <B>'. time2str("%D", $cust_credit->_date). '</B>'.
+ '<BR>Amount: $<B>'. $cust_credit->amount. '</B>'.
+ "<BR>Unapplied amount: \$<B>$credited</B>".
+ '<BR>Reason: <B>'. $cust_credit->reason. '</B>'
+ ;
+
+my @cust_bill = grep $_->owed != 0,
+ qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } );
+
+print <<END;
+<SCRIPT>
+function changed(what) {
+ cust_bill = what.options[what.selectedIndex].value;
+END
+
+foreach my $cust_bill ( @cust_bill ) {
+ my $invnum = $cust_bill->invnum;
+ my $changeto = $cust_bill->owed < $cust_credit->credited
+ ? $cust_bill->owed
+ : $cust_credit->credited;
+ print <<END;
+ if ( cust_bill == $invnum ) {
+ what.form.amount.value = "$changeto";
+ }
+END
+}
+
+print <<END;
+ if ( cust_bill == "Refund" ) {
+ what.form.amount.value = "$credited";
+ }
+}
+</SCRIPT>
+END
+
+print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!,
+ '<OPTION VALUE="">';
+foreach my $cust_bill ( @cust_bill ) {
+ print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ).
+ ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum.
+ ' - '. time2str("%D",$cust_bill->_date).
+ ' - $'. $cust_bill->owed;
+}
+print qq!<OPTION VALUE="Refund">Refund!;
+print "</SELECT>";
+
+print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!;
+
+print <<END;
+<BR>
+<INPUT TYPE="submit" VALUE="Apply">
+END
+
+print <<END;
+
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
new file mode 100755
index 0000000..4a8f705
--- /dev/null
+++ b/httemplate/edit/cust_main.cgi
@@ -0,0 +1,572 @@
+<!-- mason kludge -->
+<%
+
+ #for misplaced logic below
+ #use FS::part_pkg;
+
+ #for false laziness below (now more properly lazy)
+ #use FS::svc_acct_pop;
+
+ #for (other) false laziness below
+ #use FS::agent;
+ #use FS::type_pkgs;
+
+my $conf = new FS::Conf;
+
+#get record
+
+my $error = '';
+my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart);
+my(@invoicing_list);
+if ( $cgi->param('error') ) {
+ $error = $cgi->param('error');
+ $cust_main = new FS::cust_main ( {
+ map { $_, scalar($cgi->param($_)) } fields('cust_main')
+ } );
+ $custnum = $cust_main->custnum;
+ $saved_pkgpart = $cgi->param('pkgpart_svcpart') || '';
+ if ( $saved_pkgpart =~ /^(\d+)_/ ) {
+ $saved_pkgpart = $1;
+ } else {
+ $saved_pkgpart = '';
+ }
+ $username = $cgi->param('username');
+ $password = $cgi->param('_password');
+ $popnum = $cgi->param('popnum');
+ @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
+} elsif ( $cgi->keywords ) { #editing
+ my( $query ) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum=$1;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ if ( $cust_main->dbdef_table->column('paycvv')
+ && length($cust_main->paycvv) ) {
+ my $paycvv = $cust_main->paycvv;
+ $paycvv =~ s/./*/g;
+ $cust_main->paycvv($paycvv);
+ }
+ $saved_pkgpart = 0;
+ $username = '';
+ $password = '';
+ $popnum = 0;
+ @invoicing_list = $cust_main->invoicing_list;
+} else {
+ $custnum='';
+ $cust_main = new FS::cust_main ( {} );
+ $cust_main->otaker( &getotaker );
+ $cust_main->referral_custnum( $cgi->param('referral_custnum') );
+ $saved_pkgpart = 0;
+ $username = '';
+ $password = '';
+ $popnum = 0;
+ @invoicing_list = ();
+}
+$cgi->delete_all();
+my $action = $custnum ? 'Edit' : 'Add';
+
+# top
+
+my $p1 = popurl(1);
+print header("Customer $action", '', ' onUnload="myclose()"');
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $error, "</FONT>"
+ if $error;
+
+print qq!<FORM ACTION="${p1}process/cust_main.cgi" METHOD=POST NAME="form1" onSubmit="document.form1.submit.disabled=true">!,
+ qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!,
+ qq!Customer # !, ( $custnum ? "<B>$custnum</B>" : " (NEW)" ),
+
+;
+
+# agent
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+my %agent_search = dbdef->table('agent')->column('disabled')
+ ? ( 'disabled' => '' ) : ();
+my @agents = qsearch( 'agent', \%agent_search );
+#die "No agents created!" unless @agents;
+eidiot "You have not created any agents (or all agents are disabled). You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents;
+my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first
+if ( scalar(@agents) == 1 ) {
+ print qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!;
+} else {
+ print qq!<BR><BR>${r}Agent <SELECT NAME="agentnum" SIZE="1">!;
+ my $agent;
+ foreach $agent (sort {
+ $a->agent cmp $b->agent;
+ } @agents) {
+ print '<OPTION VALUE="', $agent->agentnum, '"',
+ " SELECTED"x($agent->agentnum==$agentnum),
+ ">". $agent->agent;
+ #">", $agent->agentnum,": ", $agent->agent;
+ }
+ print "</SELECT>";
+}
+
+#referral
+
+my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0;
+if ( $custnum && ! $conf->exists('editreferrals') ) {
+ print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$refnum">!;
+} else {
+ my(@referrals) = qsearch('part_referral',{});
+ if ( scalar(@referrals) == 0 ) {
+ eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources.";
+ } elsif ( scalar(@referrals) == 1 ) {
+ $refnum ||= $referrals[0]->refnum;
+ print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$refnum">!;
+ } else {
+ print qq!<BR><BR>${r}Advertising source <SELECT NAME="refnum" SIZE="1">!;
+ print "<OPTION> " unless $refnum;
+ my($referral);
+ foreach $referral (sort {
+ $a->refnum <=> $b->refnum;
+ } @referrals) {
+ print "<OPTION" . " SELECTED"x($referral->refnum==$refnum),
+ ">", $referral->refnum, ": ", $referral->referral;
+ }
+ print "</SELECT>";
+ }
+}
+
+#referring customer
+
+#print qq!<BR><BR>Referring Customer: !;
+my $referring_cust_main = '';
+if ( $cust_main->referral_custnum
+ and $referring_cust_main =
+ qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+) {
+ print '<BR><BR>Referring Customer: <A HREF="'. popurl(1). '/cust_main.cgi?'.
+ $cust_main->referral_custnum. '">'.
+ $cust_main->referral_custnum. ': '.
+ ( $referring_cust_main->company
+ || $referring_cust_main->last. ', '. $referring_cust_main->first ).
+ '</A><INPUT TYPE="hidden" NAME="referral_custnum" VALUE="'.
+ $cust_main->referral_custnum. '">';
+} elsif ( ! $conf->exists('disable_customer_referrals') ) {
+ print '<BR><BR>Referring customer number: <INPUT TYPE="text" NAME="referral_custnum" VALUE="">';
+} else {
+ print '<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">';
+}
+
+# contact info
+
+my($last,$first,$ss,$company,$address1,$address2,$city,$zip)=(
+ $cust_main->last,
+ $cust_main->first,
+ $cust_main->ss,
+ $cust_main->company,
+ $cust_main->address1,
+ $cust_main->address2,
+ $cust_main->city,
+ $cust_main->zip,
+);
+
+print "<BR><BR>Billing address", &itable("#cccccc"), <<END;
+<TR><TH ALIGN="right">${r}Contact&nbsp;name<BR>(last,&nbsp;first)</TH><TD COLSPAN=3>
+END
+
+print <<END;
+<INPUT TYPE="text" NAME="last" VALUE="$last"> ,
+<INPUT TYPE="text" NAME="first" VALUE="$first">
+</TD>
+END
+
+if ( $conf->exists('show_ss') ) {
+ print qq!<TD ALIGN="right">SS#</TD><TD><INPUT TYPE="text" NAME="ss" VALUE="$ss" SIZE=11></TD>!;
+} else {
+ print qq!<TD><INPUT TYPE="hidden" NAME="ss" VALUE="$ss"></TD>!;
+}
+
+print <<END;
+</TR>
+<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="company" VALUE="$company" SIZE=70></TD></TR>
+<TR><TH ALIGN="right">${r}Address</TH><TD COLSPAN=5><INPUT TYPE="text" NAME="address1" VALUE="$address1" SIZE=70></TD></TR>
+<TR><TD ALIGN="right">&nbsp;</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="address2" VALUE="$address2" SIZE=70></TD></TR>
+<TR><TH ALIGN="right">${r}City</TH><TD><INPUT TYPE="text" NAME="city" VALUE="$city"></TD><TH ALIGN="right">${r}State</TH><TD>
+END
+
+#false laziness with ship state
+my $countrydefault = $conf->config('countrydefault') || 'US';
+$cust_main->country( $countrydefault ) unless $cust_main->country;
+
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+$cust_main->state( $statedefault )
+ unless $cust_main->state || $cust_main->country ne $countrydefault;
+
+my($county_html, $state_html, $country_html) =
+ FS::cust_main_county::regionselector( $cust_main->county,
+ $cust_main->state,
+ $cust_main->country );
+
+print "$county_html $state_html";
+
+print qq!</TD><TH>${r}Zip</TH><TD><INPUT TYPE="text" NAME="zip" VALUE="$zip" SIZE=10></TD></TR>!;
+
+my($daytime,$night,$fax)=(
+ $cust_main->daytime,
+ $cust_main->night,
+ $cust_main->fax,
+);
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone';
+my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone';
+
+print <<END;
+<TR><TH ALIGN="right">${r}Country</TH><TD>$country_html</TD></TR>
+<TR><TD ALIGN="right">$daytime_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="$daytime" SIZE=18></TD></TR>
+<TR><TD ALIGN="right">$night_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="$night" SIZE=18></TD></TR>
+<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="$fax" SIZE=12></TD></TR>
+END
+
+print "</TABLE>${r}required fields<BR>";
+
+# service address
+
+if ( defined $cust_main->dbdef_table->column('ship_last') ) {
+
+ print "\n", <<END;
+ <SCRIPT>
+ function changed(what) {
+ what.form.same.checked = false;
+ }
+ function samechanged(what) {
+ if ( what.checked ) {
+END
+print " what.form.ship_$_.value = what.form.$_.value;\n"
+ for (qw( last first company address1 address2 city zip daytime night fax ));
+print <<END;
+ what.form.ship_country.selectedIndex = what.form.country.selectedIndex;
+ ship_country_changed(what.form.ship_country);
+ what.form.ship_state.selectedIndex = what.form.state.selectedIndex;
+ ship_state_changed(what.form.ship_state);
+ what.form.ship_county.selectedIndex = what.form.county.selectedIndex;
+ }
+ }
+ </SCRIPT>
+END
+
+ print '<BR>Service address ',
+ '(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)"';
+ unless ( $cust_main->ship_last && $cgi->param('same') ne 'Y' ) {
+ print ' CHECKED';
+ foreach (
+ qw( last first company address1 address2 city county state zip country
+ daytime night fax )
+ ) {
+ $cust_main->set("ship_$_", $cust_main->get($_) );
+ }
+ }
+ print '>same as billing address)<BR>';
+
+ my($ship_last,$ship_first,$ship_company,$ship_address1,$ship_address2,$ship_city,$ship_zip)=(
+ $cust_main->ship_last,
+ $cust_main->ship_first,
+ $cust_main->ship_company,
+ $cust_main->ship_address1,
+ $cust_main->ship_address2,
+ $cust_main->ship_city,
+ $cust_main->ship_zip,
+ );
+
+ print &itable("#cccccc"), <<END;
+ <TR><TH ALIGN="right">${r}Contact&nbsp;name<BR>(last,&nbsp;first)</TH><TD COLSPAN=5>
+END
+
+ print <<END;
+ <INPUT TYPE="text" NAME="ship_last" VALUE="$ship_last" onChange="changed(this)"> ,
+ <INPUT TYPE="text" NAME="ship_first" VALUE="$ship_first" onChange="changed(this)">
+END
+
+ print <<END;
+ </TD></TR>
+ <TR><TD ALIGN="right">Company</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_company" VALUE="$ship_company" SIZE=70 onChange="changed(this)"></TD></TR>
+ <TR><TH ALIGN="right">${r}Address</TH><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address1" VALUE="$ship_address1" SIZE=70 onChange="changed(this)"></TD></TR>
+ <TR><TD ALIGN="right">&nbsp;</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address2" VALUE="$ship_address2" SIZE=70 onChange="changed(this)"></TD></TR>
+ <TR><TH ALIGN="right">${r}City</TH><TD><INPUT TYPE="text" NAME="ship_city" VALUE="$ship_city" onChange="changed(this)"></TD><TH ALIGN="right">${r}State</TH><TD>
+END
+
+ #false laziness with regular state
+ $cust_main->ship_country( $countrydefault ) unless $cust_main->ship_country;
+
+ $cust_main->ship_state( $statedefault )
+ unless $cust_main->ship_state
+ || $cust_main->ship_country ne $countrydefault;
+
+ my($ship_county_html, $ship_state_html, $ship_country_html) =
+ FS::cust_main_county::regionselector( $cust_main->ship_county,
+ $cust_main->ship_state,
+ $cust_main->ship_country,
+ 'ship_',
+ 'changed(this)', );
+
+ print "$ship_county_html $ship_state_html";
+
+ print qq!</TD><TH>${r}Zip</TH><TD><INPUT TYPE="text" NAME="ship_zip" VALUE="$ship_zip" SIZE=10 onChange="changed(this)"></TD></TR>!;
+
+ my($ship_daytime,$ship_night,$ship_fax)=(
+ $cust_main->ship_daytime,
+ $cust_main->ship_night,
+ $cust_main->ship_fax,
+ );
+
+ print <<END;
+ <TR><TH ALIGN="right">${r}Country</TH><TD>$ship_country_html</TD></TR>
+ <TR><TD ALIGN="right">$daytime_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_daytime" VALUE="$ship_daytime" SIZE=18 onChange="changed(this)"></TD></TR>
+ <TR><TD ALIGN="right">$night_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_night" VALUE="$ship_night" SIZE=18 onChange="changed(this)"></TD></TR>
+ <TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_fax" VALUE="$ship_fax" SIZE=12 onChange="changed(this)"></TD></TR>
+END
+
+ print "</TABLE>${r}required fields<BR>";
+
+}
+
+# billing info
+
+sub expselect {
+ my $prefix = shift;
+ my( $m, $y ) = (0, 0);
+ if ( scalar(@_) ) {
+ my $date = shift || '01-2000';
+ if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+ ( $m, $y ) = ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $m, $y ) = ( $1, $3 );
+ } else {
+ die "unrecognized expiration date format: $date";
+ }
+ }
+
+ my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
+ for ( 1 .. 12 ) {
+ $return .= "<OPTION";
+ $return .= " SELECTED" if $_ == $m;
+ $return .= ">$_";
+ }
+ $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
+ my @t = localtime;
+ my $thisYear = $t[5] + 1900;
+ for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. 2037 ) {
+ $return .= "<OPTION";
+ $return .= " SELECTED" if $_ == $y;
+ $return .= ">$_";
+ }
+ $return .= "</SELECT>";
+
+ $return;
+}
+
+my $payby_default = $conf->config('payby-default');
+
+if ( $payby_default eq 'HIDE' ) {
+
+ $cust_main->payby('BILL') unless $cust_main->payby;
+
+ foreach my $field (qw( tax payby )) {
+ print qq!<INPUT TYPE="hidden" NAME="$field" VALUE="!.
+ $cust_main->getfield($field). '">';
+ }
+
+ print qq!<INPUT TYPE="hidden" NAME="invoicing_list" VALUE="!.
+ join(', ', $cust_main->invoicing_list). '">';
+
+ foreach my $payby (qw( CARD DCRD CHEK DCHK LECB BILL COMP )) {
+ foreach my $field (qw( payinfo payname )) {
+ print qq!<INPUT TYPE="hidden" NAME="${payby}_$field" VALUE="!.
+ $cust_main->getfield($field). '">';
+ }
+
+ #false laziness w/expselect
+ my( $m, $y );
+ my $date = $cust_main->paydate || '12-2037';
+ if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+ ( $m, $y ) = ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $m, $y ) = ( $1, $3 );
+ } else {
+ die "unrecognized expiration date format: $date";
+ }
+
+ print qq!<INPUT TYPE="hidden" NAME="${payby}_month" VALUE="$m">!.
+ qq!<INPUT TYPE="hidden" NAME="${payby}_year" VALUE="$y">!;
+
+ }
+
+} else {
+
+ print "<BR>Billing information", &itable("#cccccc"),
+ qq!<TR><TD><INPUT TYPE="checkbox" NAME="tax" VALUE="Y"!;
+ print qq! CHECKED! if $cust_main->tax eq "Y";
+ print qq!>Tax Exempt</TD></TR><TR><TD>!.
+ qq!<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"!;
+
+ #my @invoicing_list = $cust_main->invoicing_list;
+ print qq! CHECKED!
+ if ( ! @invoicing_list && ! $conf->exists('disablepostalinvoicedefault') )
+ || grep { $_ eq 'POST' } @invoicing_list;
+ print qq!>Postal mail invoice</TD></TR>!;
+ my $invoicing_list = join(', ', grep { $_ ne 'POST' } @invoicing_list );
+ print qq!<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="$invoicing_list"></TD></TR>!;
+
+ print "<TR><TD>Billing type</TD></TR>",
+ "</TABLE>", '<SCRIPT>
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1;
+ }
+ var achwindow = -1;
+ function achopen(filename,windowname,properties) {
+ achclose();
+ achwindow = window.open(filename,windowname,properties);
+ }
+ function achclose() {
+ if ( achwindow != -1 )
+ achwindow.close();
+ achwindow = -1;
+ }
+ </SCRIPT>',
+ &table("#cccccc"), "<TR>";
+
+ my($payinfo, $payname)=(
+ $cust_main->payinfo,
+ $cust_main->payname,
+ );
+
+ my %payby = (
+ 'CARD' => qq!Credit card (automatic)<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD"). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card (on-demand)<BR>${r}<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR>${r}Exp !. expselect("DCRD"). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check (automatic)<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE=""><BR>${r}ABA/Routing number <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9> (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>)<INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check (on-demand)<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE=""><BR>${r}ABA/Routing number <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9> (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>)<INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><INPUT TYPE="hidden" NAME="BILL_month" VALUE="12"><INPUT TYPE="hidden" NAME="BILL_year" VALUE="2037">Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="">!,
+ 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR>${r}Exp !. expselect("COMP"),
+);
+
+ if ( $cust_main->dbdef_table->column('paycvv') ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs
+ $payby{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+
+ my %paybychecked = (
+ 'CARD' => qq!Credit card (automatic)<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD", $cust_main->paydate). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card (on-demand)<BR>${r}<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR>${r}Exp !. expselect("DCRD", $cust_main->paydate). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check (automatic)<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account"><BR>${r}ABA/Routing number <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9> (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>)<INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check (on-demand)<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account"><BR>${r}ABA/Routing number <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9> (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>)<INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><INPUT TYPE="hidden" NAME="BILL_month" VALUE="12"><INPUT TYPE="hidden" NAME="BILL_year" VALUE="2037">Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR>${r}Exp !. expselect("COMP", $cust_main->paydate),
+);
+
+ if ( $cust_main->dbdef_table->column('paycvv') ) {
+ my $paycvv = $cust_main->paycvv;
+
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs
+ $paybychecked{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+
+ $cust_main->payby($payby_default) unless $cust_main->payby;
+ for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) {
+ print qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($cust_main->payby eq "$_") {
+ print qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ print qq!> $payby{$_}</TD>!;
+ }
+ }
+
+ print "</TR></TABLE>$r required fields for each billing type";
+
+}
+
+if ( defined $cust_main->dbdef_table->column('comments') ) {
+ print "<BR><BR>Comments", &itable("#cccccc"),
+ qq!<TR><TD><TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments">!,
+ $cust_main->comments, "</TEXTAREA>",
+ "</TD></TR></TABLE>";
+}
+
+unless ( $custnum ) {
+ # pry the wrong place for this logic. also pretty expensive
+ #use FS::part_pkg;
+
+ #false laziness, copied from FS::cust_pkg::order
+ my $pkgpart;
+ if ( scalar(@agents) == 1 ) {
+ # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
+ my($agent)=qsearchs('agent',{'agentnum'=> $agentnum });
+ $pkgpart = $agent->pkgpart_hashref;
+ } else {
+ #can't know (agent not chosen), so, allow all
+ my %typenum;
+ foreach my $agent ( @agents ) {
+ next if $typenum{$agent->typenum}++;
+ #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
+ foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround
+ }
+ }
+ #eslaf
+
+ my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
+ qsearch( 'part_pkg', { 'disabled' => '' } );
+
+ if ( @part_pkg ) {
+
+# print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"),
+#apiabuse & undesirable wrapping
+ print "<BR><BR>First package", &itable("#cccccc"),
+ qq!<TR><TD COLSPAN=2><SELECT NAME="pkgpart_svcpart">!;
+
+ print qq!<OPTION VALUE="">(none)!;
+
+ foreach my $part_pkg ( @part_pkg ) {
+ print qq!<OPTION VALUE="!,
+# $part_pkg->pkgpart. "_". $pkgpart{ $part_pkg->pkgpart }, '"';
+ $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct'), '"';
+ print " SELECTED" if $saved_pkgpart && ( $part_pkg->pkgpart == $saved_pkgpart );
+ print ">", $part_pkg->pkg, " - ", $part_pkg->comment;
+ }
+ print "</SELECT></TD></TR>";
+
+ #false laziness: (mostly) copied from edit/svc_acct.cgi
+ #$ulen = $svc_acct->dbdef_table->column('username')->length;
+ my $ulen = dbdef->table('svc_acct')->column('username')->length;
+ my $ulen2 = $ulen+2;
+ my $passwordmax = $conf->config('passwordmax') || 8;
+ my $pmax2 = $passwordmax + 2;
+ print <<END;
+<TR><TD ALIGN="right">Username</TD>
+<TD><INPUT TYPE="text" NAME="username" VALUE="$username" SIZE=$ulen2 MAXLENGTH=$ulen></TD></TR>
+<TR><TD ALIGN="right">Password</TD>
+<TD><INPUT TYPE="text" NAME="_password" VALUE="$password" SIZE=$pmax2 MAXLENGTH=$passwordmax>
+(blank to generate)</TD></TR>
+END
+
+ print '<TR><TD ALIGN="right">Access number</TD><TD WIDTH="100%">'
+ .
+ &FS::svc_acct_pop::popselector($popnum).
+ '</TD></TR></TABLE>'
+ ;
+ }
+}
+
+my $otaker = $cust_main->otaker;
+print qq!<INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker">!,
+ qq!<BR><INPUT NAME="submit" TYPE="submit" VALUE="!,
+ $custnum ? "Apply Changes" : "Add Customer", qq!">!,
+ "</FORM></BODY></HTML>",
+;
+
+%>
diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi
new file mode 100755
index 0000000..9f314a4
--- /dev/null
+++ b/httemplate/edit/cust_main_county-expand.cgi
@@ -0,0 +1,54 @@
+<!-- mason kludge -->
+<%
+
+my($taxnum, $delim, $expansion, $taxclass );
+my($query) = $cgi->keywords;
+if ( $cgi->param('error') ) {
+ $taxnum = $cgi->param('taxnum');
+ $delim = $cgi->param('delim');
+ $expansion = $cgi->param('expansion');
+ $taxclass = $cgi->param('taxclass');
+} else {
+ $query =~ /^(taxclass)?(\d+)$/
+ or die "Illegal taxnum (query $query)";
+ $taxclass = $1 ? 'taxclass' : '';
+ $taxnum = $2;
+ $delim = 'n';
+ $expansion = '';
+}
+
+my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ or die "cust_main_county.taxnum $taxnum not found";
+die "Can't expand entry!" if $cust_main_county->getfield('county');
+
+my $p1 = popurl(1);
+print header("Tax Rate (expand)", menubar(
+ 'Main Menu' => popurl(2),
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print <<END;
+ <FORM ACTION="${p1}process/cust_main_county-expand.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="taxnum" VALUE="$taxnum">
+ <INPUT TYPE="hidden" NAME="taxclass" VALUE="$taxclass">
+ Separate by
+END
+print '<INPUT TYPE="radio" NAME="delim" VALUE="n"';
+print ' CHECKED' if $delim eq 'n';
+print '>line (broken on some browsers) or',
+ '<INPUT TYPE="radio" NAME="delim" VALUE="s"';
+print ' CHECKED' if $delim eq 's';
+print '>whitespace.';
+print <<END;
+ <BR><INPUT TYPE="submit" VALUE="Submit">
+ <BR><TEXTAREA NAME="expansion" ROWS=100>$expansion</TEXTAREA>
+ </FORM>
+ </CENTER>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi
new file mode 100755
index 0000000..4bcfcbe
--- /dev/null
+++ b/httemplate/edit/cust_main_county.cgi
@@ -0,0 +1,98 @@
+<!-- mason kludge -->
+<%
+
+print header("Edit tax rates", menubar(
+ 'Main Menu' => popurl(2),
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="!, popurl(1),
+ qq!process/cust_main_county.cgi" METHOD=POST>!, &table(), <<END;
+ <TR>
+ <TH><FONT SIZE=-1>Country</FONT></TH>
+ <TH><FONT SIZE=-1>State</FONT></TH>
+ <TH><FONT SIZE=-1>County</FONT></TH>
+ <TH><FONT SIZE=-1>Taxclass</FONT><BR><FONT SIZE=-2>(per-package classification)</FONT></TH>
+END
+
+if ( dbdef->table('cust_main_county')->column('taxname') ) {
+ print '<TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH>';
+}
+
+print <<END;
+ <TH><FONT SIZE=-1>Tax</FONT></TH>
+ <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH>
+END
+
+if ( dbdef->table('cust_main_county')->column('setuptax') ) {
+ print '<TH><FONT SIZE=-1>Setup<BR>fee<BR>exempt</TH>';
+}
+if ( dbdef->table('cust_main_county')->column('recurtax') ) {
+ print '<TH><FONT SIZE=-1>Recurring<BR>fee<BR>exempt</TH>';
+}
+
+print '</TR>';
+
+foreach my $cust_main_county ( sort { $a->country cmp $b->country
+ or $a->state cmp $b->state
+ or $a->county cmp $b->county
+ } qsearch('cust_main_county',{}) ) {
+ my($hashref)=$cust_main_county->hashref;
+ print <<END;
+ <TR>
+ <TD BGCOLOR="#ffffff">$hashref->{country}</TD>
+END
+
+ print "<TD", $hashref->{state}
+ ? ' BGCOLOR="#ffffff">'.$hashref->{state}
+ : ' BGCOLOR="#cccccc">(ALL)'
+ , "</TD>";
+
+ print "<TD", $hashref->{county}
+ ? ' BGCOLOR="#ffffff">'. $hashref->{county}
+ : ' BGCOLOR="#cccccc">(ALL)'
+ , "</TD>";
+
+ print "<TD", $hashref->{taxclass}
+ ? ' BGCOLOR="#ffffff">'. $hashref->{taxclass}
+ : ' BGCOLOR="#cccccc">(ALL)'
+ , "</TD>";
+
+ print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>!
+ if dbdef->table('cust_main_county')->column('taxname');
+
+ print qq!<TD><TABLE><TR><TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6></TD><TD>%</TD></TR></TABLE></TD>!;
+ print qq!<TD><TABLE><TR><TD>\$</TD><TD><INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD></TR></TABLE></TD>!;
+
+ print qq!<TD><INPUT TYPE="checkbox" NAME="setuptax!. $hashref->{taxnum}.
+ '" VALUE="Y"'.
+ ( $hashref->{setuptax} =~ /^Y$/i ? ' CHECKED' : '' ).
+ '></TD>'
+ if dbdef->table('cust_main_county')->column('setuptax');
+
+ print qq!<TD><INPUT TYPE="checkbox" NAME="recurtax!. $hashref->{taxnum}.
+ '" VALUE="Y"'.
+ ( $hashref->{recurtax} =~ /^Y$/i ? ' CHECKED' : '' ).
+ '></TD>'
+ if dbdef->table('cust_main_county')->column('recurtax');
+
+ print '</TR>';
+
+}
+
+print <<END;
+ </TABLE>
+ <INPUT TYPE="submit" VALUE="Apply changes">
+ </FORM>
+ </CENTER>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi
new file mode 100755
index 0000000..f6ae7b2
--- /dev/null
+++ b/httemplate/edit/cust_pay.cgi
@@ -0,0 +1,129 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my($link, $linknum, $paid, $payby, $payinfo, $quickpay);
+if ( $cgi->param('error') ) {
+ $link = $cgi->param('link');
+ $linknum = $cgi->param('linknum');
+ $paid = $cgi->param('paid');
+ $payby = $cgi->param('payby');
+ $payinfo = $cgi->param('payinfo');
+ $quickpay = $cgi->param('quickpay');
+} elsif ($cgi->keywords) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $link = 'invnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = 'BILL';
+ $payinfo = "";
+ $quickpay = '';
+} elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $link = 'custnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = 'BILL';
+ $payinfo = '';
+ $quickpay = $cgi->param('quickpay');
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+my $_date = time;
+
+my $paybatch = "webui-$_date-$$-". rand() * 2**32;
+
+my $p1 = popurl(1);
+print header("Post payment", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+
+print <<END, ntable("#cccccc",2);
+ <FORM ACTION="${p1}process/cust_pay.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="link" VALUE="$link">
+ <INPUT TYPE="hidden" NAME="linknum" VALUE="$linknum">
+ <INPUT TYPE="hidden" NAME="quickpay" VALUE="$quickpay">
+END
+
+my $custnum;
+if ( $link eq 'invnum' ) {
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } )
+ or die "unknown invnum $linknum";
+ print "Invoice #<B>$linknum</B>". ntable("#cccccc",2).
+ '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'.
+ time2str("%D", $cust_bill->_date). '</TD></TR>'.
+ '<TR><TD ALIGN="right" VALIGN="top">Items</TD><TD BGCOLOR="#ffffff">';
+ foreach ( $cust_bill->cust_bill_pkg ) { #false laziness with FS::cust_bill
+ if ( $_->pkgnum ) {
+
+ my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
+ my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
+ my($pkg)=$part_pkg->pkg;
+
+ if ( $_->setup != 0 ) {
+ print "$pkg Setup<BR>"; # $money_char. sprintf("%10.2f",$_->setup);
+ print join('<BR>',
+ map { " ". $_->[0]. ": ". $_->[1] } $cust_pkg->labels
+ ). '<BR>';
+ }
+
+ if ( $_->recur != 0 ) {
+ print
+ "$pkg (" . time2str("%x",$_->sdate) . " - " .
+ time2str("%x",$_->edate) . ")<BR>";
+ #$money_char. sprintf("%10.2f",$_->recur)
+ print join('<BR>',
+ map { '--->'. $_->[0]. ": ". $_->[1] } $cust_pkg->labels
+ ). '<BR>';
+ }
+
+ } else { #pkgnum Tax
+ print "Tax<BR>" # $money_char. sprintf("%10.2f",$_->setup)
+ if $_->setup != 0;
+ }
+
+ }
+ print '</TD></TR></TABLE><BR><BR>';
+
+ $custnum = $cust_bill->custnum;
+
+} elsif ( $link eq 'custnum' ) {
+ $custnum = $linknum;
+}
+
+print small_custview($custnum, $conf->config('countrydefault'));
+
+print qq!<INPUT TYPE="hidden" NAME="_date" VALUE="$_date">!;
+print qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$payby">!;
+
+print '<BR><BR>Payment'. ntable("#cccccc", 2).
+ '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'.
+ time2str("%D",$_date). '</TD></TR>';
+
+print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="paid" VALUE="$paid" SIZE=8 MAXLENGTH=8></TD></TR>!;
+
+print qq!<TR><TD ALIGN="right">Payby</TD><TD BGCOLOR="#ffffff">$payby</TD></TR>!;
+
+#payinfo (check # now as payby="BILL" hardcoded.. what to do later?)
+print qq!<TR><TD ALIGN="right">Check #</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="payinfo" VALUE="$payinfo"></TD></TR>!;
+
+print qq!<TR><TD ALIGN="right">Auto-apply<BR>to invoices</TD><TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>!;
+
+print "</TABLE>";
+
+#paybatch
+print qq!<INPUT TYPE="hidden" NAME="paybatch" VALUE="$paybatch">!;
+
+print <<END;
+<BR>
+<INPUT TYPE="submit" VALUE="Post payment">
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
new file mode 100755
index 0000000..485d601
--- /dev/null
+++ b/httemplate/edit/cust_pkg.cgi
@@ -0,0 +1,117 @@
+<!-- mason kludge -->
+<%
+
+my %pkg = ();
+my %comment = ();
+my %all_pkg = ();
+my %all_comment = ();
+#foreach (qsearch('part_pkg', { 'disabled' => '' })) {
+# $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+# $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+#}
+foreach (qsearch('part_pkg', {} )) {
+ $all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+ $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+ next if $_->disabled;
+ $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+ $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+}
+
+my($custnum, %remove_pkg);
+if ( $cgi->param('error') ) {
+ $custnum = $cgi->param('custnum');
+ %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+ %remove_pkg = ();
+}
+
+my $p1 = popurl(1);
+print header("Add/Edit Packages", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/cust_pkg.cgi" METHOD=POST>!;
+
+print qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!;
+
+#current packages
+my @cust_pkg = qsearch('cust_pkg',{ 'custnum' => $custnum, 'cancel' => '' } );
+
+if (@cust_pkg) {
+ print <<END;
+Current packages - select to remove (services are moved to a new package below)
+<BR><BR>
+END
+
+ my $count = 0 ;
+ print qq!<TABLE>! ;
+ foreach (@cust_pkg) {
+ print '<TR>' if $count == 0;
+ my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') );
+ print qq!<TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="$pkgnum"!;
+ print " CHECKED" if $remove_pkg{$pkgnum};
+ print qq!>$pkgnum: $all_pkg{$pkgpart} - $all_comment{$pkgpart}</TD>\n!;
+ $count ++ ;
+ if ($count == 2)
+ {
+ $count = 0 ;
+ print qq!</TR>\n! ;
+ }
+ }
+ print qq!</TABLE><BR><BR>!;
+}
+
+print <<END;
+Order new packages<BR><BR>
+END
+
+my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
+my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum });
+
+my $count = 0;
+my $pkgparts = 0;
+print qq!<TABLE>!;
+foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+ $pkgparts++;
+ my($pkgpart)=$type_pkgs->pkgpart;
+ next unless exists $pkg{$pkgpart}; #skip disabled ones
+ print qq!<TR>! if ( $count == 0 );
+ my $value = $cgi->param("pkg$pkgpart") || 0;
+ print <<END;
+ <TD>
+ <INPUT TYPE="text" NAME="pkg$pkgpart" VALUE="$value" SIZE="2" MAXLENGTH="2">
+ $pkgpart: $pkg{$pkgpart} - $comment{$pkgpart}</TD>\n
+END
+ $count ++ ;
+ if ( $count == 2 ) {
+ print qq!</TR>\n! ;
+ $count = 0;
+ }
+}
+print qq!</TABLE>!;
+
+unless ( $pkgparts ) {
+ my $p2 = popurl(2);
+ my $typenum = $agent->typenum;
+ my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } );
+ my $atype = $agent_type->atype;
+ print <<END;
+(No <a href="${p2}browse/part_pkg.cgi">package definitions</a>, or agent type
+<a href="${p2}edit/agent_type.cgi?$typenum">$atype</a> not allowed to purchase
+any packages.)
+END
+}
+
+#submit
+print <<END;
+<P><INPUT TYPE="submit" VALUE="Order">
+ </FORM>
+ </BODY>
+</HTML>
+END
+%>
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
new file mode 100755
index 0000000..8955c7c
--- /dev/null
+++ b/httemplate/edit/cust_refund.cgi
@@ -0,0 +1,94 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+my $custnum = $cgi->param('custnum');
+my $refund = $cgi->param('refund');
+my $payby = $cgi->param('payby');
+my $reason = $cgi->param('reason');
+
+my( $paynum, $cust_pay ) = ( '', '' );
+if ( $cgi->param('paynum') =~ /^(\d+)$/ ) {
+ $paynum = $1;
+ $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } )
+ or die "unknown payment # $paynum";
+ $refund ||= $cust_pay->unrefunded;
+ if ( $custnum ) {
+ die "payment # $paynum is not for specified customer # $custnum"
+ unless $custnum == $cust_pay->custnum;
+ } else {
+ $custnum = $cust_pay->custnum;
+ }
+}
+die "no custnum or paynum specified!" unless $custnum;
+
+my $_date = time;
+
+my $p1 = popurl(1);
+
+print header('Refund '. ucfirst(lc($payby)). ' payment', '');
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+print <<END, small_custview($custnum, $conf->config('countrydefault'));
+ <FORM ACTION="${p1}process/cust_refund.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="refundnum" VALUE="">
+ <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">
+ <INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum">
+ <INPUT TYPE="hidden" NAME="_date" VALUE="$_date">
+ <INPUT TYPE="hidden" NAME="payby" VALUE="$payby">
+ <INPUT TYPE="hidden" NAME="payinfo" VALUE="">
+ <INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+ <INPUT TYPE="hidden" NAME="credited" VALUE="">
+ <BR>
+END
+
+if ( $cust_pay ) {
+
+ #false laziness w/FS/FS/cust_pay.pm
+ my $payby = $cust_pay->payby;
+ my $payinfo = $cust_pay->payinfo;
+ $payby =~ s/^BILL$/Check/ if $payinfo;
+ $payby =~ s/^CHEK$/Electronic check/;
+ $payinfo = $cust_pay->payinfo_masked if $payby eq 'CARD';
+
+ print '<BR>Payment'. ntable("#cccccc", 2).
+ '<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$'.
+ $cust_pay->paid. '</TD></TR>'.
+ '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'.
+ time2str("%D",$cust_pay->_date). '</TD></TR>'.
+ '<TR><TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff">'.
+ ucfirst(lc($payby)). ' # '. $payinfo. '</TD></TR>';
+ #false laziness w/FS/FS/cust_main::realtime_refund_bop
+ if ( $cust_pay->paybatch =~ /^(\w+):(\w+)(:(\w+))?$/ ) {
+ my ( $processor, $auth, $order_number ) = ( $1, $2, $4 );
+ print '<TR><TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff">'.
+ $processor. '</TD></TR>';
+ print '<TR><TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff">'.
+ $auth. '</TD></TR>'
+ if length($auth);
+ print '<TR><TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff">'.
+ $order_number. '</TD></TR>'
+ if length($order_number);
+ }
+ print '</TABLE>';
+}
+
+print '<BR>Refund'. ntable("#cccccc", 2).
+ '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'.
+ time2str("%D",$_date). '</TD></TR>';
+
+print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="refund" VALUE="$refund" SIZE=8 MAXLENGTH=8></TD></TR>!;
+
+print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!;
+
+print <<END;
+</TABLE>
+<BR>
+<INPUT TYPE="submit" VALUE="Post refund">
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/msgcat.cgi b/httemplate/edit/msgcat.cgi
new file mode 100755
index 0000000..ee9b1c6
--- /dev/null
+++ b/httemplate/edit/msgcat.cgi
@@ -0,0 +1,58 @@
+<!-- mason kludge -->
+<%
+
+print header("Edit Message catalog", menubar(
+# 'Main Menu' => $p,
+)), '<BR>';
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !. $cgi->param('error').
+ '</FONT><BR><BR>'
+ if $cgi->param('error');
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => 'en_US',
+ 'options' => { 'en_US'=>'en_US' },
+ 'form_action' => 'process/msgcat.cgi',
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="locale" VALUE="$layer">!.
+ "<BR>Messages for locale $layer<BR>". table().
+ "<TR><TH COLSPAN=2>Code</TH>".
+ "<TH>Message</TH>";
+ $html .= "<TH>en_US Message</TH>" unless $layer eq 'en_US';
+ $html .= '</TR>';
+
+ #foreach my $msgcat ( sort { $a->msgcode cmp $b->msgcode }
+ # qsearch('msgcat', { 'locale' => $layer } ) ) {
+ foreach my $msgcat ( qsearch('msgcat', { 'locale' => $layer } ) ) {
+ $html .=
+ '<TR><TD>'. $msgcat->msgnum. '</TD><TD>'. $msgcat->msgcode. '</TD>'.
+ '<TD><INPUT TYPE="text" SIZE=32 '.
+ qq! NAME="!. $msgcat->msgnum. '" '.
+ qq!VALUE="!. ($cgi->param($msgcat->msgnum)||$msgcat->msg). qq!"></TD>!;
+ unless ( $layer eq 'en_US' ) {
+ my $en_msgcat = qsearchs('msgcat', {
+ 'locale' => 'en_US',
+ 'msgcode' => $msgcat->msgcode,
+ } );
+ $html .= '<TD>'. $en_msgcat->msg. '</TD>';
+ }
+ $html .= '</TR>';
+ }
+
+ $html .= '</TABLE><BR><INPUT TYPE="submit" VALUE="Apply changes">';
+
+ $html;
+ },
+
+);
+
+print $widget->html;
+
+print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi
new file mode 100755
index 0000000..2f99ca5
--- /dev/null
+++ b/httemplate/edit/part_bill_event.cgi
@@ -0,0 +1,288 @@
+<!-- mason kludge -->
+<%
+
+if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) {
+ $cgi->param('eventpart', $1);
+} else {
+ $cgi->param('eventpart', '');
+}
+
+my ($query) = $cgi->keywords;
+my $action = '';
+my $part_bill_event = '';
+if ( $cgi->param('error') ) {
+ $part_bill_event = new FS::part_bill_event ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_bill_event')
+ } );
+}
+if ( $query && $query =~ /^(\d+)$/ ) {
+ $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1});
+} else {
+ $part_bill_event ||= new FS::part_bill_event {};
+}
+$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add';
+my $hashref = $part_bill_event->hashref;
+
+print header("$action Invoice Event Definition", menubar(
+ 'Main Menu' => popurl(2),
+ 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi',
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print '<FORM ACTION="', popurl(1), 'process/part_bill_event.cgi" METHOD=POST>'.
+ '<INPUT TYPE="hidden" NAME="eventpart" VALUE="'.
+ $part_bill_event->eventpart .'">';
+print "Invoice Event #", $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)";
+
+print ntable("#cccccc",2), <<END;
+<TR><TD ALIGN="right">Payby</TD><TD><SELECT NAME="payby">
+END
+
+for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) {
+ print qq!<OPTION VALUE="$_"!;
+ if ($part_bill_event->payby eq $_) {
+ print " SELECTED>$_</OPTION>";
+ } else {
+ print ">$_</OPTION>";
+ }
+}
+
+my $days = $hashref->{seconds}/86400;
+
+print <<END;
+</SELECT></TD></TR>
+<TR><TD ALIGN="right">Event</TD><TD><INPUT TYPE="text" NAME="event" VALUE="$hashref->{event}"></TD></TR>
+<TR><TD ALIGN="right">After</TD><TD><INPUT TYPE="text" NAME="days" VALUE="$days"> days</TD></TR>
+END
+
+print '<TR><TD ALIGN="right">Disabled</TD><TD>';
+print '<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"';
+print ' CHECKED' if $hashref->{disabled} eq "Y";
+print '>';
+print '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Action</TD><TD>';
+
+#print ntable();
+
+sub select_pkgpart {
+ my $label = shift;
+ my $plandata = shift;
+ my %selected = map { $_=>1 } split(/,\s*/, $plandata->{$label});
+ qq(<SELECT NAME="$label" MULTIPLE>).
+ join("\n", map {
+ '<OPTION VALUE="'. $_->pkgpart. '"'.
+ ( $selected{$_->pkgpart} ? ' SELECTED' : '' ).
+ '>'. $_->pkg. ' - '. $_->comment
+ } qsearch('part_pkg', { 'disabled' => '' } ) ).
+ '</SELECT>';
+}
+
+sub select_agentnum {
+ my $plandata = shift;
+ my $agentnum = $plandata->{'agentnum'};
+ '<SELECT NAME="agentnum">'.
+ join("\n", map {
+ '<OPTION VALUE="'. $_->agentnum. '"'.
+ ( $_->agentnum == $agentnum ? ' SELECTED' : '' ).
+ '>'. $_->agent
+ } qsearch('agent', { 'disabled' => '' } ) ).
+ '</SELECT>';
+}
+
+#this is pretty kludgy right here.
+tie my %events, 'Tie::IxHash',
+
+ 'fee' => {
+ 'name' => 'Late fee',
+ 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\' );',
+ 'html' =>
+ 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'.
+ '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">',
+ 'weight' => 10,
+ },
+ 'suspend' => {
+ 'name' => 'Suspend',
+ 'code' => '$cust_main->suspend();',
+ 'weight' => 10,
+ },
+ 'suspend-if-pkgpart' => {
+ 'name' => 'Suspend packages',
+ 'code' => '$cust_main->suspend_if_pkgpart(%%%if_pkgpart%%%);',
+ 'html' => sub { &select_pkgpart('if_pkgpart', @_) },
+ 'weight' => 10,
+ },
+ 'suspend-unless-pkgpart' => {
+ 'name' => 'Suspend packages except',
+ 'code' => '$cust_main->suspend_unless_pkgpart(%%%unless_pkgpart%%%);',
+ 'html' => sub { &select_pkgpart('unless_pkgpart', @_) },
+ 'weight' => 10,
+ },
+ 'cancel' => {
+ 'name' => 'Cancel',
+ 'code' => '$cust_main->cancel();',
+ 'weight' => 10,
+ },
+
+ 'addpost' => {
+ 'name' => 'Add postal invoicing',
+ 'code' => '$cust_main->invoicing_list_addpost(); "";',
+ 'weight' => 20,
+ },
+
+ 'comp' => {
+ 'name' => 'Pay invoice with a complimentary "payment"',
+ 'code' => '$cust_bill->comp();',
+ 'weight' => 30,
+ },
+
+ 'realtime-card' => {
+ 'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+ 'code' => '$cust_bill->realtime_card();',
+ 'weight' => 30,
+ },
+
+ 'realtime-check' => {
+ 'name' => 'Run check with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+ 'code' => '$cust_bill->realtime_ach();',
+ 'weight' => 30,
+ },
+
+ 'realtime-lec' => {
+ 'name' => 'Run phone bill ("LEC") billing with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+ 'code' => '$cust_bill->realtime_lec();',
+ 'weight' => 30,
+ },
+
+ 'batch-card' => {
+ 'name' => 'Add card to the pending credit card batch',
+ 'code' => '$cust_bill->batch_card();',
+ 'weight' => 40,
+ },
+
+ 'send' => {
+ 'name' => 'Send invoice (email/print)',
+ 'code' => '$cust_bill->send();',
+ 'weight' => 50,
+ },
+
+ 'send_alternate' => {
+ 'name' => 'Send invoice (email/print) with alternate template',
+ 'code' => '$cust_bill->send(\'%%%templatename%%%\');',
+ 'html' =>
+ '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">',
+ 'weight' => 50,
+ },
+
+ 'send_agent' => {
+ 'name' => 'Send invoice (email/print) ',
+ 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', %%%agentnum%%%, \'%%%agent_invoice_from%%%\');',
+ 'html' => sub {
+ '<TABLE BORDER=0>
+ <TR>
+ <TD ALIGN="right">only for agent </TD>
+ <TD>'. &select_agentnum(@_). '</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">with template </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="agent_templatename" VALUE="%%%agent_templatename%%%">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">email From: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="agent_invoice_from" VALUE="%%%agent_invoice_from%%%">
+ </TD>
+ </TR>
+ </TABLE>';
+ },
+ 'weight' => 50,
+ },
+
+ 'send_csv_ftp' => {
+ 'name' => 'Upload CSV invoice data to an FTP server',
+ 'code' => '$cust_bill->send_csv( protocol => \'ftp\',
+ server => \'%%%ftpserver%%%\',
+ username => \'%%%ftpusername%%%\',
+ password => \'%%%ftppassword%%%\',
+ dir => \'%%%ftpdir%%%\' );',
+ 'html' =>
+ '<TABLE BORDER=0><TR><TD ALIGN="right">FTP server: </TD>'.
+ '<TD><INPUT TYPE="text" NAME="ftpserver" VALUE="%%%ftpserver%%%">'.
+ '</TD></TR>'.
+ '<TR><TD ALIGN="right">FTP username: </TD><TD>'.
+ '<INPUT TYPE="text" NAME="ftpusername" VALUE="%%%ftpusername%%%">'.
+ '</TD></TR>'.
+ '<TR><TD ALIGN="right">FTP password: </TD><TD>'.
+ '<INPUT TYPE="text" NAME="ftppassword" VALUE="%%%ftppassword%%%">'.
+ '</TD></TR>'.
+ '<TR><TD ALIGN="right">FTP directory: </TD>'.
+ '<TD><INPUT TYPE="text" NAME="ftpdir" VALUE="%%%ftpdir%%%">'.
+ '</TD></TR>'.
+ '</TABLE>',
+ 'weight' => 50,
+ },
+
+ 'bill' => {
+ 'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)',
+ 'code' => '$cust_main->bill();',
+ 'weight' => 60,
+ },
+
+ 'apply' => {
+ 'name' => 'Apply unapplied payments and credits',
+ 'code' => '$cust_main->apply_payments; $cust_main->apply_credits; "";',
+ 'weight' => 70,
+ },
+
+ 'collect' => {
+ 'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)',
+ 'code' => '$cust_main->collect();',
+ 'weight' => 80,
+ },
+
+;
+
+foreach my $event ( keys %events ) {
+ my %plandata = map { /^(\w+) (.*)$/; ($1, $2); }
+ split(/\n/, $part_bill_event->plandata);
+ my $html = $events{$event}{html};
+ if ( ref($html) eq 'CODE' ) {
+ $html = &{$html}(\%plandata);
+ }
+ while ( $html =~ /%%%(\w+)%%%/ ) {
+ my $field = $1;
+ $html =~ s/%%%$field%%%/$plandata{$field}/;
+ }
+
+ print ntable( "#cccccc", 2).
+ qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !;
+ print "CHECKED " if $event eq $part_bill_event->plan;
+ print qq!VALUE="!. $event. ":". $events{$event}{weight}. ":".
+ encode_entities($events{$event}{code}).
+ qq!">$events{$event}{name}</TD>!;
+ print '<TD>'. $html. '</TD>' if $html;
+ print qq!</TR>!;
+ print '</TABLE>';
+}
+
+#print '</TABLE>';
+
+print <<END;
+</TD></TR>
+</TABLE>
+END
+
+print qq!<INPUT TYPE="submit" VALUE="!,
+ $hashref->{eventpart} ? "Apply changes" : "Add invoice event",
+ qq!">!;
+%>
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
new file mode 100644
index 0000000..b3d42bd
--- /dev/null
+++ b/httemplate/edit/part_export.cgi
@@ -0,0 +1,128 @@
+<!-- mason kludge -->
+<%
+
+#if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
+# $cgi->param('clone', $1);
+#} else {
+# $cgi->param('clone', '');
+#}
+
+my($query) = $cgi->keywords;
+my $action = '';
+my $part_export = '';
+if ( $cgi->param('error') ) {
+ $part_export = new FS::part_export ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_export')
+ } );
+} elsif ( $query =~ /^(\d+)$/ ) {
+ $part_export = qsearchs('part_export', { 'exportnum' => $1 } );
+} else {
+ $part_export = new FS::part_export;
+}
+$action ||= $part_export->exportnum ? 'Edit' : 'Add';
+
+#my $exports = FS::part_export::export_info($svcdb);
+my $exports = FS::part_export::export_info();
+
+my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports;
+$layers{''}='';
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => $part_export->exporttype,
+ 'options' => \%layers,
+ 'form_name' => 'dummy',
+ 'form_action' => 'process/part_export.cgi',
+ 'form_text' => [qw( exportnum machine )],
+# 'form_checkbox' => [qw()],
+ 'html_between' => "</TD></TR></TABLE>\n",
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!.
+ ntable("#cccccc",2);
+
+ $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'.
+ $exports->{$layer}{notes}. '</TD></TR>'
+ if $layer;
+
+ foreach my $option ( keys %{$exports->{$layer}{options}} ) {
+ my $optinfo = $exports->{$layer}{options}{$option};
+ die "Retreived non-ref export info option from $layer export: $optinfo"
+ unless ref($optinfo);
+ my $label = $optinfo->{label};
+ my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text';
+ my $value = $cgi->param($option)
+ || ( $part_export->exportnum && $part_export->option($option) )
+ || ( (exists $optinfo->{default} && !$part_export->exportnum)
+ ? $optinfo->{default}
+ : ''
+ );
+ $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!;
+ if ( $type eq 'select' ) {
+ $html .= qq!<SELECT NAME="$option">!;
+ foreach my $select_option ( @{$optinfo->{options}} ) {
+ #if ( ref($select_option) ) {
+ #} else {
+ my $selected = $select_option eq $value ? ' SELECTED' : '';
+ $html .= qq!<OPTION VALUE="$select_option"$selected>!.
+ qq!$select_option</OPTION>!;
+ #}
+ }
+ $html .= '</SELECT>';
+ } elsif ( $type eq 'textarea' ) {
+ $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!.
+ encode_entities($value). '</TEXTAREA>';
+ } elsif ( $type eq 'text' ) {
+ $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!.
+ encode_entities($value). '" SIZE=64>';
+ } elsif ( $type eq 'checkbox' ) {
+ $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!;
+ $html .= ' CHECKED' if $value;
+ $html .= '>';
+ } else {
+ $html .= "unknown type $type";
+ }
+ $html .= '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'.
+ join(',', keys %{$exports->{$layer}{options}} ). '">';
+
+ $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'.
+ $exports->{$layer}{nodomain}. '">';
+
+ $html .= '<INPUT TYPE="submit" VALUE="'.
+ ( $part_export->exportnum ? "Apply changes" : "Add export" ).
+ '">';
+
+ $html;
+ },
+);
+
+%>
+<%= header("$action Export", menubar(
+ 'Main Menu' => popurl(2),
+), ' onLoad="visualize()"')
+%>
+
+<% if ( $cgi->param('error') ) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+ <BR><BR>
+<% } %>
+
+<FORM NAME="dummy">
+<INPUT TYPE="hidden" NAME="exportnum" VALUE="<%= $part_export->exportnum %>">
+
+<%= ntable("#cccccc",2) %>
+<TR>
+ <TD ALIGN="right">Export host</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="machine" VALUE="<%= $part_export->machine %>">
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Export</TD>
+ <TD><%= $widget->html %>
+</BODY>
+</HTML>
+
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
new file mode 100755
index 0000000..460f68b
--- /dev/null
+++ b/httemplate/edit/part_pkg.cgi
@@ -0,0 +1,627 @@
+<!-- mason kludge -->
+<%
+
+if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
+ $cgi->param('clone', $1);
+} else {
+ $cgi->param('clone', '');
+}
+if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $cgi->param('pkgnum', $1);
+} else {
+ $cgi->param('pkgnum', '');
+}
+
+my ($query) = $cgi->keywords;
+my $action = '';
+my $part_pkg = '';
+if ( $cgi->param('error') ) {
+ $part_pkg = new FS::part_pkg ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_pkg')
+ } );
+}
+if ( $cgi->param('clone') ) {
+ $action='Custom Pricing';
+ my $old_part_pkg =
+ qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } );
+ $part_pkg ||= $old_part_pkg->clone;
+ $part_pkg->disabled('Y');
+} elsif ( $query && $query =~ /^(\d+)$/ ) {
+ $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1});
+} else {
+ unless ( $part_pkg ) {
+ $part_pkg = new FS::part_pkg {};
+ $part_pkg->plan('flat');
+ }
+}
+unless ( $part_pkg->plan ) { #backwards-compat
+ $part_pkg->plan('flat');
+ $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n".
+ "recur_fee=". $part_pkg->recur. "\n");
+}
+$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add';
+my $hashref = $part_pkg->hashref;
+
+
+print header("$action Package Definition", menubar(
+ 'Main Menu' => popurl(2),
+ 'View all packages' => popurl(2). 'browse/part_pkg.cgi',
+));
+#), ' onLoad="visualize()"');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+#print '<FORM ACTION="', popurl(1), 'process/part_pkg.cgi" METHOD=POST>';
+print '<FORM NAME="dummy">';
+
+#if ( $cgi->param('clone') ) {
+# print qq!<INPUT TYPE="hidden" NAME="clone" VALUE="!, $cgi->param('clone'), qq!">!;
+#}
+#if ( $cgi->param('pkgnum') ) {
+# print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="!, $cgi->param('pkgnum'), qq!">!;
+#}
+#
+#print qq!<INPUT TYPE="hidden" NAME="pkgpart" VALUE="$hashref->{pkgpart}">!,
+print "Package Part #", $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)";
+
+#false laziness w/view/cust_main.cgi
+my %freq;
+tie %freq, 'Tie::IxHash',
+ '0' => '(no recurring fee)',
+ '1d' => 'daily',
+ '1w' => 'weekly',
+ '2w' => 'biweekly (every 2 weeks)',
+ '1' => 'monthly',
+ '2' => 'bimonthly (every 2 months)',
+ '3' => 'quarterly (every 3 months)',
+ '6' => 'semiannually (every 6 months)',
+ '12' => 'annually',
+ '24' => 'biannually (every 2 years)',
+;
+if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
+ delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
+}
+
+%>
+<%= ntable("#cccccc",2) %>
+ <TR>
+ <TD ALIGN="right">Package (customer-visible)</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<%= $part_pkg->pkg %>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Comment (customer-hidden)</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%=$part_pkg->comment%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Recurring fee frequency </TD>
+ <TD>
+ <SELECT NAME="freq">
+ <% foreach my $freq ( keys %freq ) { %>
+ <OPTION VALUE="<%= $freq %>"<%= $freq eq $part_pkg->freq ? ' SELECTED' : '' %>><%= $freq{$freq} %>
+ <% } %>
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Setup fee tax exempt</TD>
+ <TD>
+<%
+
+print '<INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y"';
+print ' CHECKED' if $hashref->{setuptax} eq "Y";
+print '>';
+
+print <<END;
+</TD></TR>
+<TR><TD ALIGN="right">Recurring fee tax exempt</TD><TD>
+END
+
+print '<INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y"';
+print ' CHECKED' if $hashref->{recurtax} eq "Y";
+print '>';
+
+print '</TD></TR>';
+
+my $conf = new FS::Conf;
+#false laziness w/ view/cust_main.cgi quick order
+if ( $conf->exists('enable_taxclasses') ) {
+ print '<TR><TD ALIGN="right">Tax class</TD><TD><SELECT NAME="taxclass">';
+ my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ foreach my $taxclass ( map $_->[0], @{$sth->fetchall_arrayref} ) {
+ print qq!<OPTION VALUE="$taxclass"!;
+ print ' SELECTED' if $taxclass eq $hashref->{taxclass};
+ print qq!>$taxclass</OPTION>!;
+ }
+ print '</SELECT></TD></TR>';
+} else {
+ print
+ '<INPUT TYPE="hidden" NAME="taxclass" VALUE="'. $hashref->{taxclass}. '">';
+}
+
+print '<TR><TD ALIGN="right">Disable new orders</TD><TD>';
+print '<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"';
+print ' CHECKED' if $hashref->{disabled} eq "Y";
+print '>';
+print '</TD></TR></TABLE>';
+
+my $thead = "\n\n". ntable('#cccccc', 2).
+ '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>';
+$thead .= '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>'
+ if dbdef->table('pkg_svc')->column('primary_svc');
+$thead .= '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
+
+#unless ( $cgi->param('clone') ) {
+#dunno why...
+unless ( 0 ) {
+ #print <<END, $thead;
+ print <<END, itable(), '<TR><TD VALIGN="top">', $thead;
+<BR><BR>Enter the quantity of each service this package includes.<BR><BR>
+END
+}
+
+my @fixups = ();
+my $count = 0;
+my $columns = 3;
+my @part_svc = qsearch( 'part_svc', { 'disabled' => '' } );
+foreach my $part_svc ( @part_svc ) {
+ my $svcpart = $part_svc->svcpart;
+ my $pkgpart = $cgi->param('clone') || $part_pkg->pkgpart;
+ my $pkg_svc = $pkgpart && qsearchs( 'pkg_svc', {
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $svcpart,
+ } ) || new FS::pkg_svc ( {
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $svcpart,
+ 'quantity' => 0,
+ 'primary_svc' => '',
+ });
+ #? #next unless $pkg_svc;
+
+ push @fixups, "pkg_svc$svcpart";
+
+ #unless ( defined ($cgi->param('clone')) && $cgi->param('clone') ) {
+ #dunno why...
+ unless ( 0 ) {
+ print '<TR>'; # if $count == 0 ;
+ print qq!<TD><INPUT TYPE="text" NAME="pkg_svc$svcpart" SIZE=4 MAXLENGTH=3 VALUE="!,
+ $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0,
+ qq!"></TD>!;
+ if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ print qq!<TD><INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="$svcpart"!;
+ print ' CHECKED' if $pkg_svc->primary_svc =~ /^Y/i;
+ print '></TD>';
+ }
+ print qq!<TD><A HREF="part_svc.cgi?!,$part_svc->svcpart,
+ qq!">!, $part_svc->getfield('svc'), "</A></TD></TR>";
+# print "</TABLE></TD><TD>$thead" if ++$count == int(scalar(@part_svc) / 2);
+ $count+=1;
+ foreach ( 1 .. $columns-1 ) {
+ print "</TABLE></TD><TD VALIGN=\"top\">$thead"
+ if $count == int( $_ * scalar(@part_svc) / $columns );
+ }
+ } else {
+ print qq!<INPUT TYPE="hidden" NAME="pkg_svc$svcpart" VALUE="!,
+ $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0, qq!">\n!;
+ }
+}
+
+#unless ( $cgi->param('clone') ) {
+#dunno why...
+unless ( 0 ) {
+ print "</TR></TABLE></TD></TR></TABLE>";
+ #print "</TR></TABLE>";
+}
+
+foreach my $f ( qw( clone pkgnum ) ) {
+ print qq!<INPUT TYPE="hidden" NAME="$f" VALUE="!. $cgi->param($f). '">';
+}
+print '<INPUT TYPE="hidden" NAME="pkgpart" VALUE="'. $part_pkg->pkgpart. '">';
+
+# prolly should be in database
+tie my %plans, 'Tie::IxHash',
+ 'flat' => {
+ 'name' => 'Flat rate (anniversary billing)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => 'what.recur_fee.value',
+ },
+
+ 'flat_delayed' => {
+ 'name' => 'Free for X days, then flat rate (anniversary billing)',
+ 'fields' => {
+ 'free_days' => { 'name' => 'Initial free days',
+ 'default' => 0,
+ },
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'free_days', 'setup_fee', 'recur_fee' ],
+ 'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value',
+ 'recur' => 'what.recur_fee.value',
+ },
+
+ 'prorate' => {
+ 'name' => 'First partial month pro-rated, then flat-rate (1st of month billing)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; my $mstart = timelocal(0,0,0,1,$mon,$year); my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $sdate = $mstart; ( $part_pkg->freq - 1 ) * \' + what.recur_fee.value + \' / $part_pkg->freq + \' + what.recur_fee.value + \' / $part_pkg->freq * ($mend-$mnow) / ($mend-$mstart) ; \'',
+ },
+
+ 'subscription' => {
+ 'name' => 'First partial month full charge, then flat-rate (1st of month billing)',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,1,$mon,$year); \' + what.recur_fee.value',
+ },
+
+ 'flat_comission_cust' => {
+ 'name' => 'Flat rate with recurring commission per active customer',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'comission_amount' => { 'name' => 'Commission amount per month (per active customer)',
+ 'default' => 0,
+ },
+ 'comission_depth' => { 'name' => 'Number of layers',
+ 'default' => 1,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_main_ncancelled(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
+ },
+
+ 'flat_comission' => {
+ 'name' => 'Flat rate with recurring commission per (any) active package',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'comission_amount' => { 'name' => 'Commission amount per month (per active package)',
+ 'default' => 0,
+ },
+ 'comission_depth' => { 'name' => 'Number of layers',
+ 'default' => 1,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
+ },
+
+ 'flat_comission_pkg' => {
+ 'name' => 'Flat rate with recurring commission per (selected) active package',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_fee' => { 'name' => 'Recurring fee for this package',
+ 'default' => 0,
+ },
+ 'comission_amount' => { 'name' => 'Commission amount per month (per uncancelled package)',
+ 'default' => 0,
+ },
+ 'comission_depth' => { 'name' => 'Number of layers',
+ 'default' => 1,
+ },
+ 'comission_pkgpart' => { 'name' => 'Applicable packages<BR><FONT SIZE="-1">(hold <b>ctrl</b> to select multiple packages)</FONT>',
+ 'type' => 'select_multiple',
+ 'select_table' => 'part_pkg',
+ 'select_hash' => { 'disabled' => '' } ,
+ 'select_key' => 'pkgpart',
+ 'select_label' => 'pkg',
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount', 'comission_pkgpart' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '""; var pkgparts = ""; for ( var c=0; c < document.flat_comission_pkg.comission_pkgpart.options.length; c++ ) { if (document.flat_comission_pkg.comission_pkgpart.options[c].selected) { pkgparts = pkgparts + document.flat_comission_pkg.comission_pkgpart.options[c].value + \', \'; } } what.recur.value = \'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar( grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } ( \' + pkgparts + \' ) } $cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'',
+ },
+
+
+
+ 'sesmon_hour' => {
+ 'name' => 'Base charge plus charge per-hour from the session monitor',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included_hours' => { 'name' => 'Hours included',
+ 'default' => 0,
+ },
+ 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_flat', 'recur_included_hours', 'recur_hourly_charge' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; \' + what.recur_flat.value + \' + \' + what.recur_hourly_charge.value + \' * $hours;\'',
+ },
+
+ 'sesmon_minute' => {
+ 'name' => 'Base charge plus charge per-minute from the session monitor',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included_min' => { 'name' => 'Minutes included',
+ 'default' => 0,
+ },
+ 'recur_minly_charge' => { 'name' => 'Additional charge per minute',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [ 'setup_fee', 'recur_flat', 'recur_included_min', 'recur_minly_charge' ],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60 - \' + what.recur_included_min.value + \'; $min = 0 if $min < 0; \' + what.recur_flat.value + \' + \' + what.recur_minly_charge.value + \' * $min;\'',
+
+ },
+
+ 'sqlradacct_hour' => {
+ 'name' => 'Base charge plus charge per-hour (and for data) from an external sqlradius radacct table',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included_hours' => { 'name' => 'Hours included',
+ 'default' => 0,
+ },
+ 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
+ 'default' => 0,
+ },
+ 'recur_included_input' => { 'name' => 'Input megabytes included',
+ 'default' => 0,
+ },
+ 'recur_input_charge' => { 'name' =>
+ 'Additional charge per input megabyte',
+ 'default' => 0,
+ },
+ 'recur_included_output' => { 'name' => 'Output megabytes included',
+ 'default' => 0,
+ },
+ 'recur_output_charge' => { 'name' =>
+ 'Additional charge per output megabyte',
+ 'default' => 0,
+ },
+ 'recur_included_total' => { 'name' =>
+ 'Total input+output megabytes included',
+ 'default' => 0,
+ },
+ 'recur_total_charge' => { 'name' =>
+ 'Additional charge per input+output megabyte',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat recur_included_hours recur_hourly_charge recur_included_input recur_input_charge recur_included_output recur_output_charge recur_included_total recur_total_charge )],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $inputcharge = sprintf(\"%.2f\", \' + what.recur_input_charge.value + \' * $input); my $outputcharge = sprintf(\"%.2f\", \' + what.recur_output_charge.value + \' * $output); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); if ( \' + what.recur_total_charge.value + \' > 0 ) { push @details, \"Last month\\\'s data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\" } if ( \' + what.recur_input_charge.value + \' > 0 ) { push @details, \"Last month\\\'s download \". sprintf(\"%.1f\", $input). \" megs: \\\$$inputcharge\" } if ( \' + what.recur_output_charge.value + \' > 0 ) { push @details, \"Last month\\\'s upload \". sprintf(\"%.1f\", $output). \" megs: \\\$$outputcharge\" } if ( \' + what.recur_hourly_charge.value + \' > 0 ) { push @details, \"Last month\\\'s time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; } \' + what.recur_flat.value + \' + $hourscharge + $inputcharge + $outputcharge + $totalcharge ;\'',
+ },
+
+ 'sql_generic' => {
+ 'name' => 'Base charge plus a metered rate from a configurable SQL query',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included' => { 'name' => 'Units included',
+ 'default' => 0,
+ },
+ 'recur_unit_charge' => { 'name' => 'Additional charge per unit',
+ 'default' => 0,
+ },
+ 'datasrc' => { 'name' => 'DBI data source',
+ 'default' => '',
+ },
+ 'db_username' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'db_password' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'query' => { 'name' => 'SQL query',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat recur_included recur_unit_charge datasrc db_username db_password query )],
+ 'setup' => 'what.setup_fee.value',
+ # 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\") or die $DBI::errstr; \'',
+ 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\", \"\' + what.db_password.value + \'\" ) or die $DBI::errstr; my $sth = $dbh->prepare(\"\' + what.query.value + \'\") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq \"svc_domain\" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge.value + \';\'',
+ #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge + \';\'',
+ },
+
+
+
+ 'sql_external' => {
+ 'name' => 'Base charge plus additional fees for external services from a configurable SQL query',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'datasrc' => { 'name' => 'DBI data source',
+ 'default' => '',
+ },
+ 'db_username' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'db_password' => { 'name' => 'Database password',
+ 'default' => '',
+ },
+ 'query' => { 'name' => 'SQL query',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat datasrc db_username db_password query )],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => q!'my $dbh = DBI->connect("' + what.datasrc.value + '", "' + what.db_username.value + '", "' + what.db_password.value + '" ) or die $DBI::errstr; my $sth = $dbh->prepare("' + what.query.value + '") or die $dbh->errstr; my $price = ' + what.recur_flat.value + '; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc ){ my $id = $cust_svc->svc_x->id; $sth->execute($id) or die $sth->errstr; $price += $sth->fetchrow_arrayref->[0]; } $price;'!,
+
+ },
+
+;
+
+my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+ split("\n", $part_pkg->plandata );
+
+tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans;
+
+my @form_select = ();
+if ( $conf->exists('enable_taxclasses') ) {
+ push @form_select, 'taxclass';
+} else {
+ push @fixups, 'taxclass'; #hidden
+}
+
+my @form_radio = ();
+if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ push @form_radio, 'pkg_svc_primary';
+}
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => $part_pkg->plan,
+ 'options' => \%options,
+ 'form_name' => 'dummy',
+ 'form_action' => 'process/part_pkg.cgi',
+ 'form_text' => [ qw(pkg comment freq clone pkgnum pkgpart), @fixups ],
+ 'form_checkbox' => [ qw(setuptax recurtax disabled) ],
+ 'form_radio' => \@form_radio,
+ 'form_select' => \@form_select,
+ 'fixup_callback' => sub {
+ #my $ = @_;
+ my $html = '';
+ for my $p ( keys %plans ) {
+ $html .= "if ( what.plan.value == \"$p\" ) {
+ what.setup.value = $plans{$p}->{setup} ;
+ what.recur.value = $plans{$p}->{recur} ;
+ }\n";
+ }
+ $html;
+ },
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!.
+ ntable("#cccccc",2);
+ my $href = $plans{$layer}->{'fields'};
+ foreach my $field ( exists($plans{$layer}->{'fieldorder'})
+ ? @{$plans{$layer}->{'fieldorder'}}
+ : keys %{ $href }
+ ) {
+
+ $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
+
+ if ( ! exists($href->{$field}{'type'}) ) {
+ $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!.
+ ( exists($plandata{$field})
+ ? $plandata{$field}
+ : $href->{$field}{'default'} ).
+ qq!" onChange="fchanged(this)">!;
+ } elsif ( $href->{$field}{'type'} eq 'select_multiple' ) {
+ $html .= qq!<SELECT MULTIPLE NAME="$field" onChange="fchanged(this)">!;
+ foreach my $record (
+ qsearch( $href->{$field}{'select_table'},
+ $href->{$field}{'select_hash'} )
+ ) {
+ my $value = $record->getfield($href->{$field}{'select_key'});
+ $html .= qq!<OPTION VALUE="$value"!.
+ ( $plandata{$field} =~ /(^|, *)$value *(,|$)/
+ ? ' SELECTED'
+ : '' ).
+ '>'. $record->getfield($href->{$field}{'select_label'})
+ }
+ $html .= '</SELECT>';
+ }
+
+ $html .= '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'.
+ join(',', keys %{ $href } ). '">'.
+ '<BR><BR>';
+
+ $html .= '<INPUT TYPE="submit" VALUE="'.
+ ( $hashref->{pkgpart} ? "Apply changes" : "Add package" ).
+ '" onClick="fchanged(this)">';
+
+ $html .= '<BR><BR>don\'t edit this unless you know what you\'re doing '.
+ '<INPUT TYPE="button" VALUE="refresh expressions" '.
+ 'onClick="fchanged(this)">'.
+ ntable("#cccccc",2).
+ '<TR><TD>'.
+ '<FONT SIZE="1">Setup expression<BR>'.
+ '<INPUT TYPE="text" NAME="setup" SIZE="160" VALUE="'.
+ encode_entities($hashref->{setup}). '" onLoad="fchanged(this)">'.
+ '</FONT><BR>'.
+ '<FONT SIZE="1">Recurring espression<BR>'.
+ '<INPUT TYPE="text" NAME="recur" SIZE="160" VALUE="'.
+ encode_entities($hashref->{recur}). '" onLoad="fchanged(this)">'.
+ '</FONT>'.
+ '</TR></TD>'.
+ '</TABLE>';
+
+ $html;
+
+ },
+);
+
+%>
+
+<BR>
+Price plan <%= $widget->html %>
+ </BODY>
+</HTML>
diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi
new file mode 100755
index 0000000..f784dfa
--- /dev/null
+++ b/httemplate/edit/part_referral.cgi
@@ -0,0 +1,48 @@
+<!-- mason kludge -->
+<%
+
+my $part_referral;
+if ( $cgi->param('error') ) {
+ $part_referral = new FS::part_referral ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_referral')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $part_referral = qsearchs( 'part_referral', { 'refnum' => $1 } );
+} else { #adding
+ $part_referral = new FS::part_referral {};
+}
+my $action = $part_referral->refnum ? 'Edit' : 'Add';
+my $hashref = $part_referral->hashref;
+
+my $p1 = popurl(1);
+print header("$action Advertising source", menubar(
+ 'Main Menu' => popurl(2),
+ 'View all advertising sources' => popurl(2). "browse/part_referral.cgi",
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/part_referral.cgi" METHOD=POST>!;
+
+print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$hashref->{refnum}">!;
+#print "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)";
+
+print <<END;
+Advertising source <INPUT TYPE="text" NAME="referral" SIZE=32 VALUE="$hashref->{referral}">
+END
+
+print qq!<BR><INPUT TYPE="submit" VALUE="!,
+ $hashref->{refnum} ? "Apply changes" : "Add advertising source",
+ qq!">!;
+
+print <<END;
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
new file mode 100755
index 0000000..befd9b2
--- /dev/null
+++ b/httemplate/edit/part_svc.cgi
@@ -0,0 +1,332 @@
+<%
+my $part_svc;
+my $clone = '';
+my $error = '';
+if ( $cgi->param('magic') eq 'process' ) {
+
+ my $svcpart = $cgi->param('svcpart');
+ my $old = qsearchs('part_svc', { 'svcpart' => $svcpart }) if $svcpart;
+
+ $cgi->param( 'svc_acct__usergroup',
+ join(',', $cgi->param('svc_acct__usergroup') ) );
+
+ my $new = new FS::part_svc ( {
+ map {
+ $_, scalar($cgi->param($_));
+ # } qw(svcpart svc svcdb)
+ } ( fields('part_svc'),
+ map { my $svcdb = $_;
+ my @fields = fields($svcdb);
+ push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
+ map { ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' ) } @fields;
+ } grep defined( $FS::Record::dbdef->table($_) ),
+ qw( svc_acct svc_domain svc_forward svc_www svc_broadband )
+ )
+ } );
+
+ my %exportnums =
+ map { $_->exportnum => ( $cgi->param('exportnum'.$_->exportnum) || '') }
+ qsearch('part_export', {} );
+
+ if ( $svcpart ) {
+ $error = $new->replace($old, '1.3-COMPAT', [ 'usergroup' ], \%exportnums );
+ } else {
+ $error = $new->insert( [ 'usergroup' ], \%exportnums );
+ $svcpart = $new->getfield('svcpart');
+ }
+
+ unless ( $error ) { #no error, redirect
+ #print $cgi->redirect(popurl(3)."browse/part_svc.cgi");
+ print $cgi->redirect("${p}browse/part_svc.cgi");
+ myexit;
+ }
+
+ $part_svc = $new; #??
+ #$part_svc = new FS::part_svc ( {
+ # map { $_, scalar($cgi->param($_)) } fields('part_svc')
+ #} );
+
+} elsif ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone
+ #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query";
+ $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } )
+ or die "unknown svcpart: $1";
+ $clone = $part_svc->svcpart;
+ $part_svc->svcpart('');
+} elsif ( $cgi->keywords ) { #edit
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "malformed query: $query";
+ $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } )
+ or die "unknown svcpart: $1";
+} else { #adding
+ $part_svc = new FS::part_svc {};
+}
+
+my $action = $part_svc->svcpart ? 'Edit' : 'Add';
+my $hashref = $part_svc->hashref;
+# my $p_svcdb = $part_svc->svcdb || 'svc_acct';
+
+
+ #" onLoad=\"visualize()\""
+%>
+<!-- mason kludge -->
+<%= header("$action Service Definition",
+ menubar( 'Main Menu' => $p,
+ 'View all service definitions' => "${p}browse/part_svc.cgi"
+ ),
+ )
+%>
+
+<% if ( $error ) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $error %></FONT>
+<% } %>
+
+<FORM NAME="dummy">
+
+ Service Part #<%= $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %>
+<BR><BR>
+Service <INPUT TYPE="text" NAME="svc" VALUE="<%= $hashref->{svc} %>"><BR>
+Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR>
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $hashref->{svcpart} %>">
+<BR>
+Services are items you offer to your customers.
+<UL><LI>svc_acct - Shell accounts, POP mailboxes, SLIP/PPP and ISDN accounts
+ <LI>svc_domain - Domains
+ <LI>svc_forward - mail forwarding
+ <LI>svc_www - Virtual domain website
+ <LI>svc_broadband - Broadband/High-speed Internet service
+ <LI>svc_external - Externally-tracked service
+<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
+ <LI>svc_wo - Work orders (Partially unimplemented)
+-->
+</UL>
+For the selected table, you can give fields default or fixed (unchangable)
+values. For example, a SLIP/PPP account may have a default (or perhaps fixed)
+<B>slipip</B> of <B>0.0.0.0</B>, while a POP mailbox will probably have a fixed
+blank <B>slipip</B> as well as a fixed shell something like <B>/bin/true</B> or
+<B>/usr/bin/passwd</B>.
+<BR><BR>
+
+<%
+
+my %vfields;
+
+#these might belong somewhere else for other user interfaces
+#pry need to eventually create stuff that's shared amount UIs
+my $conf = new FS::Conf;
+my %defs = (
+ 'svc_acct' => {
+ 'dir' => 'Home directory',
+ 'uid' => 'UID (set to fixed and blank for dial-only)',
+ 'slipip' => 'IP address (Set to fixed and blank to disable dialin, or, set a value to be exported to RADIUS Framed-IP-Address. Use the special value <code>0e0</code> [zero e zero] to enable export to RADIUS without a Framed-IP-Address.)',
+# 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!,
+ 'popnum' => {
+ desc => 'Access number',
+ type => 'select',
+ select_table => 'svc_acct_pop',
+ select_key => 'popnum',
+ select_label => 'city',
+ },
+ 'username' => {
+ desc => 'Username',
+ type => 'disabled',
+ },
+ 'quota' => '',
+ '_password' => 'Password',
+ 'gid' => 'GID (when blank, defaults to UID)',
+ 'shell' => {
+ desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file)',
+ type =>'select',
+ select_list => [ $conf->config('shells') ],
+ },
+ 'finger' => 'GECOS',
+ 'domsvc' => {
+ desc =>'svcnum from svc_domain',
+ type =>'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain',
+ },
+ 'usergroup' => {
+ desc =>'ICRADIUS/FreeRADIUS groups',
+ type =>'radius_usergroup_selector',
+ },
+ },
+ 'svc_domain' => {
+ 'domain' => 'Domain',
+ },
+ 'svc_forward' => {
+ 'srcsvc' => 'service from which mail is to be forwarded',
+ 'dstsvc' => 'service to which mail is to be forwarded',
+ 'dst' => 'someone@another.domain.com to use when dstsvc is 0',
+ },
+# 'svc_charge' => {
+# 'amount' => 'amount',
+# },
+# 'svc_wo' => {
+# 'worker' => 'Worker',
+# '_date' => 'Date',
+# },
+ 'svc_www' => {
+ #'recnum' => '',
+ #'usersvc' => '',
+ },
+ 'svc_broadband' => {
+ 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.',
+ 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.',
+ 'ip_addr' => 'IP address. Leave blank for automatic assignment.',
+ 'blocknum' => 'Address block.',
+ },
+ 'svc_external' => {
+ #'id' => '',
+ #'title' => '',
+ },
+);
+
+ foreach my $svcdb (grep dbdef->table($_), keys %defs ) {
+ my $self = "FS::$svcdb"->new;
+ $vfields{$svcdb} = {};
+ foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them
+ my $pvf = $self->pvf($field);
+ my @list = $pvf->list;
+ if (scalar @list) {
+ $defs{$svcdb}->{$field} = { desc => $pvf->label,
+ type => 'select',
+ select_list => \@list };
+ } else {
+ $defs{$svcdb}->{$field} = $pvf->label;
+ } #endif
+ $vfields{$svcdb}->{$field} = $pvf;
+ warn "\$vfields{$svcdb}->{$field} = $pvf";
+ } #next $field
+ } #next $svcdb
+
+ my @dbs = $hashref->{svcdb}
+ ? ( $hashref->{svcdb} )
+ : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_external );
+
+ tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
+ my $widget = new HTML::Widgets::SelectLayers(
+ #'selected_layer' => $p_svcdb,
+ 'selected_layer' => $hashref->{svcdb} || 'svc_acct',
+ 'options' => \%svcdb,
+ 'form_name' => 'dummy',
+ #'form_action' => 'process/part_svc.cgi',
+ 'form_action' => 'part_svc.cgi', #self
+ 'form_text' => [ qw( magic svc svcpart ) ],
+ 'form_checkbox' => [ 'disabled' ],
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
+
+ my $columns = 3;
+ my $count = 0;
+ my @part_export =
+ map { qsearch( 'part_export', {exporttype => $_ } ) }
+ keys %{FS::part_export::export_info($layer)};
+ $html .= '<BR><BR>'. table().
+ table(). "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
+ foreach my $part_export ( @part_export ) {
+ $html .= '<TD><INPUT TYPE="checkbox"'.
+ ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" ';
+ $html .= 'CHECKED'
+ if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
+ && qsearchs( 'export_svc', {
+ exportnum => $part_export->exportnum,
+ svcpart => $clone || $part_svc->svcpart });
+ $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype.
+ ' to '. $part_export->machine. '</TD>';
+ $count++;
+ $html .= '</TR><TR>' unless $count % $columns;
+ }
+ $html .= '</TR></TABLE><BR><BR>';
+
+ $html .= table(). "<TH>Field</TH><TH COLSPAN=2>Modifier</TH>";
+ #yucky kludge
+ my @fields = defined( $FS::Record::dbdef->table($layer) )
+ ? grep { $_ ne 'svcnum' } fields($layer)
+ : ();
+ push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge
+ $part_svc->svcpart($clone) if $clone; #haha, undone below
+ foreach my $field (@fields) {
+ my $part_svc_column = $part_svc->part_svc_column($field);
+ my $value = $error
+ ? $cgi->param("${layer}__${field}")
+ : $part_svc_column->columnvalue;
+ my $flag = $error
+ ? $cgi->param("${layer}__${field}_flag")
+ : $part_svc_column->columnflag;
+ my $def = $defs{$layer}{$field};
+ my $desc = ref($def) ? $def->{desc} : $def;
+
+ $html .= "<TR><TD>$field";
+ $html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc;
+ $html .= "</TD>";
+ $flag = '' if ref($def) && $def->{type} eq 'disabled';
+ $html .=
+ qq!<TD><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE=""!.
+ ' CHECKED'x($flag eq ''). ">Off</TD>".
+ '<TD>';
+ unless ( ref($def) && $def->{type} eq 'disabled' ) {
+ $html .=
+ qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="D"!.
+ ' CHECKED'x($flag eq 'D'). ">Default ".
+ qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="F"!.
+ ' CHECKED'x($flag eq 'F'). ">Fixed ";
+ $html .= '<BR>';
+ }
+ if ( ref($def) ) {
+ if ( $def->{type} eq 'select' ) {
+ $html .= qq!<SELECT NAME="${layer}__${field}">!;
+ $html .= '<OPTION> </OPTION>' unless $value;
+ if ( $def->{select_table} ) {
+ foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
+ my $rvalue = $record->getfield($def->{select_key});
+ $html .= qq!<OPTION VALUE="$rvalue"!.
+ ( $rvalue==$value ? ' SELECTED>' : '>' ).
+ $record->getfield($def->{select_label}). '</OPTION>';
+ } #next $record
+ } else { # select_list
+ foreach my $item ( @{$def->{select_list}} ) {
+ $html .= qq!<OPTION VALUE="$item"!.
+ ( $item eq $value ? ' SELECTED>' : '>' ).
+ $item. '</OPTION>';
+ } #next $item
+ } #endif
+ $html .= '</SELECT>';
+ } elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
+ $html .= FS::svc_acct::radius_usergroup_selector(
+ [ split(',', $value) ], "${layer}__${field}" );
+ } elsif ( $def->{type} eq 'disabled' ) {
+ $html .=
+ qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
+ } else {
+ $html .= '<font color="#ff0000">unknown type'. $def->{type};
+ }
+ } else {
+ $html .=
+ qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value">!;
+ }
+
+ if($vfields{$layer}->{$field}) {
+ $html .= qq!<BR><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="X"!.
+ ' CHECKED'x($flag eq 'X'). ">Excluded ";
+ }
+ $html .= "</TD></TR>\n";
+ }
+ $part_svc->svcpart('') if $clone; #undone
+ $html .= "</TABLE>";
+
+ $html .= '<BR><INPUT TYPE="submit" VALUE="'.
+ ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
+
+ $html;
+
+ },
+ );
+
+%>
+Table <%= $widget->html %>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi
new file mode 100644
index 0000000..fb10321
--- /dev/null
+++ b/httemplate/edit/part_virtual_field.cgi
@@ -0,0 +1,92 @@
+<!-- mason kludge -->
+<%
+my ($vfieldpart, $part_virtual_field);
+
+if ( $cgi->param('error') ) {
+ $part_virtual_field = new FS::part_virtual_field ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')});
+ $vfieldpart = $part_virtual_field->vfieldpart;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $vfieldpart=$1;
+ $part_virtual_field=qsearchs('part_virtual_field',
+ {'vfieldpart' => $vfieldpart})
+ or die "Unknown vfieldpart!";
+
+ } else { #adding
+ $part_virtual_field = new FS::part_virtual_field({});
+ }
+}
+my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+print header("$action Virtual Field Definition", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+%>
+<FORM ACTION="<%=$p1%>process/generic.cgi" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="table" VALUE="part_virtual_field">
+<INPUT TYPE="hidden" NAME="redirect_ok"
+ VALUE="<%=popurl(2)%>browse/part_virtual_field.cgi">
+<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<%=
+ $vfieldpart%>">
+Field #<B><%=$vfieldpart or "(NEW)"%></B><BR><BR>
+
+<%=ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Name</TD>
+ <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=15 VALUE="<%=
+ $part_virtual_field->name%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Table</TD>
+ <TD><% if ($action eq 'Add') { %>
+ <SELECT SIZE=1 NAME="dbtable"><%
+ my $dbdef = dbdef; # ick
+ foreach my $dbtable (sort { $a cmp $b } $dbdef->tables) {
+ if ($dbtable !~ /^h_/
+ and $dbdef->table($dbtable)->primary_key) { %>
+ <OPTION VALUE="<%=$dbtable%>"><%=$dbtable%></OPTION><%
+ }
+ }
+ %></SELECT><%
+ } else { # Edit
+ %><%=$part_virtual_field->dbtable%>
+ <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%=$part_virtual_field->dbtable%>">
+ <% } %>
+ </TD>
+ <TR>
+ <TD ALIGN="right">Label</TD>
+ <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="20" VALUE="<%=
+ $part_virtual_field->label%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Length</TD>
+ <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<%=
+ $part_virtual_field->length%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Check</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><%=
+ $part_virtual_field->check_block%></TEXTAREA></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">List source</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><%=
+ $part_virtual_field->list_source%></TEXTAREA></TD>
+ </TR>
+</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<BR><BR>
+<FONT SIZE=-2>If you don't understand what <I>check_block</I> and
+<I>list_source</I> mean, <B>LEAVE THEM BLANK</B>. We mean it.</FONT>
+
+
+</BODY>
+</HTML>
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
new file mode 100755
index 0000000..3d697dd
--- /dev/null
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -0,0 +1,24 @@
+<%
+
+my $pkgnum = $cgi->param('pkgnum') or die;
+my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $old->hash;
+$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : '';
+$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : '';
+$hash{'last_bill'} =
+ $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : '';
+$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : '';
+my $new = new FS::cust_pkg \%hash;
+
+my $error = $new->replace($old);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string );
+} else {
+ my $custnum = $new->custnum;
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum".
+ "#cust_pkg$pkgnum" );
+}
+
+%>
diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi
new file mode 100755
index 0000000..34d799c
--- /dev/null
+++ b/httemplate/edit/process/addr_block/add.cgi
@@ -0,0 +1,20 @@
+<%
+
+my $error = '';
+my $ip_gateway = $cgi->param('ip_gateway');
+my $ip_netmask = $cgi->param('ip_netmask');
+
+my $new = new FS::addr_block {
+ ip_gateway => $ip_gateway,
+ ip_netmask => $ip_netmask,
+ routernum => 0 };
+
+$error = $new->insert;
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+}
+%>
diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi
new file mode 100755
index 0000000..85b0d7a
--- /dev/null
+++ b/httemplate/edit/process/addr_block/allocate.cgi
@@ -0,0 +1,25 @@
+<%
+my $error = '';
+my $blocknum = $cgi->param('blocknum');
+my $routernum = $cgi->param('routernum');
+
+my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+my $router = qsearchs('router', { routernum => $routernum });
+
+if($addr_block) {
+ if ($router) {
+ $error = $addr_block->allocate($router);
+ } else {
+ $error = "Cannot find router with routernum $routernum";
+ }
+} else {
+ $error = "Cannot find block with blocknum $blocknum";
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+} else {
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+}
+%>
diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi
new file mode 100755
index 0000000..cfb7ed0
--- /dev/null
+++ b/httemplate/edit/process/addr_block/deallocate.cgi
@@ -0,0 +1,24 @@
+<%
+my $error = '';
+my $blocknum = $cgi->param('blocknum');
+
+my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+
+if($addr_block) {
+ my $router = $addr_block->router;
+ if ($router) {
+ $error = $addr_block->deallocate($router);
+ } else {
+ $error = "Block is not allocated to a router";
+ }
+} else {
+ $error = "Cannot find block with blocknum $blocknum";
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+} else {
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+}
+%>
diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi
new file mode 100755
index 0000000..bb6d4ba
--- /dev/null
+++ b/httemplate/edit/process/addr_block/split.cgi
@@ -0,0 +1,19 @@
+<%
+my $error = '';
+my $blocknum = $cgi->param('blocknum');
+my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+
+if ( $addr_block) {
+ $error = $addr_block->split_block;
+} else {
+ $error = "Unknown blocknum: $blocknum";
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+}
+%>
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
new file mode 100755
index 0000000..182eeab
--- /dev/null
+++ b/httemplate/edit/process/agent.cgi
@@ -0,0 +1,28 @@
+<%
+
+my $agentnum = $cgi->param('agentnum');
+
+my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum;
+
+my $new = new FS::agent ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('agent')
+} );
+
+my $error;
+if ( $agentnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $agentnum=$new->getfield('agentnum');
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/agent.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi
new file mode 100755
index 0000000..5165945
--- /dev/null
+++ b/httemplate/edit/process/agent_type.cgi
@@ -0,0 +1,55 @@
+<%
+
+my $typenum = $cgi->param('typenum');
+my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum;
+
+my $new = new FS::agent_type ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('agent_type')
+} );
+
+my $error;
+if ( $typenum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $typenum=$new->getfield('typenum');
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string );
+} else {
+
+ #false laziness w/ edit/process/part_svc.cgi
+ foreach my $part_pkg (qsearch('part_pkg',{})) {
+ my($pkgpart)=$part_pkg->getfield('pkgpart');
+
+ my($type_pkgs)=qsearchs('type_pkgs',{
+ 'typenum' => $typenum,
+ 'pkgpart' => $pkgpart,
+ });
+ if ( $type_pkgs && ! $cgi->param("pkgpart$pkgpart") ) {
+ my($d_type_pkgs)=$type_pkgs; #need to save $type_pkgs for below.
+ $error=$d_type_pkgs->delete;
+ die $error if $error;
+
+ } elsif ( $cgi->param("pkgpart$pkgpart")
+ && ! $type_pkgs
+ ) {
+ #ok to clobber it now (but bad form nonetheless?)
+ $type_pkgs=new FS::type_pkgs ({
+ 'typenum' => $typenum,
+ 'pkgpart' => $pkgpart,
+ });
+ $error= $type_pkgs->insert;
+ die $error if $error;
+ }
+
+ }
+
+ print $cgi->redirect(popurl(3). "browse/agent_type.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi
new file mode 100755
index 0000000..0025b16
--- /dev/null
+++ b/httemplate/edit/process/cust_bill_pay.cgi
@@ -0,0 +1,43 @@
+<%
+
+$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } )
+ or die "No such paynum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } )
+ or die "Bogus credit: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+ $new = new FS::cust_refund ( {
+ 'reason' => 'Refunding payment', #enter reason in UI
+ 'refund' => $cgi->param('amount'),
+ 'payby' => 'BILL',
+ #'_date' => $cgi->param('_date'),
+ 'payinfo' => 'Cash', #enter payinfo in UI
+ 'paynum' => $paynum,
+ } );
+} else {
+ $new = new FS::cust_bill_pay ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(custnum _date amount invnum)
+ } fields('cust_bill_pay')
+ } );
+}
+
+my $error = $new->insert;
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+}
+
+
+%>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
new file mode 100755
index 0000000..85bfd44
--- /dev/null
+++ b/httemplate/edit/process/cust_credit.cgi
@@ -0,0 +1,26 @@
+<%
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+
+my $new = new FS::cust_credit ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_credit')
+} );
+
+my $error = $new->insert;
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string );
+} else {
+ if ( $cgi->param('apply') eq 'yes' ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum })
+ or die "unknown custnum $custnum";
+ $cust_main->apply_credits;
+ }
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+}
+
+%>
diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi
new file mode 100755
index 0000000..23e2e6c
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_bill.cgi
@@ -0,0 +1,43 @@
+<%
+
+$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } )
+ or die "No such crednum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } )
+ or die "Bogus credit: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+ $new = new FS::cust_refund ( {
+ 'reason' => $cust_credit->reason,
+ 'refund' => $cgi->param('amount'),
+ 'payby' => 'BILL',
+ #'_date' => $cgi->param('_date'),
+ 'payinfo' => 'Cash',
+ 'crednum' => $crednum,
+ } );
+} else {
+ $new = new FS::cust_credit_bill ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(custnum _date amount invnum)
+ } fields('cust_credit_bill')
+ } );
+}
+
+my $error = $new->insert;
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+}
+
+
+%>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
new file mode 100755
index 0000000..0b13ba9
--- /dev/null
+++ b/httemplate/edit/process/cust_main.cgi
@@ -0,0 +1,131 @@
+<%
+
+my $error = '';
+
+#unmunge stuff
+
+$cgi->param('tax','') unless defined $cgi->param('tax');
+
+$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
+
+my $payby = $cgi->param('payby');
+if ( $payby ) {
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ $cgi->param('payinfo',
+ $cgi->param($payby. '_payinfo1'). '@'. $cgi->param($payby. '_payinfo2') );
+ } else {
+ $cgi->param('payinfo', $cgi->param( $payby. '_payinfo' ) );
+ }
+ $cgi->param('paydate',
+ $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' ) );
+ $cgi->param('payname', $cgi->param( $payby. '_payname' ) );
+ $cgi->param('paycvv', $cgi->param( $payby. '_paycvv' ) )
+ if defined $cgi->param( $payby. '_paycvv' );
+}
+
+my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
+push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+$cgi->param('invoicing_list', join(',', @invoicing_list) );
+
+
+#create new record object
+
+my $new = new FS::cust_main ( {
+ map {
+ $_, scalar($cgi->param($_))
+# } qw(custnum agentnum last first ss company address1 address2 city county
+# state zip daytime night fax payby payinfo paydate payname tax
+# otaker refnum)
+ } fields('cust_main')
+} );
+
+if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) {
+ $new->setfield("ship_$_", '') foreach qw(
+ last first company address1 address2 city county state zip
+ country daytime night fax
+ );
+}
+
+#perhaps this stuff should go to cust_main.pm
+my $cust_pkg = '';
+my $svc_acct = '';
+if ( $new->custnum eq '' ) {
+
+ if ( $cgi->param('pkgpart_svcpart') ) {
+ my $x = $cgi->param('pkgpart_svcpart');
+ $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n";
+ my($pkgpart, $svcpart) = ($1, $2);
+ #false laziness: copied from FS::cust_pkg::order (which should become a
+ #FS::cust_main method)
+ my(%part_pkg);
+ # generate %part_pkg
+ # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart
+ my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum });
+ #my($type_pkgs);
+ #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+ # my($pkgpart)=$type_pkgs->pkgpart;
+ # $part_pkg{$pkgpart}++;
+ #}
+ # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
+ my $pkgpart_href = $agent->pkgpart_hashref;
+ #eslaf
+
+ # this should wind up in FS::cust_pkg!
+ $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ".
+ "purchase pkgpart ". $pkgpart
+ #unless $part_pkg{ $pkgpart };
+ unless $pkgpart_href->{ $pkgpart };
+
+ $cust_pkg = new FS::cust_pkg ( {
+ #later 'custnum' => $custnum,
+ 'pkgpart' => $pkgpart,
+ } );
+ $error ||= $cust_pkg->check;
+
+ #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } );
+
+ #$error ||= $cust_svc->check;
+
+ $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ 'username' => $cgi->param('username'),
+ '_password' => $cgi->param('_password'),
+ 'popnum' => $cgi->param('popnum'),
+ } );
+
+ my $y = $svc_acct->setdefault; # arguably should be in new method
+ $error ||= $y unless ref($y);
+ #and just in case you were silly
+ $svc_acct->svcpart($svcpart);
+ $svc_acct->username($cgi->param('username'));
+ $svc_acct->_password($cgi->param('_password'));
+ $svc_acct->popnum($cgi->param('popnum'));
+
+ $error ||= $svc_acct->check;
+
+ } elsif ( $cgi->param('username') ) { #good thing to catch
+ $error = "Can't assign username without a package!";
+ }
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg;
+ $error ||= $new->insert( \%hash, \@invoicing_list );
+} else { #create old record object
+ my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );
+ $error ||= "Old record not found!" unless $old;
+ if ( defined dbdef->table('cust_main')->column('paycvv')
+ && length($old->paycvv)
+ && $new->paycvv =~ /^\s*\*+\s*$/ ) {
+ $new->paycvv($old->paycvv);
+ }
+ $error ||= $new->replace($old, \@invoicing_list);
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum);
+}
+%>
diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi
new file mode 100755
index 0000000..5da9dea
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-collapse.cgi
@@ -0,0 +1,35 @@
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ or die "Illegal taxnum!";
+my $taxnum = $1;
+my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+ or die "Unknown taxnum $taxnum";
+
+#really should do this in a .pm & start transaction
+
+foreach my $delete ( qsearch('cust_main_county', {
+ 'country' => $cust_main_county->country,
+ 'state' => $cust_main_county->state
+ } ) ) {
+# unless ( qsearch('cust_main',{
+# 'state' => $cust_main_county->getfield('state'),
+# 'county' => $cust_main_county->getfield('county'),
+# 'country' => $cust_main_county->getfield('country'),
+# } ) ) {
+ my $error = $delete->delete;
+ die $error if $error;
+# } else {
+ #should really fix the $cust_main record
+# }
+
+}
+
+$cust_main_county->taxnum('');
+$cust_main_county->county('');
+my $error = $cust_main_county->insert;
+die $error if $error;
+
+print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
+
+%>
diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi
new file mode 100755
index 0000000..a452711
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-expand.cgi
@@ -0,0 +1,58 @@
+<%
+
+$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!";
+my $taxnum = $1;
+my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ or die ("Unknown taxnum!");
+
+my @expansion;
+if ( $cgi->param('delim') eq 'n' ) {
+ @expansion=split(/\n/,$cgi->param('expansion'));
+} elsif ( $cgi->param('delim') eq 's' ) {
+ @expansion=split(' ',$cgi->param('expansion'));
+} else {
+ die "Illegal delim!";
+}
+
+@expansion=map {
+ unless ( /^\s*([\w\- ]+)\s*$/ ) {
+ $cgi->param('error', "Illegal item in expansion");
+ print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string );
+ myexit();
+ }
+ $1;
+} @expansion;
+
+foreach ( @expansion) {
+ my(%hash)=$cust_main_county->hash;
+ my($new)=new FS::cust_main_county \%hash;
+ $new->setfield('taxnum','');
+ if ( $cgi->param('taxclass') ) {
+ $new->setfield('taxclass', $_);
+ } elsif ( ! $cust_main_county->state ) {
+ $new->setfield('state',$_);
+ } else {
+ $new->setfield('county',$_);
+ }
+ #if (datasrc =~ m/Pg/)
+ #{
+ # $new->setfield('tax',0.0);
+ #}
+ my($error)=$new->insert;
+ die $error if $error;
+}
+
+unless ( qsearch( 'cust_main', {
+ 'state' => $cust_main_county->state,
+ 'county' => $cust_main_county->county,
+ 'country' => $cust_main_county->country,
+ } )
+ || ! @expansion
+) {
+ my($error)=($cust_main_county->delete);
+ die $error if $error;
+}
+
+print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
+
+%>
diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi
new file mode 100755
index 0000000..9287ed1
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county.cgi
@@ -0,0 +1,30 @@
+<%
+
+foreach ( grep { /^tax\d+$/ } $cgi->param ) {
+ /^tax(\d+)$/ or die "Illegal form $_!";
+ my $taxnum = $1;
+ my $old = qsearchs('cust_main_county', { 'taxnum' => $taxnum })
+ or die "Couldn't find taxnum $taxnum!";
+ next unless $old->tax != $cgi->param("tax$taxnum")
+ || $old->exempt_amount != $cgi->param("exempt_amount$taxnum")
+ || $old->taxname ne $cgi->param("taxname$taxnum")
+ || $old->setuptax ne $cgi->param("setuptax$taxnum")
+ || $old->recurtax ne $cgi->param("recurtax$taxnum");
+ my %hash = $old->hash;
+ $hash{tax} = $cgi->param("tax$taxnum");
+ $hash{exempt_amount} = $cgi->param("exempt_amount$taxnum");
+ $hash{taxname} = $cgi->param("taxname$taxnum");
+ $hash{setuptax} = $cgi->param("setuptax$taxnum");
+ $hash{recurtax} = $cgi->param("recurtax$taxnum");
+ my $new = new FS::cust_main_county \%hash;
+ my $error = $new->replace($old);
+ if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_main_county.cgi?". $cgi->query_string );
+ myexit();
+ }
+}
+
+print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
+
+%>
diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi
new file mode 100755
index 0000000..82442ae
--- /dev/null
+++ b/httemplate/edit/process/cust_pay.cgi
@@ -0,0 +1,39 @@
+<%
+
+$cgi->param('linknum') =~ /^(\d+)$/
+ or die "Illegal linknum: ". $cgi->param('linknum');
+my $linknum = $1;
+
+$cgi->param('link') =~ /^(custnum|invnum)$/
+ or die "Illegal link: ". $cgi->param('link');
+my $link = $1;
+
+my $new = new FS::cust_pay ( {
+ $link => $linknum,
+ map {
+ $_, scalar($cgi->param($_));
+ } qw(paid _date payby payinfo paybatch)
+ #} fields('cust_pay')
+} );
+
+my $error = $new->insert;
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string );
+} elsif ( $link eq 'invnum' ) {
+ print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum");
+} elsif ( $link eq 'custnum' ) {
+ if ( $cgi->param('apply') eq 'yes' ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum })
+ or die "unknown custnum $linknum";
+ $cust_main->apply_payments;
+ }
+ if ( $cgi->param('quickpay') eq 'yes' ) {
+ print $cgi->redirect(popurl(3). "search/cust_main-quickpay.html");
+ } else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum");
+ }
+}
+
+%>
diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
new file mode 100755
index 0000000..df8471c
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg.cgi
@@ -0,0 +1,43 @@
+<%
+
+my $error = '';
+
+#untaint custnum
+$cgi->param('custnum') =~ /^(\d+)$/;
+my $custnum = $1;
+
+my @remove_pkgnums = map {
+ /^(\d+)$/ or die "Illegal remove_pkg value!";
+ $1;
+} $cgi->param('remove_pkg');
+
+my $error_redirect;
+my @pkgparts;
+if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi
+ $error_redirect = "misc/change_pkg.cgi";
+ @pkgparts = ($1);
+} else { #came from edit/cust_pkg.cgi
+ $error_redirect = "edit/cust_pkg.cgi";
+ foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) {
+ if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) {
+ my $num_pkgs = $1;
+ while ( $num_pkgs-- ) {
+ push @pkgparts,$pkgpart;
+ }
+ } else {
+ $error = "Illegal quantity";
+ last;
+ }
+ }
+}
+
+$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums);
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+}
+
+%>
diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi
new file mode 100755
index 0000000..7055d8e
--- /dev/null
+++ b/httemplate/edit/process/cust_refund.cgi
@@ -0,0 +1,42 @@
+<%
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum";
+
+my $error = '';
+if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) {
+ my %payby2bop = (
+ 'CARD' => 'CC',
+ 'CHEK' => 'ECHECK',
+ );
+ my $bop = $payby2bop{$1};
+ $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/
+ or die "illegal refund amount ". $cgi->param('refund');
+ my $refund = "$1$2";
+ $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
+ my $paynum = $1;
+ my $reason = $cgi->param('reason');
+ $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund,
+ 'paynum' => $paynum,
+ 'reason' => $reason, );
+} else {
+ die 'unimplemented';
+ #my $new = new FS::cust_refund ( {
+ # map {
+ # $_, scalar($cgi->param($_));
+ # } ( fields('cust_refund'), 'paynum' )
+ #} );
+ #$error = $new->insert;
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+}
+
+%>
diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi
new file mode 100644
index 0000000..187ede5
--- /dev/null
+++ b/httemplate/edit/process/cust_svc.cgi
@@ -0,0 +1,30 @@
+<%
+
+my $svcnum = $cgi->param('svcnum');
+
+my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::cust_svc ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_svc')
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $svcnum=$new->getfield('svcnum');
+}
+
+if ( $error ) {
+ #$cgi->param('error', $error);
+ #print $cgi->redirect(popurl(2). "cust_svc.cgi?". $cgi->query_string );
+ eidiot($error);
+} else {
+ my $svcdb = $new->part_svc->svcdb;
+ print $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum");
+}
+
+
diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi
new file mode 100755
index 0000000..b8c3f62
--- /dev/null
+++ b/httemplate/edit/process/domain_record.cgi
@@ -0,0 +1,34 @@
+<%
+
+my $recnum = $cgi->param('recnum');
+
+my $old = qsearchs('agent',{'recnum'=>$recnum}) if $recnum;
+
+my $new = new FS::domain_record ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('domain_record')
+} );
+
+my $error;
+if ( $recnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $recnum=$new->getfield('recnum');
+}
+
+if ( $error ) {
+# $cgi->param('error', $error);
+# print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string );
+ #no edit screen to send them back to
+%>
+<!-- mason kludge -->
+<%
+ eidiot($error);
+} else {
+ my $svcnum = $new->svcnum;
+ print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi
new file mode 100644
index 0000000..9c54feb
--- /dev/null
+++ b/httemplate/edit/process/generic.cgi
@@ -0,0 +1,70 @@
+<%
+
+# Welcome to generic.cgi.
+#
+# This script provides a generic edit/process/ backend for simple table
+# editing. All it knows how to do is take the values entered into
+# the script and insert them into the table specified by $cgi->param('table').
+# If there's an existing record with the same primary key, it will be
+# replaced. (Deletion will be added in the future.)
+#
+# Special cgi params for this script:
+# table: the name of the table to be edited. The script will die horribly
+# if it can't find the table.
+# redirect_ok: URL to be displayed after a successful edit. The value of
+# the record's primary key will be passed as a keyword.
+# Defaults to (freeside root)/view/$table.cgi.
+# redirect_error: URL to be displayed if there's an error. The original
+# query string, plus the error message, will be passed.
+# Defaults to $cgi->referer() (i.e. go back where you
+# came from).
+
+
+use FS::Record qw(qsearchs dbdef);
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+
+
+my $error;
+my $p2 = popurl(2);
+my $p3 = popurl(3);
+my $table = $cgi->param('table');
+my $dbdef = dbdef or die "Cannot fetch dbdef!";
+
+my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table";
+
+my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table";
+my $pkey_val = $cgi->param($pkey);
+
+
+#warn "new FS::Record ( $table, (hashref) )";
+my $new = FS::Record::new ( "FS::$table", {
+ map { $_, scalar($cgi->param($_)) } fields($table)
+} );
+
+#warn 'created $new of class '.ref($new);
+
+if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) {
+ # edit
+ $error = $new->replace($old);
+} else {
+ #add
+ $error = $new->insert;
+ $pkey_val = $new->getfield($pkey);
+ # New records usually don't have their primary keys set until after
+ # they've been checked/inserted, so grab the new $pkey_val so we can
+ # redirect to it.
+}
+
+my $redirect_ok = (($cgi->param('redirect_ok')) ?
+ $cgi->param('redirect_ok') : $p3."browse/generic.cgi?$table");
+my $redirect_error = (($cgi->param('redirect_error')) ?
+ $cgi->param('redirect_error') : $cgi->referer());
+
+if($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect($redirect_error . '?' . $cgi->query_string);
+} else {
+ print $cgi->redirect($redirect_ok);
+}
+%>
diff --git a/httemplate/edit/process/msgcat.cgi b/httemplate/edit/process/msgcat.cgi
new file mode 100644
index 0000000..1f94f66
--- /dev/null
+++ b/httemplate/edit/process/msgcat.cgi
@@ -0,0 +1,20 @@
+<%
+
+my $error;
+foreach my $param ( grep { /^\d+$/ } $cgi->param ) {
+ my $old = qsearchs('msgcat', { msgnum=>$param } );
+ next if $old->msg eq $cgi->param($param); #no need to update identical records
+ my $new = new FS::msgcat { $old->hash };
+ $new->msg($cgi->param($param));
+ $error = $new->replace($old);
+ last if $error;
+}
+
+if ( $error ) {
+ $cgi->param('error',$error);
+ print $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/msgcat.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi
new file mode 100755
index 0000000..77dcd24
--- /dev/null
+++ b/httemplate/edit/process/part_bill_event.cgi
@@ -0,0 +1,54 @@
+<%
+
+my $eventpart = $cgi->param('eventpart');
+
+my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart;
+
+#s/days/seconds/
+$cgi->param('seconds', int( $cgi->param('days') * 86400 ) );
+
+my $error;
+if ( ! $cgi->param('plan_weight_eventcode') ) {
+ $error = "Must select an action";
+} else {
+
+ $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s
+ or die "illegal plan_weight_eventcode:".
+ $cgi->param('plan_weight_eventcode');
+ $cgi->param('plan', $1);
+ $cgi->param('weight', $2);
+ my $eventcode = $3;
+ my $plandata = '';
+ while ( $eventcode =~ /%%%(\w+)%%%/ ) {
+ my $field = $1;
+ my $value = join(', ', $cgi->param($field) );
+ $cgi->param($field, $value); #in case it errors out
+ $eventcode =~ s/%%%$field%%%/$value/;
+ $plandata .= "$field $value\n";
+ }
+ $cgi->param('eventcode', $eventcode);
+ $cgi->param('plandata', $plandata);
+
+ my $new = new FS::part_bill_event ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_bill_event'),
+ } );
+
+ if ( $eventpart ) {
+ $error = $new->replace($old);
+ } else {
+ $error = $new->insert;
+ $eventpart = $new->getfield('eventpart');
+ }
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3)."browse/part_bill_event.cgi");
+}
+
+%>
+
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
new file mode 100644
index 0000000..fa009ed
--- /dev/null
+++ b/httemplate/edit/process/part_export.cgi
@@ -0,0 +1,39 @@
+<%
+
+my $exportnum = $cgi->param('exportnum');
+
+my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum;
+
+#fixup options
+#warn join('-', split(',',$cgi->param('options')));
+my %options = map {
+ my $value = $cgi->param($_);
+ $value =~ s/\r\n/\n/g; #browsers? (textarea)
+ $_ => $value;
+} split(',', $cgi->param('options'));
+
+my $new = new FS::part_export ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_export')
+} );
+
+my $error;
+if ( $exportnum ) {
+ #warn $old;
+ #warn $exportnum;
+ #warn $new->machine;
+ $error = $new->replace($old,\%options);
+} else {
+ $error = $new->insert(\%options);
+# $exportnum = $new->exportnum;
+}
+
+if ( $error ) {
+ $cgi->param('error', $error );
+ print $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/part_export.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
new file mode 100755
index 0000000..7eada7b
--- /dev/null
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -0,0 +1,117 @@
+<%
+
+my $dbh = dbh;
+
+my $pkgpart = $cgi->param('pkgpart');
+
+my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart;
+
+#fixup plandata
+my $plandata = $cgi->param('plandata');
+my @plandata = split(',', $plandata);
+$cgi->param('plandata',
+ join('', map { "$_=". join(', ', $cgi->param($_)). "\n" } @plandata )
+);
+
+foreach (qw( setuptax recurtax disabled )) {
+ $cgi->param($_, '') unless defined $cgi->param($_);
+}
+
+my $new = new FS::part_pkg ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_pkg')
+} );
+
+#warn "setuptax: ". $new->setuptax;
+#warn "recurtax: ". $new->recurtax;
+
+#most of the stuff below should move to part_pkg.pm
+
+foreach my $part_svc ( qsearch('part_svc', {} ) ) {
+ my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0;
+ unless ( $quantity =~ /^(\d+)$/ ) {
+ $cgi->param('error', "Illegal quantity" );
+ print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string );
+ myexit();
+ }
+}
+
+local $SIG{HUP} = 'IGNORE';
+local $SIG{INT} = 'IGNORE';
+local $SIG{QUIT} = 'IGNORE';
+local $SIG{TERM} = 'IGNORE';
+local $SIG{TSTP} = 'IGNORE';
+local $SIG{PIPE} = 'IGNORE';
+
+local $FS::UID::AutoCommit = 0;
+
+my $error;
+if ( $pkgpart ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $pkgpart=$new->pkgpart;
+}
+if ( $error ) {
+ $dbh->rollback;
+ $cgi->param('error', $error );
+ print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string );
+ myexit();
+}
+
+foreach my $part_svc (qsearch('part_svc',{})) {
+ my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0;
+ my $primary_svc =
+ $cgi->param('pkg_svc_primary') == $part_svc->svcpart ? 'Y' : '';
+ my $old_pkg_svc = qsearchs('pkg_svc', {
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ } );
+ my $old_quantity = $old_pkg_svc ? $old_pkg_svc->quantity : 0;
+ my $old_primary_svc =
+ ( $old_pkg_svc && $old_pkg_svc->dbdef_table->column('primary_svc') )
+ ? $old_pkg_svc->primary_svc
+ : '';
+ next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc;
+
+ my $new_pkg_svc = new FS::pkg_svc( {
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ 'quantity' => $quantity,
+ 'primary_svc' => $primary_svc,
+ } );
+ if ( $old_pkg_svc ) {
+ my $myerror = $new_pkg_svc->replace($old_pkg_svc);
+ if ( $myerror ) {
+ $dbh->rollback;
+ die $myerror;
+ }
+ } else {
+ my $myerror = $new_pkg_svc->insert;
+ if ( $myerror ) {
+ $dbh->rollback;
+ die $myerror;
+ }
+ }
+}
+
+unless ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $dbh->commit or die $dbh->errstr;
+ print $cgi->redirect(popurl(3). "browse/part_pkg.cgi");
+} else {
+ my($old_cust_pkg) = qsearchs( 'cust_pkg', { 'pkgnum' => $1 } );
+ my %hash = $old_cust_pkg->hash;
+ $hash{'pkgpart'} = $pkgpart;
+ my($new_cust_pkg) = new FS::cust_pkg \%hash;
+ my $myerror = $new_cust_pkg->replace($old_cust_pkg);
+ if ( $myerror ) {
+ $dbh->rollback;
+ die "Error modifying cust_pkg record: $myerror\n";
+ }
+
+ $dbh->commit or die $dbh->errstr;
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new_cust_pkg->custnum);
+}
+
+%>
diff --git a/httemplate/edit/process/part_referral.cgi b/httemplate/edit/process/part_referral.cgi
new file mode 100755
index 0000000..fd2c015
--- /dev/null
+++ b/httemplate/edit/process/part_referral.cgi
@@ -0,0 +1,28 @@
+<%
+
+my $refnum = $cgi->param('refnum');
+
+my $new = new FS::part_referral ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_referral')
+} );
+
+my $error;
+if ( $refnum ) {
+ my $old = qsearchs( 'part_referral', { 'refnum' =>$ refnum } );
+ die "(Old) Record not found!" unless $old;
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+}
+$refnum=$new->refnum;
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "part_referral.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/part_referral.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi
new file mode 100644
index 0000000..477f585
--- /dev/null
+++ b/httemplate/edit/process/quick-charge.cgi
@@ -0,0 +1,32 @@
+<%
+
+#untaint custnum
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+
+$cgi->param('amount') =~ /^\s*(\d+(\.\d{1,2})?)\s*$/
+ or die 'illegal amount '. $cgi->param('amount');
+my $amount = $1;
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum";
+
+my $error = $cust_main->charge(
+ $amount,
+ $cgi->param('pkg'),
+ '$'. sprintf("%.2f",$amount),
+ $cgi->param('taxclass')
+);
+
+if ($error) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot($error);
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum" );
+}
+
+%>
+
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
new file mode 100644
index 0000000..fd9e594
--- /dev/null
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -0,0 +1,25 @@
+<%
+
+#untaint custnum
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+$cgi->param('pkgpart') =~ /^(\d+)$/
+ or die 'illegal pkgpart '. $cgi->param('pkgpart');
+my $pkgpart = $1;
+
+my @cust_pkg = ();
+my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, );
+
+if ($error) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot($error);
+} else {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum".
+ "#cust_pkg". $cust_pkg[0]->pkgnum );
+}
+
+%>
+
diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi
new file mode 100644
index 0000000..a2fa46d
--- /dev/null
+++ b/httemplate/edit/process/router.cgi
@@ -0,0 +1,67 @@
+<%
+
+local $FS::UID::AutoCommit=0;
+
+sub check {
+ my $error = shift;
+ if($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string);
+ dbh->rollback;
+ exit;
+ }
+}
+
+my $error = '';
+my $routernum = $cgi->param('routernum');
+my $routername = $cgi->param('routername');
+my $old = qsearchs('router', { routernum => $routernum });
+my @old_psr;
+
+my $new = new FS::router {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } fields('router')
+};
+
+if($old) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $routernum = $new->routernum;
+}
+
+check($error);
+
+if ($old) {
+ @old_psr = $old->part_svc_router;
+ foreach my $psr (@old_psr) {
+ if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') {
+ # do nothing
+ } else {
+ $error = $psr->delete;
+ }
+ }
+ check($error);
+}
+
+foreach($cgi->param) {
+ if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) {
+ my $svcpart = $1;
+ if(grep {$_->svcpart == $svcpart} @old_psr) {
+ # do nothing
+ } else {
+ my $new_psr = new FS::part_svc_router { svcpart => $svcpart,
+ routernum => $routernum };
+ $error = $new_psr->insert;
+ }
+ check($error);
+ }
+}
+
+
+# Yay, everything worked!
+dbh->commit or die dbh->errstr;
+print $cgi->redirect(popurl(3). "browse/router.cgi");
+
+%>
diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi
new file mode 100755
index 0000000..950a860
--- /dev/null
+++ b/httemplate/edit/process/svc_acct.cgi
@@ -0,0 +1,49 @@
+<%
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find account (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+#unmunge popnum
+$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] );
+
+#unmunge passwd
+if ( $cgi->param('_password') eq '*HIDDEN*' ) {
+ die "fatal: no previous account to recall hidden password from!" unless $old;
+ $cgi->param('_password',$old->getfield('_password'));
+}
+
+#unmunge usergroup
+$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] );
+
+my $new = new FS::svc_acct ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir
+ # shell quota slipip)
+ } ( fields('svc_acct'), qw( pkgnum svcpart usergroup ) )
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum );
+}
+
+%>
diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi
new file mode 100755
index 0000000..46ad74d
--- /dev/null
+++ b/httemplate/edit/process/svc_acct_pop.cgi
@@ -0,0 +1,28 @@
+<%
+
+my $popnum = $cgi->param('popnum');
+
+my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum;
+
+my $new = new FS::svc_acct_pop ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('svc_acct_pop')
+} );
+
+my $error = '';
+if ( $popnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $popnum=$new->getfield('popnum');
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi");
+}
+
+%>
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
new file mode 100644
index 0000000..4912a3a
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,45 @@
+<%
+
+# If it's stupid but it works, it's not stupid.
+# -- U.S. Army
+
+local $FS::UID::AutoCommit = 0;
+my $dbh = FS::UID::dbh;
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find broadband service (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+my $new = new FS::svc_broadband ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_broadband'), qw( pkgnum svcpart ) )
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ $cgi->param('ip_addr', $new->ip_addr);
+ $dbh->rollback;
+ print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string );
+} else {
+ $dbh->commit or die $dbh->errstr;
+ print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum );
+}
+
+%>
diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi
new file mode 100755
index 0000000..19f8eb4
--- /dev/null
+++ b/httemplate/edit/process/svc_domain.cgi
@@ -0,0 +1,31 @@
+<%
+
+#remove this to actually test the domains!
+$FS::svc_domain::whois_hack = 1;
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $new = new FS::svc_domain ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(svcnum pkgnum svcpart domain action purpose)
+ } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) )
+} );
+
+my $error = '';
+if ($cgi->param('svcnum')) {
+ $error="Can't modify a domain!";
+} else {
+ $error=$new->insert;
+ $svcnum=$new->svcnum;
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi
new file mode 100755
index 0000000..728cd21
--- /dev/null
+++ b/httemplate/edit/process/svc_external.cgi
@@ -0,0 +1,29 @@
+<%
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum =$1;
+
+my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::svc_external ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_external'), qw( pkgnum svcpart ) )
+} );
+
+my $error = '';
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->getfield('svcnum');
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/edit/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi
new file mode 100755
index 0000000..bb066d8
--- /dev/null
+++ b/httemplate/edit/process/svc_forward.cgi
@@ -0,0 +1,29 @@
+<%
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum =$1;
+
+my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::svc_forward ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_forward'), qw( pkgnum svcpart ) )
+} );
+
+my $error = '';
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->getfield('svcnum');
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi
new file mode 100644
index 0000000..4091314
--- /dev/null
+++ b/httemplate/edit/process/svc_www.cgi
@@ -0,0 +1,36 @@
+<%
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_www', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find website (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+my $new = new FS::svc_www ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ #} qw(svcnum pkgnum svcpart recnum usersvc)
+ } ( fields('svc_www'), qw( pkgnum svcpart ) )
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum );
+}
+
+%>
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
new file mode 100755
index 0000000..a573c65
--- /dev/null
+++ b/httemplate/edit/router.cgi
@@ -0,0 +1,77 @@
+<HTML><BODY>
+
+<%
+
+my $router;
+if ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $router = qsearchs('router', { routernum => $1 })
+ or print $cgi->redirect(popurl(2)."browse/router.cgi") ;
+} else {
+ $router = new FS::router ( {
+ map { $_, scalar($cgi->param($_)) } fields('router')
+ } );
+}
+
+my $routernum = $router->routernum;
+my $action = $routernum ? 'Edit' : 'Add';
+
+print header("$action Router", menubar(
+ 'Main Menu' => "$p",
+ 'View all routers' => "${p}browse/router.cgi",
+));
+
+my $p3 = popurl(3);
+
+if($cgi->param('error')) {
+%> <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+<% } %>
+
+<FORM ACTION="<%=popurl(1)%>process/router.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="table" VALUE="router">
+ <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%=$p3%>/browse/router.cgi">
+ <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%=$p3%>/edit/router.cgi">
+ <INPUT TYPE="hidden" NAME="routernum" VALUE="<%=$routernum%>">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$router->svcnum%>">
+ Router #<%=$routernum or "(NEW)"%>
+
+<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%=$router->routername%>">
+
+<BR><BR>
+Custom fields:
+<BR>
+<%=table() %>
+
+<%
+foreach my $field ($router->virtual_fields) {
+ print $router->pvf($field)->widget('HTML', 'edit',
+ $router->getfield($field));
+}
+%>
+</TABLE>
+
+
+<%
+unless ($router->svcnum) {
+%>
+<BR><BR>Select the service types available on this router<BR>
+<%
+
+ foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband',
+ disabled => '' }) ) {
+ %>
+ <BR>
+ <INPUT TYPE="checkbox" NAME="svcpart_<%=$part_svc->svcpart%>"<%=
+ qsearchs('part_svc_router', { svcpart => $part_svc->svcpart,
+ routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON">
+ <A HREF="<%=${p}%>edit/part_svc.cgi?<%=$part_svc->svcpart%>">
+ <%=$part_svc->svcpart%>: <%=$part_svc->svc%></A>
+ <% } %>
+
+<% } %>
+
+ <BR><BR><INPUT TYPE="submit" VALUE="Apply changes">
+ </FORM>
+</BODY></HTML>
+
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
new file mode 100755
index 0000000..f1b8b80
--- /dev/null
+++ b/httemplate/edit/svc_acct.cgi
@@ -0,0 +1,301 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+my @shells = $conf->config('shells');
+
+my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct, @groups);
+if ( $cgi->param('error') ) {
+ $svc_acct = new FS::svc_acct ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_acct')
+ } );
+ $svcnum = $svc_acct->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+ @groups = $cgi->param('radius_usergroup');
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_acct) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+
+ @groups = $svc_acct->radius_groups;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) {
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+
+ $svc_acct = new FS::svc_acct({svcpart => $svcpart});
+
+ $svcnum='';
+
+ #set gecos
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ if ($cust_pkg) {
+ my($cust_main)=qsearchs('cust_main',{'custnum'=> $cust_pkg->custnum } );
+ unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+ $svc_acct->setfield('finger',
+ $cust_main->getfield('first') . " " . $cust_main->getfield('last')
+ );
+ }
+ }
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ if ( $part_svc_column->columnname eq 'usergroup' ) {
+ @groups = split(',', $part_svc_column->columnvalue);
+ } else {
+ $svc_acct->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+ }
+
+ }
+}
+
+#fixed radius groups always override & display
+if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
+ @groups = split(',', $part_svc->part_svc_column('usergroup')->columnvalue);
+}
+
+my $action = $svcnum ? 'Edit' : 'Add';
+
+my $svc = $part_svc->getfield('svc');
+
+my $otaker = getotaker;
+
+my $username = $svc_acct->username;
+my $password;
+if ( $svc_acct->_password ) {
+ if ( $conf->exists('showpasswords') || ! $svcnum ) {
+ $password = $svc_acct->_password;
+ } else {
+ $password = "*HIDDEN*";
+ }
+} else {
+ $password = '';
+}
+
+my $ulen = $conf->config('usernamemax')
+ || $svc_acct->dbdef_table->column('username')->length;
+my $ulen2 = $ulen+2;
+
+my $pmax = $conf->config('passwordmax') || 8;
+my $pmax2 = $pmax+2;
+
+my $p1 = popurl(1);
+print header("$action $svc account");
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+
+print 'Service # '. ( $svcnum ? "<B>$svcnum</B>" : " (NEW)" ). '<BR>'.
+ 'Service: <B>'. $part_svc->svc. '</B><BR><BR>'.
+ <<END;
+ <FORM NAME="OneTrueForm" ACTION="${p1}process/svc_acct.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">
+ <INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">
+END
+
+print &ntable("#cccccc",2), <<END;
+<TR><TD ALIGN="right">Username</TD>
+<TD><INPUT TYPE="text" NAME="username" VALUE="$username" SIZE=$ulen2 MAXLENGTH=$ulen></TD></TR>
+<TR><TD ALIGN="right">Password</TD>
+<TD><INPUT TYPE="text" NAME="_password" VALUE="$password" SIZE=$pmax2 MAXLENGTH=$pmax>
+(blank to generate)</TD>
+</TR>
+END
+
+my $sec_phrase = $svc_acct->sec_phrase;
+if ( $conf->exists('security_phrase') ) {
+ print <<END;
+ <TR><TD ALIGN="right">Security phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase" SIZE=32>
+ (for forgotten passwords)</TD>
+ </TD>
+END
+} else {
+ print qq!<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="$sec_phrase">!;
+}
+
+#domain
+my $domsvc = $svc_acct->domsvc || 0;
+if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) {
+ print qq!<INPUT TYPE="hidden" NAME="domsvc" VALUE="$domsvc">!;
+} else {
+ my %svc_domain = ();
+
+ if ( $domsvc ) {
+ my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } );
+ if ( $svc_domain ) {
+ $svc_domain{$svc_domain->svcnum} = $svc_domain;
+ } else {
+ warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc";
+ }
+ }
+
+ if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'D' ) {
+ my $svc_domain = qsearchs('svc_domain', {
+ 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue,
+ } );
+ if ( $svc_domain ) {
+ $svc_domain{$svc_domain->svcnum} = $svc_domain;
+ } else {
+ warn "unknown svc_domain.svcnum for part_svc_column domsvc: ".
+ $part_svc->part_svc_column('domsvc')->columnvalue;
+ }
+ }
+
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } );
+ if ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) {
+ my @cust_svc =
+ map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
+ qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } );
+ foreach my $cust_svc ( @cust_svc ) {
+ my $svc_domain =
+ qsearchs('svc_domain', { 'svcnum' => $cust_svc->svcnum } );
+ $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain;
+ }
+ } else {
+ %svc_domain = map { $_->svcnum => $_ } qsearch('svc_domain', {} );
+ }
+ print qq!<TR><TD ALIGN="right">Domain</TD>!.
+ qq!<TD><SELECT NAME="domsvc" SIZE=1>\n!;
+ foreach my $svcnum (
+ sort { $svc_domain{$a}->domain cmp $svc_domain{$b}->domain }
+ keys %svc_domain
+ ) {
+ my $svc_domain = $svc_domain{$svcnum};
+ print qq!<OPTION VALUE="!. $svc_domain->svcnum. qq!"!.
+ ( $svc_domain->svcnum == $domsvc ? ' SELECTED' : '' ).
+ '>'. $svc_domain->domain. "\n" ;
+ }
+ print "</SELECT></TD></TR>";
+}
+
+#pop
+my $popnum = $svc_acct->popnum || 0;
+if ( $part_svc->part_svc_column('popnum')->columnflag eq "F" ) {
+ print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$popnum">!;
+} else {
+ print qq!<TR><TD ALIGN="right">Access number</TD>!.
+ qq!<TD>!. FS::svc_acct_pop::popselector($popnum). '</TD></TR>';
+}
+
+my($uid,$gid,$finger,$dir)=(
+ $svc_acct->uid,
+ $svc_acct->gid,
+ $svc_acct->finger,
+ $svc_acct->dir,
+);
+
+print <<END;
+<INPUT TYPE="hidden" NAME="uid" VALUE="$uid">
+<INPUT TYPE="hidden" NAME="gid" VALUE="$gid">
+END
+
+if ( !$finger && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+ print '<INPUT TYPE="hidden" NAME="finger" VALUE="">';
+} else {
+ print '<TR><TD ALIGN="right">GECOS</TD>'.
+ qq!<TD><INPUT TYPE="text" NAME="finger" VALUE="$finger"></TD></TR>!;
+}
+print qq!<INPUT TYPE="hidden" NAME="dir" VALUE="$dir">!;
+
+my $shell = $svc_acct->shell;
+if ( $part_svc->part_svc_column('shell')->columnflag eq "F"
+ || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' )
+ ) {
+ print qq!<INPUT TYPE="hidden" NAME="shell" VALUE="$shell">!;
+} else {
+ print qq!<TR><TD ALIGN="right">Shell</TD><TD><SELECT NAME="shell" SIZE=1>!;
+ my($etc_shell);
+ foreach $etc_shell (@shells) {
+ print "<OPTION", $etc_shell eq $shell ? ' SELECTED' : '', ">",
+ $etc_shell, "\n";
+ }
+ print "</SELECT></TD></TR>";
+}
+
+my($quota,$slipip)=(
+ $svc_acct->quota,
+ $svc_acct->slipip,
+);
+
+if ( $part_svc->part_svc_column('quota')->columnflag eq "F" )
+{
+ print qq!<INPUT TYPE="hidden" NAME="quota" VALUE="$quota">!;
+} else {
+ print <<END;
+ <TR><TD ALIGN="right">Quota:</TD>
+ <TD> <INPUT TYPE="text" NAME="quota" VALUE="$quota" ></TD>
+ </TR>
+END
+}
+
+if ( $part_svc->part_svc_column('slipip')->columnflag eq "F" ) {
+ print qq!<INPUT TYPE="hidden" NAME="slipip" VALUE="$slipip">!;
+} else {
+ print qq!<TR><TD ALIGN="right">IP</TD><TD><INPUT TYPE="text" NAME="slipip" VALUE="$slipip"></TD></TR>!;
+}
+
+foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) {
+ $r =~ /^^r(adius|[cr])_(.+)$/ or next; #?
+ my $a = $2;
+ if ( $part_svc->part_svc_column($r)->columnflag eq 'F' ) {
+ print qq!<INPUT TYPE="hidden" NAME="$r" VALUE="!.
+ $svc_acct->getfield($r). '">';
+ } else {
+ print qq!<TR><TD ALIGN="right">$FS::raddb::attrib{$a}</TD><TD><INPUT TYPE="text" NAME="$r" VALUE="!.
+ $svc_acct->getfield($r). '"></TD></TR>';
+ }
+}
+
+print '<TR><TD ALIGN="right">RADIUS groups</TD>';
+if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
+ print '<TD BGCOLOR="#ffffff">'. join('<BR>', @groups);
+} else {
+ print '<TD>'. &FS::svc_acct::radius_usergroup_selector( \@groups );
+}
+print '</TD></TR>';
+
+foreach my $field ($svc_acct->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ print $svc_acct->pvf($field)->widget('HTML', 'edit',
+ $svc_acct->getfield($field));
+ }
+}
+
+#submit
+print qq!</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">!;
+
+print <<END;
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi
new file mode 100755
index 0000000..399502a
--- /dev/null
+++ b/httemplate/edit/svc_acct_pop.cgi
@@ -0,0 +1,56 @@
+<!-- mason kludge -->
+<%
+
+my $svc_acct_pop;
+if ( $cgi->param('error') ) {
+ $svc_acct_pop = new FS::svc_acct_pop ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my($query)=$cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1});
+} else { #adding
+ $svc_acct_pop = new FS::svc_acct_pop {};
+}
+my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add';
+my $hashref = $svc_acct_pop->hashref;
+
+my $p1 = popurl(1);
+print header("$action Access Number", menubar(
+ 'Main Menu' => popurl(2),
+ 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi",
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/svc_acct_pop.cgi" METHOD=POST>!;
+
+#display
+
+print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$hashref->{popnum}">!,
+ "POP #", $hashref->{popnum} ? $hashref->{popnum} : "(NEW)";
+
+print <<END;
+<PRE>
+City <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="$hashref->{city}">
+State <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="$hashref->{state}">
+Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{ac}">
+Exchange <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{exch}">
+Local <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="$hashref->{loc}">
+</PRE>
+END
+
+print qq!<BR><INPUT TYPE="submit" VALUE="!,
+ $hashref->{popnum} ? "Apply changes" : "Add Access Number",
+ qq!">!;
+
+print <<END;
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
new file mode 100644
index 0000000..9e064c5
--- /dev/null
+++ b/httemplate/edit/svc_broadband.cgi
@@ -0,0 +1,175 @@
+<!-- mason kludge -->
+<%
+
+# If it's stupid but it works, it's still stupid.
+# -Kristian
+
+
+use HTML::Widgets::SelectLayers;
+use Tie::IxHash;
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband );
+if ( $cgi->param('error') ) {
+ $svc_broadband = new FS::svc_broadband ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart)
+ } );
+ $svcnum = $svc_broadband->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $svc_broadband->svcpart;
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_broadband) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart });
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_broadband->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+my $action = $svc_broadband->svcnum ? 'Edit' : 'Add';
+
+if ($pkgnum) {
+
+ #Nothing?
+
+} elsif ( $action eq 'Edit' ) {
+
+ #Nothing?
+
+} else {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my $p1 = popurl(1);
+
+my ($ip_addr, $speed_up, $speed_down, $blocknum) =
+ ($svc_broadband->ip_addr,
+ $svc_broadband->speed_up,
+ $svc_broadband->speed_down,
+ $svc_broadband->blocknum);
+
+%>
+
+<%=header("Broadband Service $action", '')%>
+
+<% if ($cgi->param('error')) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT><BR>
+<% } %>
+
+Service #<B><%=$svcnum ? $svcnum : "(NEW)"%></B><BR><BR>
+
+<FORM ACTION="<%=${p1}%>process/svc_broadband.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%=$pkgnum%>">
+ <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%=$svcpart%>">
+
+ <%=&ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff">
+<% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { %>
+ <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%=$ip_addr%>"><%=$ip_addr%>
+<% } else { %>
+ <INPUT TYPE="text" NAME="ip_addr" VALUE="<%=$ip_addr%>">
+<% } %>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Download speed</TD>
+ <TD BGCOLOR="#ffffff">
+<% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { %>
+ <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%=$speed_down%>"><%=$speed_down%>Kbps
+<% } else { %>
+ <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%=$speed_down%>">Kbps
+<% } %>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Upload speed</TD>
+ <TD BGCOLOR="#ffffff">
+<% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { %>
+ <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%=$speed_up%>"><%=$speed_up%>Kbps
+<% } else { %>
+ <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%=$speed_up%>">Kbps
+<% } %>
+ </TD>
+ </TR>
+<% if ($action eq 'Add') { %>
+ <TR>
+ <TD ALIGN="right">Router/Block</TD>
+ <TD BGCOLOR="#ffffff">
+ <SELECT NAME="blocknum">
+<%
+ warn $svc_broadband->svcpart;
+ foreach my $router ($svc_broadband->allowed_routers) {
+ warn $router->routername;
+ foreach my $addr_block ($router->addr_block) {
+%>
+ <OPTION VALUE="<%=$addr_block->blocknum%>"<%=($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>>
+ <%=$router->routername%>:<%=$addr_block->ip_gateway%>/<%=$addr_block->ip_netmask%></OPTION>
+<%
+ }
+ }
+%>
+ </SELECT>
+ </TD>
+ </TR>
+<% } else { %>
+
+ <TR>
+ <TD ALIGN="right">Router/Block</TD>
+ <TD BGCOLOR="#ffffff">
+ <%=$svc_broadband->addr_block->router->routername%>:<%=$svc_broadband->addr_block->NetAddr%>
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$svc_broadband->blocknum%>">
+ </TD>
+ </TR>
+
+<% } %>
+
+<%
+foreach my $field ($svc_broadband->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' &&
+ $part_svc->part_svc_column($field)->columnflag ne 'X') {
+ print $svc_broadband->pvf($field)->widget('HTML', 'edit',
+ $svc_broadband->getfield($field));
+ }
+} %>
+ </TABLE>
+ <BR>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Submit">
+</FORM>
+</BODY>
+</HTML>
+
diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
new file mode 100755
index 0000000..ca0e339
--- /dev/null
+++ b/httemplate/edit/svc_domain.cgi
@@ -0,0 +1,98 @@
+<!-- mason kludge -->
+<%
+
+my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc,
+ $svc_domain);
+if ( $cgi->param('error') ) {
+ $svc_domain = new FS::svc_domain ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_domain')
+ } );
+ $svcnum = $svc_domain->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $kludge_action = $cgi->param('action');
+ $purpose = $cgi->param('purpose');
+ $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ $kludge_action = '';
+ $purpose = '';
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_domain) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ $svc_domain = new FS::svc_domain({});
+
+ foreach $_ (split(/-/,$query)) {
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_domain->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+
+}
+my $action = $svcnum ? 'Edit' : 'Add';
+
+my $svc = $part_svc->getfield('svc');
+
+my $otaker = getotaker;
+
+my $domain = $svc_domain->domain;
+
+my $p1 = popurl(1);
+print header("$action $svc", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print <<END;
+ <FORM ACTION="${p1}process/svc_domain.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">
+ <INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">
+END
+
+print qq!<INPUT TYPE="radio" NAME="action" VALUE="N"!;
+print ' CHECKED' if $kludge_action eq 'N';
+print qq!>New!;
+print qq!<BR><INPUT TYPE="radio" NAME="action" VALUE="M"!;
+print ' CHECKED' if $kludge_action eq 'M';
+print qq!>Transfer!;
+
+print <<END;
+<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="$domain" SIZE=28 MAXLENGTH=63>
+<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="$purpose" SIZE=64>
+<P><INPUT TYPE="submit" VALUE="Submit">
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi
new file mode 100644
index 0000000..bcfc85e
--- /dev/null
+++ b/httemplate/edit/svc_external.cgi
@@ -0,0 +1,105 @@
+<!-- mason kludge -->
+<%
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external );
+if ( $cgi->param('error') ) {
+ $svc_external = new FS::svc_external ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_external')
+ } );
+ $svcnum = $svc_external->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_external) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $svc_external = new FS::svc_external { svcpart => $svcpart };
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_external->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+my $action = $svc_external->svcnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+print header("External service $action", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/svc_external.cgi" METHOD=POST>!;
+
+#display
+
+
+#svcnum
+print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
+print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>";
+
+#pkgnum
+print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+#svcpart
+print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+
+my($id,$title)=(
+ $svc_external->id,
+ $svc_external->title,
+);
+
+print &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">External ID</TD><TD>'.
+ qq!<INPUT TYPE="text" NAME="id" VALUE="$id">!.
+ '</TD></TR>'.
+ '<TR><TD ALIGN="right">Title</TD><TD>'.
+ qq!<INPUT TYPE="text" NAME="title" VALUE="$title">!.
+ '</TD></TR>';
+
+foreach my $field ($svc_external->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ print $svc_external->pvf($field)->widget('HTML', 'edit',
+ $svc_external->getfield($field));
+ }
+}
+
+%>
+
+</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi
new file mode 100755
index 0000000..2b9d35a
--- /dev/null
+++ b/httemplate/edit/svc_forward.cgi
@@ -0,0 +1,177 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward);
+if ( $cgi->param('error') ) {
+ $svc_forward = new FS::svc_forward ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_forward')
+ } );
+ $svcnum = $svc_forward->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+
+ my($query) = $cgi->keywords;
+
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_forward) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ $svc_forward = new FS::svc_forward({});
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_forward->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+ }
+
+}
+my $action = $svc_forward->svcnum ? 'Edit' : 'Add';
+
+my %email;
+
+#starting with those currently attached
+foreach my $method (qw( srcsvc_acct dstsvc_acct )) {
+ my $svc_acct = $svc_forward->$method();
+ $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct;
+}
+
+if ($pkgnum) {
+
+ #find all possible user svcnums (and emails)
+
+ #and including the rest for this customer
+ my($u_part_svc,@u_acct_svcparts);
+ foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
+ push @u_acct_svcparts,$u_part_svc->getfield('svcpart');
+ }
+
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ my($custnum)=$cust_pkg->getfield('custnum');
+ my($i_cust_pkg);
+ foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+ my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
+ my($acct_svcpart);
+ foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding
+ #record(s) in cust_svc ( for this
+ #pkgnum ! )
+ foreach my $i_cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum,
+ 'svcpart' => $acct_svcpart } )
+ ) {
+ my $svc_acct =
+ qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } );
+ $email{$svc_acct->svcnum} = $svc_acct->email;
+ }
+ }
+ }
+
+} elsif ( $action eq 'Add' ) {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my($srcsvc,$dstsvc,$dst)=(
+ $svc_forward->srcsvc,
+ $svc_forward->dstsvc,
+ $svc_forward->dst,
+);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+
+#display
+
+%>
+
+<%= header("Mail Forward $action") %>
+
+<% if ( $cgi->param('error') ) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+ <BR><BR>
+<% } %>
+
+Service #<%= $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
+Service: <B><%= $part_svc->svc %></B><BR><BR>
+
+<FORM ACTION="process/svc_forward.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>">
+
+<SCRIPT TYPE="text/javascript">
+function srcchanged(what) {
+ if ( what.options[what.selectedIndex].value == 0 ) {
+ what.form.src.disabled = false;
+ what.form.src.style.backgroundColor = "white";
+ } else {
+ what.form.src.disabled = true;
+ what.form.src.style.backgroundColor = "lightgrey";
+ }
+}
+function dstchanged(what) {
+ if ( what.options[what.selectedIndex].value == 0 ) {
+ what.form.dst.disabled = false;
+ what.form.dst.style.backgroundColor = "white";
+ } else {
+ what.form.dst.disabled = true;
+ what.form.dst.style.backgroundColor = "lightgrey";
+ }
+}
+</SCRIPT>
+
+<%= ntable("#cccccc",2) %>
+<TR><TD ALIGN="right">Email to</TD>
+<TD><SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
+<% foreach $_ (keys %email) { %>
+ <OPTION<%= $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION>
+<% } %>
+<% if ( $svc_forward->dbdef_table->column('src') ) { %>
+ <OPTION <%= $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+<% } %>
+</SELECT>
+<% if ( $svc_forward->dbdef_table->column('src') ) { %>
+<INPUT TYPE="text" NAME="src" VALUE="<%= $src %>" <%= ( $src || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+<% } %>
+</TD></TR>
+
+<TR><TD ALIGN="right">Forwards to</TD>
+<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)">
+<% foreach $_ (keys %email) { %>
+ <OPTION<%= $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION>
+<% } %>
+<OPTION <%= $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+</SELECT>
+<INPUT TYPE="text" NAME="dst" VALUE="<%= $dst %>" <%= ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+</TD></TR>
+ </TABLE>
+<BR><INPUT TYPE="submit" VALUE="Submit">
+</FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
new file mode 100644
index 0000000..4989bb6
--- /dev/null
+++ b/httemplate/edit/svc_www.cgi
@@ -0,0 +1,221 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www );
+if ( $cgi->param('error') ) {
+ $svc_www = new FS::svc_www ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_www')
+ } );
+ $svcnum = $svc_www->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_www) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $svc_www = new FS::svc_www { svcpart => $svcpart };
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_www->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+my $action = $svc_www->svcnum ? 'Edit' : 'Add';
+
+my( %svc_acct, %arec );
+if ($pkgnum) {
+
+ my @u_acct_svcparts;
+ foreach my $svcpart (
+ map { $_->svcpart } qsearch( 'part_svc', { 'svcdb' => 'svc_acct' } )
+ ) {
+ next if $conf->exists('svc_www-usersvc_svcpart')
+ && ! grep { $svcpart == $_ }
+ $conf->config('svc_www-usersvc_svcpart');
+ push @u_acct_svcparts, $svcpart;
+ }
+
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ my($custnum)=$cust_pkg->getfield('custnum');
+ my($i_cust_pkg);
+ foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+ my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
+ my($acct_svcpart);
+ foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding
+ #record(s) in cust_svc ( for this
+ #pkgnum ! )
+ my($i_cust_svc);
+ foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) {
+ my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')});
+ $svc_acct{$svc_acct->getfield('svcnum')}=
+ $svc_acct->cust_svc->part_svc->svc. ': '. $svc_acct->email;
+ }
+ }
+ }
+
+
+ my($d_part_svc,@d_acct_svcparts);
+ foreach $d_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_domain'}) ) {
+ push @d_acct_svcparts,$d_part_svc->getfield('svcpart');
+ }
+
+ foreach $i_cust_pkg ( qsearch( 'cust_pkg', { 'custnum' => $custnum } ) ) {
+ my $cust_pkgnum = $i_cust_pkg->pkgnum;
+
+ foreach my $acct_svcpart (@d_acct_svcparts) {
+
+ foreach my $i_cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum,
+ 'svcpart' => $acct_svcpart } )
+ ) {
+ my $svc_domain =
+ qsearchs( 'svc_domain', { 'svcnum' => $i_cust_svc->svcnum } );
+
+ my $extra_sql = "AND ( rectype = 'A' OR rectype = 'CNAME' )";
+ unless ( $conf->exists('svc_www-enable_subdomains') ) {
+ $extra_sql .= " AND ( reczone = '\@' OR reczone = '".
+ $svc_domain->domain. ".' )";
+ }
+
+ foreach my $domain_rec (
+ qsearch( 'domain_record',
+ {
+ 'svcnum' => $svc_domain->svcnum,
+ },
+ '',
+ $extra_sql,
+ )
+ ) {
+ $arec{$domain_rec->recnum} = $domain_rec->zone;
+ }
+
+ if ( $conf->exists('svc_www-enable_subdomains') ) {
+ $arec{'www.'. $svc_domain->domain} = 'www.'. $svc_domain->domain
+ unless qsearchs( 'domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => 'www',
+ } )
+ || qsearchs( 'domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => 'www.'.$svc-domain->domain.'.',
+ } );
+ }
+
+ $arec{'@.'. $svc_domain->domain} = $svc_domain->domain
+ unless qsearchs('domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => '@',
+ } )
+ || qsearchs('domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => $svc_domain->domain.'.',
+ } );
+
+ }
+
+ }
+ }
+
+} elsif ( $action eq 'Edit' ) {
+
+ my($domain_rec) = qsearchs('domain_record', { 'recnum'=>$svc_www->recnum });
+ $arec{$svc_www->recnum} = join '.', $domain_rec->recdata, $domain_rec->reczone;
+
+} else {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+
+my $p1 = popurl(1);
+print header("Web Hosting $action", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/svc_www.cgi" METHOD=POST>!;
+
+#display
+
+
+
+#svcnum
+print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
+print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>";
+
+#pkgnum
+print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+#svcpart
+print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+
+my($recnum,$usersvc)=(
+ $svc_www->recnum,
+ $svc_www->usersvc,
+);
+
+print &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Zone</TD><TD><SELECT NAME="recnum" SIZE=1>';
+foreach $_ (keys %arec) {
+ print "<OPTION", $_ eq $recnum ? " SELECTED" : "",
+ qq! VALUE="$_">$arec{$_}!;
+}
+print "</SELECT></TD></TR>";
+
+print '<TR><TD ALIGN="right">Username</TD><TD><SELECT NAME="usersvc" SIZE=1>';
+foreach $_ (keys %svc_acct) {
+ print "<OPTION", ($_ eq $usersvc) ? " SELECTED" : "",
+ qq! VALUE="$_">$svc_acct{$_}!;
+}
+print "</SELECT></TD></TR>";
+
+foreach my $field ($svc_www->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ print $svc_www->pvf($field)->widget('HTML', 'edit',
+ $svc_www->getfield($field));
+ }
+}
+
+print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">';
+
+print <<END;
+
+ </FORM>
+ </BODY>
+</HTML>
+END
+%>
diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js
new file mode 100644
index 0000000..e9d6a22
--- /dev/null
+++ b/httemplate/elements/calendar-en.js
@@ -0,0 +1,123 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mishoo@infoiasi.ro>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2003\n" + // don't translate this this ;-)
+"For latest version visit: http://dynarch.com/mishoo/calendar.epl\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js
new file mode 100644
index 0000000..55e22b9
--- /dev/null
+++ b/httemplate/elements/calendar-setup.js
@@ -0,0 +1,181 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+
+// $Id: calendar-setup.js,v 1.4 2004-09-22 11:04:41 ivan Exp $
+
+/**
+ * This function "patches" an input field (or other element) to use a calendar
+ * widget for date selection.
+ *
+ * The "params" is a single object that can have the following properties:
+ *
+ * prop. name | description
+ * -------------------------------------------------------------------------------------------------
+ * inputField | the ID of an input field to store the date
+ * displayArea | the ID of a DIV or other element to show the date
+ * button | ID of a button or other element that will trigger the calendar
+ * eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
+ * ifFormat | date format that will be stored in the input field
+ * daFormat | the date format that will be used to display the date in displayArea
+ * singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
+ * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc.
+ * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation
+ * range | array with 2 elements. Default: [1900, 2999] -- the range of years available
+ * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
+ * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
+ * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
+ * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
+ * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
+ * onClose | function that gets called when the calendar is closed. [default]
+ * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
+ * date | the date that the calendar will be initially displayed to
+ * showsTime | default: false; if true the calendar will include a time selector
+ * timeFormat | the time format; can be "12" or "24", default is "12"
+ * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
+ * step | configures the step of the years in drop-down boxes; default: 2
+ * position | configures the calendar absolute position; default: null
+ * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible
+ * showOthers | if "true" (but default: "false") it will show days from other months too
+ *
+ * None of them is required, they all have default values. However, if you
+ * pass none of "inputField", "displayArea" or "button" you'll get a warning
+ * saying "nothing to setup".
+ */
+Calendar.setup = function (params) {
+ function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
+
+ param_default("inputField", null);
+ param_default("displayArea", null);
+ param_default("button", null);
+ param_default("eventName", "click");
+ param_default("ifFormat", "%Y/%m/%d");
+ param_default("daFormat", "%Y/%m/%d");
+ param_default("singleClick", true);
+ param_default("disableFunc", null);
+ param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
+ param_default("firstDay", 0); // defaults to "Sunday" first
+ param_default("align", "Br");
+ param_default("range", [1900, 2999]);
+ param_default("weekNumbers", true);
+ param_default("flat", null);
+ param_default("flatCallback", null);
+ param_default("onSelect", null);
+ param_default("onClose", null);
+ param_default("onUpdate", null);
+ param_default("date", null);
+ param_default("showsTime", false);
+ param_default("timeFormat", "24");
+ param_default("electric", true);
+ param_default("step", 2);
+ param_default("position", null);
+ param_default("cache", false);
+ param_default("showOthers", false);
+
+ var tmp = ["inputField", "displayArea", "button"];
+ for (var i in tmp) {
+ if (typeof params[tmp[i]] == "string") {
+ params[tmp[i]] = document.getElementById(params[tmp[i]]);
+ }
+ }
+ if (!(params.flat || params.inputField || params.displayArea || params.button)) {
+ alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
+ return false;
+ }
+
+ function onSelect(cal) {
+ var p = cal.params;
+ var update = (cal.dateClicked || p.electric);
+ if (update && p.flat) {
+ if (typeof p.flatCallback == "function")
+ p.flatCallback(cal);
+ else
+ alert("No flatCallback given -- doing nothing.");
+ return false;
+ }
+ if (update && p.inputField) {
+ p.inputField.value = cal.date.print(p.ifFormat);
+ if (typeof p.inputField.onchange == "function")
+ p.inputField.onchange();
+ }
+ if (update && p.displayArea)
+ p.displayArea.innerHTML = cal.date.print(p.daFormat);
+ if (update && p.singleClick && cal.dateClicked)
+ cal.callCloseHandler();
+ if (update && typeof p.onUpdate == "function")
+ p.onUpdate(cal);
+ };
+
+ if (params.flat != null) {
+ if (typeof params.flat == "string")
+ params.flat = document.getElementById(params.flat);
+ if (!params.flat) {
+ alert("Calendar.setup:\n Flat specified but can't find parent.");
+ return false;
+ }
+ var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.params = params;
+ cal.weekNumbers = params.weekNumbers;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.create(params.flat);
+ cal.show();
+ return false;
+ }
+
+ var triggerEl = params.button || params.displayArea || params.inputField;
+ triggerEl["on" + params.eventName] = function() {
+ var dateEl = params.inputField || params.displayArea;
+ var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
+ var mustCreate = false;
+ var cal = window.calendar;
+ if (!(cal && params.cache)) {
+ window.calendar = cal = new Calendar(params.firstDay,
+ params.date,
+ params.onSelect || onSelect,
+ params.onClose || function(cal) { cal.hide(); });
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.weekNumbers = params.weekNumbers;
+ mustCreate = true;
+ } else {
+ if (params.date)
+ cal.setDate(params.date);
+ cal.hide();
+ }
+ cal.showsOtherMonths = params.showOthers;
+ cal.yearStep = params.step;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ cal.parseDate(dateEl.value || dateEl.innerHTML);
+ cal.refresh();
+ if (!params.position)
+ cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
+ else
+ cal.showAt(params.position[0], params.position[1]);
+ return false;
+ };
+};
diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css
new file mode 100644
index 0000000..6001cfa
--- /dev/null
+++ b/httemplate/elements/calendar-win2k-2.css
@@ -0,0 +1,270 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #f4e8f0;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #e4d8e0;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #c4b8c0;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+ font-size: 80%;
+ color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+ color: #faa;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #f4e8f0;
+}
+
+.calendar tbody .rowhilite td {
+ background: #e4d8e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #d4c8d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ padding: 2px 2px 0px 2px;
+ background: #e4d8e0;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #f4e8f0;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #e4d8e0;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #e4d8e0;
+ font-size: 90%;
+ padding: 1px;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+ width: 4em;
+}
+
+.calendar .combo .active {
+ background: #d4c8d0;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar .combo .hilite {
+ background: #408;
+ color: #fea;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #f4f0e8;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #766;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js
new file mode 100644
index 0000000..ec18d80
--- /dev/null
+++ b/httemplate/elements/calendar.js
@@ -0,0 +1,1715 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ------------------------------------------------------------------
+ *
+ * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze"
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id: calendar.js,v 1.4 2004-09-22 11:04:41 ivan Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.timeout = null;
+ this.onSelected = onSelected || null;
+ this.onClose = onClose || null;
+ this.dragging = false;
+ this.hidden = false;
+ this.minYear = 1970;
+ this.maxYear = 2050;
+ this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+ this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+ this.isPopup = true;
+ this.weekNumbers = true;
+ this.firstDayOfWeek = firstDayOfWeek; // 0 for Sunday, 1 for Monday, etc.
+ this.showsOtherMonths = false;
+ this.dateStr = dateStr;
+ this.ar_days = null;
+ this.showsTime = false;
+ this.time24 = true;
+ this.yearStep = 2;
+ // HTML elements
+ this.table = null;
+ this.element = null;
+ this.tbody = null;
+ this.firstdayname = null;
+ // Combo boxes
+ this.monthsCombo = null;
+ this.yearsCombo = null;
+ this.hilitedMonth = null;
+ this.activeMonth = null;
+ this.hilitedYear = null;
+ this.activeYear = null;
+ // Information
+ this.dateClicked = false;
+
+ // one-time initializations
+ if (typeof Calendar._SDN == "undefined") {
+ // table of short day names
+ if (typeof Calendar._SDN_len == "undefined")
+ Calendar._SDN_len = 3;
+ var ar = new Array();
+ for (var i = 8; i > 0;) {
+ ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+ }
+ Calendar._SDN = ar;
+ // table of short month names
+ if (typeof Calendar._SMN_len == "undefined")
+ Calendar._SMN_len = 3;
+ ar = new Array();
+ for (var i = 12; i > 0;) {
+ ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+ }
+ Calendar._SMN = ar;
+ }
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+ !/opera/i.test(navigator.userAgent) );
+
+Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+// library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+ var SL = 0, ST = 0;
+ var is_div = /^div$/i.test(el.tagName);
+ if (is_div && el.scrollLeft)
+ SL = el.scrollLeft;
+ if (is_div && el.scrollTop)
+ ST = el.scrollTop;
+ var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+ if (el.offsetParent) {
+ var tmp = this.getAbsolutePos(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+ var related = evt.relatedTarget;
+ if (!related) {
+ var type = evt.type;
+ if (type == "mouseover") {
+ related = evt.fromElement;
+ } else if (type == "mouseout") {
+ related = evt.toElement;
+ }
+ }
+ while (related) {
+ if (related == el) {
+ return true;
+ }
+ related = related.parentNode;
+ }
+ return false;
+};
+
+Calendar.removeClass = function(el, className) {
+ if (!(el && el.className)) {
+ return;
+ }
+ var cls = el.className.split(" ");
+ var ar = new Array();
+ for (var i = cls.length; i > 0;) {
+ if (cls[--i] != className) {
+ ar[ar.length] = cls[i];
+ }
+ }
+ el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+ Calendar.removeClass(el, className);
+ el.className += " " + className;
+};
+
+Calendar.getElement = function(ev) {
+ if (Calendar.is_ie) {
+ return window.event.srcElement;
+ } else {
+ return ev.currentTarget;
+ }
+};
+
+Calendar.getTargetElement = function(ev) {
+ if (Calendar.is_ie) {
+ return window.event.srcElement;
+ } else {
+ return ev.target;
+ }
+};
+
+Calendar.stopEvent = function(ev) {
+ ev || (ev = window.event);
+ if (Calendar.is_ie) {
+ ev.cancelBubble = true;
+ ev.returnValue = false;
+ } else {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+ if (el.attachEvent) { // IE
+ el.attachEvent("on" + evname, func);
+ } else if (el.addEventListener) { // Gecko / W3C
+ el.addEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = func;
+ }
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+ if (el.detachEvent) { // IE
+ el.detachEvent("on" + evname, func);
+ } else if (el.removeEventListener) { // Gecko / W3C
+ el.removeEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = null;
+ }
+};
+
+Calendar.createElement = function(type, parent) {
+ var el = null;
+ if (document.createElementNS) {
+ // use the XHTML namespace; IE won't normally get here unless
+ // _they_ "fix" the DOM2 implementation.
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ } else {
+ el = document.createElement(type);
+ }
+ if (typeof parent != "undefined") {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+ with (Calendar) {
+ addEvent(el, "mouseover", dayMouseOver);
+ addEvent(el, "mousedown", dayMouseDown);
+ addEvent(el, "mouseout", dayMouseOut);
+ if (is_ie) {
+ addEvent(el, "dblclick", dayMouseDblClick);
+ el.setAttribute("unselectable", true);
+ }
+ }
+};
+
+Calendar.findMonth = function(el) {
+ if (typeof el.month != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.month != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.findYear = function(el) {
+ if (typeof el.year != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.year != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.showMonthsCombo = function () {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var mc = cal.monthsCombo;
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ if (cal.activeMonth) {
+ Calendar.removeClass(cal.activeMonth, "active");
+ }
+ var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+ Calendar.addClass(mon, "active");
+ cal.activeMonth = mon;
+ var s = mc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var mcw = mc.offsetWidth;
+ if (typeof mcw == "undefined")
+ // Konqueror brain-dead techniques
+ mcw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var yc = cal.yearsCombo;
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ if (cal.activeYear) {
+ Calendar.removeClass(cal.activeYear, "active");
+ }
+ cal.activeYear = null;
+ var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+ var yr = yc.firstChild;
+ var show = false;
+ for (var i = 12; i > 0; --i) {
+ if (Y >= cal.minYear && Y <= cal.maxYear) {
+ yr.firstChild.data = Y;
+ yr.year = Y;
+ yr.style.display = "block";
+ show = true;
+ } else {
+ yr.style.display = "none";
+ }
+ yr = yr.nextSibling;
+ Y += fwd ? cal.yearStep : -cal.yearStep;
+ }
+ if (show) {
+ var s = yc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var ycw = yc.offsetWidth;
+ if (typeof ycw == "undefined")
+ // Konqueror brain-dead techniques
+ ycw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+ }
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ if (cal.timeout) {
+ clearTimeout(cal.timeout);
+ }
+ var el = cal.activeDiv;
+ if (!el) {
+ return false;
+ }
+ var target = Calendar.getTargetElement(ev);
+ ev || (ev = window.event);
+ Calendar.removeClass(el, "active");
+ if (target == el || target.parentNode == el) {
+ Calendar.cellClick(el, ev);
+ }
+ var mon = Calendar.findMonth(target);
+ var date = null;
+ if (mon) {
+ date = new Date(cal.date);
+ if (mon.month != date.getMonth()) {
+ date.setMonth(mon.month);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ } else {
+ var year = Calendar.findYear(target);
+ if (year) {
+ date = new Date(cal.date);
+ if (year.year != date.getFullYear()) {
+ date.setFullYear(year.year);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ }
+ }
+ with (Calendar) {
+ removeEvent(document, "mouseup", tableMouseUp);
+ removeEvent(document, "mouseover", tableMouseOver);
+ removeEvent(document, "mousemove", tableMouseOver);
+ cal._hideCombos();
+ _C = null;
+ return stopEvent(ev);
+ }
+};
+
+Calendar.tableMouseOver = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return;
+ }
+ var el = cal.activeDiv;
+ var target = Calendar.getTargetElement(ev);
+ if (target == el || target.parentNode == el) {
+ Calendar.addClass(el, "hilite active");
+ Calendar.addClass(el.parentNode, "rowhilite");
+ } else {
+ if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+ Calendar.removeClass(el, "active");
+ Calendar.removeClass(el, "hilite");
+ Calendar.removeClass(el.parentNode, "rowhilite");
+ }
+ ev || (ev = window.event);
+ if (el.navtype == 50 && target != el) {
+ var pos = Calendar.getAbsolutePos(el);
+ var w = el.offsetWidth;
+ var x = ev.clientX;
+ var dx;
+ var decrease = true;
+ if (x > pos.x + w) {
+ dx = x - pos.x - w;
+ decrease = false;
+ } else
+ dx = pos.x - x;
+
+ if (dx < 0) dx = 0;
+ var range = el._range;
+ var current = el._current;
+ var count = Math.floor(dx / 10) % range.length;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ while (count-- > 0)
+ if (decrease) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.firstChild.data = newval;
+
+ cal.onUpdateTime();
+ }
+ var mon = Calendar.findMonth(target);
+ if (mon) {
+ if (mon.month != cal.date.getMonth()) {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ Calendar.addClass(mon, "hilite");
+ cal.hilitedMonth = mon;
+ } else if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ } else {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ var year = Calendar.findYear(target);
+ if (year) {
+ if (year.year != cal.date.getFullYear()) {
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ Calendar.addClass(year, "hilite");
+ cal.hilitedYear = year;
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+ if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+ return Calendar.stopEvent(ev);
+ }
+};
+
+Calendar.calDragIt = function (ev) {
+ var cal = Calendar._C;
+ if (!(cal && cal.dragging)) {
+ return false;
+ }
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posX = ev.pageX;
+ posY = ev.pageY;
+ }
+ cal.hideShowCovered();
+ var st = cal.element.style;
+ st.left = (posX - cal.xOffs) + "px";
+ st.top = (posY - cal.yOffs) + "px";
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ cal.dragging = false;
+ with (Calendar) {
+ removeEvent(document, "mousemove", calDragIt);
+ removeEvent(document, "mouseup", calDragEnd);
+ tableMouseUp(ev);
+ }
+ cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (el.disabled) {
+ return false;
+ }
+ var cal = el.calendar;
+ cal.activeDiv = el;
+ Calendar._C = cal;
+ if (el.navtype != 300) with (Calendar) {
+ if (el.navtype == 50) {
+ el._current = el.firstChild.data;
+ addEvent(document, "mousemove", tableMouseOver);
+ } else
+ addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
+ addClass(el, "hilite active");
+ addEvent(document, "mouseup", tableMouseUp);
+ } else if (cal.isPopup) {
+ cal._dragStart(ev);
+ }
+ if (el.navtype == -1 || el.navtype == 1) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+ } else if (el.navtype == -2 || el.navtype == 2) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+ } else {
+ cal.timeout = null;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+ Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+ if (Calendar.is_ie) {
+ document.selection.empty();
+ }
+};
+
+Calendar.dayMouseOver = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+ return false;
+ }
+ if (el.ttip) {
+ if (el.ttip.substr(0, 1) == "_") {
+ el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+ }
+ el.calendar.tooltips.firstChild.data = el.ttip;
+ }
+ if (el.navtype != 300) {
+ Calendar.addClass(el, "hilite");
+ if (el.caldate) {
+ Calendar.addClass(el.parentNode, "rowhilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+ with (Calendar) {
+ var el = getElement(ev);
+ if (isRelated(el, ev) || _C || el.disabled) {
+ return false;
+ }
+ removeClass(el, "hilite");
+ if (el.caldate) {
+ removeClass(el.parentNode, "rowhilite");
+ }
+ el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"];
+ return stopEvent(ev);
+ }
+};
+
+/**
+ * A generic "click" handler :) handles all types of buttons defined in this
+ * calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+ var cal = el.calendar;
+ var closing = false;
+ var newdate = false;
+ var date = null;
+ if (typeof el.navtype == "undefined") {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ cal.date = new Date(el.caldate);
+ date = cal.date;
+ newdate = true;
+ // a date was clicked
+ if (!(cal.dateClicked = !el.otherMonth))
+ cal._init(cal.firstDayOfWeek, date);
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = (el.navtype == 0) ? new Date() : new Date(cal.date);
+ // unless "today" was clicked, we assume no date was clicked so
+ // the selected handler will know not to close the calenar when
+ // in single-click mode.
+ // cal.dateClicked = (el.navtype == 0);
+ cal.dateClicked = false;
+ var year = date.getFullYear();
+ var mon = date.getMonth();
+ function setMonth(m) {
+ var day = date.getDate();
+ var max = date.getMonthDays(m);
+ if (day > max) {
+ date.setDate(max);
+ }
+ date.setMonth(m);
+ };
+ switch (el.navtype) {
+ case 400:
+ Calendar.removeClass(el, "hilite");
+ var text = Calendar._TT["ABOUT"];
+ if (typeof text != "undefined") {
+ text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+ } else {
+ // FIXME: this should be removed as soon as lang files get updated!
+ text = "Help and about box text is not translated into this language.\n" +
+ "If you know this language and you feel generous please update\n" +
+ "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+ "and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n" +
+ "Thank you!\n" +
+ "http://dynarch.com/mishoo/calendar.epl\n";
+ }
+ alert(text);
+ return;
+ case -2:
+ if (year > cal.minYear) {
+ date.setFullYear(year - 1);
+ }
+ break;
+ case -1:
+ if (mon > 0) {
+ setMonth(mon - 1);
+ } else if (year-- > cal.minYear) {
+ date.setFullYear(year);
+ setMonth(11);
+ }
+ break;
+ case 1:
+ if (mon < 11) {
+ setMonth(mon + 1);
+ } else if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ setMonth(0);
+ }
+ break;
+ case 2:
+ if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ }
+ break;
+ case 100:
+ cal.setFirstDayOfWeek(el.fdow);
+ return;
+ case 50:
+ var range = el._range;
+ var current = el.firstChild.data;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ if (ev && ev.shiftKey) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.firstChild.data = newval;
+ cal.onUpdateTime();
+ return;
+ case 0:
+ // TODAY will bring us here
+ if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+ // remember, "date" was previously set to new
+ // Date() if TODAY was clicked; thus, it
+ // contains today date.
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ }
+ }
+ if (newdate) {
+ cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ }
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ * This function creates the calendar inside the given parent. If _par is
+ * null than it creates a popup calendar inside the BODY element. If _par is
+ * an element, be it BODY, then it creates a non-popup calendar (still
+ * hidden). Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+ var parent = null;
+ if (! _par) {
+ // default parent is the document body, in which case we create
+ // a popup calendar.
+ parent = document.getElementsByTagName("body")[0];
+ this.isPopup = true;
+ } else {
+ parent = _par;
+ this.isPopup = false;
+ }
+ this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+ var table = Calendar.createElement("table");
+ this.table = table;
+ table.cellSpacing = 0;
+ table.cellPadding = 0;
+ table.calendar = this;
+ Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+ var div = Calendar.createElement("div");
+ this.element = div;
+ div.className = "calendar";
+ if (this.isPopup) {
+ div.style.position = "absolute";
+ div.style.display = "none";
+ }
+ div.appendChild(table);
+
+ var thead = Calendar.createElement("thead", table);
+ var cell = null;
+ var row = null;
+
+ var cal = this;
+ var hh = function (text, cs, navtype) {
+ cell = Calendar.createElement("td", row);
+ cell.colSpan = cs;
+ cell.className = "button";
+ if (navtype != 0 && Math.abs(navtype) <= 2)
+ cell.className += " nav";
+ Calendar._add_evs(cell);
+ cell.calendar = cal;
+ cell.navtype = navtype;
+ if (text.substr(0, 1) != "&") {
+ cell.appendChild(document.createTextNode(text));
+ }
+ else {
+ // FIXME: dirty hack for entities
+ cell.innerHTML = text;
+ }
+ return cell;
+ };
+
+ row = Calendar.createElement("tr", thead);
+ var title_length = 6;
+ (this.isPopup) && --title_length;
+ (this.weekNumbers) && ++title_length;
+
+ hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+ this.title = hh("", title_length, 300);
+ this.title.className = "title";
+ if (this.isPopup) {
+ this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ this.title.style.cursor = "move";
+ hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("&#x00ab;", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("&#x2039;", 1, -1);
+ this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+ this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+ this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+ this._nav_nm = hh("&#x203a;", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("&#x00bb;", 1, 2);
+ this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+ // day names
+ row = Calendar.createElement("tr", thead);
+ row.className = "daynames";
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.className = "name wn";
+ cell.appendChild(document.createTextNode(Calendar._TT["WK"]));
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ if (!i) {
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+ this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+ this._displayWeekdays();
+
+ var tbody = Calendar.createElement("tbody", table);
+ this.tbody = tbody;
+
+ for (i = 6; i > 0; --i) {
+ row = Calendar.createElement("tr", tbody);
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+
+ if (this.showsTime) {
+ row = Calendar.createElement("tr", tbody);
+ row.className = "time";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ cell.innerHTML = Calendar._TT["TIME"] || "&nbsp;";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = this.weekNumbers ? 4 : 3;
+
+ (function(){
+ function makeTimePart(className, init, range_start, range_end) {
+ var part = Calendar.createElement("span", cell);
+ part.className = className;
+ part.appendChild(document.createTextNode(init));
+ part.calendar = cal;
+ part.ttip = Calendar._TT["TIME_PART"];
+ part.navtype = 50;
+ part._range = [];
+ if (typeof range_start != "number")
+ part._range = range_start;
+ else {
+ for (var i = range_start; i <= range_end; ++i) {
+ var txt;
+ if (i < 10 && range_end >= 10) txt = '0' + i;
+ else txt = '' + i;
+ part._range[part._range.length] = txt;
+ }
+ }
+ Calendar._add_evs(part);
+ return part;
+ };
+ var hrs = cal.date.getHours();
+ var mins = cal.date.getMinutes();
+ var t12 = !cal.time24;
+ var pm = (hrs > 12);
+ if (t12 && pm) hrs -= 12;
+ var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+ var span = Calendar.createElement("span", cell);
+ span.appendChild(document.createTextNode(":"));
+ span.className = "colon";
+ var M = makeTimePart("minute", mins, 0, 59);
+ var AP = null;
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ if (t12)
+ AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+ else
+ cell.innerHTML = "&nbsp;";
+
+ cal.onSetTime = function() {
+ var hrs = this.date.getHours();
+ var mins = this.date.getMinutes();
+ var pm = (hrs > 12);
+ if (pm && t12) hrs -= 12;
+ H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.firstChild.data = (mins < 10) ? ("0" + mins) : mins;
+ if (t12)
+ AP.firstChild.data = pm ? "pm" : "am";
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.firstChild.data, 10);
+ if (t12) {
+ if (/pm/i.test(AP.firstChild.data) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.firstChild.data) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.firstChild.data, 10));
+ date.setFullYear(y);
+ date.setMonth(m);
+ date.setDate(d);
+ this.dateClicked = false;
+ this.callHandler();
+ };
+ })();
+ } else {
+ this.onSetTime = this.onUpdateTime = function() {};
+ }
+
+ var tfoot = Calendar.createElement("tfoot", table);
+
+ row = Calendar.createElement("tr", tfoot);
+ row.className = "footrow";
+
+ cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+ cell.className = "ttip";
+ if (this.isPopup) {
+ cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ cell.style.cursor = "move";
+ }
+ this.tooltips = cell;
+
+ div = Calendar.createElement("div", this.element);
+ this.monthsCombo = div;
+ div.className = "combo";
+ for (i = 0; i < Calendar._MN.length; ++i) {
+ var mn = Calendar.createElement("div");
+ mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+ mn.month = i;
+ mn.appendChild(document.createTextNode(Calendar._SMN[i]));
+ div.appendChild(mn);
+ }
+
+ div = Calendar.createElement("div", this.element);
+ this.yearsCombo = div;
+ div.className = "combo";
+ for (i = 12; i > 0; --i) {
+ var yr = Calendar.createElement("div");
+ yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+ yr.appendChild(document.createTextNode(""));
+ div.appendChild(yr);
+ }
+
+ this._init(this.firstDayOfWeek, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ if (!window.calendar) {
+ return false;
+ }
+ (Calendar.is_ie) && (ev = window.event);
+ var cal = window.calendar;
+ var act = (Calendar.is_ie || ev.type == "keypress");
+ if (ev.ctrlKey) {
+ switch (ev.keyCode) {
+ case 37: // KEY left
+ act && Calendar.cellClick(cal._nav_pm);
+ break;
+ case 38: // KEY up
+ act && Calendar.cellClick(cal._nav_py);
+ break;
+ case 39: // KEY right
+ act && Calendar.cellClick(cal._nav_nm);
+ break;
+ case 40: // KEY down
+ act && Calendar.cellClick(cal._nav_ny);
+ break;
+ default:
+ return false;
+ }
+ } else switch (ev.keyCode) {
+ case 32: // KEY space (now)
+ Calendar.cellClick(cal._nav_now);
+ break;
+ case 27: // KEY esc
+ act && cal.callCloseHandler();
+ break;
+ case 37: // KEY left
+ case 38: // KEY up
+ case 39: // KEY right
+ case 40: // KEY down
+ if (act) {
+ var date = cal.date.getDate() - 1;
+ var el = cal.currentDateEl;
+ var ne = null;
+ var prev = (ev.keyCode == 37) || (ev.keyCode == 38);
+ switch (ev.keyCode) {
+ case 37: // KEY left
+ (--date >= 0) && (ne = cal.ar_days[date]);
+ break;
+ case 38: // KEY up
+ date -= 7;
+ (date >= 0) && (ne = cal.ar_days[date]);
+ break;
+ case 39: // KEY right
+ (++date < cal.ar_days.length) && (ne = cal.ar_days[date]);
+ break;
+ case 40: // KEY down
+ date += 7;
+ (date < cal.ar_days.length) && (ne = cal.ar_days[date]);
+ break;
+ }
+ if (!ne) {
+ if (prev) {
+ Calendar.cellClick(cal._nav_pm);
+ } else {
+ Calendar.cellClick(cal._nav_nm);
+ }
+ date = (prev) ? cal.date.getMonthDays() : 1;
+ el = cal.currentDateEl;
+ ne = cal.ar_days[date - 1];
+ }
+ Calendar.removeClass(el, "selected");
+ Calendar.addClass(ne, "selected");
+ cal.date = new Date(ne.caldate);
+ cal.callHandler();
+ cal.currentDateEl = ne;
+ }
+ break;
+ case 13: // KEY enter
+ if (act) {
+ cal.callHandler();
+ cal.hide();
+ }
+ break;
+ default:
+ return false;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+/**
+ * (RE)Initializes the calendar to the given date and firstDayOfWeek
+ */
+Calendar.prototype._init = function (firstDayOfWeek, date) {
+ var today = new Date();
+ this.table.style.visibility = "hidden";
+ var year = date.getFullYear();
+ if (year < this.minYear) {
+ year = this.minYear;
+ date.setFullYear(year);
+ } else if (year > this.maxYear) {
+ year = this.maxYear;
+ date.setFullYear(year);
+ }
+ this.firstDayOfWeek = firstDayOfWeek;
+ this.date = new Date(date);
+ var month = date.getMonth();
+ var mday = date.getDate();
+ var no_days = date.getMonthDays();
+
+ // calendar voodoo for computing the first day that would actually be
+ // displayed in the calendar, even if it's from the previous month.
+ // WARNING: this is magic. ;-)
+ date.setDate(1);
+ var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
+ if (day1 < 0)
+ day1 += 7;
+ date.setDate(-day1);
+ date.setDate(date.getDate() + 1);
+
+ var row = this.tbody.firstChild;
+ var MN = Calendar._SMN[month];
+ var ar_days = new Array();
+ var weekend = Calendar._TT["WEEKEND"];
+ for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.firstChild.data = date.getWeekNumber();
+ cell = cell.nextSibling;
+ }
+ row.className = "daysrow";
+ var hasdays = false;
+ for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(date.getDate() + 1)) {
+ var iday = date.getDate();
+ var wday = date.getDay();
+ cell.className = "day";
+ var current_month = (date.getMonth() == month);
+ if (!current_month) {
+ if (this.showsOtherMonths) {
+ cell.className += " othermonth";
+ cell.otherMonth = true;
+ } else {
+ cell.className = "emptycell";
+ cell.innerHTML = "&nbsp;";
+ cell.disabled = true;
+ continue;
+ }
+ } else {
+ cell.otherMonth = false;
+ hasdays = true;
+ }
+ cell.disabled = false;
+ cell.firstChild.data = iday;
+ if (typeof this.getDateStatus == "function") {
+ var status = this.getDateStatus(date, year, month, iday);
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ ar_days[ar_days.length] = cell;
+ cell.caldate = new Date(date);
+ cell.ttip = "_";
+ if (current_month && iday == mday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (date.getFullYear() == today.getFullYear() &&
+ date.getMonth() == today.getMonth() &&
+ iday == today.getDate()) {
+ cell.className += " today";
+ cell.ttip += Calendar._TT["PART_TODAY"];
+ }
+ if (weekend.indexOf(wday.toString()) != -1) {
+ cell.className += cell.otherMonth ? " oweekend" : " weekend";
+ }
+ }
+ }
+ if (!(hasdays || this.showsOtherMonths))
+ row.className = "emptyrow";
+ }
+ this.ar_days = ar_days;
+ this.title.firstChild.data = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ this.table.style.visibility = "visible";
+ // PROFILE
+ // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+/**
+ * Calls _init function above for going to a certain date (but only if the
+ * date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+ if (!date.equalsTo(this.date)) {
+ this._init(this.firstDayOfWeek, date);
+ }
+};
+
+/**
+ * Refreshes the calendar. Useful if the "disabledHandler" function is
+ * dynamic, meaning that the list of disabled date can change at runtime.
+ * Just * call this function if you think that the list of disabled dates
+ * should * change.
+ */
+Calendar.prototype.refresh = function () {
+ this._init(this.firstDayOfWeek, this.date);
+};
+
+/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
+Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
+ this._init(firstDayOfWeek, this.date);
+ this._displayWeekdays();
+};
+
+/**
+ * Allows customization of what dates are enabled. The "unaryFunction"
+ * parameter must be a function object that receives the date (as a JS Date
+ * object) and returns a boolean value. If the returned value is true then
+ * the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+ this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+ this.minYear = a;
+ this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+ if (this.onSelected) {
+ this.onSelected(this, this.date.print(this.dateFormat));
+ }
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+ if (this.onClose) {
+ this.onClose(this);
+ }
+ this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+ var el = this.element.parentNode;
+ el.removeChild(this.element);
+ Calendar._C = null;
+ window.calendar = null;
+};
+
+/**
+ * Moves the calendar element to a different section in the DOM tree (changes
+ * its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+ var el = this.element;
+ el.parentNode.removeChild(el);
+ new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown. If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+ if (!window.calendar) {
+ return false;
+ }
+ var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+ for (; el != null && el != calendar.element; el = el.parentNode);
+ if (el == null) {
+ // calls closeHandler which should hide the calendar.
+ window.calendar.callCloseHandler();
+ return Calendar.stopEvent(ev);
+ }
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+ var rows = this.table.getElementsByTagName("tr");
+ for (var i = rows.length; i > 0;) {
+ var row = rows[--i];
+ Calendar.removeClass(row, "rowhilite");
+ var cells = row.getElementsByTagName("td");
+ for (var j = cells.length; j > 0;) {
+ var cell = cells[--j];
+ Calendar.removeClass(cell, "hilite");
+ Calendar.removeClass(cell, "active");
+ }
+ }
+ this.element.style.display = "block";
+ this.hidden = false;
+ if (this.isPopup) {
+ window.calendar = this;
+ Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.hideShowCovered();
+};
+
+/**
+ * Hides the calendar. Also removes any "hilite" from the class of any TD
+ * element.
+ */
+Calendar.prototype.hide = function () {
+ if (this.isPopup) {
+ Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.element.style.display = "none";
+ this.hidden = true;
+ this.hideShowCovered();
+};
+
+/**
+ * Shows the calendar at a given absolute position (beware that, depending on
+ * the calendar element style -- position property -- this might be relative
+ * to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+ var s = this.element.style;
+ s.left = x + "px";
+ s.top = y + "px";
+ this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+ var self = this;
+ var p = Calendar.getAbsolutePos(el);
+ if (!opts || typeof opts != "string") {
+ this.showAt(p.x, p.y + el.offsetHeight);
+ return true;
+ }
+ function fixPosition(box) {
+ if (box.x < 0)
+ box.x = 0;
+ if (box.y < 0)
+ box.y = 0;
+ var cp = document.createElement("div");
+ var s = cp.style;
+ s.position = "absolute";
+ s.right = s.bottom = s.width = s.height = "0px";
+ document.body.appendChild(cp);
+ var br = Calendar.getAbsolutePos(cp);
+ document.body.removeChild(cp);
+ if (Calendar.is_ie) {
+ br.y += document.body.scrollTop;
+ br.x += document.body.scrollLeft;
+ } else {
+ br.y += window.scrollY;
+ br.x += window.scrollX;
+ }
+ var tmp = box.x + box.width - br.x;
+ if (tmp > 0) box.x -= tmp;
+ tmp = box.y + box.height - br.y;
+ if (tmp > 0) box.y -= tmp;
+ };
+ this.element.style.display = "block";
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ var w = self.element.offsetWidth;
+ var h = self.element.offsetHeight;
+ self.element.style.display = "none";
+ var valign = opts.substr(0, 1);
+ var halign = "l";
+ if (opts.length > 1) {
+ halign = opts.substr(1, 1);
+ }
+ // vertical alignment
+ switch (valign) {
+ case "T": p.y -= h; break;
+ case "B": p.y += el.offsetHeight; break;
+ case "C": p.y += (el.offsetHeight - h) / 2; break;
+ case "t": p.y += el.offsetHeight - h; break;
+ case "b": break; // already there
+ }
+ // horizontal alignment
+ switch (halign) {
+ case "L": p.x -= w; break;
+ case "R": p.x += el.offsetWidth; break;
+ case "C": p.x += (el.offsetWidth - w) / 2; break;
+ case "r": p.x += el.offsetWidth - w; break;
+ case "l": break; // already there
+ }
+ p.width = w;
+ p.height = h + 40;
+ self.monthsCombo.style.display = "none";
+ fixPosition(p);
+ self.showAt(p.x, p.y);
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+ this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+ this.ttDateFormat = str;
+};
+
+/**
+ * Tries to identify the date represented in a string. If successful it also
+ * calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function (str, fmt) {
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ if (!fmt) {
+ fmt = this.dateFormat;
+ }
+ var b = fmt.match(/%./g);
+ var i = 0, j = 0;
+ var hr = 0;
+ var min = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (!a[i])
+ continue;
+ switch (b[i]) {
+ case "%d":
+ case "%e":
+ d = parseInt(a[i], 10);
+ break;
+
+ case "%m":
+ m = parseInt(a[i], 10) - 1;
+ break;
+
+ case "%Y":
+ case "%y":
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ break;
+
+ case "%b":
+ case "%B":
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+ }
+ break;
+
+ case "%H":
+ case "%I":
+ case "%k":
+ case "%l":
+ hr = parseInt(a[i], 10);
+ break;
+
+ case "%P":
+ case "%p":
+ if (/pm/i.test(a[i]) && hr < 12)
+ hr += 12;
+ break;
+
+ case "%M":
+ min = parseInt(a[i], 10);
+ break;
+ }
+ }
+ if (y != 0 && m != -1 && d != 0) {
+ this.setDate(new Date(y, m, d, hr, min, 0));
+ return;
+ }
+ y = 0; m = -1; d = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
+ var t = -1;
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+ }
+ if (t != -1) {
+ if (m != -1) {
+ d = m+1;
+ }
+ m = t;
+ }
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
+ m = a[i]-1;
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ } else if (d == 0) {
+ d = a[i];
+ }
+ }
+ if (y == 0) {
+ var today = new Date();
+ y = today.getFullYear();
+ }
+ if (m != -1 && d != 0) {
+ this.setDate(new Date(y, m, d, hr, min, 0));
+ }
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ var self = this;
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ function getVisib(obj){
+ var value = obj.style.visibility;
+ if (!value) {
+ if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+ if (!Calendar.is_khtml)
+ value = document.defaultView.
+ getComputedStyle(obj, "").getPropertyValue("visibility");
+ else
+ value = '';
+ } else if (obj.currentStyle) { // IE
+ value = obj.currentStyle.visibility;
+ } else
+ value = '';
+ }
+ return value;
+ };
+
+ var tags = new Array("applet", "iframe", "select");
+ var el = self.element;
+
+ var p = Calendar.getAbsolutePos(el);
+ var EX1 = p.x;
+ var EX2 = el.offsetWidth + EX1;
+ var EY1 = p.y;
+ var EY2 = el.offsetHeight + EY1;
+
+ for (var k = tags.length; k > 0; ) {
+ var ar = document.getElementsByTagName(tags[--k]);
+ var cc = null;
+
+ for (var i = ar.length; i > 0;) {
+ cc = ar[--i];
+
+ p = Calendar.getAbsolutePos(cc);
+ var CX1 = p.x;
+ var CX2 = cc.offsetWidth + CX1;
+ var CY1 = p.y;
+ var CY2 = cc.offsetHeight + CY1;
+
+ if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = cc.__msh_save_visibility;
+ } else {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = "hidden";
+ }
+ }
+ }
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+ var fdow = this.firstDayOfWeek;
+ var cell = this.firstdayname;
+ var weekend = Calendar._TT["WEEKEND"];
+ for (var i = 0; i < 7; ++i) {
+ cell.className = "day name";
+ var realday = (i + fdow) % 7;
+ if (i) {
+ cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
+ cell.navtype = 100;
+ cell.calendar = this;
+ cell.fdow = realday;
+ Calendar._add_evs(cell);
+ }
+ if (weekend.indexOf(realday.toString()) != -1) {
+ Calendar.addClass(cell, "weekend");
+ }
+ cell.firstChild.data = Calendar._SDN[(i + fdow) % 7];
+ cell = cell.nextSibling;
+ }
+};
+
+/** Internal function. Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+ this.monthsCombo.style.display = "none";
+ this.yearsCombo.style.display = "none";
+};
+
+/** Internal function. Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+ if (this.dragging) {
+ return;
+ }
+ this.dragging = true;
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posY = ev.clientY + window.scrollY;
+ posX = ev.clientX + window.scrollX;
+ }
+ var st = this.element.style;
+ this.xOffs = posX - parseInt(st.left);
+ this.yOffs = posY - parseInt(st.top);
+ with (Calendar) {
+ addEvent(document, "mousemove", calDragIt);
+ addEvent(document, "mouseup", calDragEnd);
+ }
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR = 60 * Date.MINUTE;
+Date.DAY = 24 * Date.HOUR;
+Date.WEEK = 7 * Date.DAY;
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+ var year = this.getFullYear();
+ if (typeof month == "undefined") {
+ month = this.getMonth();
+ }
+ if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+ return 29;
+ } else {
+ return Date._MD[month];
+ }
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+ var time = now - then;
+ return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+ var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var DoW = d.getDay();
+ d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
+ var ms = d.valueOf(); // GMT
+ d.setMonth(0);
+ d.setDate(4); // Thu in Week 1
+ return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
+};
+
+/** Checks dates equality (ignores time) */
+Date.prototype.equalsTo = function(date) {
+ return ((this.getFullYear() == date.getFullYear()) &&
+ (this.getMonth() == date.getMonth()) &&
+ (this.getDate() == date.getDate()) &&
+ (this.getHours() == date.getHours()) &&
+ (this.getMinutes() == date.getMinutes()));
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+ var m = this.getMonth();
+ var d = this.getDate();
+ var y = this.getFullYear();
+ var wn = this.getWeekNumber();
+ var w = this.getDay();
+ var s = {};
+ var hr = this.getHours();
+ var pm = (hr >= 12);
+ var ir = (pm) ? (hr - 12) : hr;
+ var dy = this.getDayOfYear();
+ if (ir == 0)
+ ir = 12;
+ var min = this.getMinutes();
+ var sec = this.getSeconds();
+ s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+ s["%A"] = Calendar._DN[w]; // full weekday name
+ s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+ s["%B"] = Calendar._MN[m]; // full month name
+ // FIXME: %c : preferred date and time representation for the current locale
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+ s["%e"] = d; // the day of the month (range 1 to 31)
+ // FIXME: %D : american date style: %m/%d/%y
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+ s["%n"] = "\n"; // a newline character
+ s["%p"] = pm ? "PM" : "AM";
+ s["%P"] = pm ? "pm" : "am";
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
+ // FIXME: %R : the time in 24-hour notation %H:%M
+ s["%s"] = Math.floor(this.getTime() / 1000);
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+ s["%t"] = "\t"; // a tab character
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
+ // FIXME: %x : preferred date representation for the current locale without the time
+ // FIXME: %X : preferred time representation for the current locale without the date
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+ s["%Y"] = y; // year with the century
+ s["%%"] = "%"; // a literal '%' character
+
+ var re = /%./g;
+ if (!Calendar.is_ie5)
+ return str.replace(re, function (par) { return s[par] || par; });
+
+ var a = str.match(re);
+ for (var i = 0; i < a.length; i++) {
+ var tmp = s[a[i]];
+ if (tmp) {
+ re = new RegExp(a[i], 'g');
+ str = str.replace(re, tmp);
+ }
+ }
+
+ return str;
+};
+
+Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
+Date.prototype.setFullYear = function(y) {
+ var d = new Date(this);
+ d.__msh_oldSetFullYear(y);
+ if (d.getMonth() != this.getMonth())
+ this.setDate(28);
+ this.__msh_oldSetFullYear(y);
+};
+
+// END: DATE OBJECT PATCHES
+
+
+// global object that remembers the calendar
+window.calendar = null;
diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js
new file mode 100644
index 0000000..6a8e326
--- /dev/null
+++ b/httemplate/elements/calendar_stripped.js
@@ -0,0 +1,12 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ------------------------------------------------------------------
+ *
+ * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze"
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+ Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=firstDayOfWeek;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.firstChild.data;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date=new Date(el.caldate);date=cal.date;newdate=true;if(!(cal.dateClicked=!el.otherMonth))cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setFirstDayOfWeek(el.fdow);return;case 50:var range=el._range;var current=el.firstChild.data;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}}if(newdate){cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;if(text.substr(0,1)!="&"){cell.appendChild(document.createTextNode(text));}else{cell.innerHTML=text;}return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("&#x00d7;",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("&#x00ab;",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("&#x2039;",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("&#x203a;",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("&#x00bb;",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.appendChild(document.createTextNode(Calendar._TT["WK"]));}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||"&nbsp;";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.appendChild(document.createTextNode(init));part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.appendChild(document.createTextNode(":"));span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML="&nbsp;";cal.onSetTime=function(){var hrs=this.date.getHours();var mins=this.date.getMinutes();var pm=(hrs>12);if(pm&&t12)hrs-=12;H.firstChild.data=(hrs<10)?("0"+hrs):hrs;M.firstChild.data=(mins<10)?("0"+mins):mins;if(t12)AP.firstChild.data=pm?"pm":"am";};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.firstChild.data,10);if(t12){if(/pm/i.test(AP.firstChild.data)&&h<12)h+=12;else if(/am/i.test(AP.firstChild.data)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.firstChild.data,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.appendChild(document.createTextNode(Calendar._SMN[i]));div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;case 40:date+=7;(date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;}if(!ne){if(prev){Calendar.cellClick(cal._nav_pm);}else{Calendar.cellClick(cal._nav_nm);}date=(prev)?cal.date.getMonthDays():1;el=cal.currentDateEl;ne=cal.ar_days[date-1];}Calendar.removeClass(el,"selected");Calendar.addClass(ne,"selected");cal.date=new Date(ne.caldate);cal.callHandler();cal.currentDateEl=ne;}break;case 13:if(act){cal.callHandler();cal.hide();}break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date();this.table.style.visibility="hidden";var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false;for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(date.getDate()+1)){var iday=date.getDate();var wday=date.getDay();cell.className="day";var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML="&nbsp;";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){var status=this.getDateStatus(date,year,month,iday);if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=new Date(date);cell.ttip="_";if(current_month&&iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==today.getFullYear()&&date.getMonth()==today.getMonth()&&iday==today.getDate()){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1){cell.className+=cell.otherMonth?" oweekend":" weekend";}}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window.calendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=fmt.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(!a[i])continue;switch(b[i]){case "%d":case "%e":d=parseInt(a[i],10);break;case "%m":m=parseInt(a[i],10)-1;break;case "%Y":case "%y":y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;break;case "%M":min=parseInt(a[i],10);break;}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));return;}y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));}};Calendar.prototype.hideShowCovered=function(){var self=this;Calendar.continuation_for_the_fucking_khtml_browser=function(){function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=self.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(self.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype._displayWeekdays=function(){var fdow=this.firstDayOfWeek;var cell=this.firstdayname;var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<7;++i){cell.className="day name";var realday=(i+fdow)%7;if(i){cell.ttip=Calendar._TT["DAY_FIRST"].replace("%s",Calendar._DN[realday]);cell.navtype=100;cell.calendar=this;cell.fdow=realday;Calendar._add_evs(cell);}if(weekend.indexOf(realday.toString())!=-1){Calendar.addClass(cell,"weekend");}cell.firstChild.data=Calendar._SDN[(i+fdow)%7];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i<a.length;i++){var tmp=s[a[i]];if(tmp){re=new RegExp(a[i],'g');str=str.replace(re,tmp);}}return str;};Date.prototype.__msh_oldSetFullYear=Date.prototype.setFullYear;Date.prototype.setFullYear=function(y){var d=new Date(this);d.__msh_oldSetFullYear(y);if(d.getMonth()!=this.getMonth())this.setDate(28);this.__msh_oldSetFullYear(y);};window.calendar=null; \ No newline at end of file
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
new file mode 100644
index 0000000..10e4e40
--- /dev/null
+++ b/httemplate/elements/header.html
@@ -0,0 +1,21 @@
+<%
+ my($title, $menubar) = ( shift, shift );
+ my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+ my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+%>
+ <HTML>
+ <HEAD>
+ <TITLE>
+ <%= $title %>
+ </TITLE>
+ <META HTTP-Equiv="Cache-Control" Content="no-cache">
+ <META HTTP-Equiv="Pragma" Content="no-cache">
+ <META HTTP-Equiv="Expires" Content="0">
+ <%= $head %>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8"<%= $etc %>>
+ <FONT SIZE=6>
+ <%= $title %>
+ </FONT>
+ <BR><BR>
+ <%= $menubar ? "$menubar<BR><BR>" : '' %>
diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html
new file mode 100644
index 0000000..87a5031
--- /dev/null
+++ b/httemplate/elements/menubar.html
@@ -0,0 +1,8 @@
+<%
+ my($item, $url, @html);
+ while (@_) {
+ ($item, $url) = splice(@_,0,2);
+ push @html, qq!<A HREF="$url">$item</A>!;
+ }
+%>
+<%= join(' | ', @html) %>
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html
new file mode 100644
index 0000000..0510d32
--- /dev/null
+++ b/httemplate/elements/pager.html
@@ -0,0 +1,42 @@
+<%
+
+ my %opt = @_;
+
+ my $pager = '';
+ if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) {
+ unless ( $opt{'offset'} == 0 ) {
+ $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'});
+%>
+
+ <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A>
+
+<%
+ }
+ my $page = 0;
+ for ( my $poff = 0; $poff < $opt{'total'}; $poff += $opt{'maxrecords'} ) {
+ $page++;
+ if ( $opt{'offset'} == $poff ) {
+%>
+
+ <FONT SIZE="+2"><%= $page %></FONT>
+
+<%
+ } else {
+ $cgi->param('offset', $poff);
+%>
+
+ <A HREF="<%= $cgi->self_url %>"><%= $page %></A>
+
+<%
+ }
+ }
+ unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) {
+ $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'});
+%>
+
+ <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A>
+
+<%
+ }
+ }
+%>
diff --git a/httemplate/elements/small_custview.html b/httemplate/elements/small_custview.html
new file mode 100644
index 0000000..1e8ae73
--- /dev/null
+++ b/httemplate/elements/small_custview.html
@@ -0,0 +1,2 @@
+<% my $conf = new FS::Conf; %>
+<%= small_custview( shift, shift || $conf->config('countrydefault') ) %>
diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html
new file mode 100644
index 0000000..3b61087
--- /dev/null
+++ b/httemplate/elements/table.html
@@ -0,0 +1,8 @@
+<%
+ my $color = shift;
+ if ( $color ) {
+%>
+ <TABLE BGCOLOR="<%= $color %>" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<% } else { %>
+ <TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<% } %>
diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi
new file mode 100755
index 0000000..bb3d23a
--- /dev/null
+++ b/httemplate/graph/money_time-graph.cgi
@@ -0,0 +1,68 @@
+<%
+
+#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+my ($curmon,$curyear) = (localtime(time))[4,5];
+
+#find first month
+my $syear = $cgi->param('syear') || 1899+$curyear;
+my $smonth = $cgi->param('smonth') || $curmon+1;
+
+#find last month
+my $eyear = $cgi->param('eyear') || 1900+$curyear;
+my $emonth = $cgi->param('emonth') || $curmon+1;
+#if ( $emonth++>12 ) { $emonth-=12; $eyear++; }
+
+#my @labels;
+#my %data;
+
+my @items = qw( invoiced netsales credits payments receipts );
+my %label = (
+ 'invoiced' => 'Gross Sales (invoiced)',
+ 'netsales' => 'Net Sales (invoiced - applied credits)',
+ 'credits' => 'Credits',
+ 'payments' => 'Gross Receipts (payments)',
+ 'receipts' => 'Net Receipts/Cashflow (payments - refunds)',
+);
+my %color = (
+ 'invoiced' => [ 153, 153, 255 ], #light blue
+ 'netsales' => [ 0, 0, 204 ], #blue
+ 'credits' => [ 204, 0, 0 ], #red
+ 'payments' => [ 153, 204, 153 ], #light green
+ 'receipts' => [ 0, 204, 0 ], #green
+);
+
+my $report = new FS::Report::Table::Monthly (
+ 'items' => \@items,
+ 'start_month' => $smonth,
+ 'start_year' => $syear,
+ 'end_month' => $emonth,
+ 'end_year' => $eyear,
+);
+my %data = %{$report->data};
+
+#my $chart = Chart::LinesPoints->new(1024,480);
+#my $chart = Chart::LinesPoints->new(768,480);
+my $chart = Chart::LinesPoints->new(976,384);
+
+my $d = 0;
+$chart->set(
+ #'min_val' => 0,
+ 'legend' => 'bottom',
+ 'colors' => { ( map { 'dataset'.$d++ => $color{$_} } @items ),
+ #'grey_background' => [ 211, 211, 211 ],
+ 'grey_background' => 'white',
+ 'background' => [ 0xe8, 0xe8, 0xe8 ], #grey
+ },
+ #'grey_background' => 'false',
+ 'legend_labels' => [ map { $label{$_} } @items ],
+ 'brush_size' => 4,
+ #'pt_size' => 12,
+);
+
+my @data = map { $data{$_} } ( 'label', @items );
+
+http_header('Content-Type' => 'image/png' );
+
+$chart->_set_colors();
+
+%><%= $chart->scalar_png(\@data) %>
diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi
new file mode 100644
index 0000000..1c7d542
--- /dev/null
+++ b/httemplate/graph/money_time.cgi
@@ -0,0 +1,125 @@
+<!-- mason kludge -->
+<%
+
+#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+my ($curmon,$curyear) = (localtime(time))[4,5];
+
+#find first month
+my $syear = $cgi->param('syear') || 1899+$curyear;
+my $smonth = $cgi->param('smonth') || $curmon+1;
+
+#find last month
+my $eyear = $cgi->param('eyear') || 1900+$curyear;
+my $emonth = $cgi->param('emonth') || $curmon+1;
+
+%>
+
+<HTML>
+ <HEAD>
+ <TITLE>Sales, Credits and Receipts Summary</TITLE>
+ </HEAD>
+<BODY BGCOLOR="#e8e8e8">
+<IMG SRC="money_time-graph.cgi?<%= $cgi->query_string %>" WIDTH="976" HEIGHT="384">
+<BR>
+
+<%= table('e8e8e8') %>
+<%
+
+my @items = qw( invoiced netsales credits payments receipts );
+my %label = (
+ 'invoiced' => 'Gross Sales',
+ 'netsales' => 'Net Sales',
+ 'credits' => 'Credits',
+ 'payments' => 'Gross Receipts',
+ 'receipts' => 'Net Receipts',
+);
+my %color = (
+ 'invoiced' => '9999ff', #light blue
+ 'netsales' => '0000cc', #blue
+ 'credits' => 'cc0000', #red
+ 'payments' => '99cc99', #light green
+ 'receipts' => '00cc00', #green
+);
+my %link = (
+ 'invoiced' => "${p}search/cust_bill.html?",
+ 'credits' => "${p}search/cust_credit.html?",
+ 'payments' => "${p}search/cust_pay.cgi?magic=_date;",
+);
+
+my $report = new FS::Report::Table::Monthly (
+ 'items' => \@items,
+ 'start_month' => $smonth,
+ 'start_year' => $syear,
+ 'end_month' => $emonth,
+ 'end_year' => $eyear,
+);
+my $data = $report->data;
+
+my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+
+%>
+
+<TR><TD></TD>
+<% foreach my $column ( @{$data->{label}} ) {
+ #$column =~ s/^(\d+)\//$mon[$1-1]<BR>/e;
+ $column =~ s/^(\d+)\//$mon[$1-1]<BR>/;
+ %>
+ <TH><%= $column %></TH>
+<% } %>
+</TR>
+
+<% foreach my $row (@items) { %>
+ <TR><TH><FONT COLOR="#<%= $color{$row} %>"><%= $label{$row} %></FONT></TH>
+ <% my $link = exists($link{$row})
+ ? qq(<A HREF="$link{$row})
+ : '';
+ my @speriod = @{$data->{speriod}};
+ my @eperiod = @{$data->{eperiod}};
+ %>
+ <% foreach my $column ( @{$data->{$row}} ) { %>
+ <TD ALIGN="right" BGCOLOR="#ffffff">
+ <%= $link ? $link. 'begin='. shift(@speriod). ';end='. shift(@eperiod). '">' : '' %><FONT COLOR="#<%= $color{$row} %>">$<%= sprintf("%.2f", $column) %></FONT><%= $link ? '</A>' : '' %>
+ </TD>
+ <% } %>
+ </TR>
+<% } %>
+</TABLE>
+
+<BR>
+<FORM METHOD="POST">
+<!--
+<INPUT TYPE="checkbox" NAME="ar">
+ Accounts receivable (invoices - applied credits)<BR>
+<INPUT TYPE="checkbox" NAME="charged">
+ Just Invoices<BR>
+<INPUT TYPE="checkbox" NAME="defer">
+ Accounts receivable, with deferred revenue (invoices - applied credits, with charges for annual/semi-annual/quarterly/etc. services deferred over applicable time period) (there has got to be a shorter description for this)<BR>
+<INPUT TYPE="checkbox" NAME="cash">
+ Cashflow (payments - refunds)<BR>
+<BR>
+-->
+From <SELECT NAME="smonth">
+<% foreach my $mon ( 1..12 ) { %>
+<OPTION VALUE="<%= $mon %>"<%= $mon == $smonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %>
+<% } %>
+</SELECT>
+<SELECT NAME="syear">
+<% foreach my $y ( 1999 .. 2010 ) { %>
+<OPTION VALUE="<%= $y %>"<%= $y == $syear ? ' SELECTED' : '' %>><%= $y %>
+<% } %>
+</SELECT>
+ to <SELECT NAME="emonth">
+<% foreach my $mon ( 1..12 ) { %>
+<OPTION VALUE="<%= $mon %>"<%= $mon == $emonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %>
+<% } %>
+</SELECT>
+<SELECT NAME="eyear">
+<% foreach my $y ( 1999 .. 2010 ) { %>
+<OPTION VALUE="<%= $y %>"<%= $y == $eyear ? ' SELECTED' : '' %>><%= $y %>
+<% } %>
+</SELECT>
+
+<INPUT TYPE="submit" VALUE="Redisplay">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/images/ach.png b/httemplate/images/ach.png
new file mode 100644
index 0000000..fdcd5e6
--- /dev/null
+++ b/httemplate/images/ach.png
Binary files differ
diff --git a/httemplate/images/calendar.png b/httemplate/images/calendar.png
new file mode 100644
index 0000000..1632661
--- /dev/null
+++ b/httemplate/images/calendar.png
Binary files differ
diff --git a/httemplate/images/cvv2.png b/httemplate/images/cvv2.png
new file mode 100644
index 0000000..4610dcb
--- /dev/null
+++ b/httemplate/images/cvv2.png
Binary files differ
diff --git a/httemplate/images/cvv2_amex.png b/httemplate/images/cvv2_amex.png
new file mode 100644
index 0000000..21c36a0
--- /dev/null
+++ b/httemplate/images/cvv2_amex.png
Binary files differ
diff --git a/httemplate/images/small-logo.png b/httemplate/images/small-logo.png
new file mode 100644
index 0000000..a8fe807
--- /dev/null
+++ b/httemplate/images/small-logo.png
Binary files differ
diff --git a/httemplate/index.html b/httemplate/index.html
new file mode 100644
index 0000000..98a28ab
--- /dev/null
+++ b/httemplate/index.html
@@ -0,0 +1,212 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ Freeside Main Menu
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#FFFFFF">
+ <table width="100%">
+ <tr><td>
+ <IMG BORDER=0 ALT="freeside" SRC="images/small-logo.png">
+ </td><td valign="top">
+ <font color="#7f007b" size=7></font>
+ </td><td align=right valign=bottom>
+ version %%%VERSION%%%
+ <BR><A HREF="http://www.sisd.com/freeside">Freeside&nbsp;home&nbsp;page</A>
+ <BR><A HREF="docs/">Documentation</A>
+ </td></tr>
+ </table>
+
+<BR>
+[<A NAME="customer_service" style="background-color: #cccccc"> Sales / Customer service </A>]
+[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
+[ <A HREF="#reports">Reports</A> ]
+[ <A HREF="#sysadmin">Sysadmin</A> ]
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0" WIDTH="100%" BGCOLOR="#eeeeee">
+ <TR><TH BGCOLOR="#cccccc">Sales / Customer service</TH></TR>
+ <TR><TD>
+ <BR><FONT SIZE="+1"><A HREF="edit/cust_main.cgi">New Customer</A></FONT>
+ <BR>
+ <BR><FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="custnum_on" VALUE="1">Customer # <INPUT TYPE="text" NAME="custnum_text"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=custnum">all customers by customer number</A></FORM>
+ <FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="last_on" VALUE="1">Last name <INPUT TYPE="text" NAME="last_text"><SELECT NAME="last_type"><OPTION SELECTED VALUE="All">(all)</OPTION><OPTION>Fuzzy<OPTION>Substring</OPTION><OPTION>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=last">all customers by last name</A></FORM>
+ <FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="company_on" VALUE="1">Company <INPUT TYPE="text" NAME="company_text"><SELECT NAME="company_type"><OPTION SELECTED VALUE="All">(all)</OPTION><OPTION>Fuzzy<OPTION>Substring</OPTION><OPTION>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/cust_main.cgi?browse=company">all customers by company</A></FORM>
+<!-- <FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="address2_on" VALUE="1">Unit <INPUT TYPE="text" NAME="address2_text"><INPUT TYPE="submit" VALUE="Search"></FORM>-->
+ <FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="phone_on" VALUE="1">Phone # <INPUT TYPE="text" NAME="phone_text"><INPUT TYPE="submit" VALUE="Search"></FORM>
+ <BR><FORM ACTION="search/svc_acct.cgi" METHOD="POST">Username <INPUT TYPE="text" NAME="username"><SELECT NAME="username_type"><OPTION VALUE="All">(all)</OPTION><OPTION>Fuzzy</OPTION><OPTION>Substring</OPTION><OPTION SELECTED>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_acct.cgi?username">all accounts by username</A> or <A HREF="search/svc_acct.cgi?uid">uid</A></FORM>
+ <BR><FORM ACTION="search/svc_domain.cgi" METHOD="POST">Domain <INPUT TYPE="text" NAME="domain"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_domain.cgi?domain">all domains</A></FORM>
+ <BR><A HREF="search/svc_forward.cgi?svcnum">all mail forwards by svcnum</A><BR>
+ <BR><A HREF="search/svc_www.cgi?svcnum">all virtual hosts by svcnum</A><BR>
+ <BR><A HREF="search/svc_external.cgi?svcnum">all external services by svcnum</A><BR>
+ <BR>
+ </TD></TR>
+ </TABLE>
+
+
+
+ <BR><BR><BR>
+
+
+[ <A HREF="#customer_service">Sales / Customer service</A> ]
+[<A NAME="bookkeeping" style="background-color: #cccccc"> Bookkeeping / Collections </A>]
+[ <A HREF="#reports">Reports</A> ]
+[ <A HREF="#sysadmin">Sysadmin</A> ]
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
+ <TR><TH BGCOLOR="#cccccc">Bookkeeping / Collections</TH></TR>
+ <TR><TD>
+ <BR><A HREF="search/cust_main-quickpay.html">Quick payment entry</A>
+ <BR>
+ <BR><FORM ACTION="search/cust_main.cgi" METHOD="POST">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM>
+ <FORM ACTION="search/cust_bill.html" METHOD="POST">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM>
+ <FORM ACTION="search/cust_pay.cgi" METHOD="POST">Check # <INPUT TYPE="text" NAME="payinfo" SIZE="8"><INPUT TYPE="hidden" NAME="payby" VALUE="BILL"><INPUT TYPE="submit" VALUE="Search"></FORM>
+ <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg_report.cgi">Packages (by next bill date range)</A>
+ <BR><BR>Invoice reports
+ <UL>
+ <LI><a href="search/cust_bill_event.html">Invoice event errors (failed credit cards, procesoor or printer problems, etc.)</a>
+ <LI>open invoices (<A HREF="search/cust_bill.html?OPEN_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN_custnum">by customer number</A>)
+ <LI>15 day open invoices (<A HREF="search/cust_bill.html?OPEN15_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN15_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN15_custnum">by customer number</A>)
+ <LI>30 day open invoices (<A HREF="search/cust_bill.html?OPEN30_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN30_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN30_custnum">by customer number</A>)
+ <LI>60 day open invoices (<A HREF="search/cust_bill.html?OPEN60_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN60_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN60_custnum">by customer number</A>)
+ <LI>90 day open invoices (<A HREF="search/cust_bill.html?OPEN90_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN90_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN90_custnum">by customer number</A>)
+ <LI>120 day open invoices (<A HREF="search/cust_bill.html?OPEN120_invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?OPEN120_date">by date</A>) (<A HREF="search/cust_bill.html?OPEN120_custnum">by customer number</A>)
+ <LI>all invoices (<A HREF="search/cust_bill.html?invnum">by invoice number</A>) (<A HREF="search/cust_bill.html?date">by date</A>) (<A HREF="search/cust_bill.html?custnum">by customer number</A>)
+ </UL>
+ <A HREF="search/report_cust_pay.html">Payment report (by type and/or date range)</A>
+ <BR><BR><A HREF="search/report_cust_credit.html">Credit report (by employee and/or date range)</A>
+ <BR><BR><A HREF="graph/money_time.cgi">Sales, Credits and Receipts Summary</A>
+ <BR><BR><A HREF="search/report_receivables.cgi">Accounts Receivable Aging Summary</A>
+ <BR><BR><A HREF="search/report_prepaid_income.html">Prepaid Income (Unearned Revenue) Report</A>
+ <BR><BR><A HREF="search/report_tax.html">Sales Tax Liability Report</A>
+ <BR><BR>
+ <CENTER><HR WIDTH="94%" NOSHADE></CENTER><BR>
+ <A NAME="admin">Administration</a>
+ <ul>
+ <LI><A HREF="browse/part_pkg.cgi">View/Edit package definitions</A>
+ - One or more services are grouped together into a package and
+ given pricing information. Customers purchase packages, not
+ services.
+<!-- <LI><A HREF="browse/agent_type.cgi">View/Edit agent types</A>
+ - Agent types define groups of package definitions that you can
+ then assign to particular agents.
+ <LI><A HREF="browse/agent.cgi">View/Edit agents</A>
+ - Agents are resellers of your service. Agents may be limited
+ to a subset of your full offerings (via their type).
+-->
+ <LI><A HREF="browse/cust_main_county.cgi">View/Edit locales and tax rates</A>
+ - Change tax rates, or break down a country into states, or a state
+ into counties and assign different tax rates to each.
+ <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices
+ </ul>
+ <BR>
+ </TD></TR>
+ </TABLE>
+
+
+
+ <BR><BR><BR>
+
+
+
+[ <A HREF="#customer_service">Sales / Customer service</A> ]
+[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
+[<A NAME="reports" style="background-color: #cccccc"> Reports </A>]
+[ <A HREF="#sysadmin">Sysadmin</A> ]
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
+ <TR><TH BGCOLOR="#cccccc">Reports</TH></TR>
+ <TR><TD>
+ <BR>
+ <A HREF="search/sqlradius.html">RADIUS sessions</A><BR><BR>
+ Auditing pre-Freeside services with no customer record
+ <UL>
+ <LI>unlinked accounts (<A HREF="search/svc_acct.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_acct.cgi?UN_username">by username</A>) (<A HREF="search/svc_acct.cgi?UN_uid">by uid</A>)
+<!-- <LI>unlinked mail forwards (<A HREF="search/svc_forward.cgi?UN_svcnum">by service number</A>) (by ?)) -->
+ <LI>unlinked domains (<A HREF="search/svc_domain.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_domain.cgi?UN_domain">by domain</A>)
+ <LI>unlinked externals (<A HREF="search/svc_external.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_external.cgi?UN_id">by id</A>)
+ </UL>
+ Packages
+ <UL>
+ <LI><A HREF="search/cust_pkg.cgi?pkgnum">all packages (by package number)</A>
+ <LI><A HREF="search/cust_pkg.cgi?magic=suspended">suspended packages (by package number)</A>
+ <LI><A HREF="search/cust_pkg.cgi?APKG_pkgnum">packages with unconfigured services (by package number)</A>
+ <LI><A HREF="search/cust_pkg_report.cgi">packages (by next bill date range)</A>
+ </UL>
+ <A HREF="browse/part_pkg.cgi?active=1">Package definitions (by number of active packages)</A><BR><BR>
+ <A HREF="browse/part_svc.cgi?active=1">Service definitions (by number of active services)</A><BR><BR>
+ Customers
+ <UL>
+ <LI><A HREF="search/cust_main-otaker.cgi">Search customers by ordering employee</A>
+ </UL>
+ <FORM ACTION="search/sql.html" METHOD="POST">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM>
+
+ <BR>
+ </TD></TR>
+ </TABLE>
+
+
+
+ <BR><BR><BR>
+
+
+[ <A HREF="#customer_service">Sales / Customer service</A> ]
+[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
+[ <A HREF="#reports">Reports</A> ]
+[<A NAME="sysadmin" style="background-color: #cccccc"> Sysadmin </A>]
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
+ <TR><TH BGCOLOR="#cccccc">Sysadmin</TH></TR>
+ <TR><TD>
+ <BR>
+ <!-- <BR>View active NAS ports:
+ <A HREF="browse/nas.cgi">session server</A>
+ <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A>
+ <BR> -->
+ <A HREF="browse/queue.cgi">View pending job queue</A>
+ <BR><A HREF="misc/cust_main-import.cgi">Batch import customers from CSV file</A>
+ <BR><A HREF="misc/cust_main-import_charges.cgi">Batch import charges from CSV file</A>
+ <BR><A HREF="misc/dump.cgi">Download database dump</A>
+ <BR><BR><CENTER><HR WIDTH="94%" NOSHADE></CENTER><BR>
+ <A NAME="config" HREF="config/config-view.cgi">Configuration</a><!-- - <font size="+2" color="#ff0000">start here</font> -->
+ <BR><BR><A NAME="admin">Administration</a>
+ <ul>
+ <LI><A HREF="browse/part_export.cgi">View/Edit exports</A>
+ - Provisioning services to external machines, databases and APIs.
+ <LI><A HREF="browse/part_svc.cgi">View/Edit service definitions</A>
+ - Services are items you offer to your customers.
+ <LI><A HREF="browse/part_pkg.cgi">View/Edit package definitions</A>
+ - One or more services are grouped together into a package and
+ given pricing information. Customers purchase packages, not
+ services.
+ <LI><A HREF="browse/agent_type.cgi">View/Edit agent types</A>
+ - Agent types define groups of package definitions that you can
+ then assign to particular agents.
+ <LI><A HREF="browse/agent.cgi">View/Edit agents</A>
+ - Agents are resellers of your service. Agents may be limited
+ to a subset of your full offerings (via their type).
+ <LI><A HREF="browse/part_referral.cgi">View/Edit advertising sources</A>
+ - Where a customer heard about your service. Tracked for
+ informational purposes.
+ <LI><A HREF="browse/cust_main_county.cgi">View/Edit locales and tax rates</A>
+ - Change tax rates, or break down a country into states, or a state
+ into counties and assign different tax rates to each.
+ <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit access numbers</A>
+ - Points of Presence
+ <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices
+ <LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels.
+ <LI><A HREF="browse/part_virtual_field.cgi">View/Edit virtual fields</A>
+ - Locally defined fields
+ <LI><A HREF="browse/router.cgi">View/Edit routers</A>
+ - Broadband access routers
+ <LI><A HREF="browse/addr_block.cgi">View/Edit address blocks</A>
+ - Manage address blocks and block assignments to broadband routers.
+ </ul>
+ <BR>
+ </TD></TR>
+ </TABLE>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ <BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+ </BODY>
+</HTML>
diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi
new file mode 100755
index 0000000..44d85b8
--- /dev/null
+++ b/httemplate/misc/bill.cgi
@@ -0,0 +1,38 @@
+<%
+
+#untaint custnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d*)$/;
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
+die "Can't find customer!\n" unless $cust_main;
+
+my $error = $cust_main->bill(
+# 'time'=>$time
+ );
+#&eidiot($error) if $error;
+
+unless ( $error ) {
+ $cust_main->apply_payments;
+ $cust_main->apply_credits;
+
+ $error = $cust_main->collect(
+ # 'invoice-time'=>$time,
+ #'batch_card'=> 'yes',
+ #'batch_card'=> 'no',
+ #'report_badcard'=> 'yes',
+ #'retry_card' => 'yes',
+ 'retry' => 'yes',
+ );
+}
+#&eidiot($error) if $error;
+
+if ( $error ) {
+%>
+<!-- mason kludge -->
+<%
+ &idiot($error);
+} else {
+ print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum");
+}
+%>
diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi
new file mode 100755
index 0000000..43e439b
--- /dev/null
+++ b/httemplate/misc/cancel-unaudited.cgi
@@ -0,0 +1,34 @@
+<%
+
+my $dbh = dbh;
+
+#untaint svcnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+
+#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
+#die "Unknown svcnum!" unless $svc_acct;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+die "Unknown svcnum!" unless $cust_svc;
+my $cust_pkg = $cust_svc->cust_pkg;
+if ( $cust_pkg ) {
+ &eidiot( 'This account has already been audited. Cancel the '.
+ qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum.
+ '#cust_pkg'. $cust_pkg->pkgnum. '">'.
+ 'package</A> instead.');
+}
+
+my $error = $cust_svc->cancel;
+
+if ( $error ) {
+ %>
+<!-- mason kludge -->
+<%
+ &eidiot($error);
+} else {
+ print $cgi->redirect(popurl(2));
+}
+
+%>
diff --git a/httemplate/misc/cancel_pkg.cgi b/httemplate/misc/cancel_pkg.cgi
new file mode 100755
index 0000000..0487677
--- /dev/null
+++ b/httemplate/misc/cancel_pkg.cgi
@@ -0,0 +1,15 @@
+<%
+
+#untaint pkgnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal pkgnum";
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+
+my $error = $cust_pkg->cancel;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
+
+%>
diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi
new file mode 100755
index 0000000..3402b61
--- /dev/null
+++ b/httemplate/misc/catchall.cgi
@@ -0,0 +1,133 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my($svc_domain, $svcnum, $pkgnum, $svcpart, $part_svc);
+if ( $cgi->param('error') ) {
+ $svc_domain = new FS::svc_domain ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_domain')
+ } );
+ $svcnum = $svc_domain->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $svcnum=$1;
+ $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_domain) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ } else {
+
+ die "Invalid (svc_domain) svcnum!";
+
+ }
+}
+
+my %email;
+if ($pkgnum) {
+
+ #find all possible user svcnums (and emails)
+
+ #starting with that currently attached
+ if ($svc_domain->catchall) {
+ my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall});
+ $email{$svc_domain->catchall} = $svc_acct->email;
+ }
+
+ #and including the rest for this customer
+ my($u_part_svc,@u_acct_svcparts);
+ foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
+ push @u_acct_svcparts,$u_part_svc->getfield('svcpart');
+ }
+
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ my($custnum)=$cust_pkg->getfield('custnum');
+ my($i_cust_pkg);
+ foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+ my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
+ my($acct_svcpart);
+ foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding
+ #record(s) in cust_svc ( for this
+ #pkgnum ! )
+ my($i_cust_svc);
+ foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) {
+ my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')});
+ $email{$svc_acct->getfield('svcnum')}=$svc_acct->email;
+ }
+ }
+ }
+
+} else {
+
+ my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall});
+ $email{$svc_domain->catchall} = $svc_acct->email;
+}
+
+# add an absence of a catchall
+$email{''} = "(none)";
+
+my $p1 = popurl(1);
+print header("Domain Catchall Edit", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/catchall.cgi" METHOD=POST>!;
+
+#display
+
+ #formatting
+ print "<PRE>";
+
+#svcnum
+print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
+print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>";
+
+#pkgnum
+print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+#svcpart
+print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+
+my($domain,$catchall)=(
+ $svc_domain->domain,
+ $svc_domain->catchall,
+);
+
+print qq!<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">!;
+
+#catchall
+print qq!\n\nMail to <I>(anything)</I>@<B>$domain</B> forwards to <SELECT NAME="catchall" SIZE=1>!;
+foreach $_ (keys %email) {
+ print "<OPTION", $_ eq $catchall ? " SELECTED" : "",
+ qq! VALUE="$_">$email{$_}!;
+}
+print "</SELECT>";
+
+ #formatting
+ print "</PRE>\n";
+
+print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!;
+
+print <<END;
+
+ </FORM>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
new file mode 100755
index 0000000..5346fd9
--- /dev/null
+++ b/httemplate/misc/change_pkg.cgi
@@ -0,0 +1,66 @@
+<!-- mason kludge -->
+<%
+
+my $pkgnum;
+if ( $cgi->param('error') ) {
+ #$custnum = $cgi->param('custnum');
+ #%remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg');
+ $pkgnum = ($cgi->param('remove_pkg'))[0];
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ #$custnum = $1;
+ $pkgnum = $1;
+ #%remove_pkg = ();
+}
+
+my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } )
+ or die "unknown pkgnum $pkgnum";
+my $custnum = $cust_pkg->custnum;
+
+my $conf = new FS::Conf;
+
+my $p1 = popurl(1);
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+my $agent = $cust_main->agent;
+
+print header("Change Package", menubar(
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Main Menu' => $p,
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+print small_custview( $cust_main, $conf->config('countrydefault') ).
+ qq!<FORM ACTION="${p}edit/process/cust_pkg.cgi" METHOD=POST>!.
+ qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
+ qq!<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="$pkgnum">!.
+ '<BR>Current package: '. $part_pkg->pkg. ' - '. $part_pkg->comment.
+ qq!<BR>New package: <SELECT NAME="new_pkgpart"><OPTION VALUE=0></OPTION>!;
+
+foreach my $part_pkg (
+ grep { ! $_->disabled && $_->pkgpart != $cust_pkg->pkgpart }
+ map { $_->part_pkg } $agent->agent_type->type_pkgs
+) {
+ my $pkgpart = $part_pkg->pkgpart;
+ print qq!<OPTION VALUE="$pkgpart"!;
+ print ' SELECTED' if $cgi->param('error')
+ && $cgi->param('new_pkgpart') == $pkgpart;
+ print qq!>$pkgpart: !. $part_pkg->pkg. ' - '. $part_pkg->comment. '</OPTION>';
+}
+
+print <<END;
+</SELECT>
+<BR><BR><INPUT TYPE="submit" VALUE="Change package">
+ </FORM>
+ </BODY>
+</HTML>
+END
+%>
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
new file mode 100755
index 0000000..257c338
--- /dev/null
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint custnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal custnum";
+my $custnum = $1;
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+my @errors = $cust_main->cancel;
+eidiot(join(' / ', @errors)) if scalar(@errors);
+
+#print $cgi->redirect($p. "view/cust_main.cgi?". $cust_main->custnum);
+print $cgi->redirect($p);
+
+%>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
new file mode 100644
index 0000000..6b36f47
--- /dev/null
+++ b/httemplate/misc/cust_main-import.cgi
@@ -0,0 +1,51 @@
+<!-- mason kludge -->
+<%= header('Batch Customer Import') %>
+<FORM ACTION="process/cust_main-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import a CSV file containing customer records.<BR><BR>
+Default file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i><BR><BR>
+
+<%
+ #false laziness with edit/cust_main.cgi
+ my @agents = qsearch( 'agent', {} );
+ die "No agents created!" unless @agents;
+ my $agentnum = $agents[0]->agentnum; #default to first
+
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+<% } else { %>
+ <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1">
+ <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+<%
+ my @referrals = qsearch('part_referral',{});
+ die "No advertising sources created!" unless @referrals;
+ my $refnum = $referrals[0]->refnum; #default to first
+
+ if ( scalar(@referrals) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>">
+<% } else { %>
+ <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1">
+ <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %>
+ <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+ First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %>
+ <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+<% } %>
+</SELECT><BR><BR>
+
+ CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR>
+ <INPUT TYPE="submit" VALUE="Import">
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi
new file mode 100644
index 0000000..0822b9e
--- /dev/null
+++ b/httemplate/misc/cust_main-import_charges.cgi
@@ -0,0 +1,14 @@
+<!-- mason kludge -->
+<%= header('Batch Customer Charge') %>
+<FORM ACTION="process/cust_main-import_charges.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import a CSV file containing customer charges.<BR><BR>
+Default file format is CSV, with the following field order: <i>custnum, amount, description</i><BR><BR>
+If <i>amount</i> is negative, a credit will be applied instead.<BR><BR>
+<BR><BR>
+
+ CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR>
+ <INPUT TYPE="submit" VALUE="Import">
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi
new file mode 100755
index 0000000..30de04d
--- /dev/null
+++ b/httemplate/misc/delete-cust_credit.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint crednum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit',{'crednum'=>$crednum});
+my $custnum = $cust_credit->custnum;
+
+my $error = $cust_credit->delete;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/delete-cust_pay.cgi b/httemplate/misc/delete-cust_pay.cgi
new file mode 100755
index 0000000..3efd918
--- /dev/null
+++ b/httemplate/misc/delete-cust_pay.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum});
+my $custnum = $cust_pay->custnum;
+
+my $error = $cust_pay->delete;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi
new file mode 100755
index 0000000..4302317
--- /dev/null
+++ b/httemplate/misc/delete-customer.cgi
@@ -0,0 +1,60 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+die "Customer deletions not enabled" unless $conf->exists('deletecustomers');
+
+my($custnum, $new_custnum);
+if ( $cgi->param('error') ) {
+ $custnum = $cgi->param('custnum');
+ $new_custnum = $cgi->param('new_custnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "Illegal query: $query";
+ $custnum = $1;
+ $new_custnum = '';
+}
+my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } )
+ or die "Customer not found: $custnum";
+
+print header('Delete customer');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print
+ qq!<form action="!, popurl(1), qq!process/delete-customer.cgi" method=post>!,
+ qq!<input type="hidden" name="custnum" value="$custnum">!;
+
+if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) {
+ print "Move uncancelled packages to customer number ",
+ qq!<input type="text" name="new_custnum" value="$new_custnum"><br><br>!;
+}
+
+print <<END;
+This will <b>completely remove</b> all traces of this customer record. This
+is <B>not</B> what you want if this is a real customer who has simply
+canceled service with you. For that, cancel all of the customer's packages.
+(you can optionally hide cancelled customers with the <a href="../config/config-view.cgi#hidecancelledcustomers">hidecancelledcustomers</a> configuration option)
+<br>
+<br>Are you <b>absolutely sure</b> you want to delete this customer?
+<br><input type="submit" value="Yes">
+</form></body></html>
+END
+
+#Deleting a customer you have financial records on (i.e. credits) is
+#typically considered fraudulant bookkeeping. Remember, deleting
+#customers should ONLY be used for completely bogus records. You should
+#NOT delete real customers who simply discontinue service.
+#
+#For real customers who simply discontinue service, cancel all of the
+#customer's packages. Customers with all cancelled packages are not
+#billed. There is no need to take further action to prevent billing on
+#customers with all cancelled packages.
+#
+#Also see the "hidecancelledcustomers" and "hidecancelledpackages"
+#configuration options, which will allow you to surpress the display of
+#cancelled customers and packages, respectively.
+
+%>
diff --git a/httemplate/misc/delete-domain_record.cgi b/httemplate/misc/delete-domain_record.cgi
new file mode 100755
index 0000000..dcc2d50
--- /dev/null
+++ b/httemplate/misc/delete-domain_record.cgi
@@ -0,0 +1,15 @@
+<%
+
+#untaint recnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal recnum";
+my $recnum = $1;
+
+my $domain_record = qsearchs('domain_record',{'recnum'=>$recnum});
+
+my $error = $domain_record->delete;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/svc_domain.cgi?". $domain_record->svcnum);
+
+%>
diff --git a/httemplate/misc/delete-part_export.cgi b/httemplate/misc/delete-part_export.cgi
new file mode 100755
index 0000000..7c4ab8b
--- /dev/null
+++ b/httemplate/misc/delete-part_export.cgi
@@ -0,0 +1,15 @@
+<%
+
+#untaint exportnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal exportnum";
+my $exportnum = $1;
+
+my $part_export = qsearchs('part_export',{'exportnum'=>$exportnum});
+
+my $error = $part_export->delete;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "browse/part_export.cgi");
+
+%>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
new file mode 100644
index 0000000..306ef5d
--- /dev/null
+++ b/httemplate/misc/download-batch.cgi
@@ -0,0 +1,16 @@
+<%
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+http_header('Content-Type' => 'text/plain' );
+
+for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch('cust_pay_batch', {} )
+) {
+
+$cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+my( $mon, $y ) = ( $2, $1 );
+$mon = "0$mon" if $mon < 10;
+my $exp = "$mon$y";
+
+%>,,,,<%= $cust_pay_batch->cardnum %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %>
+<% } %>
diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi
new file mode 100644
index 0000000..dc1323b
--- /dev/null
+++ b/httemplate/misc/dump.cgi
@@ -0,0 +1,19 @@
+<%
+ if ( driver_name =~ /^Pg$/ ) {
+ my $dbname = (split(':', datasrc))[2];
+ if ( $dbname =~ /[;=]/ ) {
+ my %elements = map { /^(\w+)=(.*)$/; $1=>$2 } split(';', $dbname);
+ $dbname = $elements{'dbname'};
+ }
+ open(DUMP,"pg_dump $dbname |");
+ } else {
+ eidiot "don't (yet) know how to dump ". driver_name. " databases\n";
+ }
+
+ http_header('Content-Type' => 'text/plain' );
+
+ while (<DUMP>) {
+ print $_;
+ }
+ close DUMP;
+%>
diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi
new file mode 100755
index 0000000..a560a18
--- /dev/null
+++ b/httemplate/misc/email-invoice.cgi
@@ -0,0 +1,23 @@
+<%
+
+my $conf = new FS::Conf;
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d*)$/;
+my $invnum = $1;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+my $error = send_email(
+ 'from' => $cust_bill->_agent_invoice_from || $conf->config('invoice_from'),
+ 'to' => [ grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ],
+ 'subject' => 'Invoice',
+ 'body' => [ $cust_bill->print_text ],
+);
+eidiot($error) if $error;
+
+my $custnum = $cust_bill->getfield('custnum');
+print $cgi->redirect("${p}view/cust_main.cgi?$custnum");
+
+%>
diff --git a/httemplate/misc/expire_pkg.cgi b/httemplate/misc/expire_pkg.cgi
new file mode 100755
index 0000000..b59674a
--- /dev/null
+++ b/httemplate/misc/expire_pkg.cgi
@@ -0,0 +1,55 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $pkgnum = $1;
+
+#get package record
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+die "Unknown pkgnum $pkgnum" unless $cust_pkg;
+my $part_pkg = $cust_pkg->part_pkg;
+
+my $custnum = $cust_pkg->getfield('custnum');
+
+my $date = $cust_pkg->expire ? time2str('%D', $cust_pkg->expire) : '';
+
+%>
+
+<%= header('Expire package', menubar(
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Main Menu' => popurl(2)
+)) %>
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
+<%= $pkgnum %>: <%= $part_pkg->pkg. ' - '. $part_pkg->comment %>
+
+<FORM NAME="formname" ACTION="process/expire_pkg.cgi" METHOD="post">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+<TABLE>
+ <TR>
+ <TD>Cancel package on </TD>
+ <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<%= $date %>">
+ <IMG SRC="<%= $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date">
+ <BR><I>m/d/y</I>
+ </TD>
+ </TR>
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "expire_date",
+ ifFormat: "%m/%d/%Y",
+ button: "expire_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+<INPUT TYPE="submit" VALUE="Cancel later">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi
new file mode 100755
index 0000000..18cd378
--- /dev/null
+++ b/httemplate/misc/link.cgi
@@ -0,0 +1,74 @@
+<!-- mason kludge -->
+<%
+
+my %link_field = (
+ 'svc_acct' => 'username',
+ 'svc_domain' => 'domain',
+);
+
+my %link_field2 = (
+ 'svc_acct' => { label => 'Domain',
+ field => 'domsvc',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'domain'
+ },
+);
+
+my($query) = $cgi->keywords;
+my($pkgnum, $svcpart) = ('', '');
+foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart});
+my $svc = $part_svc->getfield('svc');
+my $svcdb = $part_svc->getfield('svcdb');
+my $link_field = $link_field{$svcdb};
+my $link_field2 = $link_field2{$svcdb};
+
+%>
+
+<%= header("Link to existing $svc") %>
+<FORM ACTION="<%= popurl(1) %>process/link.cgi" METHOD=POST>
+
+<% if ( $link_field ) { %>
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="">
+ <INPUT TYPE="hidden" NAME="link_field" VALUE="<%= $link_field %>">
+ <%= $link_field %> of existing service: <INPUT TYPE="text" NAME="link_value">
+ <BR>
+ <% if ( $link_field2 ) { %>
+ <INPUT TYPE="hidden" NAME="link_field2" VALUE="<%= $link_field2->{field} %>">
+ <%= $link_field2->{'label'} %> of existing service:
+ <% if ( $link_field2->{'type'} eq 'select' ) { %>
+ <% if ( $link_field2->{'select_table'} ) { %>
+ <SELECT NAME="link_value2">
+ <OPTION> </OPTION>
+ <% foreach my $r ( qsearch( $link_field2->{'select_table'}, {})) { %>
+ <% my $key = $link_field2->{'select_key'}; %>
+ <% my $label = $link_field2->{'select_label'}; %>
+ <OPTION VALUE="<%= $r->$key() %>"><%= $r->$label() %></OPTION>
+ <% } %>
+ </SELECT>
+ <% } else { %>
+ Don't know how to process secondary link field for <%= $svcdb %>
+ (type=>select but no select_table)
+ <% } %>
+ <% } else { %>
+ Don't know how to process secondary link field for <%= $svcdb %>
+ (unknown type <%= $link_field2->{'type'} %>)
+ <% } %>
+ <BR>
+ <% } %>
+<% } else { %>
+ Service # of existing service: <INPUT TYPE="text" NAME="svcnum" VALUE="">
+<% } %>
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>">
+<BR><INPUT TYPE="submit" VALUE="Link">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi
new file mode 100644
index 0000000..2f3b738
--- /dev/null
+++ b/httemplate/misc/meta-import.cgi
@@ -0,0 +1,64 @@
+<!-- mason kludge -->
+<%= header('Import') %>
+<FORM ACTION="process/meta-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import data from a DBI data source<BR><BR>
+
+<%
+ #false laziness with edit/cust_main.cgi
+ my @agents = qsearch( 'agent', {} );
+ die "No agents created!" unless @agents;
+ my $agentnum = $agents[0]->agentnum; #default to first
+
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+<% } else { %>
+ <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1">
+ <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+<%
+ my @referrals = qsearch('part_referral',{});
+ die "No advertising sources created!" unless @referrals;
+ my $refnum = $referrals[0]->refnum; #default to first
+
+ if ( scalar(@referrals) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>">
+<% } else { %>
+ <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1">
+ <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %>
+ <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+ First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %>
+ <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+<% } %>
+</SELECT><BR><BR>
+
+ <table>
+ <tr>
+ <td align="right">DBI data source: </td>
+ <td><INPUT TYPE="text" NAME="data_source"></td>
+ </tr>
+ <tr>
+ <td align="right">DBI username: </td>
+ <td><INPUT TYPE="text" NAME="username"></td>
+ </tr>
+ <tr>
+ <td align="right">DBI password: </td>
+ <td><INPUT TYPE="text" NAME="password"></td>
+ </tr>
+ </table>
+ <INPUT TYPE="submit" VALUE="Import">
+
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
new file mode 100644
index 0000000..02c6c54
--- /dev/null
+++ b/httemplate/misc/payment.cgi
@@ -0,0 +1,209 @@
+<%
+ my %type = ( 'CARD' => 'credit card',
+ 'CHEK' => 'electronic check (ACH)',
+ );
+
+ $cgi->param('payby') =~ /^(CARD|CHEK)$/
+ or die "unknown payby ". $cgi->param('payby');
+ my $payby = $1;
+
+ $cgi->param('custnum') =~ /^(\d+)$/
+ or die "illegal custnum ". $cgi->param('custnum');
+ my $custnum = $1;
+
+ my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } );
+ die "unknown custnum $custnum" unless $cust_main;
+
+ my $balance = $cust_main->balance;
+
+ my $payinfo = '';
+
+ #false laziness w/selfservice make_payment.html shortcut for one-country
+ my $conf = new FS::Conf;
+ my %states = map { $_->state => 1 }
+ qsearch('cust_main_county', {
+ 'country' => $conf->config('defaultcountry') || 'US'
+ } );
+ my @states = sort { $a cmp $b } keys %states;
+
+ my $paybatch = "webui-payment-". time. "-$$-". rand() * 2**32;
+
+%>
+<%= include( '/elements/header.html', "Process $type{$payby} payment" ) %>
+<%= include( '/elements/small_custview.html', $cust_main ) %>
+<FORM NAME="OneTrueForm" ACTION="process/payment.cgi" METHOD="POST" onSubmit="document.OneTrueForm.process.disabled=true">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>">
+<INPUT TYPE="hidden" NAME="payby" VALUE="<%= $payby %>">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%= $paybatch %>">
+<SCRIPT>
+var mywindow = -1;
+function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+}
+function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1;
+}
+var achwindow = -1;
+function achopen(filename,windowname,properties) {
+ achclose();
+ achwindow = window.open(filename,windowname,properties);
+}
+function achclose() {
+ if ( achwindow != -1 )
+ achwindow.close();
+ achwindow = -1;
+}
+</SCRIPT>
+<% #include( '/elements/table.html', '#cccccc' ) %>
+<%= ntable('#cccccc') %>
+ <TR>
+ <TD ALIGN="right">Payment amount</TD>
+ <TD>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<%= $balance > 0 ? sprintf("%.2f", $balance) : '' %>">
+ </TD></TR></TABLE>
+ </TD>
+ </TR>
+<% if ( $payby eq 'CARD' ) {
+ my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
+ my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
+ my $address1 = $cust_main->address1;
+ my $address2 = $cust_main->address2;
+ my $city = $cust_main->city;
+ my $state = $cust_main->state;
+ my $zip = $cust_main->zip;
+ if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ $payinfo = $cust_main->payinfo;
+ $paycvv = $cust_main->paycvv;
+ ( $month, $year ) = $cust_main->paydate_monthyear;
+ $payname = $cust_main->payname if $cust_main->payname;
+ }
+%>
+ <TR>
+ <TD ALIGN="right">Card&nbsp;number</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%=$payinfo%>"> </TD>
+ <TD>Exp.</TD>
+ <TD>
+ <SELECT NAME="month">
+ <% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { %>
+ <OPTION<%= $_ == $month ? ' SELECTED' : '' %>><%= $_ %>
+ <% } %>
+ </SELECT>
+ </TD>
+ <TD> / </TD>
+ <TD>
+ <SELECT NAME="year">
+ <% my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { %>
+ <OPTION<%= $_ == $year ? ' SELECTED' : '' %>><%= $_ %>
+ <% } %>
+ </SELECT>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">CVV2</TD>
+ <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<%= $paycvv %>" SIZE=4 MAXLENGTH=4>
+ (<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+ <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+ </TR><TR>
+ <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%=$address1%>">
+ </TD>
+ </TR><TR>
+ <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%=$address2%>">
+ </TD>
+ </TR><TR>
+ <TD ALIGN="right">City</TD>
+ <TD>
+ <TABLE>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%=$city%>">
+ </TD>
+ <TD>State</TD>
+ <TD>
+ <SELECT NAME="state">
+ <% for ( @states ) { %>
+ <OPTION<%= $_ eq $state ? ' SELECTED' : '' %>><%= $_ %>
+ <% } %>
+ </SELECT>
+ </TD>
+ <TD>Zip</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%=$zip%>">
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ </TR>
+
+<% } elsif ( $payby eq 'CHEK' ) {
+ my( $payinfo1, $payinfo2, $payname, $ss ) = ( '', '', '', '' );
+ if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+ $cust_main->payinfo =~ /^(\d+)\@(\d+)$/
+ or die "unparsable payinfo ". $cust_main->payinfo;
+ ($payinfo1, $payinfo2) = ($1, $2);
+ $payname = $cust_main->payname;
+ $ss = $cust_main->ss;
+ }
+%>
+ <INPUT TYPE="hidden" NAME="month" VALUE="12">
+ <INPUT TYPE="hidden" NAME="year" VALUE="2037">
+ <TR>
+ <TD ALIGN="right">Account&nbsp;number</TD>
+ <TD><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="<%=$payinfo1%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">ABA/Routing&nbsp;number</TD>
+ <TD>
+ <INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="<%=$payinfo2%>">
+ (<A HREF="javascript:achopen('../docs/ach.html','ach','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=384,height=256')">help</A>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Bank&nbsp;name</TD>
+ <TD><INPUT TYPE="text" NAME="payname" VALUE="<%=$payname%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">
+ Account&nbsp;holder<BR>
+ Social&nbsp;security&nbsp;or&nbsp;tax&nbspID&nbsp;#
+ </TD>
+ <TD><INPUT TYPE="text" NAME="ss" VALUE="<%=$ss%>"></TD>
+ </TR>
+
+<% } %>
+
+<TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
+ Remember this information
+ </TD>
+</TR><TR>
+ <TD COLSPAN=2>
+ <INPUT TYPE="checkbox"<%= ( ( $payby eq 'CARD' && $cust_main->payby ne 'DCRD' ) || ( $payby eq 'CHEK' && $cust_main->payby eq 'CHEK' ) ) ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
+ Charge future payments to this <%= $type{$payby} %> automatically
+ </TD>
+</TR>
+</TABLE>
+<BR>
+<INPUT TYPE="submit" NAME="process" VALUE="Process payment">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi
new file mode 100755
index 0000000..144f615
--- /dev/null
+++ b/httemplate/misc/print-invoice.cgi
@@ -0,0 +1,29 @@
+<%
+
+my $conf = new FS::Conf;
+my $lpr = $conf->config('lpr');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d*)$/;
+my $invnum = $1;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+ open(LPR,"|$lpr") or die "Can't open $lpr: $!";
+
+ if ( $conf->exists('invoice_latex') ) {
+ print LPR $cust_bill->print_ps; #( date )
+ } else {
+ print LPR $cust_bill->print_text; #( date )
+ }
+
+ close LPR
+ or die $! ? "Error closing $lpr: $!"
+ : "Exit status $? from $lpr";
+
+my $custnum = $cust_bill->getfield('custnum');
+
+print $cgi->redirect("${p}view/cust_main.cgi?$custnum");
+
+%>
diff --git a/httemplate/misc/process/catchall.cgi b/httemplate/misc/process/catchall.cgi
new file mode 100755
index 0000000..44a63f9
--- /dev/null
+++ b/httemplate/misc/process/catchall.cgi
@@ -0,0 +1,33 @@
+<%
+
+$FS::svc_domain::whois_hack=1;
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum =$1;
+
+my $old = qsearchs('svc_domain',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::svc_domain ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_domain'), qw( pkgnum svcpart ) )
+} );
+
+$new->setfield('action' => 'M');
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->getfield('svcnum');
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "catchall.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi
new file mode 100644
index 0000000..9e1adce
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import.cgi
@@ -0,0 +1,30 @@
+<%
+
+ my $fh = $cgi->upload('csvfile');
+ #warn $cgi;
+ #warn $fh;
+
+ my $error = defined($fh)
+ ? FS::cust_main::batch_import( {
+ filehandle => $fh,
+ agentnum => scalar($cgi->param('agentnum')),
+ refnum => scalar($cgi->param('refnum')),
+ pkgpart => scalar($cgi->param('pkgpart')),
+ 'fields' => [qw( cust_pkg.setup dayphone first last address1 address2
+ city state zip comments )],
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Import sucessful') %> <%
+ }
+%>
diff --git a/httemplate/misc/process/cust_main-import_charges.cgi b/httemplate/misc/process/cust_main-import_charges.cgi
new file mode 100644
index 0000000..14df1bd
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import_charges.cgi
@@ -0,0 +1,26 @@
+<%
+
+ my $fh = $cgi->upload('csvfile');
+ #warn $cgi;
+ #warn $fh;
+
+ my $error = defined($fh)
+ ? FS::cust_main::batch_charge( {
+ filehandle => $fh,
+ 'fields' => [qw( custnum amount pkg )],
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import_charges.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Import sucessful') %> <%
+ }
+%>
diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi
new file mode 100755
index 0000000..16bdbae
--- /dev/null
+++ b/httemplate/misc/process/delete-customer.cgi
@@ -0,0 +1,29 @@
+<%
+
+my $conf = new FS::Conf;
+die "Customer deletions not enabled" unless $conf->exists('deletecustomers');
+
+$cgi->param('custnum') =~ /^(\d+)$/;
+my $custnum = $1;
+my $new_custnum;
+if ( $cgi->param('new_custnum') ) {
+ $cgi->param('new_custnum') =~ /^(\d+)$/
+ or die "Illegal new customer number: ". $cgi->param('new_custnum');
+ $new_custnum = $1;
+} else {
+ $new_custnum = '';
+}
+my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } )
+ or die "Customer not found: $custnum";
+
+my $error = $cust_main->delete($new_custnum);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string );
+} elsif ( $new_custnum ) {
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum");
+} else {
+ print $cgi->redirect(popurl(3));
+}
+%>
diff --git a/httemplate/misc/process/expire_pkg.cgi b/httemplate/misc/process/expire_pkg.cgi
new file mode 100755
index 0000000..dc35592
--- /dev/null
+++ b/httemplate/misc/process/expire_pkg.cgi
@@ -0,0 +1,25 @@
+<%
+
+#untaint date & pkgnum
+
+my $date;
+if ( $cgi->param('date') ) {
+ str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
+ $date=$1;
+} else {
+ $date='';
+}
+
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum";
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $cust_pkg->hash;
+$hash{expire}=$date;
+my $new = new FS::cust_pkg ( \%hash );
+my $error = $new->replace($cust_pkg);
+&eidiot($error) if $error;
+
+print $cgi->redirect(popurl(3). "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
+
+%>
diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi
new file mode 100755
index 0000000..acdd1ad
--- /dev/null
+++ b/httemplate/misc/process/link.cgi
@@ -0,0 +1,57 @@
+<%
+
+$cgi->param('pkgnum') =~ /^(\d+)$/;
+my $pkgnum = $1;
+$cgi->param('svcpart') =~ /^(\d+)$/;
+my $svcpart = $1;
+$cgi->param('svcnum') =~ /^(\d*)$/;
+my $svcnum = $1;
+
+unless ( $svcnum ) {
+ my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart});
+ my $svcdb = $part_svc->getfield('svcdb');
+ $cgi->param('link_field') =~ /^(\w+)$/;
+ my $link_field = $1;
+ my %search = ( $link_field => $cgi->param('link_value') );
+ if ( $cgi->param('link_field2') =~ /^(\w+)$/ ) {
+ $search{$1} = $cgi->param('link_value2');
+ }
+ my $svc_x = ( sort { ($b->cust_svc->pkgnum > 0) <=> ($a->cust_svc->pkgnum > 0)
+ or ($b->cust_svc->svcpart == $svcpart)
+ <=> ($a->cust_svc->svcpart == $svcpart)
+ }
+ qsearch( $svcdb, \%search )
+ )[0];
+ eidiot("$link_field not found!") unless $svc_x;
+ $svcnum = $svc_x->svcnum;
+}
+
+my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+die "svcnum not found!" unless $old;
+my $conf = new FS::Conf;
+my($error, $new);
+if ( $old->pkgnum && ! $conf->exists('legacy_link-steal') ) {
+ $error = "svcnum $svcnum already linked to package ". $old->pkgnum;
+} else {
+ $new = new FS::cust_svc ({
+ 'svcnum' => $svcnum,
+ 'pkgnum' => $pkgnum,
+ 'svcpart' => $svcpart,
+ });
+
+ $error = $new->replace($old);
+}
+
+unless ($error) {
+ #no errors, so let's view this customer.
+ my $custnum = $new->cust_pkg->custnum;
+ print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum".
+ "#cust_pkg$pkgnum" );
+} else {
+%>
+<!-- mason kludge -->
+<%
+ idiot($error);
+}
+
+%>
diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi
new file mode 100644
index 0000000..59d236f
--- /dev/null
+++ b/httemplate/misc/process/meta-import.cgi
@@ -0,0 +1,178 @@
+<!-- mason kludge -->
+<%= header('Map tables') %>
+
+<SCRIPT>
+var gSafeOnload = new Array();
+var gSafeOnsubmit = new Array();
+window.onload = SafeOnload;
+function SafeAddOnLoad(f) {
+ gSafeOnload[gSafeOnload.length] = f;
+}
+function SafeOnload() {
+ for (var i=0;i<gSafeOnload.length;i++)
+ gSafeOnload[i]();
+}
+function SafeAddOnSubmit(f) {
+ gSafeOnsubmit[gSafeOnsubmit.length] = f;
+}
+function SafeOnsubmit() {
+ for (var i=0;i<gSafeOnsubmit.length;i++)
+ gSafeOnsubmit[i]();
+}
+</SCRIPT>
+
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="meta-import.cgi">
+
+<%
+ #use DBIx::DBSchema;
+ my $schema = new_native DBIx::DBSchema
+ map { $cgi->param($_) } qw( data_source username password );
+ foreach my $field (qw( data_source username password )) { %>
+ <INPUT TYPE="hidden" NAME=<%= $field %> VALUE="<%= $cgi->param($field) %>">
+ <% }
+
+ my %schema;
+ use Tie::DxHash;
+ tie %schema, 'Tie::DxHash';
+ if ( $cgi->param('schema') ) {
+ my $schema_string = $cgi->param('schema');
+ %> <INPUT TYPE="hidden" NAME="schema" VALUE="<%=$schema_string%>"> <%
+ %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/
+ or die "guru meditation #420: $_";
+ ( $1 => $2 );
+ }
+ split( /\n/, $schema_string );
+ }
+
+ #first page
+ unless ( $cgi->param('magic') ) { %>
+
+ <INPUT TYPE="hidden" NAME="magic" VALUE="process">
+ <%= hashmaker('schema', [ $schema->tables ],
+ [ grep !/^h_/, dbdef->tables ], ) %>
+ <br><INPUT TYPE="submit" VALUE="done">
+ <%
+
+ #second page
+ } elsif ( $cgi->param('magic') eq 'process' ) { %>
+
+ <INPUT TYPE="hidden" NAME="magic" VALUE="process2">
+ <%
+
+ my %unique;
+ foreach my $table ( keys %schema ) {
+
+ my @from_columns = $schema->table($table)->columns;
+ my @fs_columns = dbdef->table($schema{$table})->columns;
+
+ %>
+ <%= hashmaker( $table.'__'.$unique{$table}++,
+ \@from_columns => \@fs_columns,
+ $table => $schema{$table}, ) %>
+ <br><hr><br>
+ <%
+
+ }
+
+ %>
+ <br><INPUT TYPE="submit" VALUE="done">
+ <%
+
+ #third (results)
+ } elsif ( $cgi->param('magic') eq 'process2' ) {
+
+ print "<pre>\n";
+
+ my %unique;
+ foreach my $table ( keys %schema ) {
+ ( my $spaces = $table ) =~ s/./ /g;
+ print "'$table' => { 'table' => '$schema{$table}',\n".
+ #(length($table) x ' '). " 'map' => {\n";
+ "$spaces 'map' => {\n";
+ my %map = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/
+ or die "guru meditation #420: $_";
+ ( $1 => $2 );
+ }
+ split( /\n/, $cgi->param($table.'__'.$unique{$table}++) );
+ foreach ( keys %map ) {
+ print "$spaces '$_' => '$map{$_}',\n";
+ }
+ print "$spaces },\n";
+ print "$spaces },\n";
+
+ }
+ print "\n</pre>";
+
+ } else {
+ warn "unrecognized magic: ". $cgi->param('magic');
+ }
+
+ %>
+</FORM>
+</BODY>
+</HTML>
+
+ <%
+ #hashmaker widget
+ sub hashmaker {
+ my($name, $from, $to, $labelfrom, $labelto) = @_;
+ my $fromsize = scalar(@$from);
+ my $tosize = scalar(@$to);
+ "<TABLE><TR><TH>$labelfrom</TH><TH>$labelto</TH></TR><TR><TD>".
+ qq!<SELECT NAME="${name}_from" SIZE=$fromsize>\n!.
+ join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$from ).
+ "</SELECT>\n<BR>".
+ qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_from()">!.
+ '</TD><TD>'.
+ qq!<SELECT NAME="${name}_to" SIZE=$tosize>\n!.
+ join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$to ).
+ "</SELECT>\n<BR>".
+ qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_to()">!.
+ '</TD></TR>'.
+ '<TR><TD COLSPAN=2>'.
+ qq!<INPUT TYPE="button" VALUE="map" onClick="toke_$name(this.form)">!.
+ '</TD></TR><TR><TD COLSPAN=2>'.
+ qq!<TEXTAREA NAME="$name" COLS=80 ROWS=8></TEXTAREA>!.
+ '</TD></TR></TABLE>'.
+ "<script>
+ function toke_$name() {
+ fromObject = document.OneTrueForm.${name}_from;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ if (fromObject.options[i].selected)
+ fromname = deleteOption_$name(fromObject,i);
+ }
+ toObject = document.OneTrueForm.${name}_to;
+ for (var i=toObject.options.length-1;i>-1;i--) {
+ if (toObject.options[i].selected)
+ toname = deleteOption_$name(toObject,i);
+ }
+ document.OneTrueForm.$name.value = document.OneTrueForm.$name.value + fromname + ' => ' + toname + '\\n';
+ }
+ function deleteOption_$name(object,index) {
+ value = object.options[index].value;
+ object.options[index] = null;
+ return value;
+ }
+ function repack_${name}_from() {
+ var object = document.OneTrueForm.${name}_from;
+ object.options.length = 0;
+ ". join("\n",
+ map { "addOption_$name(object, '$_');\n" }
+ ( sort { $a cmp $b } @$from ) ). "
+ }
+ function repack_${name}_to() {
+ var object = document.OneTrueForm.${name}_to;
+ object.options.length = 0;
+ ". join("\n",
+ map { "addOption_$name(object, '$_');\n" }
+ ( sort { $a cmp $b } @$to ) ). "
+ }
+ function addOption_$name(object,value) {
+ var length = object.length;
+ object.options[length] = new Option(value, value, false, false);
+ }
+ </script>".
+ '';
+ }
+
+%>
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
new file mode 100644
index 0000000..fa0ede8
--- /dev/null
+++ b/httemplate/misc/process/payment.cgi
@@ -0,0 +1,148 @@
+<%
+
+#some false laziness w/MyAccount::process_payment
+
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die "illegal custnum ". $cgi->param('custnum');
+my $custnum = $1;
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+die "unknown custnum $custnum" unless $cust_main;
+
+$cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/
+ or eidiot "illegal amount ". $cgi->param('amount');
+my $amount = $1;
+eidiot "amount <= 0" unless $amount > 0;
+
+$cgi->param('year') =~ /^(\d+)$/
+ or die "illegal year ". $cgi->param('year');
+my $year = $1;
+
+$cgi->param('month') =~ /^(\d+)$/
+ or die "illegal month ". $cgi->param('month');
+my $month = $1;
+
+$cgi->param('payby') =~ /^(CARD|CHEK)$/
+ or die "illegal payby ". $cgi->param('payby');
+my $payby = $1;
+my %payby2bop = (
+ 'CARD' => 'CC',
+ 'CHEK' => 'ECHECK',
+);
+my %payby2fields = (
+ 'CARD' => [ qw( address1 address2 city state zip ) ],
+ 'CHEK' => [ qw( ss ) ],
+);
+my %type = ( 'CARD' => 'credit card',
+ 'CHEK' => 'electronic check (ACH)',
+ );
+
+$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/
+ or eidiot gettext('illegal_name'). " payname: ". $cgi->param('payname');
+my $payname = $1;
+
+$cgi->param('paybatch') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or eidiot gettext('illegal_text'). " paybatch: ". $cgi->param('paybatch');
+my $paybatch = $1;
+
+my $payinfo;
+my $paycvv = '';
+if ( $payby eq 'CHEK' ) {
+
+ $cgi->param('payinfo1') =~ /^(\d+)$/
+ or eidiot "illegal account number ". $cgi->param('payinfo1');
+ my $payinfo1 = $1;
+ $cgi->param('payinfo2') =~ /^(\d+)$/
+ or eidiot "illegal ABA/routing number ". $cgi->param('payinfo2');
+ my $payinfo2 = $1;
+ $payinfo = $payinfo1. '@'. $payinfo2;
+
+} elsif ( $payby eq 'CARD' ) {
+
+ $payinfo = $cgi->param('payinfo');
+ $payinfo =~ s/\D//g;
+ $payinfo =~ /^(\d{13,16})$/
+ or eidiot gettext('invalid_card'); # . ": ". $self->payinfo;
+ $payinfo = $1;
+ validate($payinfo)
+ or eidiot gettext('invalid_card'); # . ": ". $self->payinfo;
+ eidiot gettext('unknown_card_type')
+ if cardtype($payinfo) eq "Unknown";
+
+ if ( defined $cust_main->dbdef_table->column('paycvv') ) {
+ if ( length($cgi->param('paycvv') ) ) {
+ if ( cardtype($payinfo) eq 'American Express card' ) {
+ $cgi->param('paycvv') =~ /^(\d{4})$/
+ or eidiot "CVV2 (CID) for American Express cards is four digits.";
+ $paycvv = $1;
+ } else {
+ $cgi->param('paycvv') =~ /^(\d{3})$/
+ or eidiot "CVV2 (CVC2/CID) is three digits.";
+ $paycvv = $1;
+ }
+ }
+ }
+
+} else {
+ die "unknown payby $payby";
+}
+
+my $error = $cust_main->realtime_bop( $payby2bop{$payby}, $amount,
+ 'quiet' => 1,
+ 'payinfo' => $payinfo,
+ 'paydate' => "$year-$month-01",
+ 'payname' => $payname,
+ 'paybatch' => $paybatch,
+ 'paycvv' => $paycvv,
+ map { $_ => $cgi->param($_) } @{$payby2fields{$payby}}
+);
+eidiot($error) if $error;
+
+$cust_main->apply_payments;
+
+if ( $cgi->param('save') ) {
+ my $new = new FS::cust_main { $cust_main->hash };
+ if ( $payby eq 'CARD' ) {
+ $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) );
+ } elsif ( $payby eq 'CHEK' ) {
+ $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) );
+ } else {
+ die "unknown payby $payby";
+ }
+ $new->set( 'payinfo' => $payinfo );
+ $new->set( 'paydate' => "$year-$month-01" );
+ $new->set( 'payname' => $payname );
+
+ #false laziness w/FS:;cust_main::realtime_bop - check both to make sure
+ # working correctly
+ my $conf = new FS::Conf;
+ if ( $payby eq 'CARD' &&
+ grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) {
+ $new->set( 'paycvv' => $paycvv );
+ } else {
+ $new->set( 'paycvv' => '');
+ }
+
+ $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+
+ my $error = $new->replace($cust_main);
+ eidiot "payment processed sucessfully, but error saving info: $error"
+ if $error;
+ $cust_main = $new;
+}
+
+#success!
+
+%>
+<%= include( '/elements/header.html', ucfirst($type{$payby}). ' processing sucessful',
+ include('/elements/menubar.html',
+ 'Main menu' => popurl(3),
+ "View this customer (#$custnum)" =>
+ popurl(3). "view/cust_main.cgi?$custnum",
+ ),
+
+ )
+%>
+<%= include( '/elements/small_custview.html', $cust_main ) %>
+</BODY>
+</HTML>
diff --git a/httemplate/misc/queue.cgi b/httemplate/misc/queue.cgi
new file mode 100644
index 0000000..ce9c8fb
--- /dev/null
+++ b/httemplate/misc/queue.cgi
@@ -0,0 +1,47 @@
+<%
+
+$cgi->param('action') =~ /^(new|del|(retry|remove) selected)$/
+ or die "Illegal action";
+my $action = $1;
+
+my $job;
+if ( $action eq 'new' || $action eq 'del' ) {
+ $cgi->param('jobnum') =~ /^(\d+)$/ or die "Illegal jobnum";
+ my $jobnum = $1;
+ $job = qsearchs('queue', { 'jobnum' => $1 })
+ or die "unknown jobnum $jobnum - ".
+ "it probably completed normally or was removed by another user";
+}
+
+if ( $action eq 'new' ) {
+ my %hash = $job->hash;
+ $hash{'status'} = 'new';
+ $hash{'statustext'} = '';
+ my $new = new FS::queue \%hash;
+ my $error = $new->replace($job);
+ die $error if $error;
+} elsif ( $action eq 'del' ) {
+ my $error = $job->delete;
+ die $error if $error;
+} elsif ( $action =~ /^(retry|remove) selected$/ ) {
+ foreach my $jobnum (
+ map { /^jobnum(\d+)$/; $1; } grep /^jobnum\d+$/, $cgi->param
+ ) {
+ my $job = qsearchs('queue', { 'jobnum' => $jobnum });
+ if ( $action eq 'retry selected' && $job ) { #new
+ my %hash = $job->hash;
+ $hash{'status'} = 'new';
+ $hash{'statustext'} = '';
+ my $new = new FS::queue \%hash;
+ my $error = $new->replace($job);
+ die $error if $error;
+ } elsif ( $action eq 'remove selected' && $job ) { #del
+ my $error = $job->delete;
+ die $error if $error;
+ }
+ }
+}
+
+print $cgi->redirect(popurl(2). "browse/queue.cgi");
+
+%>
diff --git a/httemplate/misc/susp_pkg.cgi b/httemplate/misc/susp_pkg.cgi
new file mode 100755
index 0000000..4a19fa8
--- /dev/null
+++ b/httemplate/misc/susp_pkg.cgi
@@ -0,0 +1,15 @@
+<%
+
+#untaint pkgnum
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal pkgnum";
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+
+my $error = $cust_pkg->suspend;
+&eidiot($error) if $error;
+
+print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
+
+%>
diff --git a/httemplate/misc/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi
new file mode 100755
index 0000000..c658d2a
--- /dev/null
+++ b/httemplate/misc/unapply-cust_credit.cgi
@@ -0,0 +1,18 @@
+<%
+
+#untaint crednum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } );
+my $custnum = $cust_credit->custnum;
+
+foreach my $cust_credit_bill ( $cust_credit->cust_credit_bill ) {
+ my $error = $cust_credit_bill->delete;
+ eidiot($error) if $error;
+}
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi
new file mode 100755
index 0000000..28643ef
--- /dev/null
+++ b/httemplate/misc/unapply-cust_pay.cgi
@@ -0,0 +1,18 @@
+<%
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+my $custnum = $cust_pay->custnum;
+
+foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) {
+ my $error = $cust_bill_pay->delete;
+ eidiot($error) if $error;
+}
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi
new file mode 100755
index 0000000..3c92a4e
--- /dev/null
+++ b/httemplate/misc/unprovision.cgi
@@ -0,0 +1,29 @@
+<%
+
+my $dbh = dbh;
+
+#untaint svcnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+
+#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
+#die "Unknown svcnum!" unless $svc_acct;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+die "Unknown svcnum!" unless $cust_svc;
+
+my $custnum = $cust_svc->cust_pkg->custnum;
+
+my $error = $cust_svc->cancel;
+
+if ( $error ) {
+ %>
+<!-- mason kludge -->
+<%
+ &eidiot($error);
+} else {
+ print $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum");
+}
+
+%>
diff --git a/httemplate/misc/unsusp_pkg.cgi b/httemplate/misc/unsusp_pkg.cgi
new file mode 100755
index 0000000..5008729
--- /dev/null
+++ b/httemplate/misc/unsusp_pkg.cgi
@@ -0,0 +1,15 @@
+<%
+
+#untaint pkgnum
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal pkgnum";
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+
+my $error = $cust_pkg->unsuspend;
+&eidiot($error) if $error;
+
+print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
+
+%>
diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi
new file mode 100644
index 0000000..5d01501
--- /dev/null
+++ b/httemplate/misc/upload-batch.cgi
@@ -0,0 +1,30 @@
+<%
+
+ my $fh = $cgi->upload('batch_results');
+ my $filename = $cgi->param('batch_results');
+ $filename =~ /^(.*[\/\\])?([^\/\\]+)$/
+ or die "unparsable filename: $filename\n";
+ my $paybatch = $2;
+
+ my $error = defined($fh)
+ ? FS::cust_pay_batch::import_results( {
+ 'filehandle' => $fh,
+ 'format' => $cgi->param('format'),
+ 'paybatch' => $paybatch,
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Batch results upload sucessful') %> <%
+ }
+%>
+
diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi
new file mode 100755
index 0000000..4eec608
--- /dev/null
+++ b/httemplate/misc/void-cust_pay.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum});
+my $custnum = $cust_pay->custnum;
+
+my $error = $cust_pay->void;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/whois.cgi b/httemplate/misc/whois.cgi
new file mode 100644
index 0000000..dd7851d
--- /dev/null
+++ b/httemplate/misc/whois.cgi
@@ -0,0 +1,25 @@
+<%
+ my $svcnum = $cgi->param('svcnum');
+ my $custnum = $cgi->param('custnum');
+ my $domain = $cgi->param('domain');
+
+%>
+<%= header("Whois $domain", menubar(
+ ( $custnum
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ()
+ ),
+ "View this domain (#$svcnum)" => "${p}view/svc_domain.cgi?$svcnum",
+ "Main menu" => $p,
+)) %>
+<% my $whois = eval { whois($domain) };
+ if ( $@ ) {
+ ( $whois = $@ ) =~ s/ at \/.*Net\/Whois\/Raw\.pm line \d+.*$//s;
+ } else {
+ $whois =~ s/^\n+//;
+ }
+%>
+<PRE><%= $whois %></PRE>
+</BODY>
+</HTML>
diff --git a/httemplate/search/cust_bill.cgi b/httemplate/search/cust_bill.cgi
new file mode 100755
index 0000000..5b0538c
--- /dev/null
+++ b/httemplate/search/cust_bill.cgi
@@ -0,0 +1,165 @@
+<%
+
+my $conf = new FS::Conf;
+my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+my $orderby = ''; #removeme
+
+my $limit = '';
+$limit .= "LIMIT $maxrecords" if $maxrecords;
+
+my $offset = $cgi->param('offset') || 0;
+$limit .= " OFFSET $offset" if $offset;
+
+my($total, $tot_amount, $tot_balance);
+
+my(@cust_bill);
+if ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ my $owed = "charged - ( select coalesce(sum(amount),0) from cust_bill_pay
+ where cust_bill_pay.invnum = cust_bill.invnum )
+ - ( select coalesce(sum(amount),0) from cust_credit_bill
+ where cust_credit_bill.invnum = cust_bill.invnum )";
+ my @where;
+ if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) {
+ my($open, $days, $field) = ($1, $2, $3);
+ $field = "_date" if $field eq 'date';
+ $orderby = "ORDER BY cust_bill.$field";
+ push @where, "0 != $owed" if $open;
+ push @where, "cust_bill._date < ". (time-86400*$days) if $days;
+ } else {
+ die "unknown query string $query";
+ }
+
+ my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : '';
+
+ my $statement = "SELECT COUNT(*), sum(charged), sum($owed)
+ FROM cust_bill $extra_sql";
+ my $sth = dbh->prepare($statement) or die dbh->errstr. " doing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+
+ ( $total, $tot_amount, $tot_balance ) = @{$sth->fetchrow_arrayref};
+
+ @cust_bill = qsearch(
+ 'cust_bill',
+ {},
+ "cust_bill.*, $owed as owed",
+ "$extra_sql $orderby $limit"
+ );
+} else {
+ $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/;
+ my $invnum = $2;
+ @cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum } );
+ $total = scalar(@cust_bill);
+}
+
+#if ( scalar(@cust_bill) == 1 ) {
+if ( $total == 1 ) {
+ my $invnum = $cust_bill[0]->invnum;
+ print $cgi->redirect(popurl(2). "view/cust_bill.cgi?$invnum"); #redirect
+} elsif ( scalar(@cust_bill) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot("Invoice not found.");
+} else {
+%>
+<!-- mason kludge -->
+<%
+
+ #begin pager
+ my $pager = '';
+ if ( $total != scalar(@cust_bill) && $maxrecords ) {
+ unless ( $offset == 0 ) {
+ $cgi->param('offset', $offset - $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Previous</FONT></B></A> ';
+ }
+ my $poff;
+ my $page;
+ for ( $poff = 0; $poff < $total; $poff += $maxrecords ) {
+ $page++;
+ if ( $offset == $poff ) {
+ $pager .= qq!<FONT SIZE="+2">$page</FONT> !;
+ } else {
+ $cgi->param('offset', $poff);
+ $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !;
+ }
+ }
+ unless ( $offset + $maxrecords > $total ) {
+ $cgi->param('offset', $offset + $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Next</FONT></B></A> ';
+ }
+ }
+ #end pager
+
+ print header("Invoice Search Results", menubar(
+ 'Main Menu', popurl(2)
+ )).
+ "$total matching invoices found<BR>".
+ "\$$tot_balance total balance<BR>".
+ "\$$tot_amount total amount<BR>".
+ "<BR>$pager". table(). <<END;
+ <TR>
+ <TH></TH>
+ <TH>Balance</TH>
+ <TH>Amount</TH>
+ <TH>Date</TH>
+ <TH>Contact name</TH>
+ <TH>Company</TH>
+ </TR>
+END
+
+ foreach my $cust_bill ( @cust_bill ) {
+ my($invnum, $owed, $charged, $date ) = (
+ $cust_bill->invnum,
+ sprintf("%.2f", $cust_bill->getfield('owed')),
+ sprintf("%.2f", $cust_bill->charged),
+ $cust_bill->_date,
+ );
+ my $pdate = time2str("%b %d %Y", $date);
+
+ my $rowspan = 1;
+
+ my $view = popurl(2). "view/cust_bill.cgi?$invnum";
+ print <<END;
+ <TR>
+ <TD ROWSPAN=$rowspan><A HREF="$view">$invnum</A></TD>
+ <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view">\$$owed</A></TD>
+ <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view">\$$charged</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="$view">$pdate</A></TD>
+END
+ my $custnum = $cust_bill->custnum;
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ if ( $cust_main ) {
+ my $cview = popurl(2). "view/cust_main.cgi?". $cust_main->custnum;
+ my ( $name, $company ) = (
+ $cust_main->last. ', '. $cust_main->first,
+ $cust_main->company,
+ );
+ print <<END;
+ <TD ROWSPAN=$rowspan><A HREF="$cview">$name</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="$cview">$company</A></TD>
+END
+ } else {
+ print <<END
+ <TD ROWSPAN=$rowspan COLSPAN=2>WARNING: couldn't find cust_main.custnum $custnum (cust_bill.invnum $invnum)</TD>
+END
+ }
+
+ print "</TR>";
+ }
+ $tot_balance = sprintf("%.2f", $tot_balance);
+ $tot_amount = sprintf("%.2f", $tot_amount);
+ print "</TABLE>$pager<BR>". table(). <<END;
+ <TR><TD>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</TD><TH>Total<BR>Balance</TH><TH>Total<BR>Amount</TH></TR>
+ <TR><TD></TD><TD ALIGN="right">\$$tot_balance</TD><TD ALIGN="right">\$$tot_amount</TD></TD></TR>
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+}
+
+%>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
new file mode 100755
index 0000000..3ae624a
--- /dev/null
+++ b/httemplate/search/cust_bill.html
@@ -0,0 +1,101 @@
+<%
+ my( $count_query, $sql_query );
+ if ( $cgi->param('begin') || $cgi->param('end') || $cgi->keywords ) {
+
+ my $owed =
+ "charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill_pay.invnum = cust_bill.invnum )
+ - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_credit_bill.invnum = cust_bill.invnum )";
+
+ my @where;
+ my $orderby = 'ORDER BY cust_bill._date';
+
+ if ( $cgi->param('begin') =~ /^(\d+)$/ ) {
+ push @where, "cust_bill._date >= $1",
+ }
+ if ( $cgi->param('end') =~ /^(\d+)$/ ) {
+ push @where, "cust_bill._date < $1",
+ }
+
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) {
+ my($open, $days, $field) = ($1, $2, $3);
+ $field = "_date" if $field eq 'date';
+ $orderby = "ORDER BY cust_bill.$field";
+ push @where, "0 != $owed" if $open;
+ push @where, "cust_bill._date < ". (time-86400*$days) if $days;
+ }
+
+ my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : '';
+
+ $count_query = "SELECT COUNT(*), sum(charged), sum($owed)
+ FROM cust_bill $extra_sql";
+
+ $sql_query = {
+ 'table' => 'cust_bill',
+ 'hashref' => {},
+ 'select' => "cust_bill.*, $owed as owed",
+ 'extra_sql' => "$extra_sql $orderby"
+ };
+
+ } else {
+ $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/;
+ $count_query = "SELECT COUNT(*) FROM cust_bill WHERE invnum = $2";
+ $sql_query = {
+ 'table' => 'cust_bill',
+ 'hashref' => { 'invnum' => $2 },
+ #'select' => '*',
+ };
+ }
+
+ my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ];
+ my $clink = sub {
+ my $cust_bill = shift;
+ my $cust_main = $cust_bill->cust_main;
+ $cust_main
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+ };
+
+%>
+<%= include( 'elements/search.html',
+ 'title' => 'Invoice Search Results',
+ 'name' => 'invoices',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total invoiced',
+ '$%.2f total outstanding balance',
+ ],
+ 'redirect' => $link,
+ 'header' =>
+ [ 'Invoice #', qw(Balance Amount Date), 'Contact name',
+ 'Company' ],
+ 'fields' => [
+ 'invnum',
+ sub { sprintf('$%.2f', shift->get('owed') ) },
+ sub { sprintf('$%.2f', shift->charged ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ sub { my $cust_bill = shift;
+ my $cust_main = $cust_bill->cust_main;
+ $cust_main
+ ? $cust_main->get('last'). ', '. $cust_main->first
+ : "WARNING: can't find cust_main.custnum ".
+ $cust_bill->custnum. ' (cust_bill.invnum '.
+ $cust_bill->invnum. ')';
+ },
+ sub { my $cust_main = shift->cust_main;
+ $cust_main ? $cust_main->company : '';
+ },
+ ],
+ 'links' => [
+ $link,
+ $link,
+ $link,
+ $link,
+ $clink,
+ $clink,
+ ],
+
+ )
+%>
diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi
new file mode 100644
index 0000000..7c2b3a2
--- /dev/null
+++ b/httemplate/search/cust_bill_event.cgi
@@ -0,0 +1,62 @@
+<!-- mason kludge -->
+<%
+
+#false laziness with view/cust_bill.cgi
+
+$cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/;
+my $beginning = str2time($1) || 0;
+
+$cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/;
+my $ending = ( $1 ? str2time($1) : 4294880896 ) + 86399;
+
+my @cust_bill_event =
+ sort { $a->_date <=> $b->_date }
+ qsearch('cust_bill_event', {
+ _date => { op=> '>=', value=>$beginning },
+ statustext => { op=> '!=', value=>'' },
+# i wish...
+# _date => { op=> '<=', value=>$ending },
+ }, '', "AND _date <= $ending");
+
+%>
+
+<%= header('Failed billing events') %>
+
+<%= table() %>
+<TR>
+ <TH>Event</TH>
+ <TH>Date</TH>
+ <TH>Status</TH>
+ <TH>Invoice</TH>
+ <TH>(bill) name</TH>
+ <TH>company</TH>
+<% if ( defined dbdef->table('cust_main')->column('ship_last') ) { %>
+ <TH>(service) name</TH>
+ <TH>company</TH>
+<% } %>
+</TR>
+
+<% foreach my $cust_bill_event ( @cust_bill_event ) {
+ my $status = $cust_bill_event->status;
+ $status .= ': '.$cust_bill_event->statustext if $cust_bill_event->statustext;
+ my $cust_bill = $cust_bill_event->cust_bill;
+ my $cust_main = $cust_bill->cust_main;
+ my $invlink = "${p}view/cust_bill.cgi?". $cust_bill->invnum;
+ my $custlink = "${p}view/cust_main.cgi?". $cust_main->custnum;
+%>
+<TR>
+ <TD><%= $cust_bill_event->part_bill_event->event %></TD>
+ <TD><%= time2str("%a %b %e %T %Y", $cust_bill_event->_date) %></TD>
+ <TD><%= $status %></TD>
+ <TD><A HREF="<%=$invlink%>">Invoice #<%= $cust_bill->invnum %> (<%= time2str("%D", $cust_bill->_date ) %>)</A></TD>
+ <TD><A HREF="<%=$custlink%>"><%= $cust_main->last. ', '. $cust_main->first %></A></TD>
+ <TD><A HREF="<%=$custlink%>"><%= $cust_main->company %></A></TD>
+ <% if ( defined dbdef->table('cust_main')->column('ship_last') ) { %>
+ <TD><A HREF="<%=$custlink%>"><%= $cust_main->ship_last. ', '. $cust_main->ship_first %></A></TD>
+ <TD><A HREF="<%=$custlink%>"><%= $cust_main->ship_company %></A></TD>
+ <% } %>
+</TR>
+<% } %>
+</TABLE>
+
+</BODY></HTML>
diff --git a/httemplate/search/cust_bill_event.html b/httemplate/search/cust_bill_event.html
new file mode 100755
index 0000000..cd96ddf
--- /dev/null
+++ b/httemplate/search/cust_bill_event.html
@@ -0,0 +1,54 @@
+<HTML>
+ <HEAD>
+ <TITLE>Invoice event errors</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Invoice event errors</H1>
+ <FORM ACTION="cust_bill_event.cgi" METHOD="post">
+ <TABLE>
+ <!--<TR>
+ <TD ALIGN="right">Customer type</TD>
+ <TD><SELECT MULTIPLE NAME="perhaps_payby">
+ <OPTION SELECTED VALUE="CARD">Credit card (automatic)
+ <OPTION SELECTED VALUE="CHEK">E-check (automatic)
+ <OPTION SELECTED VALUE="LECB">Phone bill billing
+ <OPTION SELECTED VALUE="BILL">Billing
+ <OPTION SELECTED VALUE="DCRD">Credit card (on-demand)
+ <OPTION SELECTED VALUE="DCHK">E-check (on-demand)
+ </TD>
+ </TR>
+ -->
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html
new file mode 100755
index 0000000..faaa7a8
--- /dev/null
+++ b/httemplate/search/cust_credit.html
@@ -0,0 +1,80 @@
+<%
+ #my( $count_query, $sql_query );
+
+ my @search = ();
+
+ if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) {
+ push @search, "otaker = '$1'";
+ }
+
+ #false laziness with cust_pkg.cgi and cust_pay.cgi
+ if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{1,10})$/ ) {
+ my $beginning = str2time($1);
+ push @search, "_date >= $beginning ";
+ }
+ if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{1,10})$/ ) {
+ my $ending = str2time($1) + 86399;
+ push @search, " _date <= $ending ";
+ }
+
+ if ( $cgi->param('begin')
+ && $cgi->param('begin') =~ /^(\d+)$/ ) {
+ push @search, "_date >= $1 ";
+ }
+ if ( $cgi->param('end')
+ && $cgi->param('end') =~ /^(\d+)$/ ) {
+ push @search, " _date < $1 ";
+ }
+
+ my $where = scalar(@search)
+ ? 'WHERE '. join(' AND ', @search)
+ : '';
+
+ my $count_query = "SELECT COUNT(*), SUM(amount) FROM cust_credit $where";
+ my $sql_query = {
+ 'table' => 'cust_credit',
+ 'hashref' => {},
+ 'extra_sql' => $where,
+ };
+
+ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+%>
+<%= include( 'elements/search.html',
+ 'title' => 'Credit Search Results',
+ 'name' => 'credits',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total credited', ],
+ #'redirect' => $link,
+ 'header' =>
+ [ qw(Amount Date), 'Cust #', 'Contact name',
+ qw(Company By Reason) ],
+ 'fields' => [
+ #'crednum',
+ sub { sprintf('$%.2f', shift->amount ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ 'custnum',
+ sub { my $cust_main = shift->cust_main;
+ $cust_main->get('last'). ', '. $cust_main->first;
+ },
+ sub { my $cust_main = shift->cust_main;
+ $cust_main->company;
+ },
+ 'otaker',
+ 'reason',
+ ],
+ 'align' => 'rrrllll',
+ 'links' => [
+ '',
+ '',
+ $clink,
+ $clink,
+ $clink,
+ '',
+ '',
+ ],
+ )
+%>
diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi
new file mode 100755
index 0000000..4421436
--- /dev/null
+++ b/httemplate/search/cust_main-otaker.cgi
@@ -0,0 +1,28 @@
+<HTML>
+ <HEAD>
+ <TITLE>Customer Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Customer Search
+ </FONT>
+ <BR>
+ <FORM ACTION="cust_main.cgi" METHOD="post">
+ Search for <B>Order taker</B>:
+ <INPUT TYPE="hidden" NAME="otaker_on" VALUE="TRUE">
+ <% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main")
+ or die dbh->errstr;
+ $sth->execute() or die $sth->errstr;
+# my @otakers = map { $_->[0] } @{$sth->selectall_arrayref};
+ %>
+ <SELECT NAME="otaker">
+ <% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) { %>
+ <OPTION><%= $otaker->[0] %></OTAKER>
+ <% } %>
+ </SELECT>
+ <P><INPUT TYPE="submit" VALUE="Search">
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_main-payinfo.html b/httemplate/search/cust_main-payinfo.html
new file mode 100755
index 0000000..671b5ef
--- /dev/null
+++ b/httemplate/search/cust_main-payinfo.html
@@ -0,0 +1,20 @@
+<HTML>
+ <HEAD>
+ <TITLE>Customer Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Customer Search
+ </FONT>
+ <BR>
+ <FORM ACTION="cust_main.cgi" METHOD="post">
+ Search for <B>Credit card #</B>:
+ <INPUT TYPE="hidden" NAME="card_on" VALUE="TRUE">
+ <INPUT TYPE="text" NAME="card">
+
+ <P><INPUT TYPE="submit" VALUE="Search">
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html
new file mode 100755
index 0000000..d48f1d0
--- /dev/null
+++ b/httemplate/search/cust_main-quickpay.html
@@ -0,0 +1,44 @@
+<HTML>
+ <HEAD>
+ <TITLE>Quick payment entry</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Quick payment entry
+ </FONT>
+ <BR><BR>
+ <A HREF="../">Main Menu</A><BR><BR>
+ <FORM ACTION="cust_main.cgi" METHOD="post">
+ <INPUT TYPE="hidden" NAME="quickpay" VALUE="yes">
+ <INPUT TYPE="checkbox" NAME="last_on" CHECKED> Search for <B>last name</B>:
+ <INPUT TYPE="text" NAME="last_text">
+ using search method: <SELECT NAME="last_type">
+ <OPTION>All
+ <OPTION>Fuzzy
+ <OPTION>Substring
+ <OPTION SELECTED>Exact
+ </SELECT>
+
+ <P><INPUT TYPE="checkbox" NAME="company_on" CHECKED> Search for <B>company</B>:
+ <INPUT TYPE="text" NAME="company_text">
+ using search methods: <SELECT NAME="company_type">
+ <OPTION>All
+ <OPTION>Fuzzy
+ <OPTION>Substring
+ <OPTION SELECTED>Exact
+ </SELECT>
+
+ <P><INPUT TYPE="submit" VALUE="Search">
+
+ </FORM>
+
+ <HR>Explanation of search methods:
+ <UL>
+ <LI><B>All</B> - Try all search methods.
+ <LI><B>Fuzzy</B> - Searches for matches that are close to your text.
+ <LI><B>Substring</B> - Searches for matches that contain your text.
+ <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods.
+ </UL>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
new file mode 100755
index 0000000..27f23de
--- /dev/null
+++ b/httemplate/search/cust_main.cgi
@@ -0,0 +1,608 @@
+<%
+
+my $conf = new FS::Conf;
+my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+#my $cache;
+
+#my $monsterjoin = <<END;
+#cust_main left outer join (
+# ( cust_pkg left outer join part_pkg using(pkgpart)
+# ) left outer join (
+# (
+# (
+# ( cust_svc left outer join part_svc using (svcpart)
+# ) left outer join svc_acct using (svcnum)
+# ) left outer join svc_domain using(svcnum)
+# ) left outer join svc_forward using(svcnum)
+# ) using (pkgnum)
+#) using (custnum)
+#END
+
+#my $monsterjoin = <<END;
+#cust_main left outer join (
+# ( cust_pkg left outer join part_pkg using(pkgpart)
+# ) left outer join (
+# (
+# (
+# ( cust_svc left outer join part_svc using (svcpart)
+# ) left outer join (
+# svc_acct left outer join (
+# select svcnum, domain, catchall from svc_domain
+# ) as svc_acct_domsvc (
+# svc_acct_svcnum, svc_acct_domain, svc_acct_catchall
+# ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum
+# ) using (svcnum)
+# ) left outer join svc_domain using(svcnum)
+# ) left outer join svc_forward using(svcnum)
+# ) using (pkgnum)
+#) using (custnum)
+#END
+
+my $limit = '';
+$limit .= "LIMIT $maxrecords" if $maxrecords;
+
+my $offset = $cgi->param('offset') || 0;
+$limit .= " OFFSET $offset" if $offset;
+
+my $total = 0;
+
+my(@cust_main, $sortby, $orderby);
+if ( $cgi->param('browse')
+ || $cgi->param('otaker_on')
+ || $cgi->param('agentnum_on')
+) {
+
+ my %search = ();
+ if ( $cgi->param('browse') ) {
+ my $query = $cgi->param('browse');
+ if ( $query eq 'custnum' ) {
+ $sortby=\*custnum_sort;
+ $orderby = "ORDER BY custnum";
+ } elsif ( $query eq 'last' ) {
+ $sortby=\*last_sort;
+ $orderby = "ORDER BY LOWER(last || ' ' || first)";
+ } elsif ( $query eq 'company' ) {
+ $sortby=\*company_sort;
+ $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )";
+ } else {
+ die "unknown browse field $query";
+ }
+ } else {
+ $sortby = \*last_sort; #??
+ $orderby = "ORDER BY LOWER(last || ' ' || first)"; #??
+ if ( $cgi->param('otaker_on') ) {
+ $cgi->param('otaker') =~ /^(\w{1,32})$/ or eidiot "Illegal otaker\n";
+ $search{otaker} = $1;
+ } elsif ( $cgi->param('agentnum_on') ) {
+ $cgi->param('agentnum') =~ /^(\d+)$/ or eidiot "Illegal agentnum\n";
+ $search{agentnum} = $1;
+ } else {
+ die "unknown query...";
+ }
+ }
+
+ my @qual = ();
+
+ my $ncancelled = '';
+
+ if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledcustomers')
+ && ! $cgi->param('showcancelledcustomers') )
+ ) {
+ #grep { $_->ncancelled_pkgs || ! $_->all_pkgs }
+ push @qual, "
+ ( 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0
+ )
+ )
+ OR 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )
+ )
+ ";
+ }
+
+ push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled');
+ push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect');
+ push @qual, FS::cust_main->active_sql if $cgi->param('active');
+ push @qual, FS::cust_main->susp_sql if $cgi->param('suspended');
+
+ #EWWWWWW
+ my $qual = join(' AND ',
+ map { "$_ = ". dbh->quote($search{$_}) } keys %search );
+
+ my $addl_qual = join(' AND ', @qual);
+
+ if ( $addl_qual ) {
+ $qual .= ' AND ' if $qual;
+ $qual .= $addl_qual;
+ }
+
+ $qual = " WHERE $qual" if $qual;
+ my $statement = "SELECT COUNT(*) FROM cust_main $qual";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+
+ $total = $sth->fetchrow_arrayref->[0];
+
+ if ( $addl_qual ) {
+ if ( %search ) {
+ $addl_qual = " AND $addl_qual";
+ } else {
+ $addl_qual = " WHERE $addl_qual";
+ }
+ }
+
+ @cust_main = qsearch('cust_main', \%search, '',
+ "$addl_qual $orderby $limit" );
+
+# foreach my $cust_main ( @just_cust_main ) {
+#
+# my @one_cust_main;
+# $FS::Record::DEBUG=1;
+# ( $cache, @one_cust_main ) = jsearch(
+# "$monsterjoin",
+# { 'custnum' => $cust_main->custnum },
+# '',
+# '',
+# 'cust_main',
+# 'custnum',
+# );
+# push @cust_main, @one_cust_main;
+# }
+
+} else {
+ @cust_main=();
+ $sortby = \*last_sort;
+
+ push @cust_main, @{&custnumsearch}
+ if $cgi->param('custnum_on') && $cgi->param('custnum_text');
+ push @cust_main, @{&cardsearch}
+ if $cgi->param('card_on') && $cgi->param('card');
+ push @cust_main, @{&lastsearch}
+ if $cgi->param('last_on') && $cgi->param('last_text');
+ push @cust_main, @{&companysearch}
+ if $cgi->param('company_on') && $cgi->param('company_text');
+ push @cust_main, @{&address2search}
+ if $cgi->param('address2_on') && $cgi->param('address2_text');
+ push @cust_main, @{&phonesearch}
+ if $cgi->param('phone_on') && $cgi->param('phone_text');
+ push @cust_main, @{&referralsearch}
+ if $cgi->param('referral_custnum');
+
+ if ( $cgi->param('company_on') && $cgi->param('company_text') ) {
+ $sortby = \*company_sort;
+ push @cust_main, @{&companysearch};
+ }
+
+ @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main
+ if ! $cgi->param('cancelled')
+ && (
+ $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledcustomers')
+ && ! $cgi->param('showcancelledcustomers') )
+ );
+
+ my %saw = ();
+ @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+}
+
+my %all_pkgs;
+if ( $conf->exists('hidecancelledpackages' ) ) {
+ %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main;
+} else {
+ %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main;
+}
+#%all_pkgs = ();
+
+if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) {
+ if ( $cgi->param('quickpay') eq 'yes' ) {
+ print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum);
+ } else {
+ print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum);
+ }
+ #exit;
+} elsif ( scalar(@cust_main) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot "No matching customers found!\n";
+} else {
+%>
+<!-- mason kludge -->
+<%
+
+ $total ||= scalar(@cust_main);
+ print header("Customer Search Results",menubar(
+ 'Main Menu', popurl(2)
+ )), "$total matching customers found ";
+
+ #begin pager
+ my $pager = '';
+ if ( $total != scalar(@cust_main) && $maxrecords ) {
+ unless ( $offset == 0 ) {
+ $cgi->param('offset', $offset - $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Previous</FONT></B></A> ';
+ }
+ my $poff;
+ my $page;
+ for ( $poff = 0; $poff < $total; $poff += $maxrecords ) {
+ $page++;
+ if ( $offset == $poff ) {
+ $pager .= qq!<FONT SIZE="+2">$page</FONT> !;
+ } else {
+ $cgi->param('offset', $poff);
+ $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !;
+ }
+ }
+ unless ( $offset + $maxrecords > $total ) {
+ $cgi->param('offset', $offset + $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Next</FONT></B></A> ';
+ }
+ }
+ #end pager
+
+ unless ( $cgi->param('cancelled') ) {
+ if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledcustomers')
+ && ! $cgi->param('showcancelledcustomers')
+ )
+ ) {
+ $cgi->param('showcancelledcustomers', 1);
+ $cgi->param('offset', 0);
+ print qq!( <a href="!. $cgi->self_url. qq!">show!;
+ } else {
+ $cgi->param('showcancelledcustomers', 0);
+ $cgi->param('offset', 0);
+ print qq!( <a href="!. $cgi->self_url. qq!">hide!;
+ }
+ print ' cancelled customers</a> )';
+ }
+ if ( $cgi->param('referral_custnum') ) {
+ $cgi->param('referral_custnum') =~ /^(\d+)$/
+ or eidiot "Illegal referral_custnum\n";
+ my $referral_custnum = $1;
+ my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } );
+ print '<FORM METHOD=POST>'.
+ qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!.
+ 'referrals of <A HREF="'. popurl(2).
+ "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ".
+ ( $cust_main->company
+ || $cust_main->last. ', '. $cust_main->first ).
+ '</A>';
+ print "\n",<<END;
+ <SCRIPT>
+ function changed(what) {
+ what.form.submit();
+ }
+ </SCRIPT>
+END
+ print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">';
+ my $max = 8; #config file
+ $cgi->param('referral_depth') =~ /^(\d*)$/
+ or eidiot "Illegal referral_depth";
+ my $referral_depth = $1;
+
+ foreach my $depth ( 1 .. $max ) {
+ print '<OPTION',
+ ' SELECTED'x($depth == $referral_depth),
+ ">$depth";
+ }
+ print "</SELECT> levels deep".
+ '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'.
+ '</FORM>';
+ }
+
+ print "<BR><BR>". $pager. &table(). <<END;
+ <TR>
+ <TH></TH>
+ <TH>(bill) name</TH>
+ <TH>company</TH>
+END
+
+if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ print <<END;
+ <TH>(service) name</TH>
+ <TH>company</TH>
+END
+}
+
+print <<END;
+ <TH>Packages</TH>
+ <TH COLSPAN=2>Services</TH>
+ </TR>
+END
+
+ my(%saw,$cust_main);
+ my $p = popurl(2);
+ foreach $cust_main (
+ sort $sortby grep(!$saw{$_->custnum}++, @cust_main)
+ ) {
+ my($custnum,$last,$first,$company)=(
+ $cust_main->custnum,
+ $cust_main->getfield('last'),
+ $cust_main->getfield('first'),
+ $cust_main->company,
+ );
+
+ my(@lol_cust_svc);
+ my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} );
+ foreach ( @{$all_pkgs{$custnum}} ) {
+ #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } );
+ my @cust_svc = $_->cust_svc;
+ push @lol_cust_svc, \@cust_svc;
+ $rowspan += scalar(@cust_svc) || 1;
+ }
+
+ #my($rowspan) = scalar(@{$all_pkgs{$custnum}});
+ my $view;
+ if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) {
+ $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum;
+ } else {
+ $view = $p. 'view/cust_main.cgi?'. $custnum;
+ }
+ my $pcompany = $company
+ ? qq!<A HREF="$view"><FONT SIZE=-1>$company</FONT></A>!
+ : '<FONT SIZE=-1>&nbsp;</FONT>';
+ print <<END;
+ <TR>
+ <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$custnum</FONT></A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$last, $first</FONT></A></TD>
+ <TD ROWSPAN=$rowspan>$pcompany</TD>
+END
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ my($ship_last,$ship_first,$ship_company)=(
+ $cust_main->ship_last || $cust_main->getfield('last'),
+ $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first,
+ $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company,
+ );
+ my $pship_company = $ship_company
+ ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>!
+ : '<FONT SIZE=-1>&nbsp;</FONT>';
+ print <<END;
+ <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$ship_last, $ship_first</FONT></A></TD>
+ <TD ROWSPAN=$rowspan>$pship_company</A></TD>
+END
+ }
+
+ my($n1)='';
+ foreach ( @{$all_pkgs{$custnum}} ) {
+ my $pkgnum = $_->pkgnum;
+# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } );
+ my $part_pkg = $_->part_pkg;
+
+ my $pkg = $part_pkg->pkg;
+ my $comment = $part_pkg->comment;
+ my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum";
+ my @cust_svc = @{shift @lol_cust_svc};
+ #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } );
+ my $rowspan = scalar(@cust_svc) || 1;
+
+ print $n1, qq!<TD ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!;
+ my($n2)='';
+ foreach my $cust_svc ( @cust_svc ) {
+ my($label, $value, $svcdb) = $cust_svc->label;
+ my($svcnum) = $cust_svc->svcnum;
+ my($sview) = $p.'view';
+ print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!,
+ qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!;
+ $n2="</TR><TR>";
+ }
+ #print qq!</TR><TR>\n!;
+ $n1="</TR><TR>";
+ }
+ print "</TR>";
+ }
+
+ print "</TABLE>$pager</BODY></HTML>";
+
+}
+
+#undef $cache; #does this help?
+
+#
+
+sub last_sort {
+ lc($a->getfield('last')) cmp lc($b->getfield('last'))
+ || lc($a->first) cmp lc($b->first);
+}
+
+sub company_sort {
+ return -1 if $a->company && ! $b->company;
+ return 1 if ! $a->company && $b->company;
+ lc($a->company) cmp lc($b->company)
+ || lc($a->getfield('last')) cmp lc($b->getfield('last'))
+ || lc($a->first) cmp lc($b->first);;
+}
+
+sub custnum_sort {
+ $a->getfield('custnum') <=> $b->getfield('custnum');
+}
+
+sub custnumsearch {
+
+ my $custnum = $cgi->param('custnum_text');
+ $custnum =~ s/\D//g;
+ $custnum =~ /^(\d{1,23})$/ or eidiot "Illegal customer number\n";
+ $custnum = $1;
+
+ [ qsearchs('cust_main', { 'custnum' => $custnum } ) ];
+}
+
+sub cardsearch {
+
+ my($card)=$cgi->param('card');
+ $card =~ s/\D//g;
+ $card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n";
+ my($payinfo)=$1;
+
+ [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}),
+ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'})
+ ];
+}
+
+sub referralsearch {
+ $cgi->param('referral_custnum') =~ /^(\d+)$/
+ or eidiot "Illegal referral_custnum";
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } )
+ or eidiot "Customer $1 not found";
+ my $depth;
+ if ( $cgi->param('referral_depth') ) {
+ $cgi->param('referral_depth') =~ /^(\d+)$/
+ or eidiot "Illegal referral_depth";
+ $depth = $1;
+ } else {
+ $depth = 1;
+ }
+ [ $cust_main->referral_cust_main($depth) ];
+}
+
+sub lastsearch {
+ my(%last_type);
+ my @cust_main;
+ foreach ( $cgi->param('last_type') ) {
+ $last_type{$_}++;
+ }
+
+ $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/
+ or eidiot "Illegal last name";
+ my($last)=$1;
+
+ if ( $last_type{'Exact'} || $last_type{'Fuzzy'} ) {
+ push @cust_main, qsearch( 'cust_main',
+ { 'last' => { 'op' => 'ILIKE',
+ 'value' => $last } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'ship_last' => { 'op' => 'ILIKE',
+ 'value' => $last } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+ }
+
+ if ( $last_type{'Substring'} || $last_type{'All'} ) {
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'last' => { 'op' => 'ILIKE',
+ 'value' => "%$last%" } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'ship_last' => { 'op' => 'ILIKE',
+ 'value' => "%$last%" } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ }
+
+ if ( $last_type{'Fuzzy'} || $last_type{'All'} ) {
+ push @cust_main, FS::cust_main->fuzzy_search( { 'last' => $last } );
+ }
+
+ #if ($last_type{'Sound-alike'}) {
+ #}
+
+ \@cust_main;
+}
+
+sub companysearch {
+
+ my(%company_type);
+ my @cust_main;
+ foreach ( $cgi->param('company_type') ) {
+ $company_type{$_}++
+ };
+
+ $cgi->param('company_text') =~
+ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or eidiot "Illegal company";
+ my $company = $1;
+
+ if ( $company_type{'Exact'} || $company_type{'Fuzzy'} ) {
+ push @cust_main, qsearch( 'cust_main',
+ { 'company' => { 'op' => 'ILIKE',
+ 'value' => $company } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'ship_company' => { 'op' => 'ILIKE',
+ 'value' => $company } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+ }
+
+ if ( $company_type{'Substring'} || $company_type{'All'} ) {
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'company' => { 'op' => 'ILIKE',
+ 'value' => "%$company%" } } );
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'ship_company' => { 'op' => 'ILIKE',
+ 'value' => "%$company%" } })
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ }
+
+ if ( $company_type{'Fuzzy'} || $company_type{'All'} ) {
+ push @cust_main, FS::cust_main->fuzzy_search( { 'company' => $company } );
+ }
+
+ if ($company_type{'Sound-alike'}) {
+ }
+
+ \@cust_main;
+}
+
+sub address2search {
+ my @cust_main;
+
+ $cgi->param('address2_text') =~
+ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+ or eidiot "Illegal address2";
+ my $address2 = $1;
+
+ push @cust_main, qsearch( 'cust_main',
+ { 'address2' => { 'op' => 'ILIKE',
+ 'value' => $address2 } } );
+ push @cust_main, qsearch( 'cust_main',
+ { 'address2' => { 'op' => 'ILIKE',
+ 'value' => $address2 } } )
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ \@cust_main;
+}
+
+sub phonesearch {
+ my @cust_main;
+
+ my $phone = $cgi->param('phone_text');
+
+ #(no longer really) false laziness with Record::ut_phonen
+ #only works with US/CA numbers...
+ $phone =~ s/\D//g;
+ if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) {
+ $phone = "$1-$2-$3";
+ $phone .= " x$4" if $4;
+ } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) {
+ $phone = "$1-$2";
+ } elsif ( $phone =~ /^(\d{3,4})$/ ) {
+ $phone = $1;
+ } else {
+ eidiot gettext('illegal_phone'). ": $phone";
+ }
+
+ my @fields = qw(daytime night fax);
+ push @fields, qw(ship_daytime ship_night ship_fax)
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ for my $field ( @fields ) {
+ push @cust_main, qsearch ( 'cust_main',
+ { $field => { 'op' => 'LIKE',
+ 'value' => "%$phone%" } } );
+ }
+
+ \@cust_main;
+}
+
+%>
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
new file mode 100755
index 0000000..5a066e4
--- /dev/null
+++ b/httemplate/search/cust_main.html
@@ -0,0 +1,42 @@
+<HTML>
+ <HEAD>
+ <TITLE>Customer Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Customer Search
+ </FONT>
+ <BR><BR>
+ <FORM ACTION="cust_main.cgi" METHOD="post">
+ <INPUT TYPE="checkbox" NAME="last_on" CHECKED> Search for <B>last name</B>:
+ <INPUT TYPE="text" NAME="last_text">
+ using search method: <SELECT NAME="last_type">
+ <OPTION SELECTED>All
+ <OPTION>Fuzzy
+ <OPTION>Substring
+ <OPTION>Exact
+ </SELECT>
+
+ <P><INPUT TYPE="checkbox" NAME="company_on" CHECKED> Search for <B>company</B>:
+ <INPUT TYPE="text" NAME="company_text">
+ using search methods: <SELECT NAME="company_type">
+ <OPTION SELECTED>All
+ <OPTION>Fuzzy
+ <OPTION>Substring
+ <OPTION>Exact
+ </SELECT>
+
+ <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while. Please be patient.
+
+ </FORM>
+
+ <HR>Explanation of search methods:
+ <UL>
+ <LI><B>All</B> - Try all search methods.
+ <LI><B>Fuzzy</B> - Searches for matches that are close to your text.
+ <LI><B>Substring</B> - Searches for matches that contain your text.
+ <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods.
+ </UL>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi
new file mode 100755
index 0000000..3f5b72a
--- /dev/null
+++ b/httemplate/search/cust_pay.cgi
@@ -0,0 +1,137 @@
+<%
+ my( $count_query, $sql_query );
+ if ( $cgi->param('magic') && $cgi->param('magic') eq '_date' ) {
+
+ my %search;
+ my @search;
+
+ if ( $cgi->param('payby') ) {
+ $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/
+ or die "illegal payby ". $cgi->param('payby');
+ $search{'payby'} = $1;
+ if ( $3 ) {
+ if ( $3 eq 'VisaMC' ) {
+ #avoid posix regexes for portability
+ push @search, " ( substring(payinfo from 1 for 1) = '4' ".
+ " OR substring(payinfo from 1 for 2) = '51' ".
+ " OR substring(payinfo from 1 for 2) = '52' ".
+ " OR substring(payinfo from 1 for 2) = '53' ".
+ " OR substring(payinfo from 1 for 2) = '54' ".
+ " OR substring(payinfo from 1 for 2) = '54' ".
+ " OR substring(payinfo from 1 for 2) = '55' ".
+ " ) ";
+ } elsif ( $3 eq 'Amex' ) {
+ push @search, " ( substring(payinfo from 1 for 2 ) = '34' ".
+ " OR substring(payinfo from 1 for 2 ) = '37' ".
+ " ) ";
+ } elsif ( $3 eq 'Discover' ) {
+ push @search, " substring(payinfo from 1 for 4 ) = '6011' ";
+ } else {
+ die "unknown card type $3";
+ }
+ }
+ }
+
+ #false laziness with cust_pkg.cgi
+ if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $beginning = str2time($1);
+ push @search, "_date >= $beginning ";
+ }
+ if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $ending = str2time($1) + 86399;
+ push @search, " _date <= $ending ";
+ }
+ if ( $cgi->param('begin')
+ && $cgi->param('begin') =~ /^(\d+)$/ ) {
+ push @search, "_date >= $1 ";
+ }
+ if ( $cgi->param('end')
+ && $cgi->param('end') =~ /^(\d+)$/ ) {
+ push @search, " _date < $1 ";
+ }
+
+ my $search;
+ if ( @search ) {
+ $search = ( scalar(keys %search) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @search);
+ }
+
+ my $hsearch = join(' AND ', map { "$_ = '$search{$_}'" } keys %search );
+ $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay ".
+ ( $hsearch ? " WHERE $hsearch " : '' ).
+ $search;
+
+ $sql_query = {
+ 'table' => 'cust_pay',
+ 'hashref' => \%search,
+ 'extra_sql' => "$search ORDER BY _date",
+ };
+
+ } else {
+
+ $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";
+ my $payinfo = $1;
+
+ $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby";
+ my $payby = $1;
+
+ $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay ".
+ "WHERE payinfo = '$payinfo' AND payby = '$payby'";
+
+ $sql_query = {
+ 'table' => 'cust_pay',
+ 'hashref' => { 'payinfo' => $payinfo,
+ 'payby' => $payby },
+ 'extra_sql' => "ORDER BY _date",
+ };
+
+ }
+
+ my $link = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+%>
+<%= include( 'elements/search.html',
+ 'title' => 'Payment Search Results',
+ 'name' => 'payments',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ '$%.2f total paid', ],
+ 'header' =>
+ [ qw(Payment Amount Date), 'Cust #', 'Contact name',
+ 'Company', ],
+ 'fields' => [
+ sub {
+ my $cust_pay = shift;
+ if ( $cust_pay->payby eq 'CARD' ) {
+ 'Card #'. $cust_pay->payinfo_masked;
+ } elsif ( $cust_pay->payby eq 'CHEK' ) {
+ 'E-check acct#'. $cust_pay->payinfo;
+ } elsif ( $cust_pay->payby eq 'BILL' ) {
+ 'Check #'. $cust_pay->payinfo;
+ } else {
+ $cust_pay->payby. ' '. $cust_pay->payinfo;
+ }
+ },
+ sub { sprintf('$%.2f', shift->paid ) },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ 'custnum',
+ sub { my $cust_main = shift->cust_main;
+ $cust_main->get('last'). ', '. $cust_main->first;
+ },
+ sub { my $cust_main = shift->cust_main;
+ $cust_main->company;
+ },
+ ],
+ 'align' => 'lrrrll',
+ 'links' => [
+ '',
+ '',
+ '',
+ $link,
+ $link,
+ $link,
+ ],
+ )
+%>
diff --git a/httemplate/search/cust_pay.html b/httemplate/search/cust_pay.html
new file mode 100755
index 0000000..3848d66
--- /dev/null
+++ b/httemplate/search/cust_pay.html
@@ -0,0 +1,18 @@
+<HTML>
+ <HEAD>
+ <TITLE>Check # Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Check # Search
+ </FONT>
+ <BR><BR>
+ <FORM ACTION="cust_pay.cgi" METHOD="post">
+ Search for <B>check #</B>:
+ <INPUT TYPE="text" NAME="payinfo">
+ <INPUT TYPE="hidden" NAME="payby" VALUE="BILL">
+ <BR><BR><INPUT TYPE="submit" VALUE="Search">
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
new file mode 100755
index 0000000..6d26317
--- /dev/null
+++ b/httemplate/search/cust_pkg.cgi
@@ -0,0 +1,363 @@
+<%
+
+my $conf = new FS::Conf;
+my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {});
+
+my $limit = '';
+$limit .= "LIMIT $maxrecords" if $maxrecords;
+
+my $offset = $cgi->param('offset') || 0;
+$limit .= " OFFSET $offset" if $offset;
+
+my $total;
+
+my($query) = $cgi->keywords;
+my $sortby;
+my @cust_pkg;
+
+if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
+ $sortby=\*bill_sort;
+
+ #false laziness with cust_pay.cgi
+ my $range = '';
+ if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $beginning = str2time($1);
+ $range = " WHERE bill >= $beginning ";
+ }
+ if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $ending = str2time($1) + 86399;
+ $range .= ( $range ? ' AND ' : ' WHERE ' ). " bill <= $ending ";
+ }
+
+ $range .= ( $range ? 'AND ' : ' WHERE ' ). '( cancel IS NULL OR cancel = 0 )';
+
+ if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) {
+ $range .= ( $range ? 'AND ' : ' WHERE ' ).
+ "$1 = ( SELECT agentnum FROM cust_main".
+ " WHERE cust_main.custnum = cust_pkg.custnum )";
+ }
+
+ #false laziness with below
+ my $statement = "SELECT COUNT(*) FROM cust_pkg $range";
+ warn $statement;
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+
+ $total = $sth->fetchrow_arrayref->[0];
+
+ @cust_pkg = qsearch('cust_pkg',{}, '', " $range ORDER BY bill $limit" );
+
+} else {
+
+ my $qual = '';
+ if ( $cgi->param('magic') &&
+ $cgi->param('magic') =~ /^(active|suspended|canceled)$/
+ ) {
+
+ if ( $cgi->param('magic') eq 'active' ) {
+ $qual = 'WHERE ( susp IS NULL OR susp = 0 )'.
+ ' AND ( cancel IS NULL OR cancel = 0)';
+ } elsif ( $cgi->param('magic') eq 'suspended' ) {
+ $qual = 'WHERE susp IS NOT NULL AND susp != 0'.
+ ' AND ( cancel IS NULL OR cancel = 0)';
+ } elsif ( $cgi->param('magic') eq 'canceled' ) {
+ $qual = 'WHERE cancel IS NOT NULL AND cancel != 0';
+ } else {
+ die "guru meditation #420";
+ }
+
+ $sortby = \*pkgnum_sort;
+
+ if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) {
+ $qual .= " AND pkgpart = $1";
+ }
+
+ } elsif ( $query eq 'pkgnum' ) {
+
+ $sortby=\*pkgnum_sort;
+
+ } elsif ( $query eq 'APKG_pkgnum' ) {
+
+ $sortby=\*pkgnum_sort;
+
+ #@cust_pkg=();
+ ##perhaps this should go in cust_pkg as a qsearch-like constructor?
+ #my($cust_pkg);
+ #foreach $cust_pkg (
+ # qsearch('cust_pkg',{}, '', "ORDER BY pkgnum $limit" )
+ #) {
+ # my($flag)=0;
+ # my($pkg_svc);
+ # PKG_SVC:
+ # foreach $pkg_svc (qsearch('pkg_svc',{ 'pkgpart' => $cust_pkg->pkgpart })) {
+ # if ( $pkg_svc->quantity
+ # > scalar(qsearch('cust_svc',{
+ # 'pkgnum' => $cust_pkg->pkgnum,
+ # 'svcpart' => $pkg_svc->svcpart,
+ # }))
+ # )
+ # {
+ # $flag=1;
+ # last PKG_SVC;
+ # }
+ # }
+ # push @cust_pkg, $cust_pkg if $flag;
+ #}
+
+ if ( driver_name eq 'mysql' ) {
+ #$query = "DROP TABLE temp1_$$,temp2_$$;";
+ #my $sth = dbh->prepare($query);
+ #$sth->execute;
+
+ $query = "CREATE TEMPORARY TABLE temp1_$$ TYPE=MYISAM
+ SELECT cust_svc.pkgnum,cust_svc.svcpart,COUNT(*) as count
+ FROM cust_pkg,cust_svc,pkg_svc
+ WHERE cust_pkg.pkgnum = cust_svc.pkgnum
+ AND cust_svc.svcpart = pkg_svc.svcpart
+ AND cust_pkg.pkgpart = pkg_svc.pkgpart
+ GROUP BY cust_svc.pkgnum,cust_svc.svcpart";
+ my $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
+
+ $sth->execute or die "Error executing \"$query\": ". $sth->errstr;
+
+ $query = "CREATE TEMPORARY TABLE temp2_$$ TYPE=MYISAM
+ SELECT cust_pkg.pkgnum FROM cust_pkg
+ LEFT JOIN pkg_svc ON (cust_pkg.pkgpart=pkg_svc.pkgpart)
+ LEFT JOIN temp1_$$ ON (cust_pkg.pkgnum = temp1_$$.pkgnum
+ AND pkg_svc.svcpart=temp1_$$.svcpart)
+ WHERE ( pkg_svc.quantity > temp1_$$.count
+ OR temp1_$$.pkgnum IS NULL )
+ AND pkg_svc.quantity != 0;";
+ $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
+ $sth->execute or die "Error executing \"$query\": ". $sth->errstr;
+ $qual = " LEFT JOIN temp2_$$ ON cust_pkg.pkgnum = temp2_$$.pkgnum
+ WHERE temp2_$$.pkgnum IS NOT NULL";
+
+ } else {
+
+ $qual = "
+ WHERE 0 <
+ ( SELECT count(*) FROM pkg_svc
+ WHERE pkg_svc.pkgpart = cust_pkg.pkgpart
+ AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc
+ WHERE cust_svc.pkgnum = cust_pkg.pkgnum
+ AND cust_svc.svcpart = pkg_svc.svcpart
+ )
+ )
+ ";
+
+ }
+
+ } else {
+ die "Empty or unknown QUERY_STRING!";
+ }
+
+ my $statement = "SELECT COUNT(*) FROM cust_pkg $qual";
+ my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+
+ $total = $sth->fetchrow_arrayref->[0];
+
+ my $tblname = driver_name eq 'mysql' ? 'cust_pkg.' : '';
+ @cust_pkg =
+ qsearch('cust_pkg',{}, '', "$qual ORDER BY ${tblname}pkgnum $limit" );
+
+ if ( driver_name eq 'mysql' ) {
+ $query = "DROP TABLE temp1_$$,temp2_$$;";
+ my $sth = dbh->prepare($query) or die dbh->errstr. " doing $query";
+ $sth->execute; # or die "Error executing \"$query\": ". $sth->errstr;
+ }
+
+}
+
+if ( scalar(@cust_pkg) == 1 ) {
+ print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum.
+ "#cust_pkg". $cust_pkg[0]->pkgnum );
+ #exit;
+} elsif ( scalar(@cust_pkg) == 0 ) { #error
+%>
+<!-- mason kludge -->
+<%
+ eidiot("No packages found");
+} else {
+%>
+<!-- mason kludge -->
+<%
+ $total ||= scalar(@cust_pkg);
+
+ #begin pager
+ my $pager = '';
+ if ( $total != scalar(@cust_pkg) && $maxrecords ) {
+ unless ( $offset == 0 ) {
+ $cgi->param('offset', $offset - $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Previous</FONT></B></A> ';
+ }
+ my $poff;
+ my $page;
+ for ( $poff = 0; $poff < $total; $poff += $maxrecords ) {
+ $page++;
+ if ( $offset == $poff ) {
+ $pager .= qq!<FONT SIZE="+2">$page</FONT> !;
+ } else {
+ $cgi->param('offset', $poff);
+ $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !;
+ }
+ }
+ unless ( $offset + $maxrecords > $total ) {
+ $cgi->param('offset', $offset + $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Next</FONT></B></A> ';
+ }
+ }
+ #end pager
+
+ print header('Package Search Results',''),
+ "$total matching packages found<BR><BR>$pager", &table(), <<END;
+ <TR>
+ <TH>Package</TH>
+ <TH><FONT SIZE=-1>Setup</FONT></TH>
+END
+
+ print '<TH><FONT SIZE=-1>Last<BR>bill</FONT></TH>'
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
+ print <<END;
+ <TH><FONT SIZE=-1>Next<BR>bill</FONT></TH>
+ <TH><FONT SIZE=-1>Susp.</FONT></TH>
+ <TH><FONT SIZE=-1>Expire</FONT></TH>
+ <TH><FONT SIZE=-1>Cancel</FONT></TH>
+ <TH><FONT SIZE=-1>Cust#</FONT></TH>
+ <TH>(bill) name</TH>
+ <TH>company</TH>
+END
+
+ print '<TH>(service) name</TH><TH>company</TH>'
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ print '<TH COLSPAN=2>Services</TH></TR>';
+
+ my $n1 = '<TR>';
+ my(%saw,$cust_pkg);
+ foreach $cust_pkg (
+ sort $sortby grep(!$saw{$_->pkgnum}++, @cust_pkg)
+ ) {
+ my($cust_main)=qsearchs('cust_main',{'custnum'=>$cust_pkg->custnum});
+ my($pkgnum, $setup, $bill, $susp, $expire, $cancel,
+ $custnum, $last, $first, $company ) = (
+ $cust_pkg->pkgnum,
+ $cust_pkg->getfield('setup')
+ ? time2str("%D", $cust_pkg->getfield('setup') )
+ : '',
+ $cust_pkg->getfield('bill')
+ ? time2str("%D", $cust_pkg->getfield('bill') )
+ : '',
+ $cust_pkg->getfield('susp')
+ ? time2str("%D", $cust_pkg->getfield('susp') )
+ : '',
+ $cust_pkg->getfield('expire')
+ ? time2str("%D", $cust_pkg->getfield('expire') )
+ : '',
+ $cust_pkg->getfield('cancel')
+ ? time2str("%D", $cust_pkg->getfield('cancel') )
+ : '',
+ $cust_pkg->custnum,
+ $cust_main ? $cust_main->last : '',
+ $cust_main ? $cust_main->first : '',
+ $cust_main ? $cust_main->company : '',
+ );
+
+ my $last_bill = $cust_pkg->getfield('last_bill')
+ ? time2str("%D", $cust_pkg->getfield('last_bill') )
+ : ''
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
+ my($ship_last, $ship_first, $ship_company);
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ ($ship_last, $ship_first, $ship_company) = (
+ $cust_main
+ ? ( $cust_main->ship_last || $cust_main->getfield('last') )
+ : '',
+ $cust_main
+ ? ( $cust_main->ship_last
+ ? $cust_main->ship_first
+ : $cust_main->first )
+ : '',
+ $cust_main
+ ? ( $cust_main->ship_last
+ ? $cust_main->ship_company
+ : $cust_main->company )
+ : '',
+ );
+ }
+ my $pkg = $part_pkg{$cust_pkg->pkgpart}->pkg;
+ #$pkg .= ' - '. $part_pkg{$cust_pkg->pkgpart}->comment;
+ my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } );
+ my $rowspan = scalar(@cust_svc) || 1;
+ my $p = popurl(2);
+ print $n1, <<END;
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum"><FONT SIZE=-1>$pkgnum - $pkg</FONT></A></TD>
+ <TD ROWSPAN=$rowspan>$setup</TD>
+END
+
+ print "<TD ROWSPAN=$rowspan>$last_bill</TD>"
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
+ print <<END;
+ <TD ROWSPAN=$rowspan>$bill</TD>
+ <TD ROWSPAN=$rowspan>$susp</TD>
+ <TD ROWSPAN=$rowspan>$expire</TD>
+ <TD ROWSPAN=$rowspan>$cancel</TD>
+END
+ if ( $cust_main ) {
+ print <<END;
+ <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></FONT></TD>
+ <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$last, $first</A></FONT></TD>
+ <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$company</A></FONT></TD>
+END
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ print <<END;
+ <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$ship_last, $ship_first</A></FONT></TD>
+ <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$ship_company</A></FONT></TD>
+END
+ }
+ } else {
+ my $colspan = defined dbdef->table('cust_main')->column('ship_last')
+ ? 5 : 3;
+ print <<END;
+ <TD ROWSPAN=$rowspan COLSPAN=$colspan>WARNING: couldn't find cust_main.custnum $custnum (cust_pkg.pkgnum $pkgnum)</TD>
+END
+ }
+
+ my $n2 = '';
+ foreach my $cust_svc ( @cust_svc ) {
+ my($label, $value, $svcdb) = $cust_svc->label;
+ my $svcnum = $cust_svc->svcnum;
+ my $sview = $p. "view";
+ print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!,
+ qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!;
+ $n2="</TR><TR>";
+ }
+
+ $n1 = "</TR><TR>";
+
+ }
+ print '</TR>';
+
+ print "</TABLE>$pager</BODY></HTML>";
+
+}
+
+sub pkgnum_sort {
+ $a->getfield('pkgnum') <=> $b->getfield('pkgnum');
+}
+
+sub bill_sort {
+ $a->getfield('bill') <=> $b->getfield('bill');
+}
+
+%>
diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi
new file mode 100755
index 0000000..b316745
--- /dev/null
+++ b/httemplate/search/cust_pkg_report.cgi
@@ -0,0 +1,63 @@
+<HTML>
+ <HEAD>
+ <TITLE>Packages</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Packages</H1>
+ <FORM ACTION="cust_pkg.cgi" METHOD="post">
+ <INPUT TYPE="hidden" NAME="magic" VALUE="bill">
+ Return packages with next bill date:<BR><BR>
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+<% my %agent_search = dbdef->table('agent')->column('disabled')
+ ? ( 'disabled' => '' ) : ();
+ my @agents = qsearch( 'agent', \%agent_search );
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agents[0]->agentnum %>">
+<% } else { %>
+
+ <TR>
+ <TD ALIGN="right">Agent: </TD>
+ <TD><SELECT NAME="agentnum"><OPTION VALUE="">(all)
+ <% foreach my $agent ( sort { $a->agent cmp $b->agent; } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>"><%= $agent->agent %>
+ <% } %>
+ </TD>
+ </TR>
+<% } %>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+
+ </FORM>
+
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
new file mode 100644
index 0000000..566ea83
--- /dev/null
+++ b/httemplate/search/elements/search.html
@@ -0,0 +1,139 @@
+<%
+
+ my(%opt) = @_;
+
+ my %align = (
+ 'l' => 'left',
+ 'r' => 'right',
+ 'c' => 'center',
+ ' ' => '',
+ '.' => '',
+ );
+ $opt{align} = [ map $align{$_}, split(//, $opt{align}) ],
+ unless !$opt{align} || ref($opt{align});
+
+ if ( ref($opt{'query'}) ) {
+
+ }
+
+ unless (exists($opt{'count_query'}) && length($opt{'count_query'})) {
+ ( $opt{'count_query'} = $opt{'query'} ) =~
+ s/^\s*SELECT\s*(.*?)\s+FROM\s/SELECT COUNT(*) FROM /i;
+ }
+
+ my $conf = new FS::Conf;
+ my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+ my $limit = $maxrecords ? "LIMIT $maxrecords" : '';
+
+ my $offset = $cgi->param('offset') || 0;
+ $limit .= " OFFSET $offset" if $offset;
+
+ my $count_sth = dbh->prepare($opt{'count_query'})
+ or die "Error preparing $opt{'count_query'}: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $opt{'count_query'}: ". $count_sth->errstr;
+ my $count_arrayref = $count_sth->fetchrow_arrayref;
+ my $total = $count_arrayref->[0];
+
+ #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n";
+
+ my $header = $opt{'header'};
+ my $rows;
+ if ( ref($opt{'query'}) ) {
+ #eval "use FS::$opt{'query'};";
+ $rows = [ qsearch(
+ $opt{'query'}->{'table'},
+ $opt{'query'}->{'hashref'} || {},
+ $opt{'query'}->{'select'},
+ $opt{'query'}->{'extra_sql'}. " $limit",
+ ) ];
+ } else {
+ my $sth = dbh->prepare("$opt{'query'} $limit")
+ or die "Error preparing $opt{'query'}: ". dbh->errstr;
+ $sth->execute
+ or die "Error executing $opt{'query'}: ". $sth->errstr;
+
+ #can get # of rows without fetching them all?
+ $rows = $sth->fetchall_arrayref;
+
+ $header ||= $sth->{NAME};
+ }
+
+ if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 ) {
+ my( $url, $method ) = @{$opt{'redirect'}};
+ redirect( $url. $rows->[0]->$method() );
+ } else {
+ $opt{'name'} =~ s/s$// if $total == 1;
+%>
+<%= include( '/elements/header.html', $opt{'title'},
+ include( '/elements/menubar.html', 'Main menu' => $p )
+ )
+%>
+<% my $pager = include ( '/elements/pager.html',
+ 'offset' => $offset,
+ 'num_rows' => scalar(@$rows),
+ 'total' => $total,
+ 'maxrecords' => $maxrecords,
+ );
+%>
+<% unless ( $total ) { %>
+ No matching <%= $opt{'name'} %> found.<BR>
+<% } else { %>
+ <%= $total %> total <%= $opt{'name'} %><BR>
+ <% if ( $opt{'count_addl'} ) { %>
+ <% my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { %>
+ <%= sprintf( $count, $count_arrayref->[++$n] ) %><BR>
+ <% } %>
+ <% } %>
+ <BR><%= $pager %>
+ <%= include( '/elements/table.html' ) %>
+ <TR>
+ <% foreach my $header ( @$header ) { %>
+ <TH><%= $header %></TH>
+ <% } %>
+ </TR>
+ <% foreach my $row ( @$rows ) { %>
+ <TR>
+ <% if ( $opt{'fields'} ) {
+ my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+ my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+ foreach my $field ( @{$opt{'fields'}} ) {
+ my $align = $aligns ? shift @$aligns : '';
+ $align = " ALIGN=$align" if $align;
+ my $a = '';
+ if ( $links ) {
+ my $link = shift @$links;
+ $link = &{$link}($row) if ref($link) eq 'CODE';
+ if ( $link ) {
+ my( $url, $method ) = @{$link};
+ if ( ref($method) eq 'CODE' ) {
+ $a = $url. &{$method}($row);
+ } else {
+ $a = $url. $row->$method();
+ }
+ $a = qq(<A HREF="$a">);
+ }
+ }
+ %>
+ <% if ( ref($field) eq 'CODE' ) { %>
+ <TD<%= $align %>><%= $a %><%= &{$field}($row) %><%= $a ? '</A>' : '' %></TD>
+ <% } else { %>
+ <TD<%= $align %>><%= $a %><%= $row->$field() %><%= $a ? '</A>' : '' %></TD>
+ <% } %>
+ <% } %>
+ <% } else { %>
+ <% foreach ( @$row ) { %>
+ <TD><%= $_ %></TD>
+ <% } %>
+ <% } %>
+ </TR>
+ <% } %>
+
+ </TABLE>
+ <%= $pager %>
+<% } %>
+</BODY>
+</HTML>
+<% } %>
+
diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html
new file mode 100644
index 0000000..ceffca7
--- /dev/null
+++ b/httemplate/search/report_cust_credit.html
@@ -0,0 +1,58 @@
+<HTML>
+ <HEAD>
+ <TITLE>Credit report criteria</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Credit report criteria</H1>
+ <FORM ACTION="cust_credit.html" METHOD="post">
+ <INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">Credits by employee: </TD>
+<%
+ my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_credit")
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref};
+%>
+ <TD><SELECT NAME="otaker">
+ <OPTION VALUE="">all</OPTION>
+ <% foreach my $otaker ( @otakers ) { %>
+ <OPTION VALUE="<%= $otaker %>"><%= $otaker %></OPTION>
+ <% } %>
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html
new file mode 100644
index 0000000..95198c7
--- /dev/null
+++ b/httemplate/search/report_cust_pay.html
@@ -0,0 +1,55 @@
+<HTML>
+ <HEAD>
+ <TITLE>Payment report criteria</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Payment report criteria</H1>
+ <FORM ACTION="cust_pay.cgi" METHOD="post">
+ <INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">Payments of type: </TD>
+ <TD><SELECT NAME="payby">
+ <OPTION VALUE="">all</OPTION>
+ <OPTION VALUE="CARD">credit card (all)</OPTION>
+ <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION>
+ <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION>
+ <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ <OPTION VALUE="BILL">check / cash</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi
new file mode 100644
index 0000000..1677591
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.cgi
@@ -0,0 +1,86 @@
+<!-- mason kludge -->
+<%
+
+ #doesn't yet deal with daily/weekly packages
+
+ #needs to be re-written in sql for efficiency
+
+ my $time = time;
+
+ my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time;
+ $now =~ /^(\d+)$/ or die "unparsable date?";
+ $now = $1;
+
+ my( $total, $total_legacy ) = ( 0, 0 );
+
+ my @cust_bill_pkg =
+ grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ }
+ qsearch( 'cust_bill_pkg', {
+ 'recur' => { op=>'!=', value=>0 },
+ 'edate' => { op=>'>', value=>$now },
+ }, );
+
+ my @cust_pkg =
+ grep { $_->part_pkg->recur != 0
+ && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/
+ }
+ qsearch ( 'cust_pkg', {
+ 'bill' => { op=>'>', value=>$now }
+ } );
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg) {
+ my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
+
+ my $elapsed = $now - $cust_bill_pkg->sdate;
+ $elapsed = 0 if $elapsed < 0;
+
+ my $remaining = 1 - $elapsed/$period;
+
+ my $unearned = $remaining * $cust_bill_pkg->recur;
+ $total += $unearned;
+
+ }
+
+ foreach my $cust_pkg ( @cust_pkg ) {
+ my $period = $cust_pkg->bill - $cust_pkg->last_bill;
+
+ my $elapsed = $now - $cust_pkg->last_bill;
+ $elapsed = 0 if $elapsed < 0;
+
+ my $remaining = 1 - $elapsed/$period;
+
+ my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy
+ $total_legacy += $unearned;
+
+ }
+
+ $total = sprintf('%.2f', $total);
+ $total_legacy = sprintf('%.2f', $total_legacy);
+
+%>
+
+<%= header( 'Prepaid Income (Unearned Revenue) Report',
+ menubar( 'Main Menu'=>$p, ) ) %>
+<%= table() %>
+ <TR>
+ <TH>Actual Unearned Revenue</TH>
+ <TH>Legacy Unearned Revenue</TH>
+ </TR>
+ <TR>
+ <TD ALIGN="right">$<%= $total %>
+ <TD ALIGN="right">
+ <%= $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%>
+ </TD>
+ </TR>
+
+</TABLE>
+<BR>
+Actual unearned revenue is the amount of unearned revenue Freeside has
+actually invoiced for packages with longer-than monthly terms.
+<BR><BR>
+Legacy unearned revenue is the amount of unearned revenue represented by
+customer packages. This number may be larger than actual unearned
+revenue if you have imported longer-than monthly customer packages from
+a previous billing system.
+</BODY>
+</HTML>
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
new file mode 100644
index 0000000..e8b6ac4
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.html
@@ -0,0 +1,39 @@
+<HTML>
+ <HEAD>
+ <TITLE>Prepaid Income (Unearned Revenue) Report</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Prepaid Income (Unearned Revenue) Report</H1>
+ <FORM ACTION="report_prepaid_income.cgi" METHOD="post">
+ <TABLE>
+ <TR>
+ <TD>Prepaid income (unearned revenue) as of </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now">
+ <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+ </TR>
+ <TR>
+ <TD>
+ </TD>
+ <TD><i>m/d/y</i></TD>
+ </TR>
+ </TABLE>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "date_text",
+ ifFormat: "%m/%d/%Y",
+ button: "date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+ <INPUT TYPE="submit" VALUE="Generate report">
+ </BODY>
+</HTML>
+ <TABLE>
+
diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi
new file mode 100755
index 0000000..0e95ad7
--- /dev/null
+++ b/httemplate/search/report_receivables.cgi
@@ -0,0 +1,158 @@
+<!-- mason kludge -->
+<%
+
+ my $charged = <<END;
+ sum( charged
+ - coalesce(
+ ( select sum(amount) from cust_bill_pay
+ where cust_bill.invnum = cust_bill_pay.invnum )
+ ,0
+ )
+ - coalesce(
+ ( select sum(amount) from cust_credit_bill
+ where cust_bill.invnum = cust_credit_bill.invnum )
+ ,0
+ )
+
+ )
+END
+
+ my $owed_cols = <<END;
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-2592000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_0_30,
+
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-5184000
+ and cust_bill._date <= extract(epoch from now())-2592000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_30_60,
+
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-7776000
+ and cust_bill._date <= extract(epoch from now())-5184000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_60_90,
+
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date <= extract(epoch from now())-7776000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_90_plus,
+
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_total
+END
+
+ my $recurring = <<END;
+ 0 < ( select freq from part_pkg
+ where cust_pkg.pkgpart = part_pkg.pkgpart )
+END
+
+ my $packages_cols = <<END;
+
+ ( select count(*) from cust_pkg
+ where cust_main.custnum = cust_pkg.custnum
+ and $recurring
+ and ( cancel = 0 or cancel is null )
+ ) as uncancelled_pkgs,
+
+ ( select count(*) from cust_pkg
+ where cust_main.custnum = cust_pkg.custnum
+ and $recurring
+ and ( cancel = 0 or cancel is null )
+ and ( susp = 0 or susp is null )
+ ) as active_pkgs
+
+END
+
+ my $sql = <<END;
+
+select *, $owed_cols, $packages_cols from cust_main
+where 0 <
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ )
+
+order by coalesce(lower(company), ''), lower(last)
+
+END
+
+ my $total_sql = "select $owed_cols";
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
+ $total_sth->execute or die $total_sth->errstr;
+
+%>
+<%= header('Accounts Receivable Aging Summary', menubar( 'Main Menu'=>$p, ) ) %>
+<%= table() %>
+ <TR>
+ <TH>Customer</TH>
+ <TH>Status</TH>
+ <TH>0-30</TH>
+ <TH>30-60</TH>
+ <TH>60-90</TH>
+ <TH>90+</TH>
+ <TH>Total</TH>
+ </TR>
+<% while ( my $row = $sth->fetchrow_hashref() ) {
+ my $status = 'Cancelled';
+ my $statuscol = 'FF0000';
+ if ( $row->{uncancelled_pkgs} ) {
+ $status = 'Suspended';
+ $statuscol = 'FF9900';
+ if ( $row->{active_pkgs} ) {
+ $status = 'Active';
+ $statuscol = '00CC00';
+ }
+ }
+%>
+ <TR>
+ <TD><A HREF="<%= $p %>view/cust_main.cgi?<%= $row->{'custnum'} %>"><%= $row->{'custnum'} %>:
+ <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %><%= $row->{'last'}. ', '. $row->{'first'} %><%= $row->{'company'} ? ')' : '' %></A>
+ </TD>
+ <TD><B><FONT SIZE=-1 COLOR="#<%= $statuscol %>"><%= $status %></FONT></B></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD>
+ <TD ALIGN="right"><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></TD>
+ </TR>
+<% } %>
+<% my $row = $total_sth->fetchrow_hashref(); %>
+ <TR>
+ <TD COLSPAN=6>&nbsp;</TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2><I>Total</I></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD>
+ <TD ALIGN="right"><I><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></I></TD>
+ </TR>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
new file mode 100755
index 0000000..f37e127
--- /dev/null
+++ b/httemplate/search/report_tax.cgi
@@ -0,0 +1,184 @@
+<!-- mason kludge -->
+<%
+
+my $user = getotaker;
+
+$cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/;
+my $pbeginning = $1;
+my $beginning = $1 ? str2time($1) : 0;
+
+$cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/;
+my $pending = $1;
+my $ending = ( $1 ? str2time($1) : 4294880896 ) + 86399;
+
+my($total, $exempt, $taxable, $tax) = ( 0, 0, 0, 0 );
+my $out = 'Out of taxable region(s)';
+my %regions;
+foreach my $r (
+ qsearch('cust_main_county', {}, '',
+ "WHERE 0 < ( SELECT COUNT(*) FROM cust_main
+ WHERE ( cust_main.county = cust_main_county.county
+ OR cust_main_county.county = ''
+ OR cust_main_county.county IS NULL )
+ AND ( cust_main.state = cust_main_county.state
+ OR cust_main_county.state = ''
+ OR cust_main_county.state IS NULL )
+ AND ( cust_main.country = cust_main_county.country )
+ LIMIT 1
+ )"
+ )
+) {
+ #warn $r->county. ' '. $r->state. ' '. $r->country. "\n";
+ my $label;
+ if ( $r->tax == 0 ) {
+ $label = $out;
+ } elsif ( $r->taxname ) {
+ $label = $r->taxname;
+ } else {
+ $label = $r->country;
+ $label = $r->state.", $label" if $r->state;
+ $label = $r->county." county, $label" if $r->county;
+ }
+
+ my $fromwhere = "
+ FROM cust_bill_pkg
+ JOIN cust_bill USING ( invnum )
+ JOIN cust_main USING ( custnum )
+ JOIN cust_pkg USING ( pkgnum )
+ JOIN part_pkg USING ( pkgpart )
+ WHERE _date >= $beginning AND _date <= $ending
+ AND ( county = ? OR ? = '' )
+ AND ( state = ? OR ? = '' )
+ AND ( country = ? )
+ AND payby != 'COMP'
+ ";
+ my @param = qw( county county state state country ); # taxclass);
+
+ my $num_others =
+ scalar_sql( $r, [qw( country state state county county taxname taxname )],
+ "SELECT COUNT(*) FROM cust_main_county
+ WHERE country = ?
+ AND ( state = ? OR ( state IS NULL AND ? = '' ) )
+ AND ( county = ? OR ( county IS NULL AND ? = '' ) )
+ AND ( taxname = ? OR ( taxname IS NULL AND ? = '' ) ) "
+ );
+
+ die "didn't even find self?" unless $num_others;
+
+ if ( $num_others > 1 ) {
+ $fromwhere .= " AND ( taxclass = ? ) ";
+ push @param, 'taxclass';
+ }
+
+ my $nottax = 'pkgnum != 0';
+
+ my $a = scalar_sql($r, \@param,
+ "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax"
+ );
+ $total += $a;
+ $regions{$label}->{'total'} += $a;
+
+ foreach my $e ( grep { $r->get($_.'tax') =~ /^Y/i }
+ qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) {
+ my $x = scalar_sql($r, \@param,
+ "SELECT SUM($e) $fromwhere AND $nottax"
+ );
+ $exempt += $x;
+ $regions{$label}->{'exempt'} += $x;
+ }
+
+ foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i }
+ qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) {
+ my $t = scalar_sql($r, \@param,
+ "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )"
+ );
+ $taxable += $t;
+ $regions{$label}->{'taxable'} += $t;
+
+ my $x = scalar_sql($r, \@param,
+ "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'"
+ );
+ $exempt += $x;
+ $regions{$label}->{'exempt'} += $x;
+ }
+
+ if ( defined($regions{$label}->{'rate'})
+ && $regions{$label}->{'rate'} != $r->tax.'%' ) {
+ $regions{$label}->{'rate'} = 'variable';
+ } else {
+ $regions{$label}->{'rate'} = $r->tax.'%';
+ }
+
+ #match itemdesc if necessary!
+ my $named_tax = $r->taxname ? 'AND itemdesc = '. dbh->quote($r->taxname) : '';
+ my $x = scalar_sql($r, \@param,
+ "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere ".
+ "AND pkgnum = 0 $named_tax",
+ );
+ $tax += $x;
+ $regions{$label}->{'tax'} += $x;
+
+ $regions{$label}->{'label'} = $label;
+
+}
+
+#ordering
+my @regions = map $regions{$_},
+ sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+ keys %regions;
+
+push @regions, {
+ 'label' => 'Total',
+ 'total' => $total,
+ 'exempt' => $exempt,
+ 'taxable' => $taxable,
+ 'rate' => '',
+ 'tax' => $tax,
+};
+
+#--
+
+#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up
+#to FS::Report or FS::Record or who the fuck knows where)
+sub scalar_sql {
+ my( $r, $param, $sql ) = @_;
+ #warn "$sql\n";
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute( map $r->$_(), @$param )
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ $sth->fetchrow_arrayref->[0] || 0;
+}
+
+%>
+
+<%= header( "Sales Tax Report - $pbeginning through ".($pending||'now'),
+ menubar( 'Main Menu'=>$p, ) ) %>
+<%= table() %>
+ <TR>
+ <TH ROWSPAN=2></TH>
+ <TH COLSPAN=3>Sales</TH>
+ <TH ROWSPAN=2>Rate</TH>
+ <TH ROWSPAN=2>Tax</TH>
+ </TR>
+ <TR>
+ <TH>Total</TH>
+ <TH>Non-taxable</TH>
+ <TH>Taxable</TH>
+ </TR>
+ <% foreach my $region ( @regions ) { %>
+ <TR>
+ <TD><%= $region->{'label'} %></TD>
+ <TD ALIGN="right">$<%= sprintf('%.2f', $region->{'total'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf('%.2f', $region->{'exempt'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf('%.2f', $region->{'taxable'} ) %></TD>
+ <TD ALIGN="right"><%= $region->{'rate'} %></TD>
+ <TD ALIGN="right">$<%= sprintf('%.2f', $region->{'tax'} ) %></TD>
+ </TR>
+ <% } %>
+
+</TABLE>
+
+</BODY>
+</HTML>
+
+
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
new file mode 100755
index 0000000..d217e56
--- /dev/null
+++ b/httemplate/search/report_tax.html
@@ -0,0 +1,44 @@
+<HTML>
+ <HEAD>
+ <TITLE>Tax Report Criteria</TITLE>
+ <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Tax Report Criteria</H1>
+ <FORM ACTION="report_tax.cgi" METHOD="post">
+ Return <B>tax report</B> for period:
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
+
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html
new file mode 100644
index 0000000..b28c045
--- /dev/null
+++ b/httemplate/search/sql.html
@@ -0,0 +1,7 @@
+<%= include( 'elements/search.html',
+ 'title' => 'Query Results',
+ 'name' => 'rows',
+ 'query' => 'SELECT '. ( $cgi->param('sql')
+ || eidiot('Empty query') ),
+ )
+%>
diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi
new file mode 100644
index 0000000..b506ba1
--- /dev/null
+++ b/httemplate/search/sqlradius.cgi
@@ -0,0 +1,260 @@
+<%= include( '/elements/header.html', 'RADIUS Sessions',
+ include('/elements/menubar.html',
+ 'Main menu' => $p, # popurl(2),
+ ),
+
+ )
+%>
+
+<%
+ ###
+ # parse cgi params
+ ###
+
+ #sort of false laziness w/cust_pay.cgi
+ my $beginning = '';
+ my $ending = '';
+ if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ $beginning = str2time($1);
+ }
+ if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ $ending = str2time($1) + 86399;
+ }
+ if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) {
+ $beginning = $1;
+ }
+ if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) {
+ $ending = $1;
+ }
+
+ my $cgi_svc_acct = '';
+ if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) {
+ $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } );
+ } elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) {
+ my %search = { 'username' => $1 };
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } );
+ if ( $svc_domain ) {
+ $search{'domsvc'} = $svc_domain->svcnum;
+ } else {
+ delete $search{'username'};
+ }
+ $cgi_svc_acct = qsearchs( 'svc_acct', \%search )
+ if keys %search;
+ } elsif ( $cgi->param('username') =~ /^(.+)$/ ) {
+ $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } );
+ }
+
+ my $ip = '';
+ if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) {
+ $ip = $1;
+ }
+
+ ###
+ # field formatting subroutines
+ ###
+
+ my %user2svc_acct = ();
+ my $user_format = sub {
+ my ( $user, $session, $part_export ) = @_;
+
+ my $svc_acct = '';
+ if ( exists $user2svc_acct{$user} ) {
+ $svc_acct = $user2svc_acct{$user};
+ } else {
+ my %search = ();
+ if ( $part_export->exporttype eq 'sqlradius_withdomain' ) {
+ my $domain;
+ if ( $user =~ /^([^@]+)\@([^@]+)$/ ) {
+ $search{'username'} = $1;
+ $domain = $2;
+ } else {
+ $search{'username'} = $user;
+ $domain = $session->{'realm'};
+ }
+ my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } );
+ if ( $svc_domain ) {
+ $search{'domsvc'} = $svc_domain->svcnum;
+ } else {
+ delete $search{'username'};
+ }
+ } elsif ( $part_export->exporttype eq 'sqlradius' ) {
+ $search{'username'} = $user;
+ } else {
+ die 'unknown export type '. $part_export->exporttype.
+ " for $part_export\n";
+ }
+ if ( keys %search ) {
+ my @svc_acct =
+ grep { qsearchs( 'export_svc', {
+ 'exportnum' => $part_export->exportnum,
+ 'svcpart' => $_->cust_svc->svcpart,
+ } )
+ } qsearch( 'svc_acct', \%search );
+ if ( @svc_acct ) {
+ warn 'multiple svc_acct records for user $user found; '.
+ 'using first arbitrarily'
+ if scalar(@svc_acct) > 1;
+ $user2svc_acct{$user} = $svc_acct = shift @svc_acct;
+ }
+ }
+ }
+
+ if ( $svc_acct ) {
+ my $svcnum = $svc_acct->svcnum;
+ qq(<A HREF="${p}view/svc_acct.cgi?$svcnum"><B>$user</B></A>);
+ } else {
+ "<B>$user</B>";
+ }
+
+ };
+
+ my $customer_format = sub {
+ my( $unused, $session ) = @_;
+ return '&nbsp;' unless exists $user2svc_acct{$session->{'username'}};
+ my $svc_acct = $user2svc_acct{$session->{'username'}};
+ my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+ return '&nbsp;' unless $cust_pkg;
+ my $cust_main = $cust_pkg->cust_main;
+
+ qq!<A HREF="${p}view/cust_main.cgi?!. $cust_main->custnum. '">'.
+ $cust_pkg->cust_main->name. '</A>';
+ };
+
+ my $time_format = sub {
+ my $time = shift;
+ return '&nbsp;' if $time == 0;
+ my $pretty = time2str('%T%P %a&nbsp;%b&nbsp;%o&nbsp;%Y', $time );
+ $pretty =~ s/ (\d)(st|dn|rd|th)/$1$2/;
+ $pretty;
+ };
+
+ my $duration_format = sub {
+ my $seconds = shift;
+ my $hour = int($seconds/3600);
+ my $min = int( ($seconds%3600) / 60 );
+ my $sec = $seconds%60;
+ '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>'.
+ '<TR><TD ALIGN="right">'.
+ ( $hour ? "<B>$hour</B>h" : '&nbsp;' ).
+ '</TD><TD ALIGN="right">'.
+ ( ( $hour || $min ) ? "<B>$min</B>m" : '&nbsp;' ).
+ '</TD><TD ALIGN="right">'.
+ "<B>$sec</B>s".
+ '</TD></TR></TABLE>';
+ };
+
+ my $octets_format = sub {
+ my $octets = shift;
+ my $megs = $octets / 1048576;
+ sprintf('<B>%.3f</B>&nbsp;megs', $megs);
+ #my $gigs = $octets / 1073741824
+ #sprintf('<B>%.3f</B> gigabytes', $gigs);
+ };
+
+ ###
+ # the fields
+ ###
+
+ tie my %fields, 'Tie::IxHash',
+ 'username' => {
+ name => 'User',
+ attrib => 'UserName',
+ fmt => $user_format,
+ align => 'left',
+ },
+ 'realm' => {
+ name => 'Realm',
+ attrib => 'Realm',
+ align => 'left',
+ },
+ 'dummy' => {
+ name => 'Customer',
+ attrib => '',
+ fmt => $customer_format,
+ align => 'left',
+ },
+ 'framedipaddress' => {
+ name => 'IP&nbsp;Address',
+ attrib => 'Framed-IP-Address',
+ fmt => sub { my $ip = shift;
+ length($ip) ? $ip : '&nbsp';
+ },
+ align => 'right',
+ },
+ 'acctstarttime' => {
+ name => 'Start&nbsp;time',
+ attrib => 'Acct-Start-Time',
+ fmt => $time_format,
+ align => 'left',
+ },
+ 'acctstoptime' => {
+ name => 'End&nbsp;time',
+ attrib => 'Acct-Stop-Time',
+ fmt => $time_format,
+ align => 'left',
+ },
+ 'acctsessiontime' => {
+ name => 'Duration',
+ attrib => 'Acct-Session-Time',
+ fmt => $duration_format,
+ align => 'right',
+ },
+ 'acctinputoctets' => {
+ name => 'Upload', # (from user)',
+ attrib => 'Acct-Input-Octets',
+ fmt => $octets_format,
+ align => 'right',
+ },
+ 'acctoutputoctets' => {
+ name => 'Download', # (to user)',
+ attrib => 'Acct-Output-Octets',
+ fmt => $octets_format,
+ align => 'right',
+ },
+ ;
+ $fields{$_}->{fmt} ||= sub { length($_[0]) ? shift : '&nbsp'; }
+ foreach keys %fields;
+
+ ###
+ # and finally, display the thing
+ ###
+
+ foreach my $part_export ( map $_->rebless,
+ qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ),
+ qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } )
+ ) {
+ %user2svc_acct = ();
+%>
+
+<%= $part_export->exporttype %> to <%= $part_export->machine %><BR>
+<%= include( '/elements/table.html' ) %>
+<TR>
+ <% foreach my $field ( keys %fields ) { %>
+ <TH>
+ <%= $fields{$field}->{name} %><BR>
+ <FONT SIZE=-2><%= $fields{$field}->{attrib} %></FONT>
+ </TH>
+ <% } %>
+</TR>
+<% foreach my $session (
+ @{ $part_export->usage_sessions( $beginning, $ending, $cgi_svc_acct, $ip ) }
+) { %>
+ <TR>
+ <% foreach my $field ( keys %fields ) { %>
+ <TD ALIGN="<%= $fields{$field}->{align} %>">
+ <%= &{ $fields{$field}->{fmt} }( $session->{$field},
+ $session,
+ $part_export,
+ )
+ %>
+ </TD>
+ <% } %>
+ </TR>
+<% } %>
+
+</TABLE>
+<BR><BR>
+
+<% } %>
diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html
new file mode 100644
index 0000000..48a3d86
--- /dev/null
+++ b/httemplate/search/sqlradius.html
@@ -0,0 +1,70 @@
+<%= include( '/elements/header.html', 'Search RADIUS sessions', '', '', '
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+') %>
+<FORM NAME="OneTrueForm" ACTION="sqlradius.cgi" METHOD="POST">
+<% #include( '/elements/table.html' ) %>
+<%= ntable('#cccccc') %>
+<TR>
+ <TD ALIGN="right">Username: </TD>
+ <TD><INPUT TYPE="text" NAME="username"></TD>
+</TR>
+<TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1"><I>(leave blank to show all users)</I></FONT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">IP address: </TD>
+ <TD><INPUT TYPE="text" NAME="ip"></TD>
+</TR>
+<TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1"><I>(leave blank to show all IPs)</I></FONT></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">From: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+ <SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+ </SCRIPT>
+</TR>
+<TR>
+ <TD></TD>
+ <TD><i>m/d/y</i></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">To: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor:pointer" TITLE="Select date">
+ </TD>
+ <SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+ </SCRIPT>
+</TR>
+<TR>
+ <TD></TD>
+ <TD><i>m/d/y</i>
+ <BR><FONT SIZE="-1">(leave one or both dates blank for an open-ended search)</FONT>
+ </TD>
+</TR>
+</TABLE>
+<BR><INPUT TYPE="submit" VALUE="View sessions">
+</FORM>
+</BODY>
+</HTML>
+
+
diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi
new file mode 100755
index 0000000..1e4a03d
--- /dev/null
+++ b/httemplate/search/svc_acct.cgi
@@ -0,0 +1,294 @@
+<%
+
+my $conf = new FS::Conf;
+my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+my $orderby = ''; #removeme
+
+my $limit = '';
+$limit .= "LIMIT $maxrecords" if $maxrecords;
+
+my $offset = $cgi->param('offset') || 0;
+$limit .= " OFFSET $offset" if $offset;
+
+my $total;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+
+my $unlinked = '';
+if ( $query =~ /^UN_(.*)$/ ) {
+ $query = $1;
+ my $empty = driver_name eq 'Pg' ? qq('') : qq("");
+ if ( driver_name eq 'mysql' ) {
+ $unlinked = "LEFT JOIN cust_svc ON cust_svc.svcnum = svc_acct.svcnum
+ WHERE cust_svc.pkgnum IS NULL
+ OR cust_svc.pkgnum = 0
+ OR cust_svc.pkgnum = $empty";
+ } else {
+ $unlinked = "
+ WHERE 0 <
+ ( SELECT count(*) FROM cust_svc
+ WHERE cust_svc.svcnum = svc_acct.svcnum
+ AND ( pkgnum IS NULL OR pkgnum = 0 )
+ )
+ ";
+ }
+}
+
+my $tblname = driver_name eq 'mysql' ? 'svc_acct.' : '';
+my(@svc_acct, $sortby);
+if ( $query eq 'svcnum' ) {
+ $sortby=\*svcnum_sort;
+ $orderby = "ORDER BY ${tblname}svcnum";
+} elsif ( $query eq 'username' ) {
+ $sortby=\*username_sort;
+ $orderby = "ORDER BY ${tblname}username";
+} elsif ( $query eq 'uid' ) {
+ $sortby=\*uid_sort;
+ $orderby = ( $unlinked ? ' AND' : ' WHERE' ).
+ " ${tblname}uid IS NOT NULL ORDER BY ${tblname}uid";
+} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) {
+ $unlinked .= ( $unlinked ? 'AND' : 'WHERE' ).
+ " popnum = $1";
+ $sortby=\*username_sort;
+ $orderby = "ORDER BY ${tblname}username";
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ $unlinked .= ( $unlinked ? ' AND' : ' WHERE' ).
+ " $1 = ( SELECT svcpart FROM cust_svc ".
+ " WHERE cust_svc.svcnum = svc_acct.svcnum ) ";
+ $sortby=\*uid_sort;
+ #$sortby=\*svcnum_sort;
+} else {
+ $sortby=\*uid_sort;
+ @svc_acct = @{&usernamesearch};
+}
+
+
+if ( $query eq 'svcnum'
+ || $query eq 'username'
+ || $query eq 'uid'
+ || $cgi->param('popnum') =~ /^(\d+)$/
+ || $cgi->param('svcpart') =~ /^(\d+)$/
+ ) {
+
+ my $statement = "SELECT COUNT(*) FROM svc_acct $unlinked";
+ my $sth = dbh->prepare($statement)
+ or die dbh->errstr. " doing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+
+ $total = $sth->fetchrow_arrayref->[0];
+
+ @svc_acct = qsearch('svc_acct', {}, '', "$unlinked $orderby $limit");
+
+}
+
+if ( scalar(@svc_acct) == 1 ) {
+ my($svcnum)=$svc_acct[0]->svcnum;
+ print $cgi->redirect(popurl(2). "view/svc_acct.cgi?$svcnum"); #redirect
+ #exit;
+} elsif ( scalar(@svc_acct) == 0 ) { #error
+%>
+<!-- mason kludge -->
+<%
+ idiot("Account not found");
+} else {
+%>
+<!-- mason kludge -->
+<%
+ $total ||= scalar(@svc_acct);
+
+ #begin pager
+ my $pager = '';
+ if ( $total != scalar(@svc_acct) && $maxrecords ) {
+ unless ( $offset == 0 ) {
+ $cgi->param('offset', $offset - $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Previous</FONT></B></A> ';
+ }
+ my $poff;
+ my $page;
+ for ( $poff = 0; $poff < $total; $poff += $maxrecords ) {
+ $page++;
+ if ( $offset == $poff ) {
+ $pager .= qq!<FONT SIZE="+2">$page</FONT> !;
+ } else {
+ $cgi->param('offset', $poff);
+ $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !;
+ }
+ }
+ unless ( $offset + $maxrecords > $total ) {
+ $cgi->param('offset', $offset + $maxrecords);
+ $pager .= '<A HREF="'. $cgi->self_url.
+ '"><B><FONT SIZE="+1">Next</FONT></B></A> ';
+ }
+ }
+ #end pager
+
+ print header("Account Search Results",menubar('Main Menu'=>popurl(2))),
+ "$total matching accounts found<BR><BR>$pager",
+ &table(), <<END;
+ <TR>
+ <TH><FONT SIZE=-1>#</FONT></TH>
+ <TH><FONT SIZE=-1>Username</FONT></TH>
+ <TH><FONT SIZE=-1>Domain</FONT></TH>
+ <TH><FONT SIZE=-1>UID</FONT></TH>
+ <TH><FONT SIZE=-1>Service</FONT></TH>
+ <TH><FONT SIZE=-1>Cust#</FONT></TH>
+ <TH><FONT SIZE=-1>(bill) name</FONT></TH>
+ <TH><FONT SIZE=-1>company</FONT></TH>
+END
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ print <<END;
+ <TH><FONT SIZE=-1>(service) name</FONT></TH>
+ <TH><FONT SIZE=-1>company</FONT></TH>
+END
+ }
+ print "</TR>";
+
+ my(%saw,$svc_acct);
+ my $p = popurl(2);
+ foreach $svc_acct (
+ sort $sortby grep(!$saw{$_->svcnum}++, @svc_acct)
+ ) {
+ my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct->svcnum })
+ or die "No cust_svc record for svcnum ". $svc_acct->svcnum;
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $cust_svc->svcpart })
+ or die "No part_svc record for svcpart ". $cust_svc->svcpart;
+
+ my $domain;
+ my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc });
+ if ( $svc_domain ) {
+ $domain = "<A HREF=\"${p}view/svc_domain.cgi?". $svc_domain->svcnum.
+ "\">". $svc_domain->domain. "</A>";
+ } else {
+ die "No svc_domain.svcnum record for svc_acct.domsvc: ".
+ $svc_acct->domsvc;
+ }
+ my($cust_pkg,$cust_main);
+ if ( $cust_svc->pkgnum ) {
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc->pkgnum })
+ or die "No cust_pkg record for pkgnum ". $cust_svc->pkgnum;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg->custnum })
+ or die "No cust_main record for custnum ". $cust_pkg->custnum;
+ }
+ my($svcnum, $username, $uid, $svc, $custnum, $last, $first, $company) = (
+ $svc_acct->svcnum,
+ $svc_acct->getfield('username'),
+ $svc_acct->getfield('uid'),
+ $part_svc->svc,
+ $cust_svc->pkgnum ? $cust_main->custnum : '',
+ $cust_svc->pkgnum ? $cust_main->getfield('last') : '',
+ $cust_svc->pkgnum ? $cust_main->getfield('first') : '',
+ $cust_svc->pkgnum ? $cust_main->company : '',
+ );
+ my($pcustnum) = $custnum
+ ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\"><FONT SIZE=-1>$custnum</FONT></A>"
+ : "<I>(unlinked)</I>"
+ ;
+ my $pname = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$last, $first</A>" : '';
+ my $pcompany = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$company</A>" : '';
+ my($pship_name, $pship_company);
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ my($ship_last, $ship_first, $ship_company) = (
+ $cust_svc->pkgnum ? ( $cust_main->ship_last || $last ) : '',
+ $cust_svc->pkgnum ? ( $cust_main->ship_last
+ ? $cust_main->ship_first
+ : $first
+ ) : '',
+ $cust_svc->pkgnum ? ( $cust_main->ship_last
+ ? $cust_main->ship_company
+ : $company
+ ) : '',
+ );
+ $pship_name = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$ship_last, $ship_first</A>" : '';
+ $pship_company = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$ship_company</A>" : '';
+ }
+ print <<END;
+ <TR>
+ <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$svcnum</FONT></A></TD>
+ <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$username</FONT></A></TD>
+ <TD><FONT SIZE=-1>$domain</FONT></TD>
+ <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$uid</FONT></A></TD>
+ <TD><FONT SIZE=-1>$svc</FONT></TH>
+ <TD><FONT SIZE=-1>$pcustnum</FONT></TH>
+ <TD><FONT SIZE=-1>$pname<FONT></TH>
+ <TD><FONT SIZE=-1>$pcompany</FONT></TH>
+END
+ if ( defined dbdef->table('cust_main')->column('ship_last') ) {
+ print <<END;
+ <TD><FONT SIZE=-1>$pship_name<FONT></TH>
+ <TD><FONT SIZE=-1>$pship_company</FONT></TH>
+END
+ }
+ print "</TR>";
+
+ }
+
+ print "</TABLE>$pager<BR>".
+ '</BODY></HTML>';
+
+}
+
+sub svcnum_sort {
+ $a->getfield('svcnum') <=> $b->getfield('svcnum');
+}
+
+sub username_sort {
+ $a->getfield('username') cmp $b->getfield('username');
+}
+
+sub uid_sort {
+ $a->getfield('uid') <=> $b->getfield('uid');
+}
+
+sub usernamesearch {
+
+ my @svc_acct;
+
+ my %username_type;
+ foreach ( $cgi->param('username_type') ) {
+ $username_type{$_}++;
+ }
+
+ $cgi->param('username') =~ /^([\w\-\.\&]+)$/; #untaint username_text
+ my $username = $1;
+
+ if ( $username_type{'Exact'} || $username_type{'Fuzzy'} ) {
+ push @svc_acct, qsearch( 'svc_acct',
+ { 'username' => { 'op' => 'ILIKE',
+ 'value' => $username } } );
+ }
+
+ if ( $username_type{'Substring'} || $username_type{'All'} ) {
+ push @svc_acct, qsearch( 'svc_acct',
+ { 'username' => { 'op' => 'ILIKE',
+ 'value' => "%$username%" } } );
+ }
+
+ if ( $username_type{'Fuzzy'} || $username_type{'All'} ) {
+ &FS::svc_acct::check_and_rebuild_fuzzyfiles;
+ my $all_username = &FS::svc_acct::all_username;
+
+ my %username;
+ if ( $username_type{'Fuzzy'} || $username_type{'All'} ) {
+ foreach ( amatch($username, [ qw(i) ], @$all_username) ) {
+ $username{$_}++;
+ }
+ }
+
+ #if ($username_type{'Sound-alike'}) {
+ #}
+
+ foreach ( keys %username ) {
+ push @svc_acct, qsearch('svc_acct',{'username'=>$_});
+ }
+
+ }
+
+ #[ qsearch('svc_acct',{'username'=>$username}) ];
+ \@svc_acct;
+
+}
+
+%>
diff --git a/httemplate/search/svc_acct.html b/httemplate/search/svc_acct.html
new file mode 100755
index 0000000..7423605
--- /dev/null
+++ b/httemplate/search/svc_acct.html
@@ -0,0 +1,19 @@
+<HTML>
+ <HEAD>
+ <TITLE>Account Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Account Search
+ </FONT>
+ <BR><BR>
+ <FORM ACTION="svc_acct.cgi" METHOD="post">
+ Search for <B>username</B>:
+ <INPUT TYPE="text" NAME="username">
+
+ <P><INPUT TYPE="submit" VALUE="Search">
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi
new file mode 100755
index 0000000..948b1d9
--- /dev/null
+++ b/httemplate/search/svc_domain.cgi
@@ -0,0 +1,161 @@
+<%
+
+my $conf = new FS::Conf;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+my(@svc_domain,$sortby);
+if ( $query eq 'svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_domain=qsearch('svc_domain',{});
+} elsif ( $query eq 'domain' ) {
+ $sortby=\*domain_sort;
+ @svc_domain=qsearch('svc_domain',{});
+} elsif ( $query eq 'UN_svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_domain = grep qsearchs('cust_svc',{
+ 'svcnum' => $_->svcnum,
+ 'pkgnum' => '',
+ }), qsearch('svc_domain',{});
+} elsif ( $query eq 'UN_domain' ) {
+ $sortby=\*domain_sort;
+ @svc_domain = grep qsearchs('cust_svc',{
+ 'svcnum' => $_->svcnum,
+ 'pkgnum' => '',
+ }), qsearch('svc_domain',{});
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ @svc_domain =
+ qsearch( 'svc_domain', {}, '',
+ " WHERE $1 = ( SELECT svcpart FROM cust_svc ".
+ " WHERE cust_svc.svcnum = svc_domain.svcnum ) "
+ );
+ $sortby=\*svcnum_sort;
+} else {
+ $cgi->param('domain') =~ /^([\w\-\.]+)$/;
+ my($domain)=$1;
+ #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain});
+ @svc_domain = qsearchs('svc_domain',{'domain'=>$domain});
+}
+
+if ( scalar(@svc_domain) == 1 ) {
+ print $cgi->redirect(popurl(2). "view/svc_domain.cgi?". $svc_domain[0]->svcnum);
+ #exit;
+} elsif ( scalar(@svc_domain) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot "No matching domains found!\n";
+} else {
+%>
+<!-- mason kludge -->
+<%
+ my($total)=scalar(@svc_domain);
+ print header("Domain Search Results",''), <<END;
+
+ $total matching domains found
+ <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TH>Service #</TH>
+ <TH>Domain</TH>
+<!-- <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+ <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+-->
+ </TR>
+END
+
+# my(%saw); # if we've multiple domains with the same
+ # svcnum, then we've a corrupt database
+
+ foreach my $svc_domain (
+# sort $sortby grep(!$saw{$_->svcnum}++, @svc_domain)
+ sort $sortby (@svc_domain)
+ ) {
+ my($svcnum,$domain)=(
+ $svc_domain->svcnum,
+ $svc_domain->domain,
+ );
+
+ #don't display all accounts here
+ my $rowspan = 1;
+
+ #my @svc_acct=qsearch('svc_acct',{'domsvc' => $svcnum});
+ #my $rowspan = 0;
+ #
+ #my $n1 = '';
+ #my($svc_acct, @rows);
+ #foreach $svc_acct (
+ # sort {$b->getfield('username') cmp $a->getfield('username')} (@svc_acct)
+ #) {
+ #
+ # my (@forwards) = ();
+ #
+ # my($svcnum,$username)=(
+ # $svc_acct->svcnum,
+ # $svc_acct->username,
+ # );
+ #
+ # my @svc_forward = qsearch( 'svc_forward', { 'srcsvc' => $svcnum } );
+ # my $svc_forward;
+ # foreach $svc_forward (@svc_forward) {
+ # my($dstsvc,$dst) = (
+ # $svc_forward->dstsvc,
+ # $svc_forward->dst,
+ # );
+ # if ($dstsvc) {
+ # my $dst_svc_acct=qsearchs( 'svc_acct', { 'svcnum' => $dstsvc } );
+ # my $destination=$dst_svc_acct->email;
+ # push @forwards, qq!<TD><A HREF="!, popurl(2),
+ # qq!view/svc_acct.cgi?$dstsvc">$destination</A>!,
+ # qq!</TD></TR>!
+ # ;
+ # }else{
+ # push @forwards, qq!<TD>$dst</TD></TR>!
+ # ;
+ # }
+ # }
+ #
+ # push @rows, qq!$n1<TD ROWSPAN=!, (scalar(@svc_forward) || 1),
+ # qq!><A HREF="!. popurl(2). qq!view/svc_acct.cgi?$svcnum">!,
+ # #print '', ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser );
+ # ( ($username eq '*') ? "<I>(anything)</I>" : $username ),
+ # qq!\@$domain</A> </TD>!,
+ # ;
+ #
+ # push @rows, @forwards;
+ #
+ # $rowspan += (scalar(@svc_forward) || 1);
+ # $n1 = "</TR><TR>";
+ #}
+ ##end of false laziness
+ #
+ #
+
+ print <<END;
+ <TR>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_domain.cgi?$svcnum">$svcnum</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_domain.cgi?$svcnum">$domain</A></TD>
+END
+
+ #print @rows;
+ print "</TR>";
+
+ }
+
+ print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+}
+
+sub svcnum_sort {
+ $a->getfield('svcnum') <=> $b->getfield('svcnum');
+}
+
+sub domain_sort {
+ $a->getfield('domain') cmp $b->getfield('domain');
+}
+
+
+%>
diff --git a/httemplate/search/svc_domain.html b/httemplate/search/svc_domain.html
new file mode 100755
index 0000000..94bb9a6
--- /dev/null
+++ b/httemplate/search/svc_domain.html
@@ -0,0 +1,19 @@
+<HTML>
+ <HEAD>
+ <TITLE>Domain Search</TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <FONT SIZE=7>
+ Domain Search
+ </FONT>
+ <BR><BR>
+ <FORM ACTION="svc_domain.cgi" METHOD="post">
+ Search for <B>domain</B>:
+ <INPUT TYPE="text" NAME="domain">
+
+ <P><INPUT TYPE="submit" VALUE="Search">
+
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi
new file mode 100755
index 0000000..c5ac134
--- /dev/null
+++ b/httemplate/search/svc_external.cgi
@@ -0,0 +1,101 @@
+<%
+
+my $conf = new FS::Conf;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+my(@svc_external,$sortby);
+if ( $query eq 'svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_external=qsearch('svc_external',{});
+} elsif ( $query eq 'id' ) {
+ $sortby=\*id_sort;
+ @svc_external=qsearch('svc_external',{});
+} elsif ( $query eq 'UN_svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_external = grep qsearchs('cust_svc',{
+ 'svcnum' => $_->svcnum,
+ 'pkgnum' => '',
+ }), qsearch('svc_external',{});
+} elsif ( $query eq 'UN_id' ) {
+ $sortby=\*id_sort;
+ @svc_external = grep qsearchs('cust_svc',{
+ 'svcnum' => $_->svcnum,
+ 'pkgnum' => '',
+ }), qsearch('svc_external',{});
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ @svc_external =
+ qsearch( 'svc_external', {}, '',
+ " WHERE $1 = ( SELECT svcpart FROM cust_svc ".
+ " WHERE cust_svc.svcnum = svc_external.svcnum ) "
+ );
+ $sortby=\*svcnum_sort;
+} else {
+ $cgi->param('id') =~ /^([\w\-\.]+)$/;
+ my($id)=$1;
+ #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain});
+ @svc_external = qsearchs('svc_external',{'id'=>$id});
+}
+
+if ( scalar(@svc_external) == 1 ) {
+ print $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum);
+ #exit;
+} elsif ( scalar(@svc_external) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot "No matching external services found!\n";
+} else {
+%>
+<!-- mason kludge -->
+<%= header("External Search Results",'') %>
+
+ <%= scalar(@svc_external) %> matching external services found
+ <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TH>Service #</TH>
+ <TH><%= FS::Msgcat::_gettext('svc_external-id') || 'External&nbsp;ID' %></TH>
+ <TH><%= FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH>
+ </TR>
+
+<%
+ foreach my $svc_external (
+ sort $sortby (@svc_external)
+ ) {
+ my($svcnum, $id, $title)=(
+ $svc_external->svcnum,
+ $svc_external->id,
+ $svc_external->title,
+ );
+
+ my $rowspan = 1;
+
+ print <<END;
+ <TR>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD>
+END
+
+ #print @rows;
+ print "</TR>";
+
+ }
+
+ print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+}
+
+sub svcnum_sort {
+ $a->getfield('svcnum') <=> $b->getfield('svcnum');
+}
+
+sub id_sort {
+ $a->getfield('id') <=> $b->getfield('id');
+}
+
+%>
diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi
new file mode 100755
index 0000000..10094bc
--- /dev/null
+++ b/httemplate/search/svc_forward.cgi
@@ -0,0 +1,79 @@
+<%
+
+my $conf = new FS::Conf;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+my(@svc_forward,$sortby);
+if ( $query eq 'svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_forward=qsearch('svc_forward',{});
+} else {
+ eidiot('unimplemented');
+}
+
+if ( scalar(@svc_forward) == 1 ) {
+ print $cgi->redirect(popurl(2). "view/svc_forward.cgi?". $svc_forward[0]->svcnum);
+ #exit;
+} elsif ( scalar(@svc_forward) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot "No matching forwards found!\n";
+} else {
+%>
+<!-- mason kludge -->
+<%
+ my $total = scalar(@svc_forward);
+ print header("Mail forward Search Results",''), <<END;
+
+ $total matching mail forwards found
+ <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TH>Service #<BR><FONT SIZE=-1>(click to view forward)</FONT></TH>
+ <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+ <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+ </TR>
+END
+
+ foreach my $svc_forward (
+ sort $sortby (@svc_forward)
+ ) {
+ my $svcnum = $svc_forward->svcnum;
+
+ my $src = $svc_forward->src;
+ $src = "<I>(anything)</I>$src" if $src =~ /^@/;
+ if ( $svc_forward->srcsvc_acct ) {
+ $src = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->srcsvc. '">'.
+ $svc_forward->srcsvc_acct->email. '</A>';
+ }
+
+ my $dst = $svc_forward->dst;
+ if ( $svc_forward->dstsvc_acct ) {
+ $dst = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->dstsvc. '">'.
+ $svc_forward->dstsvc_acct->email. '</A>';
+ }
+
+ print <<END;
+ <TR>
+ <TD><A HREF="${p}view/svc_forward.cgi?$svcnum">$svcnum</A></TD>
+ <TD>$src</TD>
+ <TD>$dst</TD>
+ </TR>
+END
+
+ }
+
+ print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+}
+
+sub svcnum_sort {
+ $a->getfield('svcnum') <=> $b->getfield('svcnum');
+}
+
+%>
diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi
new file mode 100755
index 0000000..1f05c23
--- /dev/null
+++ b/httemplate/search/svc_www.cgi
@@ -0,0 +1,42 @@
+<%
+
+#my $conf = new FS::Conf;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+my(@svc_www, $orderby);
+if ( $query eq 'svcnum' ) {
+ $orderby = 'ORDER BY svcnum';
+} else {
+ eidiot('unimplemented');
+}
+
+my $count_query = 'SELECT COUNT(*) FROM svc_www';
+my $sql_query = {
+ 'table' => 'svc_www',
+ 'hashref' => {},
+ 'extra_sql' => $orderby,
+};
+
+my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ];
+#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ];
+my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ];
+
+
+%>
+<%= include( 'elements/search.html',
+ 'title' => 'Virtual Host Search Results',
+ 'name' => 'virtual hosts',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'header' => [ '#', 'Zone', 'User', ],
+ 'fields' => [ 'svcnum',
+ sub { $_[0]->domain_record->zone },
+ sub { $_[0]->svc_acct->email },
+ ],
+ 'links' => [ $link,
+ '',
+ $ulink,
+ ],
+ )
+%>
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
new file mode 100755
index 0000000..a72a605
--- /dev/null
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -0,0 +1,18 @@
+<%
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
+my $templatename = $2;
+my $invnum = $3;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+my $pdf = $cust_bill->print_pdf( '', $templatename);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+%>
+<%= $pdf %>
diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
new file mode 100755
index 0000000..8485a15
--- /dev/null
+++ b/httemplate/view/cust_bill-ps.cgi
@@ -0,0 +1,14 @@
+<%
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2;
+my $invnum = $3;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+http_header('Content-Type' => 'application/postscript' );
+%>
+<%= $cust_bill->print_ps( '', $templatename) %>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
new file mode 100755
index 0000000..34f5331
--- /dev/null
+++ b/httemplate/view/cust_bill.cgi
@@ -0,0 +1,82 @@
+<!-- mason kludge -->
+<%
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2;
+my $invnum = $3;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Invoice #$invnum not found!" unless $cust_bill;
+my $custnum = $cust_bill->getfield('custnum');
+
+#my $printed = $cust_bill->printed;
+
+print header('Invoice View', menubar(
+ "Main Menu" => $p,
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+));
+
+print qq!<A HREF="${p}edit/cust_pay.cgi?$invnum">Enter payments (check/cash) against this invoice</A> | !
+ if $cust_bill->owed > 0;
+
+print qq!<A HREF="${p}misc/print-invoice.cgi?$invnum">Reprint this invoice</A>!;
+if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ print qq! | <A HREF="${p}misc/email-invoice.cgi?$invnum">!.
+ qq!Re-email this invoice</A>!;
+}
+
+print '<BR><BR>';
+
+my $conf = new FS::Conf;
+if ( $conf->exists('invoice_latex') ) {
+ my $link = "${p}view/cust_bill-pdf.cgi?";
+ $link .= "$templatename-" if $templatename;
+ $link .= "$invnum.pdf";
+ print menubar(
+ 'View typeset invoice' => $link,
+ ), '<BR><BR>';
+}
+
+#false laziness with search/cust_bill_event.cgi
+
+unless ( $templatename ) {
+ print table(). '<TR><TH>Event</TH><TH>Date</TH><TH>Status</TH></TR>';
+ foreach my $cust_bill_event (
+ sort { $a->_date <=> $b->_date } $cust_bill->cust_bill_event
+ ) {
+ my $status = $cust_bill_event->status;
+ $status .= ': '. $cust_bill_event->statustext
+ if $cust_bill_event->statustext;
+ my $part_bill_event = $cust_bill_event->part_bill_event;
+ print '<TR><TD>'. $part_bill_event->event;
+
+ if (
+ $part_bill_event->plan eq 'send_alternate'
+ && $part_bill_event->plandata =~ /^templatename (.*)$/m
+ ) {
+ my $templatename = $1;
+ print qq! ( <A HREF="${p}view/cust_bill.cgi?$templatename-$invnum">!.
+ 'view text</A> | '.
+ qq!<A HREF="${p}view/cust_bill-pdf.cgi?$templatename-$invnum.pdf">!.
+ 'view typeset</A> )';
+ }
+
+ print '</TD><TD>'.
+ time2str("%a %b %e %T %Y", $cust_bill_event->_date). '</TD><TD>'.
+ $status. '</TD></TR>';
+ }
+ print '</TABLE><BR>';
+}
+
+print '<PRE>', $cust_bill->print_text('', $templatename);
+
+ #formatting
+ print <<END;
+ </PRE></FONT>
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
new file mode 100755
index 0000000..0b51a87
--- /dev/null
+++ b/httemplate/view/cust_main.cgi
@@ -0,0 +1,1064 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my %uiview = ();
+my %uiadd = ();
+foreach my $part_svc ( qsearch('part_svc',{}) ) {
+ $uiview{$part_svc->svcpart} = popurl(2). "view/". $part_svc->svcdb . ".cgi";
+ $uiadd{$part_svc->svcpart}= popurl(2). "edit/". $part_svc->svcdb . ".cgi";
+}
+
+print header("Customer View", menubar(
+ 'Main Menu' => popurl(2)
+));
+
+%>
+
+<STYLE TYPE="text/css">
+.package TH { font-size: medium }
+.package TR { font-size: smaller }
+.package .provision { font-weight: bold }
+</STYLE>
+
+<%
+
+die "No customer specified (bad URL)!" unless $cgi->keywords;
+my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+$query =~ /^(\d+)$/;
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
+die "Customer not found!" unless $cust_main;
+
+print qq!<A HREF="${p}edit/cust_main.cgi?$custnum">Edit this customer</A>!;
+
+%>
+
+<SCRIPT>
+function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+<%
+
+print qq! | <A HREF="javascript:areyousure('${p}misc/cust_main-cancel.cgi?$custnum', 'Perminantly delete all services and cancel this customer?')">!.
+ 'Cancel this customer</A>'
+ if $cust_main->ncancelled_pkgs;
+
+print qq! | <A HREF="${p}misc/delete-customer.cgi?$custnum">!.
+ 'Delete this customer</A>'
+ if $conf->exists('deletecustomers');
+
+unless ( $conf->exists('disable_customer_referrals') ) {
+ print qq! | <A HREF="!, popurl(2),
+ qq!edit/cust_main.cgi?referral_custnum=$custnum">!,
+ qq!Refer a new customer</A>!;
+
+ print qq! | <A HREF="!, popurl(2),
+ qq!search/cust_main.cgi?referral_custnum=$custnum">!,
+ qq!View this customer's referrals</A>!;
+}
+
+print '<BR><BR>';
+
+my $signupurl = $conf->config('signupurl');
+if ( $signupurl ) {
+print "This customer's signup URL: ".
+ "<a href=\"$signupurl?ref=$custnum\">$signupurl?ref=$custnum</a><BR><BR>";
+}
+
+print '<A NAME="cust_main"></A>';
+
+print &itable(), '<TR>';
+
+print '<TD VALIGN="top">';
+
+ print "Billing address", &ntable("#cccccc"), "<TR><TD>",
+ &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Contact&nbsp;name</TD>',
+ '<TD COLSPAN=3 BGCOLOR="#ffffff">',
+ $cust_main->last, ', ', $cust_main->first,
+ '</TD>';
+print '<TD ALIGN="right">SS#</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->ss || '&nbsp', '</TD>'
+ if $conf->exists('show_ss');
+
+print '</TR>',
+ '<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->company,
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Address</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->address1,
+ '</TD></TR>',
+ ;
+ print '<TR><TD ALIGN="right">&nbsp;</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->address2, '</TD></TR>'
+ if $cust_main->address2;
+ print '<TR><TD ALIGN="right">City</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->city,
+ '</TD><TD ALIGN="right">State</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->state,
+ '</TD><TD ALIGN="right">Zip</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->zip, '</TD></TR>',
+ '<TR><TD ALIGN="right">Country</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->country,
+ '</TD></TR>',
+ ;
+ my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day&nbsp;Phone';
+ my $night_label = FS::Msgcat::_gettext('night') || 'Night&nbsp;Phone';
+ print '<TR><TD ALIGN="right">'. $daytime_label.
+ '</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->daytime || '&nbsp', '</TD></TR>',
+ '<TR><TD ALIGN="right">'. $night_label.
+ '</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->night || '&nbsp', '</TD></TR>',
+ '<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->fax || '&nbsp', '</TD></TR>',
+ '</TABLE>', "</TD></TR></TABLE>"
+ ;
+
+ if ( defined $cust_main->dbdef_table->column('ship_last') ) {
+
+ my $pre = $cust_main->ship_last ? 'ship_' : '';
+
+ print "<BR>Service address", &ntable("#cccccc"), "<TR><TD>",
+ &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Contact name</TD>',
+ '<TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}last"), ', ', $cust_main->get("${pre}first"),
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}company"),
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Address</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}address1"),
+ '</TD></TR>',
+ ;
+ print '<TR><TD ALIGN="right">&nbsp;</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}address2"), '</TD></TR>'
+ if $cust_main->get("${pre}address2");
+ print '<TR><TD ALIGN="right">City</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}city"),
+ '</TD><TD ALIGN="right">State</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}state"),
+ '</TD><TD ALIGN="right">Zip</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}zip"), '</TD></TR>',
+ '<TR><TD ALIGN="right">Country</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}country"),
+ '</TD></TR>',
+ ;
+ print '<TR><TD ALIGN="right">'. $daytime_label. '</TD>',
+ '<TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}daytime") || '&nbsp', '</TD></TR>',
+ '<TR><TD ALIGN="right">'. $night_label. '</TD>'.
+ '<TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}night") || '&nbsp', '</TD></TR>',
+ '<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ $cust_main->get("${pre}fax") || '&nbsp', '</TD></TR>',
+ '</TABLE>', "</TD></TR></TABLE>"
+ ;
+
+ }
+
+print '</TD>';
+
+print '<TD VALIGN="top">';
+
+ print &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Customer&nbsp;number</TD><TD BGCOLOR="#ffffff">',
+ $custnum, '</TD></TR>',
+ ;
+
+ my @agents = qsearch( 'agent', {} );
+ my $agent;
+ unless ( scalar(@agents) == 1 ) {
+ $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } );
+ print '<TR><TD ALIGN="right">Agent</TD><TD BGCOLOR="#ffffff">',
+ $agent->agentnum, ": ", $agent->agent, '</TD></TR>';
+ } else {
+ $agent = $agents[0];
+ }
+ my @referrals = qsearch( 'part_referral', {} );
+ unless ( scalar(@referrals) == 1 ) {
+ my $referral = qsearchs('part_referral', {
+ 'refnum' => $cust_main->refnum
+ } );
+ print '<TR><TD ALIGN="right">Advertising&nbsp;source</TD><TD BGCOLOR="#ffffff">',
+ $referral->refnum, ": ", $referral->referral, '</TD></TR>';
+ }
+ print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->otaker, '</TD></TR>';
+
+ print '<TR><TD ALIGN="right">Referring&nbsp;Customer</TD><TD BGCOLOR="#ffffff">';
+ my $referring_cust_main = '';
+ if ( $cust_main->referral_custnum
+ && ( $referring_cust_main =
+ qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+ )
+ ) {
+ print '<A HREF="'. popurl(1). 'cust_main.cgi?'.
+ $cust_main->referral_custnum. '">'.
+ $cust_main->referral_custnum. ': '.
+ ( $referring_cust_main->company
+ ? $referring_cust_main->company. ' ('.
+ $referring_cust_main->last. ', '. $referring_cust_main->first.
+ ')'
+ : $referring_cust_main->last. ', '. $referring_cust_main->first
+ ).
+ '</A>';
+ }
+ print '</TD></TR>';
+
+ print '</TABLE></TD></TR></TABLE>';
+
+print '<BR>';
+
+if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ my @invoicing_list = $cust_main->invoicing_list;
+ print "Billing information (",
+ qq!<A HREF="!, popurl(2), qq!misc/bill.cgi?$custnum">!, "Bill now</A>)",
+ &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Tax&nbsp;exempt</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->tax ? 'yes' : 'no',
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Postal&nbsp;invoices</TD><TD BGCOLOR="#ffffff">',
+ ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no',
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Email&nbsp;invoices</TD><TD BGCOLOR="#ffffff">',
+ join(', ', grep { $_ ne 'POST' } @invoicing_list ) || 'no',
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Billing&nbsp;type</TD><TD BGCOLOR="#ffffff">',
+ ;
+
+ if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) {
+ my $payinfo = $cust_main->payinfo_masked;
+ print 'Credit&nbsp;card&nbsp;',
+ ( $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' ),
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Card number</TD><TD BGCOLOR="#ffffff">',
+ $payinfo, '</TD></TR>',
+ '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->paydate, '</TD></TR>',
+ '<TR><TD ALIGN="right">Name on card</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payname, '</TD></TR>'
+ ;
+ } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') {
+ my( $account, $aba ) = split('@', $cust_main->payinfo );
+ print 'Electronic&nbsp;check&nbsp;',
+ ( $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' ),
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Account number</TD><TD BGCOLOR="#ffffff">',
+ $account, '</TD></TR>',
+ '<TR><TD ALIGN="right">ABA/Routing code</TD><TD BGCOLOR="#ffffff">',
+ $aba, '</TD></TR>',
+ '<TR><TD ALIGN="right">Bank name</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payname, '</TD></TR>'
+ ;
+ } elsif ( $cust_main->payby eq 'LECB' ) {
+ $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/;
+ my $payinfo = "$1-$2-$3";
+ print 'Phone&nbsp;bill&nbsp;billing</TD></TR>',
+ '<TR><TD ALIGN="right">Phone number</TD><TD BGCOLOR="#ffffff">',
+ $payinfo, '</TD></TR>',
+ ;
+ } elsif ( $cust_main->payby eq 'BILL' ) {
+ print 'Billing</TD></TR>';
+ print '<TR><TD ALIGN="right">P.O. </TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payinfo, '</TD></TR>',
+ if $cust_main->payinfo;
+ print '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->paydate, '</TD></TR>',
+ '<TR><TD ALIGN="right">Attention</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payname, '</TD></TR>',
+ ;
+ } elsif ( $cust_main->payby eq 'COMP' ) {
+ print 'Complimentary</TD></TR>',
+ '<TR><TD ALIGN="right">Authorized&nbsp;by</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payinfo, '</TD></TR>',
+ '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->paydate, '</TD></TR>',
+ ;
+ }
+
+ print "</TABLE></TD></TR></TABLE>";
+
+}
+
+print '</TD></TR></TABLE>';
+
+if ( defined $cust_main->dbdef_table->column('comments')
+ && $cust_main->comments =~ /[^\s\n\r]/ )
+{
+ print "<BR>Comments". &ntable("#cccccc"). "<TR><TD>".
+ &ntable("#cccccc",2).
+ '<TR><TD BGCOLOR="#ffffff"><PRE>'.
+ encode_entities($cust_main->comments).
+ '</PRE></TD></TR></TABLE></TABLE>';
+}
+
+%>
+
+</TD></TR></TABLE>
+
+<BR>
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+ if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) {
+ document.OrderPkgForm.submit.disabled = false;
+ } else {
+ document.OrderPkgForm.submit.disabled = true;
+ }
+}
+</SCRIPT>
+<FORM NAME="OrderPkgForm" ACTION="<%= $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>">
+<SELECT NAME="pkgpart" onChange="enable_order_pkg()"><OPTION>Order additional package
+
+<%
+foreach my $part_pkg (
+ qsearch( 'part_pkg', { 'disabled' => '' }, '',
+ ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '.
+ ' WHERE typenum = '. $agent->typenum.
+ ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'
+ )
+) {
+%>
+<OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg %> - <%= $part_pkg->comment %>
+<% } %>
+
+</SELECT><INPUT NAME="submit" TYPE="submit" VALUE="Order Package" disabled></FORM><BR>
+
+<%
+
+if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ print
+ qq!<FORM ACTION="${p}edit/process/quick-charge.cgi" METHOD="POST">!.
+ qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
+ qq!Description:<INPUT TYPE="text" NAME="pkg">!.
+ qq!&nbsp;Amount:<INPUT TYPE="text" NAME="amount" SIZE=6>!.
+ qq!&nbsp;!;
+
+ #false laziness w/ edit/part_pkg.cgi
+ if ( $conf->exists('enable_taxclasses') ) {
+ print '<SELECT NAME="taxclass">';
+ my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ foreach my $taxclass ( map $_->[0], @{$sth->fetchall_arrayref} ) {
+ print qq!<OPTION VALUE="$taxclass"!;
+ #print ' SELECTED' if $taxclass eq $hashref->{taxclass};
+ print qq!>$taxclass</OPTION>!;
+ }
+ print '</SELECT>';
+ } else {
+ print '<INPUT TYPE="hidden" NAME="taxclass" VALUE="">';
+ }
+
+ print qq!<INPUT TYPE="submit" VALUE="One-time charge"></FORM><BR>!;
+
+}
+
+print qq!<A NAME="cust_pkg">Packages</A> !,
+ qq!( <A HREF="!, popurl(2), qq!edit/cust_pkg.cgi?$custnum">Order and cancel packages</A> (preserves services) )!,
+;
+
+#begin display packages
+
+#get package info
+
+my $packages = get_packages($cust_main, $conf);
+
+if ( @$packages ) {
+%>
+<TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<TR>
+ <TH>Package</TH>
+ <TH>Status</TH>
+ <TH COLSPAN=2>Services</TH>
+</TR>
+<%
+foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
+ my $rowspan = 0;
+
+ if ($pkg->{cancel}) {
+ $rowspan = 0;
+ } else {
+ foreach my $svcpart (@{$pkg->{svcparts}}) {
+ $rowspan += $svcpart->{count};
+ $rowspan++ if ($svcpart->{count} < $svcpart->{quantity});
+ }
+ }
+
+%>
+<!--pkgnum: <%=$pkg->{pkgnum}%>-->
+<TR>
+ <TD ROWSPAN=<%=$rowspan%>>
+ <A NAME="cust_pkg<%=$pkg->{pkgnum}%>"><%=$pkg->{pkgnum}%></A>:
+ <%=$pkg->{pkg}%> - <%=$pkg->{comment}%><BR>
+<% unless ($pkg->{cancel}) { %>
+ (&nbsp;<%=pkg_change_link($pkg)%>&nbsp;)
+ (&nbsp;<%=pkg_dates_link($pkg)%>&nbsp;|&nbsp;<%=pkg_customize_link($pkg,$custnum)%>&nbsp;)
+<% } %>
+ </TD>
+<%
+ #foreach (qw(setup last_bill next_bill susp expire cancel)) {
+ # print qq! <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_,$conf) . qq!</TD>\n!;
+ #}
+ print "<TD ROWSPAN=$rowspan>". &itable('');
+
+ sub freq {
+
+ #false laziness w/edit/part_pkg.cgi
+ my %freq = ( #move this
+ '1d' => 'daily',
+ '1w' => 'weekly',
+ '2w' => 'biweekly (every 2 weeks)',
+ '1' => 'monthly',
+ '2' => 'bimonthly (every 2 months)',
+ '3' => 'quarterly (every 3 months)',
+ '6' => 'semiannually (every 6 months)',
+ '12' => 'annually',
+ '24' => 'biannually (every 2 years)',
+ );
+
+ my $freq = shift;
+ exists $freq{$freq} ? $freq{$freq} : "every&nbsp;$freq&nbsp;months";
+ }
+
+ #eomove
+
+ if ( $pkg->{cancel} ) { #status: cancelled
+
+ print '<TR><TD><FONT COLOR="#ff0000"><B>Cancelled&nbsp;</B></FONT></TD>'.
+ '<TD>'. pkg_datestr($pkg,'cancel',$conf). '</TD></TR>';
+ unless ( $pkg->{setup} ) {
+ print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
+ } else {
+ print "<TR><TD>Setup&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ print "<TR><TD>Suspended&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'susp',$conf). '</TD></TR>'
+ if $pkg->{'susp'};
+ }
+
+ } else {
+
+ if ( $pkg->{susp} ) { #status: suspended
+ print '<TR><TD><FONT COLOR="#FF9900"><B>Suspended</B>&nbsp;</FONT></TD>'.
+ '<TD>'. pkg_datestr($pkg,'susp',$conf). '</TD></TR>';
+ unless ( $pkg->{setup} ) {
+ print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
+ } else {
+ print "<TR><TD>Setup&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ }
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ # next bill ??
+ print "<TR><TD>Expires&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
+ if $pkg->{'expire'};
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_unsuspend_link($pkg).
+ '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
+
+ } else { #status: active
+
+ unless ( $pkg->{setup} ) { #not setup
+
+ print '<TR><TD COLSPAN=2>Not&nbsp;yet&nbsp;billed&nbsp;(';
+ unless ( $pkg->{freq} ) {
+ print 'one-time&nbsp;charge)</TD></TR>';
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_cancel_link($pkg).
+ '&nbsp;)</TD</TR>';
+ } else {
+ print 'billed&nbsp;'. freq($pkg->{freq}). ')</TD></TR>';
+ }
+
+ } else { #setup
+
+ unless ( $pkg->{freq} ) {
+ print "<TR><TD COLSPAN=2>One-time&nbsp;charge</TD></TR>".
+ '<TR><TD>Billed&nbsp;</TD><TD>'.
+ pkg_datestr($pkg,'setup',$conf). '</TD></TR>';
+ } else {
+ print '<TR><TD COLSPAN=2><FONT COLOR="#00CC00"><B>Active</B></FONT>'.
+ ',&nbsp;billed&nbsp;'. freq($pkg->{freq}). '</TD></TR>'.
+ '<TR><TD>Setup&nbsp;</TD><TD>'.
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ }
+
+ }
+
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ print "<TR><TD>Next&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'next_bill',$conf). '</TD></TR>'
+ if $pkg->{'next_bill'};
+ print "<TR><TD>Expires&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
+ if $pkg->{'expire'};
+ if ( $pkg->{freq} ) {
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_suspend_link($pkg).
+ '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
+ }
+
+ }
+
+ }
+
+ print "</TABLE></TD>\n";
+
+ if ($rowspan == 0) { print qq!</TR>\n!; next; }
+
+ my $cnt = 0;
+ foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
+ foreach my $service (@{$svcpart->{services}}) {
+ print '<TR>' if ($cnt > 0);
+%>
+ <TD><%=svc_link($svcpart,$service)%></TD>
+ <TD><%=svc_label_link($svcpart,$service)%><BR>(&nbsp;<%=svc_unprovision_link($service)%>&nbsp;)</TD>
+</TR>
+<%
+ $cnt++;
+ }
+ if ($svcpart->{count} < $svcpart->{quantity}) {
+ print qq!<TR>\n! if ($cnt > 0);
+ print qq! <TD COLSPAN=2>!.svc_provision_link($pkg, $svcpart, $conf).qq!</TD>\n</TR>\n!;
+ }
+ }
+}
+print '</TABLE>';
+}
+
+#end display packages
+%>
+
+<% if ( $conf->config('payby-default') ne 'HIDE' ) { %>
+
+ <BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
+ <A HREF="<%= $p %>edit/cust_pay.cgi?custnum=<%= $custnum %>">Post cash/check payment</A>
+ | <A HREF="<%= $p %>misc/payment.cgi?payby=CARD;custnum=<%= $custnum %>">Process credit card payment</A>
+ | <A HREF="<%= $p %>misc/payment.cgi?payby=CHEK;custnum=<%= $custnum %>">Process electronic check (ACH) payment</A>
+ <BR><A HREF="<%= $p %>edit/cust_credit.cgi?<%= $custnum %>">Post credit</A>
+ <BR>
+
+ <%
+ #get payment history
+ my @history = ();
+
+ #invoices
+ foreach my $cust_bill ($cust_main->cust_bill) {
+ my $pre = ( $cust_bill->owed > 0 )
+ ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open '
+ : '';
+ my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : '';
+ my $invnum = $cust_bill->invnum;
+ push @history, {
+ 'date' => $cust_bill->_date,
+ 'desc' => qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!. $pre.
+ "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'.
+ $post. '</A>',
+ 'charge' => $cust_bill->charged,
+ };
+ }
+
+ #payments (some false laziness w/credits)
+ foreach my $cust_pay ($cust_main->cust_pay) {
+
+ my $payby = $cust_pay->payby;
+ my $payinfo = $payby eq 'CARD'
+ ? $cust_pay->payinfo_masked
+ : $cust_pay->payinfo;
+ my @cust_bill_pay = $cust_pay->cust_bill_pay;
+ my @cust_pay_refund = $cust_pay->cust_pay_refund;
+
+ my $target = "$payby$payinfo";
+ $payby =~ s/^BILL$/Check #/ if $payinfo;
+ $payby =~ s/^CHEK$/Electronic check /;
+ $payby =~ s/^BILL$//;
+ $payby =~ s/^(CARD|COMP)$/$1 /;
+ my $info = $payby ? " ($payby$payinfo)" : '';
+
+ my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+ if ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ $apply = qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!.
+ $cust_pay->paynum. '">apply</A>)';
+ } elsif ( scalar(@cust_bill_pay) == 1
+ && scalar(@cust_pay_refund) == 0
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum;
+ } elsif ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 1
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one refund
+ $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
+ } else {
+ #complicated
+ $desc = '<BR>';
+ foreach my $app ( sort { $a->_date <=> $b->_date }
+ ( @cust_bill_pay, @cust_pay_refund ) ) {
+ if ( $app->isa('FS::cust_bill_pay') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' applied to Invoice #'. $app->invnum.
+ '<BR>';
+ #' on '. time2str("%D", $cust_bill_pay->_date).
+ } elsif ( $app->isa('FS::cust_pay_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on'. time2str("%D", $app->_date).
+ '<BR>';
+ } else {
+ die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
+ }
+ }
+ if ( $cust_pay->unapplied > 0 ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '<B><FONT COLOR="#FF0000">$'.
+ $cust_pay->unapplied. ' unapplied</FONT></B>'.
+ qq! (<A HREF="${p}edit/cust_bill_pay.cgi?!.
+ $cust_pay->paynum. '">apply</A>)'.
+ '<BR>';
+ }
+ }
+
+ my $refund = '';
+ my $refund_days = $conf->config('card_refund-days') || 120;
+ if ( $cust_pay->closed !~ /^Y/i
+ && $cust_pay->payby =~ /^(CARD|CHEK)$/
+ && time-$cust_pay->_date < $refund_days*86400
+ && $cust_pay->unrefunded > 0
+ ) {
+ $refund = qq! (<A HREF="!. qq!${p}edit/cust_refund.cgi?payby=$1;!.
+ qq!paynum=!. $cust_pay->paynum. qq!">refund</A>)!;
+ }
+
+ my $void = '';
+ if ( $cust_pay->closed !~ /^Y/i
+ && $cust_pay->payby !~ /^(CARD|CHEK)$/
+ ) {
+ $void = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to void this payment?')">!.
+ qq!void</A>)!;
+ }
+
+ my $delete = '';
+ if ( $cust_pay->closed !~ /^Y/i && $conf->exists('deletepayments') ) {
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to delete this payment?')">!.
+ qq!delete</A>)!;
+ }
+
+ my $unapply = '';
+ if ( $cust_pay->closed !~ /^Y/i
+ && $conf->exists('unapplypayments')
+ && scalar(@cust_bill_pay) ) {
+ $unapply = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to unapply this payment?')">!.
+ qq!unapply</A>)!;
+ }
+
+ push @history, {
+ 'date' => $cust_pay->_date,
+ 'desc' => $pre. "Payment$post$info$desc".
+ "$apply$refund$void$delete$unapply",
+ 'payment' => $cust_pay->paid,
+ 'target' => $target,
+ };
+ }
+
+ #voided payments
+ foreach my $cust_pay_void ($cust_main->cust_pay_void) {
+
+ my $payby = $cust_pay_void->payby;
+ my $payinfo = $payby eq 'CARD'
+ ? $cust_pay_void->payinfo_masked
+ : $cust_pay_void->payinfo;
+
+ $payby =~ s/^BILL$/Check #/ if $payinfo;
+ $payby =~ s/^CHEK$/Electronic check /;
+ $payby =~ s/^BILL$//;
+ $payby =~ s/^(CARD|COMP)$/$1 /;
+ my $info = $payby ? " ($payby$payinfo)" : '';
+
+ push @history, {
+ 'date' => $cust_pay_void->_date,
+ 'desc' => "<DEL>Payment $info</DEL> <I>voided ".
+ time2str("%D", $cust_pay_void->void_date).
+ " by ". $cust_pay_void->otaker. '</i>',
+ 'void_payment' => $cust_pay_void->paid,
+ };
+
+ }
+
+ #credits (some false laziness w/payments)
+ foreach my $cust_credit ($cust_main->cust_credit) {
+
+ my @cust_credit_bill = $cust_credit->cust_credit_bill;
+ my @cust_credit_refund = $cust_credit->cust_credit_refund;
+
+ my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+ if ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ $apply = qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!.
+ $cust_credit->crednum. '">apply</A>)';
+ } elsif ( scalar(@cust_credit_bill) == 1
+ && scalar(@cust_credit_refund) == 0
+ && $cust_credit->credited == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum;
+ } elsif ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 1
+ && $cust_credit->credited == 0 ) {
+ #applied to one refund
+ $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date);
+ } else {
+ #complicated
+ $desc = '<BR>';
+ foreach my $app ( sort { $a->_date <=> $b->_date }
+ ( @cust_credit_bill, @cust_credit_refund ) ) {
+ if ( $app->isa('FS::cust_credit_bill') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' applied to Invoice #'. $app->invnum.
+ '<BR>';
+ #' on '. time2str("%D", $app->_date).
+ } elsif ( $app->isa('FS::cust_credit_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on'. time2str("%D", $app->_date).
+ '<BR>';
+ } else {
+ die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
+ }
+ }
+ if ( $cust_credit->credited > 0 ) {
+ $desc .= '&nbsp;&nbsp;<B><FONT COLOR="#FF0000">$'.
+ $cust_credit->credited. ' unapplied</FONT></B>'.
+ qq! (<A HREF="${p}edit/cust_credit_bill.cgi?!.
+ $cust_credit->crednum. '">apply</A>)'.
+ '<BR>';
+ }
+ }
+#
+ my $delete = '';
+ if ( $cust_credit->closed !~ /^Y/i && $conf->exists('deletecredits') ) {
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum.
+ qq!', 'Are you sure you want to delete this credit?')">!.
+ qq!delete</A>)!;
+ }
+
+ my $unapply = '';
+ if ( $cust_credit->closed !~ /^Y/i
+ && $conf->exists('unapplycredits')
+ && scalar(@cust_credit_bill) ) {
+ $unapply = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum.
+ qq!', 'Are you sure you want to unapply this credit?')">!.
+ qq!unapply</A>)!;
+ }
+
+ push @history, {
+ 'date' => $cust_credit->_date,
+ 'desc' => $pre. "Credit$post by ". $cust_credit->otaker.
+ ' ('. $cust_credit->reason. ')'.
+ "$desc$apply$delete$unapply",
+ 'credit' => $cust_credit->amount,
+ };
+
+ }
+
+ #refunds
+ foreach my $cust_refund ($cust_main->cust_refund) {
+
+ my $payby = $cust_refund->payby;
+ my $payinfo = $payby eq 'CARD'
+ ? $cust_refund->payinfo_masked
+ : $cust_refund->payinfo;
+
+ $payby =~ s/^BILL$/Check #/ if $payinfo;
+ $payby =~ s/^CHEK$/Electronic check /;
+ $payby =~ s/^(CARD|COMP)$/$1 /;
+
+ push @history, {
+ 'date' => $cust_refund->_date,
+ 'desc' => "Refund ($payby$payinfo) by ". $cust_refund->otaker,
+ 'refund' => $cust_refund->refund,
+ };
+
+ }
+
+ %>
+
+ <%= table() %>
+ <TR>
+ <TH>Date</TH>
+ <TH>Description</TH>
+ <TH><FONT SIZE=-1>Charge</FONT></TH>
+ <TH><FONT SIZE=-1>Payment</FONT></TH>
+ <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
+ <TH><FONT SIZE=-1>Refund</FONT></TH>
+ <TH><FONT SIZE=-1>Balance</FONT></TH>
+ </TR>
+
+ <%
+ #display payment history
+
+ my %target;
+ my $balance = 0;
+ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
+
+ my $charge = exists($item->{'charge'})
+ ? sprintf('$%.2f', $item->{'charge'})
+ : '';
+ my $payment = exists($item->{'payment'})
+ ? sprintf('-&nbsp;$%.2f', $item->{'payment'})
+ : '';
+ $payment ||= sprintf('<DEL>-&nbsp;$%.2f</DEL>', $item->{'void_payment'})
+ if exists($item->{'void_payment'});
+ my $credit = exists($item->{'credit'})
+ ? sprintf('-&nbsp;$%.2f', $item->{'credit'})
+ : '';
+ my $refund = exists($item->{'refund'})
+ ? sprintf('$%.2f', $item->{'refund'})
+ : '';
+
+ my $target = exists($item->{'target'}) ? $item->{'target'} : '';
+
+ $balance += $item->{'charge'} if exists $item->{'charge'};
+ $balance -= $item->{'payment'} if exists $item->{'payment'};
+ $balance -= $item->{'credit'} if exists $item->{'credit'};
+ $balance += $item->{'refund'} if exists $item->{'refund'};
+ $balance = sprintf("%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ ( my $showbalance = '$'. $balance ) =~ s/^\$\-/-&nbsp;\$/;
+
+ %>
+
+ <TR>
+ <TD>
+ <% unless ( !$target || $target{$target}++ ) { %>
+ <A NAME="<%= $target %>">
+ <% } %>
+ <%= time2str("%D",$item->{'date'}) %>
+ <% if ( $target && $target{$target} == 1 ) { %>
+ </A>
+ <% } %>
+ </FONT>
+ </TD>
+ <TD><%= $item->{'desc'} %></TD>
+ <TD ALIGN="right"><%= $charge %></TD>
+ <TD ALIGN="right"><%= $payment %></TD>
+ <TD ALIGN="right"><%= $credit %></TD>
+ <TD ALIGN="right"><%= $refund %></TD>
+ <TD ALIGN="right"><%= $showbalance %></TD>
+ </TR>
+
+ <% } %>
+
+ </TABLE>
+
+<% } %>
+
+</BODY></HTML>
+
+<%
+#subroutines
+
+sub get_packages {
+ my $cust_main = shift or return undef;
+ my $conf = shift;
+
+ my @packages = ();
+
+ foreach my $cust_pkg (
+ $conf->exists('hidecancelledpackages')
+ ? $cust_main->ncancelled_pkgs
+ : $cust_main->all_pkgs
+ ) {
+
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ my %pkg = ();
+ $pkg{pkgnum} = $cust_pkg->pkgnum;
+ $pkg{pkg} = $part_pkg->pkg;
+ $pkg{pkgpart} = $part_pkg->pkgpart;
+ $pkg{comment} = $part_pkg->getfield('comment');
+ $pkg{freq} = $part_pkg->freq;
+ $pkg{setup} = $cust_pkg->getfield('setup');
+ $pkg{last_bill} = $cust_pkg->getfield('last_bill');
+ $pkg{next_bill} = $cust_pkg->getfield('bill');
+ $pkg{susp} = $cust_pkg->getfield('susp');
+ $pkg{expire} = $cust_pkg->getfield('expire');
+ $pkg{cancel} = $cust_pkg->getfield('cancel');
+
+ my %svcparts = map {
+ $_->svcpart => {
+ $_->part_svc->hash,
+ 'quantity' => $_->quantity,
+ 'count' => $cust_pkg->num_cust_svc($_->svcpart),
+ #'services' => [],
+ };
+ } $part_pkg->pkg_svc;
+
+ foreach my $cust_svc ( $cust_pkg->cust_svc ) {
+ #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n";
+ my $svc = {
+ 'svcnum' => $cust_svc->svcnum,
+ 'label' => ($cust_svc->label)[1],
+ };
+
+ #false laziness with above, to catch extraneous services. whole
+ #damn thing should be OO...
+ my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= {
+ $cust_svc->part_svc->hash,
+ 'quantity' => 0,
+ 'count' => $cust_pkg->num_cust_svc($cust_svc->svcpart),
+ #'services' => [],
+ } );
+
+ push @{$svcpart->{services}}, $svc;
+
+ }
+
+ $pkg{svcparts} = [ values %svcparts ];
+
+ push @packages, \%pkg;
+
+ }
+
+ return \@packages;
+
+}
+
+sub svc_link {
+
+ my ($svcpart, $svc) = (shift,shift) or return '';
+ return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!;
+
+}
+
+sub svc_label_link {
+
+ my ($svcpart, $svc) = (shift,shift) or return '';
+ return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!;
+
+}
+
+sub svc_provision_link {
+ my ($pkg, $svcpart, $conf) = @_;
+ ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/&nbsp;/g;
+ my $num_left = $svcpart->{quantity} - $svcpart->{count};
+ my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}";
+
+ my $url;
+ if ( $svcpart->{svcdb} eq 'svc_external'
+ && $conf->exists('svc_external-skip_manual')
+ ) {
+ $url = "${p}edit/process/$svcpart->{svcdb}.cgi?".
+ "pkgnum=$pkg->{pkgnum}&".
+ "svcpart=$svcpart->{svcpart}";
+ } else {
+ $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
+ }
+
+ my $link = qq!<A CLASS="provision" HREF="$url">!.
+ "Provision&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
+ if ( $conf->exists('legacy_link') ) {
+ $link .= '<BR>'.
+ qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
+ qq!$pkgnum_svcpart">!.
+ "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
+ }
+ $link;
+}
+
+sub svc_unprovision_link {
+ my $svc = shift or return '';
+ qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!.
+ qq!'Permanently unprovision and delete this service?')">Unprovision</A>!;
+}
+
+# This should be generalized to use config options to determine order.
+sub pkgsort_pkgnum_cancel {
+ if ($a->{cancel} and $b->{cancel}) {
+ return ($a->{pkgnum} <=> $b->{pkgnum});
+ } elsif ($a->{cancel} or $b->{cancel}) {
+ return (-1) if ($b->{cancel});
+ return (1) if ($a->{cancel});
+ return (0);
+ } else {
+ return($a->{pkgnum} <=> $b->{pkgnum});
+ }
+}
+
+sub pkg_datestr {
+ my($pkg, $field, $conf) = @_ or return '';
+ return '&nbsp;' unless $pkg->{$field};
+ my $format = $conf->exists('pkg_showtimes')
+ ? '<B>%D</B>&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
+ : '<B>%b&nbsp;%o,&nbsp;%Y</B>';
+ ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub pkg_change_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!.
+ qq!Change&nbsp;package</a>!;
+}
+
+sub pkg_suspend_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!;
+}
+
+sub pkg_unsuspend_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!;
+}
+
+sub pkg_cancel_link {
+ my $pkg = shift or return '';
+ qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !.
+ qq!'Permanently delete included services and cancel this package?')">!.
+ qq!Cancel now</A> | !.
+ qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!;
+}
+
+sub pkg_dates_link {
+ my $pkg = shift or return '';
+ qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit&nbsp;dates</A>!;
+}
+
+sub pkg_customize_link {
+ my $pkg = shift or return '';
+ my $custnum = shift;
+ qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!.
+ qq!pkgnum=$pkg->{pkgnum}">Customize</A>!;
+}
+
+%>
+
diff --git a/httemplate/view/cust_pkg.cgi b/httemplate/view/cust_pkg.cgi
new file mode 100755
index 0000000..5f0e6bf
--- /dev/null
+++ b/httemplate/view/cust_pkg.cgi
@@ -0,0 +1,164 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my %uiview = ();
+my %uiadd = ();
+foreach my $part_svc ( qsearch('part_svc',{}) ) {
+ $uiview{$part_svc->svcpart} = popurl(2). "view/". $part_svc->svcdb . ".cgi";
+ $uiadd{$part_svc->svcpart}= popurl(2). "edit/". $part_svc->svcdb . ".cgi";
+}
+
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $pkgnum = $1;
+
+#get package record
+my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+die "No package!" unless $cust_pkg;
+my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')});
+
+my $custnum = $cust_pkg->getfield('custnum');
+print header('Package View', menubar(
+ "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum",
+ 'Main Menu' => popurl(2)
+));
+
+#print info
+my ($susp,$cancel,$expire)=(
+ $cust_pkg->getfield('susp'),
+ $cust_pkg->getfield('cancel'),
+ $cust_pkg->getfield('expire'),
+);
+my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment'));
+my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill'));
+my $otaker = $cust_pkg->getfield('otaker');
+
+print <<END;
+<SCRIPT>
+function areyousure(href) {
+ if (confirm("Permanently delete included services and cancel this package?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+END
+
+print "Package information";
+print ' (<A HREF="'. popurl(2). 'misc/unsusp_pkg.cgi?'. $pkgnum.
+ '">unsuspend</A>)'
+ if ( $susp && ! $cancel );
+
+print ' (<A HREF="'. popurl(2). 'misc/susp_pkg.cgi?'. $pkgnum.
+ '">suspend</A>)'
+ unless ( $susp || $cancel );
+
+print ' (<A HREF="javascript:areyousure(\''. popurl(2). 'misc/cancel_pkg.cgi?'.
+ $pkgnum. '\')">cancel</A>)'
+ unless $cancel;
+
+print ' (<A HREF="'. popurl(2). 'edit/REAL_cust_pkg.cgi?'. $pkgnum.
+ '">edit dates</A>)';
+
+print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">',
+ $pkgnum, '</TD></TR>',
+ '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">',
+ $pkg, '</TD></TR>',
+ '<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">',
+ $comment, '</TD></TR>',
+ '<TR><TD ALIGN="right">Setup date</TD><TD BGCOLOR="#ffffff">',
+ ( $setup ? time2str("%D",$setup) : "(Not setup)" ), '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Last bill date</TD><TD BGCOLOR="#ffffff">',
+ ( $cust_pkg->get('last_bill') ? time2str("%D",$cust_pkg->get('last_bill')) : "&nbsp;" ),
+ '</TD></TR>'
+ if $cust_pkg->dbdef_table->column('last_bill');
+
+print '<TR><TD ALIGN="right">Next bill date</TD><TD BGCOLOR="#ffffff">',
+ ( $bill ? time2str("%D",$bill) : "&nbsp;" ), '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">',
+ time2str("%D",$susp), '</TD></TR>' if $susp;
+print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">',
+ time2str("%D",$expire), '</TD></TR>' if $expire;
+print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">',
+ time2str("%D",$cancel), '</TD></TR>' if $cancel;
+print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
+ $otaker, '</TD></TR>',
+ '</TABLE></TD></TR></TABLE>';
+
+unless ($expire) {
+ print <<END;
+<FORM ACTION="../misc/expire_pkg.cgi" METHOD="post">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">
+Expire (date): <INPUT TYPE="text" NAME="date" VALUE="" >
+<INPUT TYPE="submit" VALUE="Cancel later">
+END
+}
+
+unless ($cancel) {
+
+ #services
+ print '<BR>Service Information', &table();
+
+ #list of services this pkgpart includes
+ my $pkg_svc;
+ my %pkg_svc = ();
+ foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $cust_pkg->pkgpart }) ) {
+ $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
+ }
+
+ #list of records from cust_svc
+ my $svcpart;
+ foreach $svcpart (sort {$a <=> $b} keys %pkg_svc) {
+
+ my($svc)=qsearchs('part_svc',{'svcpart'=>$svcpart})->getfield('svc');
+
+ my(@cust_svc)=qsearch('cust_svc',{'pkgnum'=>$pkgnum,
+ 'svcpart'=>$svcpart,
+ });
+
+ my($enum);
+ for $enum ( 1 .. $pkg_svc{$svcpart} ) {
+
+ my($cust_svc);
+ if ( $cust_svc=shift @cust_svc ) {
+ my($svcnum)=$cust_svc->svcnum;
+ my($label, $value, $svcdb) = $cust_svc->label;
+ print <<END;
+<TR><TD><A HREF="$uiview{$svcpart}?$svcnum">(View/Edit) $svc: $value<A></TD></TR>
+END
+ } else {
+ print qq!<TR><TD>!.
+ qq!<A HREF="$uiadd{$svcpart}?pkgnum$pkgnum-svcpart$svcpart">!.
+ qq!(Provision) $svc</A>!;
+
+ print qq! or <A HREF="../misc/link.cgi?pkgnum$pkgnum-svcpart$svcpart">!.
+ qq!(Link to legacy) $svc</A>!
+ if $conf->exists('legacy_link');
+
+ print '</TD></TR>';
+ }
+
+ }
+ warn "WARNING: Leftover services pkgnum $pkgnum!" if @cust_svc;;
+ }
+
+ print "</TABLE><FONT SIZE=-1>",
+ "Choose (View/Edit) to view or edit an existing service<BR>",
+ "Choose (Provision) to setup a new service<BR>";
+
+ print "Choose (Link to legacy) to link to a legacy (pre-Freeside) service"
+ if $conf->exists('legacy_link');
+
+ print "</FONT>";
+}
+
+#formatting
+print <<END;
+ </BODY>
+</HTML>
+END
+
+%>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
new file mode 100755
index 0000000..6ca9bf0
--- /dev/null
+++ b/httemplate/view/svc_acct.cgi
@@ -0,0 +1,272 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
+die "Unknown svcnum" unless $svc_acct;
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $domain;
+if ( $svc_acct->domsvc ) {
+ my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } );
+ die "Unknown domain" unless $svc_domain;
+ $domain = $svc_domain->domain;
+} else {
+ die "No svc_domain.svcnum record for svc_acct.domsvc: ". $cust_svc->domsvc;
+}
+
+%>
+
+<SCRIPT>
+function areyousure(href) {
+ if (confirm("Permanently delete this account?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+<%= header('Account View', menubar(
+ ( ( $pkgnum || $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) account" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')" )
+ ),
+ "Main menu" => $p,
+)) %>
+
+<%
+
+#if ( $cust_pkg && $cust_pkg->part_pkg->plan eq 'sqlradacct_hour' ) {
+if ( $part_svc->part_export('sqlradius')
+ || $part_svc->part_export('sqlradius_withdomain')
+) {
+
+ my $last_bill;
+ my %plandata;
+ if ( $cust_pkg ) {
+ #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really
+ #belong in plan data
+ %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+ split("\n", $cust_pkg->part_pkg->plandata );
+
+ $last_bill = $cust_pkg->last_bill;
+ } else {
+ $last_bill = 0;
+ %plandata = ();
+ }
+
+ my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time );
+ my $hour = int($seconds/3600);
+ my $min = int( ($seconds%3600) / 60 );
+ my $sec = $seconds%60;
+
+ my $input = $svc_acct->attribute_since_sqlradacct(
+ $last_bill, time, 'AcctInputOctets'
+ ) / 1048576;
+ my $output = $svc_acct->attribute_since_sqlradacct(
+ $last_bill, time, 'AcctOutputOctets'
+ ) / 1048576;
+
+%>
+
+ RADIUS session information<BR>
+ <%= ntable('#cccccc',2) %>
+ <TR><TD BGCOLOR="#ffffff">
+
+ <% if ( $seconds ) { %>
+ Online <B><%= $hour %></B>h <B><%= $min %></B>m <B><%= $sec %></B>s
+ <% } else { %>
+ Has not logged on
+ <% } %>
+
+ <% if ( $cust_pkg ) { %>
+ since last bill (<%= time2str('%a %b %o %Y', $last_bill) %>)
+ <% if ( length($plandata{recur_included_hours}) ) { %>
+ - <%= $plandata{recur_included_hours} %> total hours in plan
+ <% } %>
+ <BR>
+ <% } else { %>
+ (no billing cycle available for unaudited account)<BR>
+ <% } %>
+
+ Upload: <B><%= sprintf("%.3f", $input) %></B> megabytes<BR>
+ Download: <B><%= sprintf("%.3f", $output) %></B> megabytes<BR>
+
+ <% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=$svcnum!; %>
+ View session detail:
+ <%= $href %>;begin=<%= $last_bill %>">this billing cycle</A>
+ | <%= $href %>;begin=<%= time-15552000 %>">past six months</A>
+ | <%= $href %>">all sessions</A>
+
+ </TD></TR></TABLE><BR>
+
+<% } %>
+
+<SCRIPT TYPE="text/javascript">
+function enable_change () {
+ if ( document.OneTrueForm.svcpart.selectedIndex > 1 ) {
+ document.OneTrueForm.submit.disabled = false;
+ } else {
+ document.OneTrueForm.submit.disabled = true;
+ }
+}
+</SCRIPT>
+<FORM NAME="OneTrueForm" ACTION="<%=$p%>edit/process/cust_svc.cgi">
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>">
+
+<% #print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!; %>
+
+<%
+ my @part_svc = ();
+ if ( $pkgnum ) {
+ @part_svc = grep { $_->svcdb eq 'svc_acct'
+ && $_->svcpart != $part_svc->svcpart }
+ $cust_pkg->available_part_svc;
+ } else {
+ @part_svc = qsearch('part_svc', {
+ svcdb => 'svc_acct',
+ disabled => '',
+ svcpart => { op=>'!=', value=>$part_svc->svcpart },
+ } );
+ }
+%>
+
+Service Information
+| <A HREF="<%=$p%>edit/svc_acct.cgi?<%=$svcnum%>">Edit this information</A>
+
+<% if ( @part_svc ) { %>
+| <SELECT NAME="svcpart" onChange="enable_change()">
+ <OPTION VALUE="">Change service</OPTION>
+ <OPTION VALUE="">--------------</OPTION>
+ <% foreach my $part_svc ( @part_svc ) { %>
+ <OPTION VALUE="<%= $part_svc->svcpart %>"><%= $part_svc->svc %></OPTION>
+ <% } %>
+ </SELECT>
+ <INPUT NAME="submit" TYPE="submit" VALUE="Change" disabled>
+<% } %>
+
+<%= &ntable("#cccccc") %><TR><TD><%= &ntable("#cccccc",2) %>
+<TR><TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%= $svcnum %></TD></TR>
+<TR><TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#ffffff"><%= $part_svc->svc %></TD></TR>
+<TR><TD ALIGN="right">Username</TD>
+ <TD BGCOLOR="#ffffff"><%= $svc_acct->username %></TD></TR>
+<TR><TD ALIGN="right">Domain</TD>
+ <TD BGCOLOR="#ffffff"><%= $domain %></TD></TR>
+
+<TR><TD ALIGN="right">Password</TD>
+ <TD BGCOLOR="#ffffff"><%
+
+my $password = $svc_acct->_password;
+if ( $password =~ /^\*\w+\* (.*)$/ ) {
+ $password = $1;
+ print "<I>(login disabled)</I> ";
+}
+if ( $conf->exists('showpasswords') ) {
+ print '<PRE>'. encode_entities($password). '</PRE>';
+} else {
+ print "<I>(hidden)</I>";
+}
+print "</TR></TD>";
+$password = '';
+
+if ( $conf->exists('security_phrase') ) {
+ my $sec_phrase = $svc_acct->sec_phrase;
+ print '<TR><TD ALIGN="right">Security phrase</TD><TD BGCOLOR="#ffffff">'.
+ $svc_acct->sec_phrase. '</TD></TR>';
+}
+
+my $svc_acct_pop = $svc_acct->popnum
+ ? qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum})
+ : '';
+print "<TR><TD ALIGN=\"right\">Access number</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct_pop->text. '</TD></TR>'
+ if $svc_acct_pop;
+
+if ($svc_acct->uid ne '') {
+ print "<TR><TD ALIGN=\"right\">Uid</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->uid. "</TD></TR>",
+ "<TR><TD ALIGN=\"right\">Gid</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->gid. "</TD></TR>",
+ "<TR><TD ALIGN=\"right\">GECOS</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->finger. "</TD></TR>",
+ "<TR><TD ALIGN=\"right\">Home directory</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->dir. "</TD></TR>",
+ "<TR><TD ALIGN=\"right\">Shell</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->shell. "</TD></TR>",
+ "<TR><TD ALIGN=\"right\">Quota</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->quota. "</TD></TR>"
+ ;
+} else {
+ print "<TR><TH COLSPAN=2>(No shell account)</TH></TR>";
+}
+
+if ($svc_acct->slipip) {
+ print "<TR><TD ALIGN=\"right\">IP address</TD><TD BGCOLOR=\"#ffffff\">".
+ ( ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' )
+ ? "<I>(Dynamic)</I>"
+ : $svc_acct->slipip
+ ). "</TD>";
+ my($attribute);
+ foreach $attribute ( grep /^radius_/, $svc_acct->fields ) {
+ #warn $attribute;
+ $attribute =~ /^radius_(.*)$/;
+ my $pattribute = $FS::raddb::attrib{$1};
+ print "<TR><TD ALIGN=\"right\">Radius (reply) $pattribute</TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute).
+ "</TD></TR>";
+ }
+ foreach $attribute ( grep /^rc_/, $svc_acct->fields ) {
+ #warn $attribute;
+ $attribute =~ /^rc_(.*)$/;
+ my $pattribute = $FS::raddb::attrib{$1};
+ print "<TR><TD ALIGN=\"right\">Radius (check) $pattribute: </TD>".
+ "<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute).
+ "</TD></TR>";
+ }
+} else {
+ print "<TR><TH COLSPAN=2>(No SLIP/PPP account)</TH></TR>";
+}
+
+print '<TR><TD ALIGN="right">RADIUS groups</TD><TD BGCOLOR="#ffffff">'.
+ join('<BR>', $svc_acct->radius_groups). '</TD></TR>';
+
+# Can this be abstracted further? Maybe a library function like
+# widget('HTML', 'view', $svc_acct) ? It would definitely make UI
+# style management easier.
+
+foreach (sort { $a cmp $b } $svc_acct->virtual_fields) {
+ print $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)),
+ "\n";
+}
+%>
+</TABLE></TD></TR></TABLE></FORM>
+<%
+
+print '<BR><BR>';
+
+print join("\n", $conf->config('svc_acct-notes') ). '<BR><BR>'.
+ joblisting({'svcnum'=>$svcnum}, 1). '</BODY></HTML>';
+
+%>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644
index 0000000..ae23386
--- /dev/null
+++ b/httemplate/view/svc_broadband.cgi
@@ -0,0 +1,142 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_broadband = qsearchs( 'svc_broadband', { 'svcnum' => $svcnum } )
+ or die "svc_broadband: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $router = $svc_broadband->addr_block->router;
+
+if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+
+my (
+ $routername,
+ $routernum,
+ $speed_down,
+ $speed_up,
+ $ip_addr
+ ) = (
+ $router->getfield('routername'),
+ $router->getfield('routernum'),
+ $svc_broadband->getfield('speed_down'),
+ $svc_broadband->getfield('speed_up'),
+ $svc_broadband->getfield('ip_addr')
+ );
+%>
+
+<%=header('Broadband Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+))
+%>
+
+<A HREF="<%=${p}%>edit/svc_broadband.cgi?<%=$svcnum%>">Edit this information</A>
+<BR>
+<%=ntable("#cccccc")%>
+ <TR>
+ <TD>
+ <%=ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%=$svcnum%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Router</TD>
+ <TD BGCOLOR="#ffffff"><%=$routernum%>: <%=$routername%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Download Speed</TD>
+ <TD BGCOLOR="#ffffff"><%=$speed_down%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Upload Speed</TD>
+ <TD BGCOLOR="#ffffff"><%=$speed_up%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff"><%=$ip_addr%></TD>
+ </TR>
+ <TR COLSPAN="2"><TD></TD></TR>
+
+<%
+foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) {
+ print $svc_broadband->pvf($_)->widget('HTML', 'view',
+ $svc_broadband->getfield($_)), "\n";
+}
+
+%>
+ </TABLE>
+ </TD>
+ </TR>
+</TABLE>
+
+<BR>
+<%=ntable("#cccccc", 2)%>
+<%
+ my $sb_router = qsearchs('router', { svcnum => $svcnum });
+ if ($sb_router) {
+ %>
+ <B>Router associated: <%=$sb_router->routername%> </B>
+ <A HREF="<%=popurl(2)%>edit/router.cgi?<%=$sb_router->routernum%>">
+ (details)
+ </A>
+ <BR>
+ <% my @addr_block;
+ if (@addr_block = $sb_router->addr_block) {
+ %>
+ <B>Address space </B>
+ <A HREF="<%=popurl(2)%>browse/addr_block.cgi">
+ (edit)
+ </A>
+ <BR>
+ <% print ntable("#cccccc", 1);
+ foreach (@addr_block) { %>
+ <TR>
+ <TD><%=$_->ip_gateway%>/<%=$_->ip_netmask%></TD>
+ </TR>
+ <% } %>
+ </TABLE>
+ <% } else { %>
+ <B>No address space allocated.</B>
+ <% } %>
+ <BR>
+ <%
+ } else {
+%>
+
+<FORM METHOD="GET" ACTION="<%=popurl(2)%>edit/router.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+Add router named
+ <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%=$svcnum%>)">
+ <INPUT TYPE="submit" VALUE="Add router">
+</FORM>
+
+<%
+}
+%>
+
+<BR>
+<%=joblisting({'svcnum'=>$svcnum}, 1)%>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
new file mode 100755
index 0000000..cd9f79d
--- /dev/null
+++ b/httemplate/view/svc_domain.cgi
@@ -0,0 +1,108 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_domain = qsearchs('svc_domain',{'svcnum'=>$svcnum});
+die "Unknown svcnum" unless $svc_domain;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $custnum=$cust_pkg->getfield('custnum');
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $email = '';
+if ($svc_domain->catchall) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } );
+ die "Unknown svcpart" unless $svc_acct;
+ $email = $svc_acct->email;
+}
+
+my $domain = $svc_domain->domain;
+
+%>
+
+<%= header('Domain View', menubar(
+ ( ( $pkgnum || $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) domain" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+)) %>
+
+Service #<%= $svcnum %>
+<BR>Service: <B><%= $part_svc->svc %></B>
+<BR>Domain name: <B><%= $domain %></B>
+<BR>Catch all email <A HREF="<%= ${p} %>misc/catchall.cgi?<%= $svcnum %>">(change)</A>:
+<%= $email ? "<B>$email</B>" : "<I>(none)<I>" %>
+<BR><BR><A HREF="<%= ${p} %>misc/whois.cgi?custnum=<%=$custnum%>;svcnum=<%=$svcnum%>;domain=<%=$domain%>">View whois information.</A>
+<BR><BR>
+<SCRIPT>
+ function areyousure(href) {
+ if ( confirm("Remove this record?") == true )
+ window.location.href = href;
+ }
+ function slave_areyousure() {
+ return confirm("Remove all records and slave from " + document.SlaveForm.recdata.value + "?");
+ }
+</SCRIPT>
+
+<% my @records; if ( @records = $svc_domain->domain_record ) { %>
+ <%= ntable("",2) %>
+ <tr><th>Zone</th><th>Type</th><th>Data</th></tr>
+
+ <% foreach my $domain_record ( @records ) {
+ my $type = $domain_record->rectype eq '_mstr'
+ ? "(slave)"
+ : $domain_record->recaf. ' '. $domain_record->rectype;
+ %>
+
+ <tr><td><%= $domain_record->reczone %></td>
+ <td><%= $type %></td>
+ <td><%= $domain_record->recdata %>
+
+ <% unless ( $domain_record->rectype eq 'SOA' ) { %>
+ (<A HREF="javascript:areyousure('<%=$p%>misc/delete-domain_record.cgi?<%=$domain_record->recnum%>')">delete</A>)
+ <% } %>
+ </td></tr>
+ <% } %>
+ </table>
+<% } %>
+
+<BR>
+<FORM METHOD="POST" ACTION="<%=$p%>edit/process/domain_record.cgi">
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+<INPUT TYPE="text" NAME="reczone">
+<INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN
+ <SELECT NAME="rectype">
+<% foreach (qw( A NS CNAME MX PTR) ) { %>
+ <OPTION VALUE="<%=$_%>"><%=$_%></OPTION>
+<% } %>
+ </SELECT>
+<INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Add record">
+</FORM><BR><BR>or<BR><BR>
+<FORM NAME="SlaveForm" METHOD="POST" ACTION="<%=$p%>edit/process/domain_record.cgi">
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+
+<% if ( @records ) { %> Delete all records and <% } %>
+Slave from nameserver IP
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+<INPUT TYPE="hidden" NAME="reczone" VALUE="@">
+<INPUT TYPE="hidden" NAME="recaf" VALUE="IN">
+<INPUT TYPE="hidden" NAME="rectype" VALUE="_mstr">
+<INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Slave domain" onClick="return slave_areyousure()">
+</FORM>
+<BR><BR><%= joblisting({'svcnum'=>$svcnum}, 1) %>
+</BODY></HTML>
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
new file mode 100644
index 0000000..49183cd
--- /dev/null
+++ b/httemplate/view/svc_external.cgi
@@ -0,0 +1,54 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_external = qsearchs( 'svc_external', { 'svcnum' => $svcnum } )
+ or die "svc_external: Unknown svcnum $svcnum";
+
+my $conf = new FS::Conf;
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+
+%>
+
+<%= header('External Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) external service" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+)) %>
+
+<A HREF="<%=$p%>edit/svc_external.cgi?<%=$svcnum%>">Edit this information</A><BR>
+<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %>
+
+<TR><TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%= $svcnum %></TD></TR>
+<TR><TD ALIGN="right"><%= FS::Msgcat::_gettext('svc_external-id') || 'External&nbsp;ID' %></TD>
+ <TD BGCOLOR="#ffffff"><%= $conf->config('svc_external-display_type') eq 'artera_turbo' ? sprintf('%010d', $svc_external->id) : $svc_external->id %></TD></TR>
+<TR><TD ALIGN="right"><%= FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TD>
+ <TD BGCOLOR="#ffffff"><%= $svc_external->title %></TD></TR>
+
+<% foreach (sort { $a cmp $b } $svc_external->virtual_fields) { %>
+ <%= $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %>
+<% } %>
+
+</TABLE></TD></TR></TABLE>
+<BR><%= joblisting({'svcnum'=>$svcnum}, 1) %>
+</BODY></HTML>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
new file mode 100755
index 0000000..52360bc
--- /dev/null
+++ b/httemplate/view/svc_forward.cgi
@@ -0,0 +1,84 @@
+<!-- mason kludge -->
+<%
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_forward = qsearchs('svc_forward',{'svcnum'=>$svcnum});
+die "Unknown svcnum" unless $svc_forward;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $custnum=$cust_pkg->getfield('custnum');
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } )
+ or die "Unkonwn svcpart";
+
+print header('Mail Forward View', menubar(
+ ( ( $pkgnum || $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) mail forward" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+));
+
+my($srcsvc,$dstsvc,$dst) = (
+ $svc_forward->srcsvc,
+ $svc_forward->dstsvc,
+ $svc_forward->dst,
+);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+
+my $svc = $part_svc->svc;
+
+my $source;
+if ($srcsvc) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc})
+ or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc";
+ $source = $svc_acct->email;
+} else {
+ $source = $src;
+}
+
+my $destination;
+if ($dstsvc) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc})
+ or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc";
+ $destination = $svc_acct->email;
+} else {
+ $destination = $dst;
+}
+
+print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!.
+ ntable("#cccccc",2).
+ '<TR><TD ALIGN="right">Service number</TD>'.
+ qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!.
+ '<TR><TD ALIGN="right">Service</TD>'.
+ qq!<TD BGCOLOR="#ffffff">$svc</TD></TR>!.
+ qq!<TR><TD ALIGN="right">Email to</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!.
+ qq!<TR><TD ALIGN="right">Forwards to </TD>!.
+ qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+ print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)),
+ "\n";
+}
+
+print qq! </TABLE>!.
+ '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
+ '</BODY></HTML>'
+;
+
+%>
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
new file mode 100644
index 0000000..2980f84
--- /dev/null
+++ b/httemplate/view/svc_www.cgi
@@ -0,0 +1,61 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_www = qsearchs( 'svc_www', { 'svcnum' => $svcnum } )
+ or die "svc_www: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $usersvc = $svc_www->usersvc;
+my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } )
+ or die "svc_www: Unknown usersvc $usersvc";
+my $email = $svc_acct->email;
+
+my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } )
+ or die "svc_www: Unknown recnum ". $svc_www->recnum;
+
+my $www = $domain_record->zone;
+
+print header('Website View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+)).
+ qq!<A HREF="${p}edit/svc_www.cgi?$svcnum">Edit this information</A><BR>!.
+ ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2).
+ qq!<TR><TD ALIGN="right">Service number</TD>!.
+ qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!.
+ qq!<TR><TD ALIGN="right">Website name</TD>!.
+ qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!.
+ qq!<TR><TD ALIGN="right">Account</TD>!.
+ qq!<TD BGCOLOR="#ffffff"><A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A></TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+ print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)),
+ "\n";
+}
+
+
+print '</TABLE></TD></TR></TABLE>'.
+ '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
+ '</BODY></HTML>'
+;
+%>
diff --git a/init.d/freeside-init b/init.d/freeside-init
new file mode 100644
index 0000000..57801dd
--- /dev/null
+++ b/init.d/freeside-init
@@ -0,0 +1,62 @@
+#! /bin/sh
+#
+# chkconfig: 345 86 16
+# description: Freeside daemons
+
+QUEUED_USER=%%%QUEUED_USER%%%
+
+FREESIDE_PATH="%%%FREESIDE_PATH%%%"
+
+SELFSERVICE_USER=%%%SELFSERVICE_USER%%%
+SELFSERVICE_MACHINES="%%%SELFSERVICE_MACHINES%%%"
+
+#INSTALLSCRIPT/INSTALLSITEBIN from Makefile.PL
+PATH="$PATH:/usr/local/bin"
+export PATH
+
+case "$1" in
+ start)
+ # Start daemons.
+ echo -n "Starting freeside-queued: "
+ freeside-queued $QUEUED_USER
+ echo "done."
+
+ for MACHINE in $SELFSERVICE_MACHINES; do
+ echo -n "Starting freeside-selfservice-server to $MACHINE: "
+ freeside-selfservice-server $SELFSERVICE_USER $MACHINE
+ echo "done."
+ done
+
+ ;;
+ stop)
+ # Stop daemons.
+ echo -n "Stopping freeside-queued: "
+ kill `cat /var/run/freeside-queued.pid`
+ echo "done."
+
+ if [ -e /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid ]
+ then
+ echo -n "Stopping (old) freeside-selfservice-server: "
+ kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid`
+ rm /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid
+ fi
+
+ for MACHINE in $SELFSERVICE_MACHINES; do
+ echo -n "Stopping freeside-selfservice-server to $MACHINE: "
+ kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid`
+ echo "done."
+ done
+
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Usage: freeside {start|stop|restart}"
+ exit 1
+esac
+
+exit 0
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/Changes b/install/5.005/DBD-Pg-1.22-fixvercmp/Changes
new file mode 100644
index 0000000..c345628
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/Changes
@@ -0,0 +1,352 @@
+1.22 Wed Mar 26 22:33:44 EST 2003
+ - Win32 compile fix for snprintf [Joe Spears]
+ - Fix memory allocation problem in bytea escaping [Barrie Slaymaker]
+ - Add utf8 support [Dominic Mitchell <dom@semantico.com>]
+ - Transform Perl arrays into PostgreSQL arrays [Alexey Slynko]
+ - Fix for foreign_key_info() [Keith Keller]
+ - Fix PG_TEXT parameter binding
+ - Doc cleanups [turnstep]
+ - Fix warning from func($table, 'table_attributes') [turnstep]
+ - Added suppport for schemas [turnstep]
+ - Fix binary to a bytea field conversion [Chris Dunlop <chris@onthe.net.au>]
+1.21 Sun Jan 12 21:00:44 EST 2003
+ - System tables no longer returned by tables(). [Dave Rolsky]
+ - Fix table_attributes to handle removal of pg_relcheck in 7.3,
+ from Ian Barwick <barwick@gmx.net>
+ - Properly reset transaction status after failed transaction when
+ autocommit is off. Properly report transaction failure message.
+ Kai <kai@xs4all.nl>
+ - New pg_bool_tf database handle that when set to true booleans are
+ returned as 't'/'f' rather than 1/0.
+
+1.20 Wed Nov 27 16:19:26 2002
+ - Maintenance transferred to GBorg,
+ http://gborg.postgresql.org/project/dbdpg/projdisplay.php. Incremented
+ version number to reflect new management. [Bruce Momjian]
+ - README cleaned up. [Bruce Momjian]
+ - Added t/15funct.t, a series of tests that determine if the meta data
+ is working. [Thomas Lowery]
+ - Added implementations of column_info() and table_info(), and
+ primary_key_info(). [Thomas Lowery]
+ - The POD formatting was cleaned up. [David Wheeler]
+ - The preparser was updated to better handle escaped characters. [Rudy
+ Lippan]
+ - Removed redundant use of strlen() in pg_error() (Jason E. Stewart).
+ - Test suite cleaned up, converted to use Test::More, and updated to use
+ standard DBI environment variables for connecting to a test database.
+ [Jason E. Stewart]
+ - Added eg/lotest.pl as a demonstration of using large objects in buffers
+ rather than files. Contributed by Garth Webb.
+ - Added LISTEN/NOTIFY functionality. Congributed by Alex Pilosov.
+ - Added constants for common PostgreSQL data types, plus simple tests to
+ make sure that they work. These are exportable via "use DBD::Pg
+ qw(:pg_types);". [David Wheeler]
+ - Deprecatated the undocumented (and invalid) use of SQL_BINARY in
+ bind_param() and documented the correct approach: "bind_param($num,
+ $val { pg_type => PG_BYTEA });". Use of SQL_BINARY in bind_param() will
+ now issue a warning if $h->{Warn} is true. [David Wheeler]
+ - Removed invalid (and broken) support for SQL_BINARY in quote(). [David
+ Wheeler]
+ - Added App::Info::RDBMS::PostgreSQL to the distribution (but it won't
+ be installed) to help Makefile.PL find the PostgreSQL include and
+ library files. [David Wheeler]
+ - Fixed compile-time warnings. [David Wheeler and Jason E. Stewart]
+
+2002-04-27 Jeffrey W. Baker <jwbaker@acm.org>
+
+ - dbdimp.c: Add default at end of switch statement for pg_type attrib.
+ - t/13pgtype.t: test for above.
+
+2002-04-09 Jeffrey W. Baker <jwbaker@acm.org>
+
+ - Pg.pm, dbdimp.c: Applied patch from
+ Thomas A. Lowery <tlowery@stlowery.net> concerning metadata
+ in table_info and so forth.
+
+2002-03-06 Jeffrey W. Baker <jwbaker@acm.org>
+ - Pg.pm (quote): Applied patch from David Wheeler <david@wheeler.net>
+ to simplfiy and speed up quoting.
+ - t/11quoting.t: Tests for above patch.
+ - t/12placeholders.t: Tests for placeholder parsing in quoted strings.
+
+2002-03-06 Jeffrey W. Baker
+ - Version 1.10 uploaded to CPAN.
+
+1.01 Jun 27, 2001
+ - fixed core dump when trying to use a BYTEA value with
+ a byte outside 0..127 Alex Pilosov <alex@pilosoft.com>
+
+1.00 May 27, 2001
+ - Fetching all records now resets Active flag as it should.
+
+0.99 May 24, 2001
+ - fix the segmentation fault in pg_error.
+
+0.98 Apr 25, 2001
+ - bug-fix for core-dump after any failed function call.
+ - applied patch from Alex Pilosov <alex@pilosoft.com>
+ which adds support for the datatype bytea
+
+0.97 Apr 20, 2001
+ - fix bug in connect method, which erroneously set the userid
+ and the password to the environment variables DBI_USER and
+ DBI_PASS.
+ - applied patch from Jan-Pieter Cornet <john@pc.xs4all.nl>,
+ which removed the special handling of a backslash when
+ used for octal presentation. Now a backslash always will
+ be escaped.
+
+0.96 Apr 09, 2001
+ - remove memory-leak in ping function, bug-fix
+ from Doug Perham <dperham@wgate.com>
+ - correct the recognition of primary keys in
+ table_attributes(). Patch from Brian Powell
+ <brian@nicklebys.com>.
+ - applied patch from David D. Kilzer <ddkilzer@lubricants-oil.com>
+ which fixes a segmentation fault in DBD::pg::blob_read() when
+ reading LOBs that required perl to reallocate space for the
+ variable holding the scalar value
+ - updated test.pl to create a test blob larger than 256 bytes
+ (now 128 Kbytes)
+ - apply patch from Tom Lane, which fixes a seg-fault when
+ inserting large amounts of text.
+ - apply patch from Peter Haworth pmh@edison.ioppublishing.com,
+ which removes the newlines from the error messages and which
+ quotes date placeholders.
+
+0.95 Jul 10, 2000
+ - add Win32 port from Bob Kline <bkline@rksystems.com>.
+
+0.94 Jul 07, 2000
+ - applied patch from Rudy Lippan <almighty@randomc.com>
+ which fixes a memory-leak with failed connections.
+ - applied patch from Hein Roehrig <hein@acm.org>
+ which fixes a bug with escaping a backslash except for
+ octal presentation
+ - applied patch from Francis J. Lacoste <francis.lacoste@iNsu.COM
+ which fixes a segmentation fault when all binded parameters are NULL
+ - adapt test.pl to avoid warnings with postgresql-7.0
+ - added support for 'COPY FROM STDIN' and 'COPY TO STDOUT'
+ - added patch from Mark Stosberg <mark@summersault.com>
+ to enhance the table_attributes subroutine
+
+0.93 Sep 29, 1999
+ - it is required now to set the environment variables POSTGRES_INCLUDE
+ and POSTGRES_LIB for compiling the module.
+ - add Win32 port from Bob Kline <bkline@rksystems.com>.
+ - support for all large-object functions via the func
+ interface.
+ - fixed bug with placeholders and casts spotted by
+ mschout@gkg.net
+ - replaced the method attributes by the method table_attributes,
+ from Scott Williams <scott@james.com>.
+ - fix type definitions for type_info_all().
+ bug spotted by "carlos" <emarcet@intramed.net.ar>.
+ - now the Pg-specific quote() method also evaluates the
+ data-type paramater.
+
+0.92 Jun 16, 1999
+ - proposal from Philip Warner <pjw@rhyme.com.au>:
+ increase BUFSIZE from 1024 to 32768 in order to improve
+ I/O performance.
+ - bug-fix in Makefile.PL for $POSTGRES_HOME not defined
+ spotted by mdalphin@amgen.com (Mark Dalphin)
+ - bug-fix for data-type datetime in type_info_all
+ spotted by Alan Grover <awgrover@iconnect-inc.com>
+ - bug-fix for escaped 's spotted by Hankin <hankin@consultco.com>
+ - removed 'large objects' related tests from test.pl
+
+0.91 Feb 14, 1999
+ - removed restriction for commercial use in copyright
+ - corrected DATA_TYPE in type_info_all()
+
+0.90 Jan 15, 1998
+ - discard parameter authtype from connect string
+ - remove work-around for bug in the large object
+ interface of postgresql
+
+0.89 Nov 05, 1998
+ - bug-fix from Jan Iven <j.iven@rz.uni-sb.de>:
+ fix problem with quoting Null in bind variables.
+
+0.88 Oct 10, 1998
+ - fixed blob_read
+ - suppressed warning when testing DBI::errstr
+
+0.87 Sep 05, 1998
+ - Pg.xs adapted to Driver.xst from DBI-1.0
+ - major rewrite of module documentation
+ - major rewrite of the test script
+ - use built-in DBI method for $dbh->do
+ - add macro dHTR in order to avoid compile errors
+ with threaded perl5.005
+ - renamed attribute AutoEscape to pg_auto_escape
+ - renamed attribute SIZE to pg_size
+ - new attribute pg_type
+ - added support for DBI->data_sources($driver)
+ - added support for $dbh->table_info
+ - blob_read documented and added to test.pl
+ - added support for attr parameter in bind_param()
+
+0.86 Aug 21, 1998
+ - added /usr/lib/ to search path for libpq.
+ - added ChopBlanks, patch from
+ Victor Krasinsky <victor@rdovira.lviv.ua>
+ - changed test.pl to test multiple database handles
+
+0.85 July 19, 1998
+ - non-printable characters in parameters will not be
+ converted to '.'. They are passed unchanged to the
+ database.
+
+0.84 July 18, 1998
+ - bug-fix from Max Cohan <mcohan@adnc.net>:
+ check for \xxx presentation before escaping backslash
+ in parameters.
+ - introduce new database handle attribute AutoEscape, which
+ controls escaping of quotes and backslashes in parameters.
+ When set to on, all quotes except at the beginning and
+ at the end of a line will be escaped and all backslashes
+ except when used to indicate an octal presentation (\xxx)
+ will be escaped. Default of AutoEscape is on.
+
+0.83 July 10, 1998
+ - bug-fix from Max Cohan <mcohan@adnc.net>:
+ using traces together with undef in place-holders dumped
+ core.
+
+0.82 June 20, 1998
+ - bug-fix from Matthew Lenz <matthew@nocturnal.org>:
+ corrected include path in Makefile.PL .
+ - added 'use strict;' to test.pl
+
+0.81 June 13, 1998
+ - bug-fix from Rolf Grossmann <grossman@securitas.net>:
+ undefined parameters in an execute statement will be
+ translated from 'undef' to 'NULL'. Also every parameter
+ for bind_param() will be quoted by default (escape quote
+ and backslash). Appropriate tests have been added to test.pl.
+ - change ping method to use libpq-interface.
+
+0.80 June 07, 1998
+ - adapted to postgresql-6.4:
+ the backend protocol has changed, which needs an adapted
+ ping method. A ping-test has been added to the test-script.
+ Also some type identifiers have changed.
+
+0.73 June 03, 1998
+ - changed include directives in Makefile.PL from
+ archlib to installarchlib and from sitearch to
+ installsitearch (Tony.Curtis@vcpc.univie.ac.at).
+ - applied patch from Junio Hamano <junio@twinsun.com>
+ quote method also doubles backslash.
+
+0.72 April 20, 1998
+ - applied patch from Michael J Schout <mschout@gkg.net>
+ which fixed the bug with queries containing the cast
+ operator.
+ - applied patch from "Irving Reid" <irving@tor.securecomputing.com>
+ which fixed a memory leak.
+
+0.71 April 04, 1998
+ - applied patch from "Irving Reid"
+ <irving@tor.securecomputing.com> which fixed the
+ the problem with the InactiveDestroy message.
+
+0.70 March 28, 1998
+ - linking again with the shared version of libpq
+ due to problems on several operating systems.
+
+0.69 March 6, 1998
+ - expanded the search path for include files
+ - module is now linked with static libpq.a
+
+0.68 March 3, 1998
+ - return to UNIX domain sockets in test-scripts
+
+0.67 February 21, 1998
+ - remove part of Driver.xst due to compile
+ error on some systems.
+
+0.66 February 19, 1998
+ - remove defines in Pg.h so that
+ it compiles also with postgresql-6.2.1
+ - changed ping method: set RaiseError=0
+
+0.65 February 14, 1998
+ - adapted to changes in DBI-0.91, so that the
+ default setting for AutoCommit and PrintError is
+ again conformant to the DBI specs.
+
+0.64 February 01, 1998
+ - changed syntax of data_source (ODBC-conformant):
+ 'dbi:Pg:dbname=dbname;host=host;port=port'
+ !!! PLEASE ADAPT YOUR SCRIPTS !!!
+ - implemented place-holders
+ - implemented ping-method
+ - added support for $dbh->{RaiseError} and $dbh->{PrintError},
+ note: DBI-default for PrintError is on !
+ - allow commit and rollback only if AutoCommit = off
+ - added documentation for $dbh->tables;
+ - new method to get meta-information about a given table:
+ $dbh->DBD::Pg::db::attributes($table);
+ - host-parameter in test.pl is set explicitly to localhost
+
+0.63 October 05, 1997
+ - adapted to PostgreSQL-6.2:
+ o $sth->rows as well as $sth->execute
+ and $sth->do return the number of
+ affected rows even for non-Select
+ statements.
+ o support for password authorization added,
+ please check the man-page for pg_passwd.
+ - the data_source parameter of the connect
+ method accepts two additional parameters
+ which are treated as host and port:
+ DBI->connect("dbi:Pg:dbname:host:port", "uid", "pwd")
+ - support for AutoCommit, please read the
+ module documentation for impacts on your
+ scripts !
+ - more perl-ish handling of data type bool,
+ please read the module documentation for
+ impacts on your scripts !
+
+0.62 August 26, 1997
+ - added blobs/README
+
+0.61 August 23, 1997
+ - adapted to DBI-0.89/Driver.xst
+ - added support for blob_read
+
+0.52 August 15, 1997
+ - added support for literal $sth->{'TYPE'},
+ pg_type.pl / pg_type.pm.
+
+0.51 August 12, 1997
+ - changed attributes to be DBI conformant:
+ o OID_STATUS to pg_oid_status
+ o CMD_STATUS to pg_cmd_status
+
+0.5 August 05, 1997
+ - support for user authentication
+ - support for bind_columns
+ - added $dbh->tables
+
+0.4 Jun 24, 1997
+ - adapted to DBI-0.84:
+ o new syntax for DBI->connect !
+ o execute returns 0E0 -> n for SELECT stmt
+ -1 for non SELECT stmt
+ -2 on error
+ - new attribute $sth->{'OID_STATUS'}
+ - new attribute $sth->{'CMD_STATUS'}
+
+0.3 Apr 24, 1997
+ - bug fix release, ( still alpha ! )
+
+0.2 Mar 13, 1997
+ - complete rewrite, ( still alpha ! )
+
+0.1 Feb 15, 1997
+ - creation, ( totally pre-alpha ! )
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/MANIFEST b/install/5.005/DBD-Pg-1.22-fixvercmp/MANIFEST
new file mode 100644
index 0000000..7d1b700
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/MANIFEST
@@ -0,0 +1,38 @@
+Changes
+MANIFEST
+Makefile.PL
+Pg.h
+Pg.pm
+Pg.xs
+README
+README.win32
+dbd-pg.pod
+dbdimp.c
+dbdimp.h
+eg/ApacheDBI.pl
+eg/lotest.pl
+eg/notify_test.patch
+t/00basic.t
+t/01connect.t
+t/01constants.t
+t/01setup.t
+t/02prepare.t
+t/03bind.t
+t/04execute.t
+t/05fetch.t
+t/06disconnect.t
+t/07reuse.t
+t/08txn.t
+t/09autocommit.t
+t/11quoting.t
+t/12placeholders.t
+t/13pgtype.t
+t/15funct.t
+t/99cleanup.t
+t/lib/App/Info.pm
+t/lib/App/Info/Handler.pm
+t/lib/App/Info/Handler/Prompt.pm
+t/lib/App/Info/RDBMS.pm
+t/lib/App/Info/RDBMS/PostgreSQL.pm
+t/lib/App/Info/Request.pm
+t/lib/App/Info/Util.pm
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/Makefile.PL b/install/5.005/DBD-Pg-1.22-fixvercmp/Makefile.PL
new file mode 100644
index 0000000..0633280
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/Makefile.PL
@@ -0,0 +1,83 @@
+
+# $Id: Makefile.PL,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+use ExtUtils::MakeMaker;
+use Config;
+use strict;
+
+use DBI 1.00;
+use DBI::DBD;
+
+my $lib;
+BEGIN {
+ my %sep = (MacOS => ':',
+ MSWin32 => '\\',
+ os2 => '\\',
+ VMS => '\\',
+ NetWare => '\\',
+ dos => '\\');
+ my $s = $sep{$^O} || '/';
+ $lib = join $s, 't', 'lib';
+}
+
+use lib $lib;
+print "Configuring Pg\n";
+print "Remember to actually read the README file !\n";
+
+my $POSTGRES_INCLUDE;
+my $POSTGRES_LIB;
+
+if ((!$ENV{POSTGRES_INCLUDE} or !$ENV{POSTGRES_LIB}) and !$ENV{POSTGRES_HOME}) {
+ # Use App::Info to get the data we need.
+ require App::Info::RDBMS::PostgreSQL;
+ require App::Info::Handler::Prompt;
+ my $p = App::Info::Handler::Prompt->new;
+ my $pg = App::Info::RDBMS::PostgreSQL->new(on_unknown => $p);
+ $POSTGRES_INCLUDE = $pg->inc_dir;
+ $POSTGRES_LIB = $pg->lib_dir;
+} elsif ((!$ENV{POSTGRES_INCLUDE} or !$ENV{POSTGRES_LIB}) and $ENV{POSTGRES_HOME}) {
+ $POSTGRES_INCLUDE = "$ENV{POSTGRES_HOME}/include";
+ $POSTGRES_LIB = "$ENV{POSTGRES_HOME}/lib";
+} else {
+ $POSTGRES_INCLUDE = "$ENV{POSTGRES_INCLUDE}";
+ $POSTGRES_LIB = "$ENV{POSTGRES_LIB}";
+}
+
+my $os = $^O;
+print "OS: $os\n";
+
+my $dbi_arch_dir;
+if ($os eq 'MSWin32') {
+ $dbi_arch_dir = "\$(INSTALLSITEARCH)/auto/DBI";
+} else {
+ $dbi_arch_dir = dbd_dbi_arch_dir();
+}
+
+my %opts = (
+ NAME => 'DBD::Pg',
+ VERSION_FROM => 'Pg.pm',
+ INC => "-I$POSTGRES_INCLUDE -I$dbi_arch_dir",
+ OBJECT => "Pg\$(OBJ_EXT) dbdimp\$(OBJ_EXT)",
+ LIBS => ["-L$POSTGRES_LIB -lpq"],
+ AUTHOR => 'http://gborg.postgresql.org/project/dbdpg/projdisplay.php',
+ ABSTRACT => 'PostgreSQL database driver for the DBI module',
+ PREREQ_PM => { 'Test::Simple' => 0.17 }, # Need Test::More
+);
+
+if ($os eq 'hpux') {
+ my $osvers = $Config{osvers};
+ if ($osvers < 10) {
+ print "Warning: Forced to build static not dynamic on $os $osvers.\a\n";
+ $opts{LINKTYPE} = 'static';
+ }
+}
+
+if ($Config{dlsrc} =~ /dl_none/) {
+ $opts{LINKTYPE} = 'static';
+}
+
+WriteMakefile(%opts);
+
+exit(0);
+
+# end of Makefile.PL
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.h b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.h
new file mode 100644
index 0000000..b77a9f8
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.h
@@ -0,0 +1,46 @@
+/*
+ $Id: Pg.h,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+ Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+ Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+
+ You may distribute under the terms of either the GNU General Public
+ License or the Artistic License, as specified in the Perl README file.
+
+*/
+
+
+#ifdef WIN32
+static int errno;
+#endif
+
+#include "libpq-fe.h"
+
+#ifdef NEVER
+#include<sys/stat.h>
+#include "libpq/libpq-fs.h"
+#endif
+#ifndef INV_READ
+#define INV_READ 0x00040000
+#endif
+#ifndef INV_WRITE
+#define INV_WRITE 0x00020000
+#endif
+
+#ifdef BUFSIZ
+#undef BUFSIZ
+#endif
+/* this should improve I/O performance for large objects */
+#define BUFSIZ 32768
+
+
+#define NEED_DBIXS_VERSION 93
+
+#include <DBIXS.h> /* installed by the DBI module */
+
+#include "dbdimp.h" /* read in our implementation details */
+
+#include <dbd_xsh.h> /* installed by the DBI module */
+
+
+/* end of Pg.h */
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.pm
new file mode 100644
index 0000000..284e563
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.pm
@@ -0,0 +1,1913 @@
+
+# $Id: Pg.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+#
+# Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+# Copyright (c) 2002 Jeffrey W. Baker
+# Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the Perl README file.
+
+
+require 5.004;
+
+$DBD::Pg::VERSION = '1.22';
+
+{
+ package DBD::Pg;
+
+ use DBI ();
+ use DynaLoader ();
+ use Exporter ();
+ @ISA = qw(DynaLoader Exporter);
+
+ %EXPORT_TAGS = (
+ pg_types => [ qw(
+ PG_BOOL PG_BYTEA PG_CHAR PG_INT8 PG_INT2 PG_INT4 PG_TEXT PG_OID
+ PG_FLOAT4 PG_FLOAT8 PG_ABSTIME PG_RELTIME PG_TINTERVAL PG_BPCHAR
+ PG_VARCHAR PG_DATE PG_TIME PG_DATETIME PG_TIMESPAN PG_TIMESTAMP
+ )]);
+
+ Exporter::export_ok_tags('pg_types');
+
+ require_version DBI 1.00;
+
+ bootstrap DBD::Pg $VERSION;
+
+ $err = 0; # holds error code for DBI::err
+ $errstr = ""; # holds error string for DBI::errstr
+ $drh = undef; # holds driver handle once initialized
+
+ sub driver{
+ return $drh if $drh;
+ my($class, $attr) = @_;
+
+ $class .= "::dr";
+
+ # not a 'my' since we use it above to prevent multiple drivers
+
+ $drh = DBI::_new_drh($class, {
+ 'Name' => 'Pg',
+ 'Version' => $VERSION,
+ 'Err' => \$DBD::Pg::err,
+ 'Errstr' => \$DBD::Pg::errstr,
+ 'Attribution' => 'PostgreSQL DBD by Edmund Mergl',
+ });
+
+ $drh;
+ }
+
+ ## Used by both the dr and db packages
+ sub pg_server_version {
+ my $dbh = shift;
+ return $dbh->{pg_server_version} if defined $dbh->{pg_server_version};
+ my ($version) = $dbh->selectrow_array("SELECT version();");
+ return 0 unless $version =~ /^PostgreSQL ([\d\.]+)/;
+ $dbh{pg_server_version} = $1;
+ return $dbh{pg_server_version};
+ }
+
+ sub pg_use_catalog {
+ my $dbh = shift;
+ my $version = DBD::Pg::pg_server_version($dbh);
+ $version =~ /^(\d+\.\d+)/;
+ return $1 < 7.3 ? "" : "pg_catalog.";
+ }
+
+ 1;
+}
+
+
+{ package DBD::Pg::dr; # ====== DRIVER ======
+ use strict;
+
+ sub data_sources {
+ my $drh = shift;
+ my $dbh = DBD::Pg::dr::connect($drh, 'dbname=template1') or return undef;
+ $dbh->{AutoCommit} = 1;
+ my $CATALOG = DBD::Pg::pg_use_catalog($dbh);
+ my $sth = $dbh->prepare("SELECT datname FROM ${CATALOG}pg_database ORDER BY datname");
+ $sth->execute or return undef;
+ my (@sources, @datname);
+ while (@datname = $sth->fetchrow_array) {
+ push @sources, "dbi:Pg:dbname=$datname[0]";
+ }
+ $sth->finish;
+ $dbh->disconnect;
+ return @sources;
+ }
+
+
+ sub connect {
+ my($drh, $dbname, $user, $auth)= @_;
+
+ # create a 'blank' dbh
+
+ my $Name = $dbname;
+ $Name =~ s/^.*dbname\s*=\s*//;
+ $Name =~ s/\s*;.*$//;
+
+ $user = "" unless defined($user);
+ $auth = "" unless defined($auth);
+
+ $user = $ENV{DBI_USER} if $user eq "";
+ $auth = $ENV{DBI_PASS} if $auth eq "";
+
+ $user = "" unless defined($user);
+ $auth = "" unless defined($auth);
+
+ my($dbh) = DBI::_new_dbh($drh, {
+ 'Name' => $Name,
+ 'User' => $user, 'CURRENT_USER' => $user,
+ });
+
+ # Connect to the database..
+ DBD::Pg::db::_login($dbh, $dbname, $user, $auth) or return undef;
+
+ $dbh;
+ }
+
+}
+
+
+{ package DBD::Pg::db; # ====== DATABASE ======
+ use strict;
+ use Carp ();
+
+ sub prepare {
+ my($dbh, $statement, @attribs)= @_;
+
+ # create a 'blank' sth
+
+ my $sth = DBI::_new_sth($dbh, {
+ 'Statement' => $statement,
+ });
+
+ DBD::Pg::st::_prepare($sth, $statement, @attribs) or return undef;
+
+ $sth;
+ }
+
+
+ sub ping {
+ my($dbh) = @_;
+
+ local $SIG{__WARN__} = sub { } if $dbh->{PrintError};
+ local $dbh->{RaiseError} = 0 if $dbh->{RaiseError};
+ my $ret = DBD::Pg::db::_ping($dbh);
+
+ return $ret;
+ }
+
+ # Column expected in statement handle returned.
+ # table_cat, table_schem, table_name, column_name, data_type, type_name,
+ # column_size, buffer_length, DECIMAL_DIGITS, NUM_PREC_RADIX, NULLABLE,
+ # REMARKS, COLUMN_DEF, SQL_DATA_TYPE, SQL_DATETIME_SUB, CHAR_OCTET_LENGTH,
+ # ORDINAL_POSITION, IS_NULLABLE
+ # The result set is ordered by TABLE_CAT, TABLE_SCHEM,
+ # TABLE_NAME and ORDINAL_POSITION.
+
+ sub column_info {
+ my ($dbh) = shift;
+ my @attrs = @_;
+ # my ($dbh, $catalog, $schema, $table, $column) = @_;
+ my $CATALOG = DBD::Pg::pg_use_catalog($dbh);
+
+ my @wh = ();
+ my @flds = qw/catname n.nspname c.relname a.attname/;
+
+ for my $idx (0 .. $#attrs) {
+ next if ($flds[$idx] eq 'catname'); # Skip catalog
+ if(defined $attrs[$idx] and length $attrs[$idx]) {
+ # Insure that the value is enclosed in single quotes.
+ $attrs[$idx] =~ s/^'?(\w+)'?$/'$1'/;
+ if ($attrs[$idx] =~ m/[,%]/) {
+ # contains a meta character.
+ push( @wh, q{( } . join ( " OR "
+ , map { m/\%/
+ ? qq{$flds[$idx] ILIKE $_ }
+ : qq{$flds[$idx] = $_ }
+ } (split /,/, $attrs[$idx]) )
+ . q{ )}
+ );
+ }
+ else {
+ push( @wh, qq{$flds[$idx] = $attrs[$idx]} );
+ }
+ }
+ }
+
+ my $wh = ""; # ();
+ $wh = join( " AND ", '', @wh ) if (@wh);
+ my $version = DBD::Pg::pg_server_version($dbh);
+ $version =~ /^(\d+\.\d+)/;
+ $version = $1;
+ my $showschema = $version < 7.3 ? "NULL::text" : "n.nspname";
+ my $schemajoin = $version < 7.3 ? "" : "LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)";
+ my $col_info_sql = qq{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , $showschema AS "TABLE_SCHEM"
+ , c.relname AS "TABLE_NAME"
+ , a.attname AS "COLUMN_NAME"
+ , t.typname AS "DATA_TYPE"
+ , NULL::text AS "TYPE_NAME"
+ , a.attlen AS "COLUMN_SIZE"
+ , NULL::text AS "BUFFER_LENGTH"
+ , NULL::text AS "DECIMAL_DIGITS"
+ , NULL::text AS "NUM_PREC_RADIX"
+ , a.attnotnull AS "NULLABLE"
+ , NULL::text AS "REMARKS"
+ , a.atthasdef AS "COLUMN_DEF"
+ , NULL::text AS "SQL_DATA_TYPE"
+ , NULL::text AS "SQL_DATETIME_SUB"
+ , NULL::text AS "CHAR_OCTET_LENGTH"
+ , a.attnum AS "ORDINAL_POSITION"
+ , a.attnotnull AS "IS_NULLABLE"
+ , a.atttypmod as atttypmod
+ , a.attnotnull as attnotnull
+ , a.atthasdef as atthasdef
+ , a.attnum as attnum
+ FROM
+ ${CATALOG}pg_attribute a
+ , ${CATALOG}pg_type t
+ , ${CATALOG}pg_class c
+ $schemajoin
+ WHERE
+ a.attrelid = c.oid
+ AND a.attnum >= 0
+ AND t.oid = a.atttypid
+ AND c.relkind in ('r','v')
+ $wh
+ ORDER BY 2, 3, 4
+ };
+
+ my $sth = $dbh->prepare( $col_info_sql ) or return undef;
+ $sth->execute();
+
+ return $sth;
+ }
+
+ sub primary_key_info {
+ my $dbh = shift;
+ my ($catalog, $schema, $table) = @_;
+ my @attrs = @_;
+ my $CATALOG = DBD::Pg::pg_use_catalog($dbh);
+
+ # TABLE_CAT:, TABLE_SCHEM:, TABLE_NAME:, COLUMN_NAME:, KEY_SEQ:
+ # , PK_NAME:
+
+ my @wh = (); my @dat = (); # Used to hold data for the attributes.
+
+ my $version = DBD::Pg::pg_server_version($dbh);
+ $version =~ /^(\d+\.\d+)/;
+ $version = $1;
+
+ my @flds = qw/catname u.usename bc.relname/;
+ $flds[1] = 'n.nspname' unless ($version < 7.3);
+
+ for my $idx (0 .. $#attrs) {
+ next if ($flds[$idx] eq 'catname'); # Skip catalog
+ if(defined $attrs[$idx] and length $attrs[$idx]) {
+ if ($attrs[$idx] =~ m/[,%_?]/) {
+ # contains a meta character.
+ push( @wh, q{( } . join ( " OR "
+ , map { push(@dat, $_);
+ m/[%_?]/
+ ? qq{$flds[$idx] iLIKE ? }
+ : qq{$flds[$idx] = ? }
+ } (split /,/, $attrs[$idx]) )
+ . q{ )}
+ );
+ }
+ else {
+ push( @dat, $attrs[$idx] );
+ push( @wh, qq{$flds[$idx] = ? } );
+ }
+ }
+ }
+
+ my $wh = '';
+ $wh = join( " AND ", '', @wh ) if (@wh);
+
+ # Base primary key selection query borrowed from phpPgAdmin.
+ my $showschema = $version < 7.3 ? "NULL::text" : "n.nspname";
+ my $schemajoin = $version < 7.3 ? "" : "LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = bc.relnamespace)";
+ my $pri_key_sql = qq{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , $showschema AS "TABLE_SCHEM"
+ , bc.relname AS "TABLE_NAME"
+ , a.attname AS "COLUMN_NAME"
+ , a.attnum AS "KEY_SEQ"
+ , ic.relname AS "PK_NAME"
+ FROM
+ ${CATALOG}pg_index i
+ , ${CATALOG}pg_attribute a
+ , ${CATALOG}pg_class ic
+ , ${CATALOG}pg_class bc
+ $schemajoin
+ WHERE
+ i.indrelid = bc.oid
+ AND i.indexrelid = ic.oid
+ AND
+ (
+ i.indkey[0] = a.attnum
+ OR
+ i.indkey[1] = a.attnum
+ OR
+ i.indkey[2] = a.attnum
+ OR
+ i.indkey[3] = a.attnum
+ OR
+ i.indkey[4] = a.attnum
+ OR
+ i.indkey[5] = a.attnum
+ OR
+ i.indkey[6] = a.attnum
+ OR
+ i.indkey[7] = a.attnum
+ OR
+ i.indkey[8] = a.attnum
+ OR
+ i.indkey[9] = a.attnum
+ OR
+ i.indkey[10] = a.attnum
+ OR
+ i.indkey[11] = a.attnum
+ OR
+ i.indkey[12] = a.attnum
+ )
+ AND a.attrelid = bc.oid
+ AND i.indproc = '0'::oid
+ AND i.indisprimary = 't'
+ $wh
+ ORDER BY 2, 3, 5
+ };
+
+ my $sth = $dbh->prepare( $pri_key_sql ) or return undef;
+ $sth->execute(@dat);
+
+ return $sth;
+ }
+
+ sub foreign_key_info {
+ # todo: verify schema work as expected
+ # add code to handle multiple-column keys correctly
+ # return something nicer for pre-7.3?
+ # try to clean up SQL, perl code
+ # create a test script?
+
+ my $dbh = shift;
+ my ($pk_catalog, $pk_schema, $pk_table,
+ $fk_catalog, $fk_schema, $fk_table) = @_;
+
+ # this query doesn't work for Postgres before 7.3
+ my $version = $dbh->pg_server_version;
+ $version =~ /^(\d+)\.(\d)/;
+ return undef if ($1.$2 < 73);
+
+ # Used to hold data for the attributes.
+ my @dat = ();
+
+ # SQL to find primary/unique keys of a table
+ my $pkey_sql = qq{
+ SELECT
+ NULL::text AS PKTABLE_CAT,
+ pknam.nspname AS PKTABLE_SCHEM,
+ pkc.relname AS PKTABLE_NAME,
+ pka.attname AS PKCOLUMN_NAME,
+ NULL::text AS FKTABLE_CAT,
+ NULL::text AS FKTABLE_SCHEM,
+ NULL::text AS FKTABLE_NAME,
+ NULL::text AS FKCOLUMN_NAME,
+ pkcon.conkey[1] AS KEY_SEQ,
+ CASE
+ WHEN pkcon.confupdtype = 'c' THEN 0
+ WHEN pkcon.confupdtype = 'r' THEN 1
+ WHEN pkcon.confupdtype = 'n' THEN 2
+ WHEN pkcon.confupdtype = 'a' THEN 3
+ WHEN pkcon.confupdtype = 'd' THEN 4
+ END AS UPDATE_RULE,
+ CASE
+ WHEN pkcon.confdeltype = 'c' THEN 0
+ WHEN pkcon.confdeltype = 'r' THEN 1
+ WHEN pkcon.confdeltype = 'n' THEN 2
+ WHEN pkcon.confdeltype = 'a' THEN 3
+ WHEN pkcon.confdeltype = 'd' THEN 4
+ END AS DELETE_RULE,
+ NULL::text AS FK_NAME,
+ pkcon.conname AS PK_NAME,
+ CASE
+ WHEN pkcon.condeferrable = 'f' THEN 7
+ WHEN pkcon.condeferred = 't' THEN 6
+ WHEN pkcon.condeferred = 'f' THEN 5
+ END AS DEFERRABILITY,
+ CASE
+ WHEN pkcon.contype = 'p' THEN 'PRIMARY'
+ WHEN pkcon.contype = 'u' THEN 'UNIQUE'
+ END AS UNIQUE_OR_PRIMARY
+ FROM
+ pg_constraint AS pkcon
+ JOIN
+ pg_class pkc ON pkc.oid=pkcon.conrelid
+ JOIN
+ pg_namespace pknam ON pkcon.connamespace=pknam.oid
+ JOIN
+ pg_attribute pka ON pka.attnum=pkcon.conkey[1] AND pka.attrelid=pkc.oid
+ };
+
+ # SQL to find foreign keys of a table
+ my $fkey_sql = qq{
+ SELECT
+ NULL::text AS PKTABLE_CAT,
+ pknam.nspname AS PKTABLE_SCHEM,
+ pkc.relname AS PKTABLE_NAME,
+ pka.attname AS PKCOLUMN_NAME,
+ NULL::text AS FKTABLE_CAT,
+ fknam.nspname AS FKTABLE_SCHEM,
+ fkc.relname AS FKTABLE_NAME,
+ fka.attname AS FKCOLUMN_NAME,
+ fkcon.conkey[1] AS KEY_SEQ,
+ CASE
+ WHEN fkcon.confupdtype = 'c' THEN 0
+ WHEN fkcon.confupdtype = 'r' THEN 1
+ WHEN fkcon.confupdtype = 'n' THEN 2
+ WHEN fkcon.confupdtype = 'a' THEN 3
+ WHEN fkcon.confupdtype = 'd' THEN 4
+ END AS UPDATE_RULE,
+ CASE
+ WHEN fkcon.confdeltype = 'c' THEN 0
+ WHEN fkcon.confdeltype = 'r' THEN 1
+ WHEN fkcon.confdeltype = 'n' THEN 2
+ WHEN fkcon.confdeltype = 'a' THEN 3
+ WHEN fkcon.confdeltype = 'd' THEN 4
+ END AS DELETE_RULE,
+ fkcon.conname AS FK_NAME,
+ pkcon.conname AS PK_NAME,
+ CASE
+ WHEN fkcon.condeferrable = 'f' THEN 7
+ WHEN fkcon.condeferred = 't' THEN 6
+ WHEN fkcon.condeferred = 'f' THEN 5
+ END AS DEFERRABILITY,
+ CASE
+ WHEN pkcon.contype = 'p' THEN 'PRIMARY'
+ WHEN pkcon.contype = 'u' THEN 'UNIQUE'
+ END AS UNIQUE_OR_PRIMARY
+ FROM
+ pg_constraint AS fkcon
+ JOIN
+ pg_constraint AS pkcon ON fkcon.confrelid=pkcon.conrelid
+ AND fkcon.confkey=pkcon.conkey
+ JOIN
+ pg_class fkc ON fkc.oid=fkcon.conrelid
+ JOIN
+ pg_class pkc ON pkc.oid=fkcon.confrelid
+ JOIN
+ pg_namespace pknam ON pkcon.connamespace=pknam.oid
+ JOIN
+ pg_namespace fknam ON fkcon.connamespace=fknam.oid
+ JOIN
+ pg_attribute fka ON fka.attnum=fkcon.conkey[1] AND fka.attrelid=fkc.oid
+ JOIN
+ pg_attribute pka ON pka.attnum=pkcon.conkey[1] AND pka.attrelid=pkc.oid
+ };
+
+ # if schema are provided, use this SQL
+ my $pk_schema_sql = " AND pknam.nspname = ? ";
+ my $fk_schema_sql = " AND fknam.nspname = ? ";
+
+ my $key_sql;
+
+ # if $fk_table: generate SQL stub, which will be same
+ # whether or not $pk_table supplied
+ if ($fk_table)
+ {
+ $key_sql = $fkey_sql . qq{
+ WHERE
+ fkc.relname = ?
+ };
+ push @dat, $fk_table;
+
+ if ($fk_schema)
+ {
+ $key_sql .= $fk_schema_sql;
+ push @dat,$fk_schema;
+ }
+ }
+
+ # if $fk_table and $pk_table: (defined by DBI, not SQL/CLI)
+ # return foreign key of $fk_table that refers to $pk_table
+ # (if any)
+ if ($pk_table and $fk_table)
+ {
+ $key_sql .= qq{
+ AND
+ pkc.relname = ?
+ };
+ push @dat, $pk_table;
+
+ if ($pk_schema)
+ {
+ $key_sql .= $pk_schema_sql;
+ push @dat,$pk_schema;
+ }
+ }
+
+ # if $fk_table but no $pk_table:
+ # return all foreign keys of $fk_table, and all
+ # primary keys of tables to which $fk_table refers
+ if (!$pk_table and $fk_table)
+ {
+ # find primary/unique keys referenced by $fk_table
+ # (this one is a little tricky)
+ $key_sql .= ' UNION ' . $pkey_sql . qq{
+ WHERE
+ pkcon.conname IN
+ (
+ SELECT
+ pkcon.conname
+ FROM
+ pg_constraint AS fkcon
+ JOIN
+ pg_constraint AS pkcon ON fkcon.confrelid=pkcon.conrelid AND
+ fkcon.confkey=pkcon.conkey
+ JOIN
+ pg_class fkc ON fkc.oid=fkcon.conrelid
+ WHERE
+ fkc.relname = ?
+ )
+ };
+ push @dat, $fk_table;
+
+ if ($fk_schema)
+ {
+ $key_sql .= $pk_schema_sql;
+ push @dat,$fk_schema;
+ }
+ }
+
+ # if $pk_table but no $fk_table:
+ # return primary key of $pk_table and all foreign keys
+ # that reference $pk_table
+ # question: what about unique keys?
+ # (DBI and SQL/CLI both state to omit unique keys)
+
+ if ($pk_table and !$fk_table)
+ {
+ # find primary key (only!) of $pk_table
+ $key_sql = $pkey_sql . qq{
+ WHERE
+ pkc.relname = ?
+ AND
+ pkcon.contype = 'p'
+ };
+ @dat = ($pk_table);
+
+ if ($pk_schema)
+ {
+ $key_sql .= $pk_schema_sql;
+ push @dat,$pk_schema;
+ }
+
+ # find all foreign keys that reference $pk_table
+ $key_sql .= 'UNION ' . $fkey_sql . qq{
+ WHERE
+ pkc.relname = ?
+ AND
+ pkcon.contype = 'p'
+ };
+ push @dat, $pk_table;
+
+ if ($pk_schema)
+ {
+ $key_sql .= $fk_schema_sql;
+ push @dat,$pk_schema;
+ }
+ }
+
+ return undef unless $key_sql;
+ my $sth = $dbh->prepare( $key_sql ) or
+ return undef;
+ $sth->execute(@dat);
+
+ return $sth;
+ }
+
+
+ sub table_info { # DBI spec: TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TABLE_TYPE, REMARKS
+ my $dbh = shift;
+ my ($catalog, $schema, $table, $type) = @_;
+ my @attrs = @_;
+
+ my $tbl_sql = ();
+
+ my $version = DBD::Pg::pg_server_version($dbh);
+ $version =~ /^(\d+\.\d+)/;
+ $version = $1;
+ my $CATALOG = DBD::Pg::pg_use_catalog($dbh);
+
+ if ( # Rules 19a
+ (defined $catalog and $catalog eq '%')
+ and (defined $schema and $schema eq '')
+ and (defined $table and $table eq '')
+ ) {
+ $tbl_sql = q{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , NULL::text AS "TABLE_TYPE"
+ , NULL::text AS "REMARKS"
+ };
+ }
+ elsif (# Rules 19b
+ (defined $catalog and $catalog eq '')
+ and (defined $schema and $schema eq '%')
+ and (defined $table and $table eq '')
+ ) {
+ $tbl_sql = ($version < 7.3) ? q{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , NULL::text AS "TABLE_TYPE"
+ , NULL::text AS "REMARKS"
+ } : q{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , n.nspname AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , NULL::text AS "TABLE_TYPE"
+ , NULL::text AS "REMARKS"
+ FROM pg_catalog.pg_namespace n
+ ORDER BY 1
+ };
+ }
+ elsif (# Rules 19c
+ (defined $catalog and $catalog eq '')
+ and (defined $schema and $schema eq '')
+ and (defined $table and $table eq '')
+ and (defined $type and $type eq '%')
+ ) {
+ # From the postgresql 7.2.1 manual 3.5 pg_class
+ # 'r' = ordinary table
+ #, 'i' = index
+ #, 'S' = sequence
+ #, 'v' = view
+ #, 's' = special
+ #, 't' = secondary TOAST table
+ $tbl_sql = q{
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'table' AS "TABLE_TYPE"
+ , 'ordinary table - r' AS "REMARKS"
+ union
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'index' AS "TABLE_TYPE"
+ , 'index - i' AS "REMARKS"
+ union
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'sequence' AS "TABLE_TYPE"
+ , 'sequence - S' AS "REMARKS"
+ union
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'view' AS "TABLE_TYPE"
+ , 'view - v' AS "REMARKS"
+ union
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'special' AS "TABLE_TYPE"
+ , 'special - s' AS "REMARKS"
+ union
+ SELECT
+ NULL::text AS "TABLE_CAT"
+ , NULL::text AS "TABLE_SCHEM"
+ , NULL::text AS "TABLE_NAME"
+ , 'secondary' AS "TABLE_TYPE"
+ , 'secondary TOAST table - t' AS "REMARKS"
+ };
+ }
+ else {
+ # Default SQL
+ my $showschema = $version < 7.3 ? "NULL::text" : "n.nspname";
+ my $schemajoin = $version < 7.3 ? "" : "LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)";
+ my $schemacase = $version < 7.3 ? "CASE WHEN c.relname ~ '^pg_' THEN 'SYSTEM TABLE' ELSE 'TABLE' END" :
+ "CASE WHEN n.nspname ~ '^pg_' THEN 'SYSTEM TABLE' ELSE 'TABLE' END";
+ $tbl_sql = qq{
+ SELECT NULL::text AS "TABLE_CAT"
+ , $showschema AS "TABLE_SCHEM"
+ , c.relname AS "TABLE_NAME"
+ , CASE
+ WHEN c.relkind = 'v' THEN 'VIEW'
+ ELSE $schemacase
+ END AS "TABLE_TYPE"
+ , d.description AS "REMARKS"
+ FROM ${CATALOG}pg_user AS u
+ , ${CATALOG}pg_class AS c
+ LEFT JOIN
+ ${CATALOG}pg_description AS d
+ ON (c.relfilenode = d.objoid AND d.objsubid = 0)
+ $schemajoin
+ WHERE
+ ((c.relkind = 'r'
+ AND c.relhasrules = FALSE) OR
+ (c.relkind = 'v'
+ AND c.relhasrules = TRUE))
+ AND c.relname !~ '^xin[vx][0-9]+'
+ AND c.relowner = u.usesysid
+ ORDER BY 1, 2, 3
+ };
+
+ # Did we receive any arguments?
+ if (@attrs) {
+ my @wh = ();
+ my @flds = qw/catname n.nspname c.relname c.relkind/;
+
+ for my $idx (0 .. $#attrs) {
+ next if ($flds[$idx] eq 'catname'); # Skip catalog
+ if(defined $attrs[$idx] and length $attrs[$idx]) {
+ # Change the "name" of the types to the real value.
+ if ($flds[$idx] =~ m/relkind/) {
+ $attrs[$idx] =~ s/^\'?table\'?/'r'/i;
+ $attrs[$idx] =~ s/^\'?index\'?/'i'/i;
+ $attrs[$idx] =~ s/^\'?sequence\'?/'S'/i;
+ $attrs[$idx] =~ s/^\'?view\'?/'v'/i;
+ $attrs[$idx] =~ s/^\'?special\'?/'s'/i;
+ $attrs[$idx] =~ s/^\'?secondary\'?/'t'/i;
+ }
+ # Insure that the value is enclosed in single quotes.
+ $attrs[$idx] =~ s/^'?(\w+)'?$/'$1'/;
+ if ($attrs[$idx] =~ m/[,%]/) {
+ # contains a meta character.
+ push( @wh, q{( } . join ( " OR "
+ , map { m/\%/
+ ? qq{$flds[$idx] LIKE $_ }
+ : qq{$flds[$idx] = $_ }
+ } (split /,/, $attrs[$idx]) )
+ . q{ )}
+ );
+ }
+ else {
+ push( @wh, qq{$flds[$idx] = $attrs[$idx]} );
+ }
+ }
+ }
+
+ my $wh = ();
+ if (@wh) {
+ $wh = join( " AND ",'', @wh );
+ $tbl_sql = qq{
+ SELECT NULL::text AS "TABLE_CAT"
+ , $showschema AS "TABLE_SCHEM"
+ , c.relname AS "TABLE_NAME"
+ , CASE
+ WHEN c.relkind = 'r' THEN
+ CASE WHEN n.nspname ~ '^pg_' THEN 'SYSTEM TABLE' ELSE 'TABLE' END
+ WHEN c.relkind = 'v' THEN 'VIEW'
+ WHEN c.relkind = 'i' THEN 'INDEX'
+ WHEN c.relkind = 'S' THEN 'SEQUENCE'
+ WHEN c.relkind = 's' THEN 'SPECIAL'
+ WHEN c.relkind = 't' THEN 'SECONDARY'
+ ELSE 'UNKNOWN'
+ END AS "TABLE_TYPE"
+ , d.description AS "REMARKS"
+ FROM ${CATALOG}pg_class AS c
+ LEFT JOIN
+ ${CATALOG}pg_description AS d
+ ON (c.relfilenode = d.objoid AND d.objsubid = 0)
+ $schemajoin
+ WHERE
+ c.relname !~ '^xin[vx][0-9]+'
+ $wh
+ ORDER BY 2, 3
+ };
+ }
+ }
+ }
+
+ my $sth = $dbh->prepare( $tbl_sql ) or return undef;
+ $sth->execute();
+
+ return $sth;
+ }
+
+
+ sub tables {
+ my($dbh) = @_;
+ my $version = DBD::Pg::pg_server_version($dbh);
+ $version =~ /^(\d+\.\d+)/;
+ $version = $1;
+ my $SQL = ($version < 7.3) ?
+ "SELECT relname AS \"TABLE_NAME\"
+ FROM pg_class
+ WHERE relkind = 'r'
+ AND relname !~ '^pg_'
+ AND relname !~ '^xin[vx][0-9]+'
+ ORDER BY 1" :
+ "SELECT n.nspname AS \"SCHEMA_NAME\", c.relname AS \"TABLE_NAME\"
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
+ WHERE c.relkind = 'r'
+ AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
+ AND pg_catalog.pg_table_is_visible(c.oid)
+ ORDER BY 1,2";
+ my $sth = $dbh->prepare($SQL) or return undef;
+ $sth->execute or return undef;
+ my (@tables, @relname);
+ while (@relname = $sth->fetchrow_array) {
+ push @tables, $version < 7.3 ? $relname[0] : "$relname[0].$relname[1]";
+ }
+ $sth->finish;
+
+ return @tables;
+ }
+
+
+ sub table_attributes {
+ my ($dbh, $table) = @_;
+ my $CATALOG = DBD::Pg::pg_use_catalog($dbh);
+ my $result = [];
+ my $attrs = $dbh->selectall_arrayref(
+ "select a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum
+ from ${CATALOG}pg_attribute a,
+ ${CATALOG}pg_class c,
+ ${CATALOG}pg_type t
+ where c.relname = ?
+ and a.attrelid = c.oid
+ and a.attnum >= 0
+ and t.oid = a.atttypid
+ order by 1
+ ", undef, $table);
+
+ return $result unless scalar(@$attrs);
+
+ # Select the array value for tables primary key.
+ my $pk_key_sql = qq{SELECT pg_index.indkey
+ FROM ${CATALOG}pg_class, ${CATALOG}pg_index
+ WHERE
+ pg_class.oid = pg_index.indrelid
+ AND pg_class.relname = '$table'
+ AND pg_index.indisprimary = 't'
+ };
+ # Expand this (returned as a string) a real array.
+ my @pk = ();
+ my $pkeys = $dbh->selectrow_array( $pk_key_sql );
+ if (defined $pkeys) {
+ foreach (split( /\s+/, $pkeys))
+ {
+ push @pk, $_;
+ }
+ }
+ my $pk_bt =
+ (@pk) ? "AND pg_attribute.attnum in (" . join ( ", ", @pk ) . ")"
+ : "";
+
+ # Get the primary key
+ my $pri_key = $dbh->selectcol_arrayref("SELECT pg_attribute.attname
+ FROM ${CATALOG}pg_class, ${CATALOG}pg_attribute, ${CATALOG}pg_index
+ WHERE pg_class.oid = pg_attribute.attrelid
+ AND pg_class.oid = pg_index.indrelid
+ $pk_bt
+ AND pg_index.indisprimary = 't'
+ AND pg_class.relname = ?
+ ORDER BY pg_attribute.attnum
+ ", undef, $table );
+ $pri_key = [] unless $pri_key;
+
+ foreach my $attr (reverse @$attrs) {
+ my ($col_name, $col_type, $size, $mod, $notnull, $hasdef, $attnum) = @$attr;
+ my $col_size = do {
+ if ($size > 0) {
+ $size;
+ } elsif ($mod > 0xffff) {
+ my $prec = ($mod & 0xffff) - 4;
+ $mod >>= 16;
+ my $dig = $mod;
+ $dig;
+ } elsif ($mod >= 4) {
+ $mod - 4;
+ } else {
+ $mod;
+ }
+ };
+
+ # Get the default value, if any
+ my ($default) = $dbh->selectrow_array("SELECT adsrc FROM ${CATALOG}pg_attrdef WHERE adnum = $attnum") if -1 == $attnum;
+ $default = '' unless $default;
+
+ # Test for any constraints
+ # Note: as of PostgreSQL 7.3 pg_relcheck has been replaced
+ # by pg_constraint. To maintain compatibility, check
+ # version number and execute appropriate query.
+
+ my $version = pg_server_version( $dbh );
+
+ my $con_query = $version < 7.3
+ ? "SELECT rcsrc FROM pg_relcheck WHERE rcname = '${table}_$col_name'"
+ : "SELECT consrc FROM pg_catalog.pg_constraint WHERE contype = 'c' AND conname = '${table}_$col_name'";
+ my ($constraint) = $dbh->selectrow_array($con_query);
+ $constraint = '' unless $constraint;
+
+ # Check to see if this is the primary key
+ my $is_primary_key = scalar(grep { /^$col_name$/i } @$pri_key) ? 1 : 0;
+
+ push @$result,
+ { NAME => $col_name,
+ TYPE => $col_type,
+ SIZE => $col_size,
+ NOTNULL => $notnull,
+ DEFAULT => $default,
+ CONSTRAINT => $constraint,
+ PRIMARY_KEY => $is_primary_key,
+ };
+ }
+
+ return $result;
+ }
+
+
+ sub type_info_all {
+ my ($dbh) = @_;
+
+ #my $names = {
+ # TYPE_NAME => 0,
+ # DATA_TYPE => 1,
+ # PRECISION => 2,
+ # LITERAL_PREFIX => 3,
+ # LITERAL_SUFFIX => 4,
+ # CREATE_PARAMS => 5,
+ # NULLABLE => 6,
+ # CASE_SENSITIVE => 7,
+ # SEARCHABLE => 8,
+ # UNSIGNED_ATTRIBUTE => 9,
+ # MONEY =>10,
+ # AUTO_INCREMENT =>11,
+ # LOCAL_TYPE_NAME =>12,
+ # MINIMUM_SCALE =>13,
+ # MAXIMUM_SCALE =>14,
+ # };
+
+ my $names = {
+ TYPE_NAME => 0,
+ DATA_TYPE => 1,
+ COLUMN_SIZE => 2, # was PRECISION originally
+ LITERAL_PREFIX => 3,
+ LITERAL_SUFFIX => 4,
+ CREATE_PARAMS => 5,
+ NULLABLE => 6,
+ CASE_SENSITIVE => 7,
+ SEARCHABLE => 8,
+ UNSIGNED_ATTRIBUTE=> 9,
+ FIXED_PREC_SCALE => 10, # was MONEY originally
+ AUTO_UNIQUE_VALUE => 11, # was AUTO_INCREMENT originally
+ LOCAL_TYPE_NAME => 12,
+ MINIMUM_SCALE => 13,
+ MAXIMUM_SCALE => 14,
+ NUM_PREC_RADIX => 15,
+ };
+
+
+ # typname |typlen|typprtlen| SQL92
+ # --------------+------+---------+ -------
+ # bool | 1| 1| BOOLEAN
+ # text | -1| -1| like VARCHAR, but automatic storage allocation
+ # bpchar | -1| -1| CHARACTER(n) bp=blank padded
+ # varchar | -1| -1| VARCHAR(n)
+ # int2 | 2| 5| SMALLINT
+ # int4 | 4| 10| INTEGER
+ # int8 | 8| 20| /
+ # money | 4| 24| /
+ # float4 | 4| 12| FLOAT(p) for p<7=float4, for p<16=float8
+ # float8 | 8| 24| REAL
+ # abstime | 4| 20| /
+ # reltime | 4| 20| /
+ # tinterval | 12| 47| /
+ # date | 4| 10| /
+ # time | 8| 16| /
+ # datetime | 8| 47| /
+ # timespan | 12| 47| INTERVAL
+ # timestamp | 4| 19| TIMESTAMP
+ # --------------+------+---------+
+
+ # DBI type definitions / PostgreSQL definitions # type needs to be DBI-specific (not pg_type)
+ #
+ # SQL_ALL_TYPES 0
+ # SQL_CHAR 1 1042 bpchar
+ # SQL_NUMERIC 2 700 float4
+ # SQL_DECIMAL 3 700 float4
+ # SQL_INTEGER 4 23 int4
+ # SQL_SMALLINT 5 21 int2
+ # SQL_FLOAT 6 700 float4
+ # SQL_REAL 7 701 float8
+ # SQL_DOUBLE 8 20 int8
+ # SQL_DATE 9 1082 date
+ # SQL_TIME 10 1083 time
+ # SQL_TIMESTAMP 11 1296 timestamp
+ # SQL_VARCHAR 12 1043 varchar
+
+ my $ti = [
+ $names,
+ # name type prec prefix suffix create params null case se unsign mon incr local min max
+ #
+ [ 'bytea', -2, 4096, '\'', '\'', undef, 1, '1', 3, undef, '0', '0', 'BYTEA', undef, undef, undef ],
+ [ 'bool', 0, 1, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'BOOLEAN', undef, undef, undef ],
+ [ 'int8', 8, 20, undef, undef, undef, 1, '0', 2, '0', '0', '0', 'LONGINT', undef, undef, undef ],
+ [ 'int2', 5, 5, undef, undef, undef, 1, '0', 2, '0', '0', '0', 'SMALLINT', undef, undef, undef ],
+ [ 'int4', 4, 10, undef, undef, undef, 1, '0', 2, '0', '0', '0', 'INTEGER', undef, undef, undef ],
+ [ 'text', 12, 4096, '\'', '\'', undef, 1, '1', 3, undef, '0', '0', 'TEXT', undef, undef, undef ],
+ [ 'float4', 6, 12, undef, undef, 'precision', 1, '0', 2, '0', '0', '0', 'FLOAT', undef, undef, undef ],
+ [ 'float8', 7, 24, undef, undef, 'precision', 1, '0', 2, '0', '0', '0', 'REAL', undef, undef, undef ],
+ [ 'abstime', 10, 20, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'ABSTIME', undef, undef, undef ],
+ [ 'reltime', 10, 20, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'RELTIME', undef, undef, undef ],
+ [ 'tinterval', 11, 47, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'TINTERVAL', undef, undef, undef ],
+ [ 'money', 0, 24, undef, undef, undef, 1, '0', 2, undef, '1', '0', 'MONEY', undef, undef, undef ],
+ [ 'bpchar', 1, 4096, '\'', '\'', 'max length', 1, '1', 3, undef, '0', '0', 'CHARACTER', undef, undef, undef ],
+ [ 'bpchar', 12, 4096, '\'', '\'', 'max length', 1, '1', 3, undef, '0', '0', 'CHARACTER', undef, undef, undef ],
+ [ 'varchar', 12, 4096, '\'', '\'', 'max length', 1, '1', 3, undef, '0', '0', 'VARCHAR', undef, undef, undef ],
+ [ 'date', 9, 10, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'DATE', undef, undef, undef ],
+ [ 'time', 10, 16, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'TIME', undef, undef, undef ],
+ [ 'datetime', 11, 47, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'DATETIME', undef, undef, undef ],
+ [ 'timespan', 11, 47, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'INTERVAL', undef, undef, undef ],
+ [ 'timestamp', 10, 19, '\'', '\'', undef, 1, '0', 2, undef, '0', '0', 'TIMESTAMP', undef, undef, undef ]
+ #
+ # intentionally omitted: char, all geometric types, all array types
+ ];
+ return $ti;
+ }
+
+
+ # Characters that need to be escaped by quote().
+ my %esc = ( "'" => '\\047', # '\\' . sprintf("%03o", ord("'")), # ISO SQL 2
+ '\\' => '\\134', # '\\' . sprintf("%03o", ord("\\")),
+ );
+
+ # Set up lookup for SQL types we don't want to escape.
+ my %no_escape = map { $_ => 1 }
+ DBI::SQL_INTEGER, DBI::SQL_SMALLINT, DBI::SQL_DECIMAL,
+ DBI::SQL_FLOAT, DBI::SQL_REAL, DBI::SQL_DOUBLE, DBI::SQL_NUMERIC;
+
+ sub quote {
+ my ($dbh, $str, $data_type) = @_;
+ return "NULL" unless defined $str;
+ return $str if $data_type && $no_escape{$data_type};
+
+ $dbh->DBI::set_err(1, "Use of SQL_BINARY invalid in quote()")
+ if $data_type && $data_type == DBI::SQL_BINARY;
+
+ $str =~ s/(['\\\0])/$esc{$1}/g;
+ return "'$str'";
+ }
+
+} # end of package DBD::Pg::db
+
+{ package DBD::Pg::st; # ====== STATEMENT ======
+
+ # all done in XS
+
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBD::Pg - PostgreSQL database driver for the DBI module
+
+=head1 SYNOPSIS
+
+ use DBI;
+
+ $dbh = DBI->connect("dbi:Pg:dbname=$dbname", "", "");
+
+ # for some advanced uses you may need PostgreSQL type values:
+ use DBD::Oracle qw(:pg_types);
+
+ # See the DBI module documentation for full details
+
+=head1 DESCRIPTION
+
+DBD::Pg is a Perl module which works with the DBI module to provide access to
+PostgreSQL databases.
+
+=head1 MODULE DOCUMENTATION
+
+This documentation describes driver specific behavior and restrictions. It is
+not supposed to be used as the only reference for the user. In any case
+consult the DBI documentation first!
+
+=head1 THE DBI CLASS
+
+=head2 DBI Class Methods
+
+=over 4
+
+=item B<connect>
+
+To connect to a database with a minimum of parameters, use the following
+syntax:
+
+ $dbh = DBI->connect("dbi:Pg:dbname=$dbname", "", "");
+
+This connects to the database $dbname at localhost without any user
+authentication. This is sufficient for the defaults of PostgreSQL.
+
+The following connect statement shows all possible parameters:
+
+ $dbh = DBI->connect("dbi:Pg:dbname=$dbname;host=$host;port=$port;" .
+ "options=$options;tty=$tty", "$username", "$password");
+
+If a parameter is undefined PostgreSQL first looks for specific environment
+variables and then it uses hard coded defaults:
+
+ parameter environment variable hard coded default
+ --------------------------------------------------
+ dbname PGDATABASE current userid
+ host PGHOST localhost
+ port PGPORT 5432
+ options PGOPTIONS ""
+ tty PGTTY ""
+ username PGUSER current userid
+ password PGPASSWORD ""
+
+If a host is specified, the postmaster on this host needs to be started with
+the C<-i> option (TCP/IP sockets).
+
+The options parameter specifies runtime options for the Postgres
+backend. Common usage is to increase the number of buffers with the C<-B>
+option. Also important is the C<-F> option, which disables automatic fsync()
+call after each transaction. For further details please refer to the
+L<postgres>.
+
+For authentication with username and password appropriate entries have to be
+made in pg_hba.conf. Please refer to the L<pg_hba.conf> and the L<pg_passwd>
+for the different types of authentication. Note that for these two parameters
+DBI distinguishes between empty and undefined. If these parameters are
+undefined DBI substitutes the values of the environment variables DBI_USER and
+DBI_PASS if present.
+
+=item B<available_drivers>
+
+ @driver_names = DBI->available_drivers;
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<data_sources>
+
+ @data_sources = DBI->data_sources('Pg');
+
+The driver supports this method. Note that the necessary database connection to
+the database template1 will be done on the localhost without any
+user-authentication. Other preferences can only be set with the environment
+variables PGHOST, DBI_USER and DBI_PASS.
+
+=item B<trace>
+
+ DBI->trace($trace_level, $trace_file)
+
+Implemented by DBI, no driver-specific impact.
+
+=back
+
+=head2 DBI Dynamic Attributes
+
+See Common Methods.
+
+=head1 METHODS COMMON TO ALL HANDLES
+
+=over 4
+
+=item B<err>
+
+ $rv = $h->err;
+
+Supported by the driver as proposed by DBI. For the connect method it returns
+PQstatus. In all other cases it returns PQresultStatus of the current handle.
+
+=item B<errstr>
+
+ $str = $h->errstr;
+
+Supported by the driver as proposed by DBI. It returns the PQerrorMessage
+related to the current handle.
+
+=item B<state>
+
+ $str = $h->state;
+
+This driver does not (yet) support the state method.
+
+=item B<trace>
+
+ $h->trace($trace_level, $trace_filename);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<trace_msg>
+
+ $h->trace_msg($message_text);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<func>
+
+This driver supports a variety of driver specific functions accessible via the
+func interface:
+
+ $attrs = $dbh->func($table, 'table_attributes');
+
+This method returns for the given table a reference to an array of hashes:
+
+ NAME attribute name
+ TYPE attribute type
+ SIZE attribute size (-1 for variable size)
+ NULLABLE flag nullable
+ DEFAULT default value
+ CONSTRAINT constraint
+ PRIMARY_KEY flag is_primary_key
+
+ $lobjId = $dbh->func($mode, 'lo_creat');
+
+Creates a new large object and returns the object-id. $mode is a bit-mask
+describing different attributes of the new object. Use the following
+constants:
+
+ $dbh->{pg_INV_WRITE}
+ $dbh->{pg_INV_READ}
+
+Upon failure it returns undef.
+
+ $lobj_fd = $dbh->func($lobjId, $mode, 'lo_open');
+
+Opens an existing large object and returns an object-descriptor for use in
+subsequent lo_* calls. For the mode bits see lo_create. Returns undef upon
+failure. Note that 0 is a perfectly correct object descriptor!
+
+ $nbytes = $dbh->func($lobj_fd, $buf, $len, 'lo_write');
+
+Writes $len bytes of $buf into the large object $lobj_fd. Returns the number
+of bytes written and undef upon failure.
+
+ $nbytes = $dbh->func($lobj_fd, $buf, $len, 'lo_read');
+
+Reads $len bytes into $buf from large object $lobj_fd. Returns the number of
+bytes read and undef upon failure.
+
+ $loc = $dbh->func($lobj_fd, $offset, $whence, 'lo_lseek');
+
+Change the current read or write location on the large object
+$obj_id. Currently $whence can only be 0 (L_SET). Returns the current location
+and undef upon failure.
+
+ $loc = $dbh->func($lobj_fd, 'lo_tell');
+
+Returns the current read or write location on the large object $lobj_fd and
+undef upon failure.
+
+ $lobj_fd = $dbh->func($lobj_fd, 'lo_close');
+
+Closes an existing large object. Returns true upon success and false upon
+failure.
+
+ $lobj_fd = $dbh->func($lobj_fd, 'lo_unlink');
+
+Deletes an existing large object. Returns true upon success and false upon
+failure.
+
+ $lobjId = $dbh->func($filename, 'lo_import');
+
+Imports a Unix file as large object and returns the object id of the new
+object or undef upon failure.
+
+ $ret = $dbh->func($lobjId, 'lo_export', 'filename');
+
+Exports a large object into a Unix file. Returns false upon failure, true
+otherwise.
+
+ $ret = $dbh->func($line, 'putline');
+
+Used together with the SQL-command 'COPY table FROM STDIN' to copy large
+amount of data into a table avoiding the overhead of using single
+insert commands. The application must explicitly send the two characters "\."
+to indicate to the backend that it has finished sending its data. See test.pl
+for an example on how to use this function.
+
+ $ret = $dbh->func($buffer, length, 'getline');
+
+Used together with the SQL-command 'COPY table TO STDOUT' to dump a complete
+table. See test.pl for an example on how to use this function.
+
+ $ret = $dbh->func('pg_notifies');
+
+Returns either undef or a reference to two-element array [ $table,
+$backend_pid ] of asynchronous notifications received.
+
+ $fd = $dbh->func('getfd');
+
+Returns fd of the actual connection to server. Can be used with select() and
+func('pg_notifies').
+
+=back
+
+=head1 ATTRIBUTES COMMON TO ALL HANDLES
+
+=over 4
+
+=item B<Warn> (boolean, inherited)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<Active> (boolean, read-only)
+
+Supported by the driver as proposed by DBI. A database handle is active while
+it is connected and statement handle is active until it is finished.
+
+=item B<Kids> (integer, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<ActiveKids> (integer, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<CachedKids> (hash ref)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<CompatMode> (boolean, inherited)
+
+Not used by this driver.
+
+=item B<InactiveDestroy> (boolean)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<PrintError> (boolean, inherited)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<RaiseError> (boolean, inherited)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<HandleError> (boolean, inherited)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<ChopBlanks> (boolean, inherited)
+
+Supported by the driver as proposed by DBI. This method is similar to the
+SQL-function RTRIM.
+
+=item B<LongReadLen> (integer, inherited)
+
+Implemented by DBI, not used by the driver.
+
+=item B<LongTruncOk> (boolean, inherited)
+
+Implemented by DBI, not used by the driver.
+
+=item B<Taint> (boolean, inherited)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<private_*>
+
+Implemented by DBI, no driver-specific impact.
+
+=back
+
+=head1 DBI DATABASE HANDLE OBJECTS
+
+=head2 Database Handle Methods
+
+=over 4
+
+=item B<selectrow_array>
+
+ @row_ary = $dbh->selectrow_array($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<selectrow_arrayref>
+
+ $ary_ref = $dbh->selectrow_arrayref($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<selectrow_hashref>
+
+ $hash_ref = $dbh->selectrow_hashref($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<selectall_arrayref>
+
+ $ary_ref = $dbh->selectall_arrayref($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<selectall_hashref>
+
+ $hash_ref = $dbh->selectall_hashref($statement, $key_field);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<selectcol_arrayref>
+
+ $ary_ref = $dbh->selectcol_arrayref($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<prepare>
+
+ $sth = $dbh->prepare($statement, \%attr);
+
+PostgreSQL does not have the concept of preparing a statement. Hence the
+prepare method just stores the statement after checking for place-holders. No
+information about the statement is available after preparing it.
+
+=item B<prepare_cached>
+
+ $sth = $dbh->prepare_cached($statement, \%attr);
+
+Implemented by DBI, no driver-specific impact. This method is not useful for
+this driver, because preparing a statement has no database interaction.
+
+=item B<do>
+
+ $rv = $dbh->do($statement, \%attr, @bind_values);
+
+Implemented by DBI, no driver-specific impact. See the notes for the execute
+method elsewhere in this document.
+
+=item B<commit>
+
+ $rc = $dbh->commit;
+
+Supported by the driver as proposed by DBI. See also the notes about
+B<Transactions> elsewhere in this document.
+
+=item B<rollback>
+
+ $rc = $dbh->rollback;
+
+Supported by the driver as proposed by DBI. See also the notes about
+B<Transactions> elsewhere in this document.
+
+=item B<disconnect>
+
+ $rc = $dbh->disconnect;
+
+Supported by the driver as proposed by DBI.
+
+=item B<ping>
+
+ $rc = $dbh->ping;
+
+This driver supports the ping-method, which can be used to check the validity
+of a database-handle. The ping method issues an empty query and checks the
+result status.
+
+=item B<table_info>
+
+ $sth = $dbh->table_info;
+
+Supported by the driver as proposed by DBI. This method returns all tables and
+views which are owned by the current user. It does not select any indexes and
+sequences. Also System tables are not selected. As TABLE_QUALIFIER the reltype
+attribute is returned and the REMARKS are undefined.
+
+=item B<foreign_key_info>
+
+ $sth = $dbh->foreign_key_info( $pk_catalog, $pk_schema, $pk_table,
+ $fk_catalog, $fk_schema, $fk_table );
+
+Supported by the driver as proposed by DBI. Unimplemented for Postgres
+servers before 7.3 (returns undef). Currently only returns information
+about first column of any multiple-column keys.
+
+=item B<tables>
+
+ @names = $dbh->tables;
+
+Supported by the driver as proposed by DBI. This method returns all tables and
+views which are owned by the current user. It does not select any indexes and
+sequences, or system tables.
+
+=item B<type_info_all>
+
+ $type_info_all = $dbh->type_info_all;
+
+Supported by the driver as proposed by DBI. Only for SQL data-types and for
+frequently used data-types information is provided. The mapping between the
+PostgreSQL typename and the SQL92 data-type (if possible) has been done
+according to the following table:
+
+ +---------------+------------------------------------+
+ | typname | SQL92 |
+ |---------------+------------------------------------|
+ | bool | BOOL |
+ | text | / |
+ | bpchar | CHAR(n) |
+ | varchar | VARCHAR(n) |
+ | int2 | SMALLINT |
+ | int4 | INT |
+ | int8 | / |
+ | money | / |
+ | float4 | FLOAT(p) p<7=float4, p<16=float8 |
+ | float8 | REAL |
+ | abstime | / |
+ | reltime | / |
+ | tinterval | / |
+ | date | / |
+ | time | / |
+ | datetime | / |
+ | timespan | TINTERVAL |
+ | timestamp | TIMESTAMP |
+ +---------------+------------------------------------+
+
+For further details concerning the PostgreSQL specific data-types please read
+the L<pgbuiltin>.
+
+=item B<type_info>
+
+ @type_info = $dbh->type_info($data_type);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<quote>
+
+ $sql = $dbh->quote($value, $data_type);
+
+This module implements its own quote method. In addition to the DBI method it
+also doubles the backslash, because PostgreSQL treats a backslash as an escape
+character.
+
+B<NOTE:> The undocumented (and invalid) support for the C<SQL_BINARY> data
+type is officially deprecated. Use C<PG_BYTEA> with C<bind_param()> instead:
+
+ $rv = $sth->bind_param($param_num, $bind_value,
+ { pg_type => DBD::Pg::PG_BYTEA });
+
+=back
+
+=head2 Database Handle Attributes
+
+=over 4
+
+=item B<AutoCommit> (boolean)
+
+Supported by the driver as proposed by DBI. According to the classification of
+DBI, PostgreSQL is a database, in which a transaction must be explicitly
+started. Without starting a transaction, every change to the database becomes
+immediately permanent. The default of AutoCommit is on, which corresponds to
+the default behavior of PostgreSQL. When setting AutoCommit to off, a
+transaction will be started and every commit or rollback will automatically
+start a new transaction. For details see the notes about B<Transactions>
+elsewhere in this document.
+
+=item B<Driver> (handle)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<Name> (string, read-only)
+
+The default method of DBI is overridden by a driver specific method, which
+returns only the database name. Anything else from the connection string is
+stripped off. Note, that here the method is read-only in contrast to the DBI
+specs.
+
+=item B<RowCacheSize> (integer)
+
+Implemented by DBI, not used by the driver.
+
+=item B<pg_auto_escape> (boolean)
+
+PostgreSQL specific attribute. If true, then quotes and backslashes in all
+parameters will be escaped in the following way:
+
+ escape quote with a quote (SQL)
+ escape backslash with a backslash
+
+The default is on. Note, that PostgreSQL also accepts quotes, which are
+escaped by a backslash. Any other ASCII character can be used directly in a
+string constant.
+
+=item B<pg_enable_utf8> (boolean)
+
+PostgreSQL specific attribute. If true, then the utf8 flag will be
+turned for returned character data (if the data is valid utf8). For
+details about the utf8 flag, see L<Encode>. This is only relevant under
+perl 5.8 and higher.
+
+B<NB>: This attribute is experimental and may be subject to change.
+
+=item B<pg_INV_READ> (integer, read-only)
+
+Constant to be used for the mode in lo_creat and lo_open.
+
+=item B<pg_INV_WRITE> (integer, read-only)
+
+Constant to be used for the mode in lo_creat and lo_open.
+
+=back
+
+=head1 DBI STATEMENT HANDLE OBJECTS
+
+=head2 Statement Handle Methods
+
+=over 4
+
+=item B<bind_param>
+
+ $rv = $sth->bind_param($param_num, $bind_value, \%attr);
+
+Supported by the driver as proposed by DBI.
+
+B<NOTE:> The undocumented (and invalid) support for the C<SQL_BINARY>
+SQL type is officially deprecated. Use C<PG_BYTEA> instead:
+
+ $rv = $sth->bind_param($param_num, $bind_value,
+ { pg_type => DBD::Pg::PG_BYTEA });
+
+=item B<bind_param_inout>
+
+Not supported by this driver.
+
+=item B<execute>
+
+ $rv = $sth->execute(@bind_values);
+
+Supported by the driver as proposed by DBI. In addition to 'UPDATE', 'DELETE',
+'INSERT' statements, for which it returns always the number of affected rows,
+the execute method can also be used for 'SELECT ... INTO table' statements.
+
+=item B<fetchrow_arrayref>
+
+ $ary_ref = $sth->fetchrow_arrayref;
+
+Supported by the driver as proposed by DBI.
+
+=item B<fetchrow_array>
+
+ @ary = $sth->fetchrow_array;
+
+Supported by the driver as proposed by DBI.
+
+=item B<fetchrow_hashref>
+
+ $hash_ref = $sth->fetchrow_hashref;
+
+Supported by the driver as proposed by DBI.
+
+=item B<fetchall_arrayref>
+
+ $tbl_ary_ref = $sth->fetchall_arrayref;
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<finish>
+
+ $rc = $sth->finish;
+
+Supported by the driver as proposed by DBI.
+
+=item B<rows>
+
+ $rv = $sth->rows;
+
+Supported by the driver as proposed by DBI. In contrast to many other drivers
+the number of rows is available immediately after executing the statement.
+
+=item B<bind_col>
+
+ $rc = $sth->bind_col($column_number, \$var_to_bind, \%attr);
+
+Supported by the driver as proposed by DBI.
+
+=item B<bind_columns>
+
+ $rc = $sth->bind_columns(\%attr, @list_of_refs_to_vars_to_bind);
+
+Supported by the driver as proposed by DBI.
+
+=item B<dump_results>
+
+ $rows = $sth->dump_results($maxlen, $lsep, $fsep, $fh);
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<blob_read>
+
+ $blob = $sth->blob_read($id, $offset, $len);
+
+Supported by this driver as proposed by DBI. Implemented by DBI but not
+documented, so this method might change.
+
+This method seems to be heavily influenced by the current implementation of
+blobs in Oracle. Nevertheless we try to be as compatible as possible. Whereas
+Oracle suffers from the limitation that blobs are related to tables and every
+table can have only one blob (data-type LONG), PostgreSQL handles its blobs
+independent of any table by using so called object identifiers. This explains
+why the blob_read method is blessed into the STATEMENT package and not part of
+the DATABASE package. Here the field parameter has been used to handle this
+object identifier. The offset and len parameter may be set to zero, in which
+case the driver fetches the whole blob at once.
+
+Starting with PostgreSQL-6.5 every access to a blob has to be put into a
+transaction. This holds even for a read-only access.
+
+See also the PostgreSQL-specific functions concerning blobs which are
+available via the func-interface.
+
+For further information and examples about blobs, please read the chapter
+about Large Objects in the PostgreSQL Programmer's Guide.
+
+=back
+
+=head2 Statement Handle Attributes
+
+=over 4
+
+=item B<NUM_OF_FIELDS> (integer, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<NUM_OF_PARAMS> (integer, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<NAME> (array-ref, read-only)
+
+Supported by the driver as proposed by DBI.
+
+=item B<NAME_lc> (array-ref, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<NAME_uc> (array-ref, read-only)
+
+Implemented by DBI, no driver-specific impact.
+
+=item B<TYPE> (array-ref, read-only)
+
+Supported by the driver as proposed by DBI, with the restriction, that the
+types are PostgreSQL specific data-types which do not correspond to
+international standards.
+
+=item B<PRECISION> (array-ref, read-only)
+
+Not supported by the driver.
+
+=item B<SCALE> (array-ref, read-only)
+
+Not supported by the driver.
+
+=item B<NULLABLE> (array-ref, read-only)
+
+Not supported by the driver.
+
+=item B<CursorName> (string, read-only)
+
+Not supported by the driver. See the note about B<Cursors> elsewhere in this
+document.
+
+=item B<Statement> (string, read-only)
+
+Supported by the driver as proposed by DBI.
+
+=item B<RowCache> (integer, read-only)
+
+Not supported by the driver.
+
+=item B<pg_size> (array-ref, read-only)
+
+PostgreSQL specific attribute. It returns a reference to an array of integer
+values for each column. The integer shows the size of the column in
+bytes. Variable length columns are indicated by -1.
+
+=item B<pg_type> (hash-ref, read-only)
+
+PostgreSQL specific attribute. It returns a reference to an array of strings
+for each column. The string shows the name of the data_type.
+
+=item B<pg_oid_status> (integer, read-only)
+
+PostgreSQL specific attribute. It returns the OID of the last INSERT command.
+
+=item B<pg_cmd_status> (integer, read-only)
+
+PostgreSQL specific attribute. It returns the type of the last
+command. Possible types are: INSERT, DELETE, UPDATE, SELECT.
+
+=back
+
+=head1 FURTHER INFORMATION
+
+=head2 Transactions
+
+The transaction behavior is now controlled with the attribute AutoCommit. For
+a complete definition of AutoCommit please refer to the DBI documentation.
+
+According to the DBI specification the default for AutoCommit is TRUE. In this
+mode, any change to the database becomes valid immediately. Any 'begin',
+'commit' or 'rollback' statement will be rejected.
+
+If AutoCommit is switched-off, immediately a transaction will be started by
+issuing a 'begin' statement. Any 'commit' or 'rollback' will start a new
+transaction. A disconnect will issue a 'rollback' statement.
+
+=head2 Large Objects
+
+The driver supports all large-objects related functions provided by libpq via
+the func-interface. Please note, that starting with PostgreSQL 6.5 any access
+to a large object - even read-only - has to be put into a transaction!
+
+=head2 Cursors
+
+Although PostgreSQL has a cursor concept, it has not been used in the current
+implementation. Cursors in PostgreSQL can only be used inside a transaction
+block. Because only one transaction block at a time is allowed, this would
+have implied the restriction, not to use any nested SELECT statements. Hence
+the execute method fetches all data at once into data structures located in
+the frontend application. This has to be considered when selecting large
+amounts of data!
+
+=head2 Data-Type bool
+
+The current implementation of PostgreSQL returns 't' for true and 'f' for
+false. From the Perl point of view a rather unfortunate choice. The DBD::Pg
+module translates the result for the data-type bool in a perl-ish like manner:
+'f' -> '0' and 't' -> '1'. This way the application does not have to check the
+database-specific returned values for the data-type bool, because Perl treats
+'0' as false and '1' as true.
+
+Boolean values can be passed to PostgreSQL as TRUE, 't', 'true', 'y', 'yes' or
+'1' for true and FALSE, 'f', 'false', 'n', 'no' or '0' for false.
+
+=head2 Schema support
+
+PostgreSQL version 7.3 introduced schema support. Note that the PostgreSQL
+schema concept may differ to that of other databases. Please refer to the
+PostgreSQL documentation for more details.
+
+Currently DBD::Pg does not provide explicit support for PostgreSQL schemas.
+However, schema functionality may be used without any restrictions by
+explicitly addressing schema objects, e.g.
+
+ my $res = $dbh->selectall_arrayref("SELECT * FROM my_schema.my_table");
+
+or by manipulating the schema search path with SET search_path, e.g.
+
+ $dbh->do("SET search_path TO my_schema, public");
+
+B<NOTE:> If you create an object with the same name as a PostgreSQL system
+object (as contained in the pg_catalog schema) and explicitly set the search
+path so that pg_catalog comes after the new object's schema, some DBD::Pg
+methods (particularly those querying PostgreSQL system objects) may fail.
+This problem should be fixed in a future release of DBD::Pg. Creating objects
+with the same name as system objects (or beginning with 'pg_') is not
+recommended practice and should be avoided in any case.
+
+=head1 SEE ALSO
+
+L<DBI>
+
+=head1 AUTHORS
+
+DBI and DBD-Oracle by Tim Bunce (Tim.Bunce@ig.co.uk)
+
+DBD-Pg by Edmund Mergl (E.Mergl@bawue.de) and Jeffrey W. Baker
+(jwbaker@acm.org). By David Wheeler <david@wheeler.net>, Jason
+Stewart <jason@openinformatics.com> and Bruce Momjian
+<pgman@candle.pha.pa.us> after v1.13.
+
+Major parts of this package have been copied from DBI and DBD-Oracle.
+
+=head1 COPYRIGHT
+
+The DBD::Pg module is free software. You may distribute under the terms of
+either the GNU General Public License or the Artistic License, as specified in
+the Perl README file.
+
+=head1 ACKNOWLEDGMENTS
+
+See also B<DBI/ACKNOWLEDGMENTS>.
+
+=cut
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.xs b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.xs
new file mode 100644
index 0000000..e5e4362
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/Pg.xs
@@ -0,0 +1,644 @@
+/*
+ $Id: Pg.xs,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+ Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+ Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+
+ You may distribute under the terms of either the GNU General Public
+ License or the Artistic License, as specified in the Perl README file.
+
+*/
+
+
+#include "Pg.h"
+
+
+#ifdef _MSC_VER
+#define strncasecmp(a,b,c) _strnicmp((a),(b),(c))
+#endif
+
+
+
+DBISTATE_DECLARE;
+
+
+MODULE = DBD::Pg PACKAGE = DBD::Pg
+
+I32
+constant(name=Nullch)
+ char *name
+ PROTOTYPE:
+ ALIAS:
+ PG_BOOL = 16
+ PG_BYTEA = 17
+ PG_CHAR = 18
+ PG_INT8 = 20
+ PG_INT2 = 21
+ PG_INT4 = 23
+ PG_TEXT = 25
+ PG_OID = 26
+ PG_FLOAT4 = 700
+ PG_FLOAT8 = 701
+ PG_ABSTIME = 702
+ PG_RELTIME = 703
+ PG_TINTERVAL = 704
+ PG_BPCHAR = 1042
+ PG_VARCHAR = 1043
+ PG_DATE = 1082
+ PG_TIME = 1083
+ PG_DATETIME = 1184
+ PG_TIMESPAN = 1186
+ PG_TIMESTAMP = 1296
+ CODE:
+ if (!ix) {
+ if (!name) name = GvNAME(CvGV(cv));
+ croak("Unknown DBD::Pg constant '%s'", name);
+ }
+ else RETVAL = ix;
+ OUTPUT:
+ RETVAL
+
+PROTOTYPES: DISABLE
+
+BOOT:
+ items = 0; /* avoid 'unused variable' warning */
+ DBISTATE_INIT;
+ /* XXX this interface will change: */
+ DBI_IMP_SIZE("DBD::Pg::dr::imp_data_size", sizeof(imp_drh_t));
+ DBI_IMP_SIZE("DBD::Pg::db::imp_data_size", sizeof(imp_dbh_t));
+ DBI_IMP_SIZE("DBD::Pg::st::imp_data_size", sizeof(imp_sth_t));
+ dbd_init(DBIS);
+
+
+# ------------------------------------------------------------
+# driver level interface
+# ------------------------------------------------------------
+MODULE = DBD::Pg PACKAGE = DBD::Pg::dr
+
+# disconnect_all renamed and ALIASed to avoid length clash on VMS :-(
+void
+discon_all_(drh)
+ SV * drh
+ ALIAS:
+ disconnect_all = 1
+ CODE:
+ D_imp_drh(drh);
+ ST(0) = dbd_discon_all(drh, imp_drh) ? &sv_yes : &sv_no;
+
+
+
+# ------------------------------------------------------------
+# database level interface
+# ------------------------------------------------------------
+MODULE = DBD::Pg PACKAGE = DBD::Pg::db
+
+void
+_login(dbh, dbname, username, pwd)
+ SV * dbh
+ char * dbname
+ char * username
+ char * pwd
+ CODE:
+ D_imp_dbh(dbh);
+ ST(0) = pg_db_login(dbh, imp_dbh, dbname, username, pwd) ? &sv_yes : &sv_no;
+
+
+int
+_ping(dbh)
+ SV * dbh
+ CODE:
+ int ret;
+ ret = dbd_db_ping(dbh);
+ if (ret == 0) {
+ XST_mUNDEF(0);
+ }
+ else {
+ XST_mIV(0, ret);
+ }
+
+void
+getfd(dbh)
+ SV * dbh
+ CODE:
+ int ret;
+ D_imp_dbh(dbh);
+
+ ret = dbd_db_getfd(dbh, imp_dbh);
+ ST(0) = sv_2mortal( newSViv( ret ) );
+
+void
+pg_notifies(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+
+ ST(0) = dbd_db_pg_notifies(dbh, imp_dbh);
+
+void
+commit(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+ warn("commit ineffective with AutoCommit enabled");
+ }
+ ST(0) = dbd_db_commit(dbh, imp_dbh) ? &sv_yes : &sv_no;
+
+
+void
+rollback(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if (DBIc_has(imp_dbh, DBIcf_AutoCommit)) {
+ warn("rollback ineffective with AutoCommit enabled");
+ }
+ ST(0) = dbd_db_rollback(dbh, imp_dbh) ? &sv_yes : &sv_no;
+
+
+void
+disconnect(dbh)
+ SV * dbh
+ CODE:
+ D_imp_dbh(dbh);
+ if ( !DBIc_ACTIVE(imp_dbh) ) {
+ XSRETURN_YES;
+ }
+ /* pre-disconnect checks and tidy-ups */
+ if (DBIc_CACHED_KIDS(imp_dbh)) {
+ SvREFCNT_dec(DBIc_CACHED_KIDS(imp_dbh));
+ DBIc_CACHED_KIDS(imp_dbh) = Nullhv;
+ }
+ /* Check for disconnect() being called whilst refs to cursors */
+ /* still exists. This possibly needs some more thought. */
+ if (DBIc_ACTIVE_KIDS(imp_dbh) && DBIc_WARN(imp_dbh) && !dirty) {
+ char *plural = (DBIc_ACTIVE_KIDS(imp_dbh)==1) ? "" : "s";
+ warn("disconnect(%s) invalidates %d active statement%s. %s",
+ SvPV(dbh,na), (int)DBIc_ACTIVE_KIDS(imp_dbh), plural,
+ "Either destroy statement handles or call finish on them before disconnecting.");
+ }
+ ST(0) = dbd_db_disconnect(dbh, imp_dbh) ? &sv_yes : &sv_no;
+
+
+void
+STORE(dbh, keysv, valuesv)
+ SV * dbh
+ SV * keysv
+ SV * valuesv
+ CODE:
+ D_imp_dbh(dbh);
+ ST(0) = &sv_yes;
+ if (!dbd_db_STORE_attrib(dbh, imp_dbh, keysv, valuesv)) {
+ if (!DBIS->set_attr(dbh, keysv, valuesv)) {
+ ST(0) = &sv_no;
+ }
+ }
+
+
+void
+FETCH(dbh, keysv)
+ SV * dbh
+ SV * keysv
+ CODE:
+ D_imp_dbh(dbh);
+ SV *valuesv = dbd_db_FETCH_attrib(dbh, imp_dbh, keysv);
+ if (!valuesv) {
+ valuesv = DBIS->get_attr(dbh, keysv);
+ }
+ ST(0) = valuesv; /* dbd_db_FETCH_attrib did sv_2mortal */
+
+
+void
+DESTROY(dbh)
+ SV * dbh
+ PPCODE:
+ D_imp_dbh(dbh);
+ ST(0) = &sv_yes;
+ if (!DBIc_IMPSET(imp_dbh)) { /* was never fully set up */
+ if (DBIc_WARN(imp_dbh) && !dirty && dbis->debug >= 2) {
+ warn("Database handle %s DESTROY ignored - never set up", SvPV(dbh,na));
+ }
+ }
+ else {
+ /* pre-disconnect checks and tidy-ups */
+ if (DBIc_CACHED_KIDS(imp_dbh)) {
+ SvREFCNT_dec(DBIc_CACHED_KIDS(imp_dbh));
+ DBIc_CACHED_KIDS(imp_dbh) = Nullhv;
+ }
+ if (DBIc_IADESTROY(imp_dbh)) { /* want's ineffective destroy */
+ DBIc_ACTIVE_off(imp_dbh);
+ }
+ if (DBIc_ACTIVE(imp_dbh)) {
+ if (DBIc_WARN(imp_dbh) && (!dirty || dbis->debug >= 3)) {
+ warn("Database handle destroyed without explicit disconnect");
+ }
+ /* The application has not explicitly disconnected. That's bad. */
+ /* To ensure integrity we *must* issue a rollback. This will be */
+ /* harmless if the application has issued a commit. If it hasn't */
+ /* then it'll ensure integrity. Consider a Ctrl-C killing perl */
+ /* between two statements that must be executed as a transaction. */
+ /* Perl will call DESTROY on the dbh and, if we don't rollback, */
+ /* the server will automatically commit! Bham! Corrupt database! */
+ if (!DBIc_has(imp_dbh,DBIcf_AutoCommit)) {
+ dbd_db_rollback(dbh, imp_dbh); /* ROLLBACK! */
+ }
+ dbd_db_disconnect(dbh, imp_dbh);
+ }
+ dbd_db_destroy(dbh, imp_dbh);
+ }
+
+
+# driver specific functions
+
+
+void
+lo_open(dbh, lobjId, mode)
+ SV * dbh
+ unsigned int lobjId
+ int mode
+ CODE:
+ int ret = pg_db_lo_open(dbh, lobjId, mode);
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+void
+lo_close(dbh, fd)
+ SV * dbh
+ int fd
+ CODE:
+ ST(0) = (-1 != pg_db_lo_close(dbh, fd)) ? &sv_yes : &sv_no;
+
+
+void
+lo_read(dbh, fd, buf, len)
+ SV * dbh
+ int fd
+ char * buf
+ int len
+ PREINIT:
+ SV *bufsv = SvROK(ST(2)) ? SvRV(ST(2)) : ST(2);
+ int ret;
+ CODE:
+ buf = SvGROW(bufsv, len + 1);
+ ret = pg_db_lo_read(dbh, fd, buf, len);
+ if (ret > 0) {
+ SvCUR_set(bufsv, ret);
+ *SvEND(bufsv) = '\0';
+ sv_setpvn(ST(2), buf, ret);
+ SvSETMAGIC(ST(2));
+ }
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_write(dbh, fd, buf, len)
+ SV * dbh
+ int fd
+ char * buf
+ int len
+ CODE:
+ int ret = pg_db_lo_write(dbh, fd, buf, len);
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_lseek(dbh, fd, offset, whence)
+ SV * dbh
+ int fd
+ int offset
+ int whence
+ CODE:
+ int ret = pg_db_lo_lseek(dbh, fd, offset, whence);
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_creat(dbh, mode)
+ SV * dbh
+ int mode
+ CODE:
+ int ret = pg_db_lo_creat(dbh, mode);
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_tell(dbh, fd)
+ SV * dbh
+ int fd
+ CODE:
+ int ret = pg_db_lo_tell(dbh, fd);
+ ST(0) = (-1 != ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_unlink(dbh, lobjId)
+ SV * dbh
+ unsigned int lobjId
+ CODE:
+ ST(0) = (-1 != pg_db_lo_unlink(dbh, lobjId)) ? &sv_yes : &sv_no;
+
+
+void
+lo_import(dbh, filename)
+ SV * dbh
+ char * filename
+ CODE:
+ unsigned int ret = pg_db_lo_import(dbh, filename);
+ ST(0) = (ret) ? sv_2mortal(newSViv(ret)) : &sv_undef;
+
+
+void
+lo_export(dbh, lobjId, filename)
+ SV * dbh
+ unsigned int lobjId
+ char * filename
+ CODE:
+ ST(0) = (-1 != pg_db_lo_export(dbh, lobjId, filename)) ? &sv_yes : &sv_no;
+
+
+void
+putline(dbh, buf)
+ SV * dbh
+ char * buf
+ CODE:
+ int ret = pg_db_putline(dbh, buf);
+ ST(0) = (-1 != ret) ? &sv_yes : &sv_no;
+
+
+void
+getline(dbh, buf, len)
+ PREINIT:
+ SV *bufsv = SvROK(ST(1)) ? SvRV(ST(1)) : ST(1);
+ INPUT:
+ SV * dbh
+ int len
+ char * buf = sv_grow(bufsv, len);
+ CODE:
+ int ret = pg_db_getline(dbh, buf, len);
+ if (*buf == '\\' && *(buf+1) == '.') {
+ ret = -1;
+ }
+ sv_setpv((SV*)ST(1), buf);
+ SvSETMAGIC(ST(1));
+ ST(0) = (-1 != ret) ? &sv_yes : &sv_no;
+
+
+void
+endcopy(dbh)
+ SV * dbh
+ CODE:
+ ST(0) = (-1 != pg_db_endcopy(dbh)) ? &sv_yes : &sv_no;
+
+
+# -- end of DBD::Pg::db
+
+
+# ------------------------------------------------------------
+# statement interface
+# ------------------------------------------------------------
+MODULE = DBD::Pg PACKAGE = DBD::Pg::st
+
+void
+_prepare(sth, statement, attribs=Nullsv)
+ SV * sth
+ char * statement
+ SV * attribs
+ CODE:
+ {
+ D_imp_sth(sth);
+ D_imp_dbh_from_sth;
+ DBD_ATTRIBS_CHECK("_prepare", sth, attribs);
+ if (!strncasecmp(statement, "begin", 5) ||
+ !strncasecmp(statement, "end", 4) ||
+ !strncasecmp(statement, "commit", 6) ||
+ !strncasecmp(statement, "abort", 5) ||
+ !strncasecmp(statement, "rollback", 8) ) {
+ warn("please use DBI functions for transaction handling");
+ ST(0) = &sv_no;
+ } else {
+ ST(0) = dbd_st_prepare(sth, imp_sth, statement, attribs) ? &sv_yes : &sv_no;
+ }
+ }
+
+
+void
+rows(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ XST_mIV(0, dbd_st_rows(sth, imp_sth));
+
+
+void
+bind_param(sth, param, value, attribs=Nullsv)
+ SV * sth
+ SV * param
+ SV * value
+ SV * attribs
+ CODE:
+ {
+ IV sql_type = 0;
+ D_imp_sth(sth);
+ if (attribs) {
+ if (SvNIOK(attribs)) {
+ sql_type = SvIV(attribs);
+ attribs = Nullsv;
+ }
+ else {
+ SV **svp;
+ DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
+ /* XXX we should perhaps complain if TYPE is not SvNIOK */
+ DBD_ATTRIB_GET_IV(attribs, "TYPE", 4, svp, sql_type);
+ }
+ }
+ ST(0) = dbd_bind_ph(sth, imp_sth, param, value, sql_type, attribs, FALSE, 0) ? &sv_yes : &sv_no;
+ }
+
+
+void
+bind_param_inout(sth, param, value_ref, maxlen, attribs=Nullsv)
+ SV * sth
+ SV * param
+ SV * value_ref
+ IV maxlen
+ SV * attribs
+ CODE:
+ {
+ IV sql_type = 0;
+ D_imp_sth(sth);
+ if (!SvROK(value_ref) || SvTYPE(SvRV(value_ref)) > SVt_PVMG) {
+ croak("bind_param_inout needs a reference to a scalar value");
+ }
+ if (SvREADONLY(SvRV(value_ref))) {
+ croak(no_modify);
+ }
+ if (attribs) {
+ if (SvNIOK(attribs)) {
+ sql_type = SvIV(attribs);
+ attribs = Nullsv;
+ }
+ else {
+ SV **svp;
+ DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
+ DBD_ATTRIB_GET_IV(attribs, "TYPE", 4, svp, sql_type);
+ }
+ }
+ ST(0) = dbd_bind_ph(sth, imp_sth, param, SvRV(value_ref), sql_type, attribs, TRUE, maxlen) ? &sv_yes : &sv_no;
+ }
+
+
+void
+execute(sth, ...)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ int ret;
+ if (items > 1) {
+ /* Handle binding supplied values to placeholders */
+ int i;
+ SV *idx;
+ imp_sth->all_params_len = 0; /* used for malloc of statement string in case we have placeholders */
+ if (items-1 != DBIc_NUM_PARAMS(imp_sth)) {
+ croak("execute called with %ld bind variables, %d needed", items-1, DBIc_NUM_PARAMS(imp_sth));
+ XSRETURN_UNDEF;
+ }
+ idx = sv_2mortal(newSViv(0));
+ for(i=1; i < items ; ++i) {
+ sv_setiv(idx, i);
+ if (!dbd_bind_ph(sth, imp_sth, idx, ST(i), 0, Nullsv, FALSE, 0)) {
+ XSRETURN_UNDEF; /* dbd_bind_ph already registered error */
+ }
+ }
+ }
+ ret = dbd_st_execute(sth, imp_sth);
+ /* remember that dbd_st_execute must return <= -2 for error */
+ if (ret == 0) { /* ok with no rows affected */
+ XST_mPV(0, "0E0"); /* (true but zero) */
+ }
+ else if (ret < -1) { /* -1 == unknown number of rows */
+ XST_mUNDEF(0); /* <= -2 means error */
+ }
+ else {
+ XST_mIV(0, ret); /* typically 1, rowcount or -1 */
+ }
+
+
+void
+fetchrow_arrayref(sth)
+ SV * sth
+ ALIAS:
+ fetch = 1
+ CODE:
+ D_imp_sth(sth);
+ AV *av = dbd_st_fetch(sth, imp_sth);
+ ST(0) = (av) ? sv_2mortal(newRV_inc((SV *)av)) : &sv_undef;
+
+
+void
+fetchrow_array(sth)
+ SV * sth
+ ALIAS:
+ fetchrow = 1
+ PPCODE:
+ D_imp_sth(sth);
+ AV *av;
+ av = dbd_st_fetch(sth, imp_sth);
+ if (av) {
+ int num_fields = AvFILL(av)+1;
+ int i;
+ EXTEND(sp, num_fields);
+ for(i=0; i < num_fields; ++i) {
+ PUSHs(AvARRAY(av)[i]);
+ }
+ }
+
+
+void
+finish(sth)
+ SV * sth
+ CODE:
+ D_imp_sth(sth);
+ D_imp_dbh_from_sth;
+ if (!DBIc_ACTIVE(imp_dbh)) {
+ /* Either an explicit disconnect() or global destruction */
+ /* has disconnected us from the database. Finish is meaningless */
+ /* XXX warn */
+ XSRETURN_YES;
+ }
+ if (!DBIc_ACTIVE(imp_sth)) {
+ /* No active statement to finish */
+ XSRETURN_YES;
+ }
+ ST(0) = dbd_st_finish(sth, imp_sth) ? &sv_yes : &sv_no;
+
+
+void
+blob_read(sth, field, offset, len, destrv=Nullsv, destoffset=0)
+ SV * sth
+ int field
+ long offset
+ long len
+ SV * destrv
+ long destoffset
+ CODE:
+ {
+ D_imp_sth(sth);
+ if (!destrv) {
+ destrv = sv_2mortal(newRV_inc(sv_2mortal(newSViv(0))));
+ }
+ ST(0) = dbd_st_blob_read(sth, imp_sth, field, offset, len, destrv, destoffset) ? SvRV(destrv) : &sv_undef;
+ }
+
+void
+STORE(sth, keysv, valuesv)
+ SV * sth
+ SV * keysv
+ SV * valuesv
+ CODE:
+ D_imp_sth(sth);
+ ST(0) = &sv_yes;
+ if (!dbd_st_STORE_attrib(sth, imp_sth, keysv, valuesv)) {
+ if (!DBIS->set_attr(sth, keysv, valuesv)) {
+ ST(0) = &sv_no;
+ }
+ }
+
+
+# FETCH renamed and ALIASed to avoid case clash on VMS :-(
+void
+FETCH_attrib(sth, keysv)
+ SV * sth
+ SV * keysv
+ ALIAS:
+ FETCH = 1
+ CODE:
+ D_imp_sth(sth);
+ SV *valuesv = dbd_st_FETCH_attrib(sth, imp_sth, keysv);
+ if (!valuesv) {
+ valuesv = DBIS->get_attr(sth, keysv);
+ }
+ ST(0) = valuesv; /* dbd_st_FETCH_attrib did sv_2mortal */
+
+
+void
+DESTROY(sth)
+ SV * sth
+ PPCODE:
+ D_imp_sth(sth);
+ ST(0) = &sv_yes;
+ if (!DBIc_IMPSET(imp_sth)) { /* was never fully set up */
+ if (DBIc_WARN(imp_sth) && !dirty && dbis->debug >= 2) {
+ warn("Statement handle %s DESTROY ignored - never set up", SvPV(sth,na));
+ }
+ }
+ else {
+ if (DBIc_IADESTROY(imp_sth)) { /* want's ineffective destroy */
+ DBIc_ACTIVE_off(imp_sth);
+ }
+ if (DBIc_ACTIVE(imp_sth)) {
+ dbd_st_finish(sth, imp_sth);
+ }
+ dbd_st_destroy(sth, imp_sth);
+ }
+
+
+# end of Pg.xs
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/README b/install/5.005/DBD-Pg-1.22-fixvercmp/README
new file mode 100644
index 0000000..7edebde
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/README
@@ -0,0 +1,166 @@
+
+DBD::Pg -- the DBI PostgreSQL interface for Perl
+
+# $Id: README,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+DESCRIPTION:
+------------
+
+This is version 1.21 of DBD-Pg. The web site for this interface is at:
+
+ http://gborg.postgresql.org/project/dbdpg/projdisplay.php
+
+For further information about DBI look at:
+
+ http://dbi.perl.org/
+
+For information about PostgreSQL, visit:
+
+ http://www.postgresql.org/
+
+COPYRIGHT:
+----------
+
+ Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+ Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+ Copyright (c) 2002 Jeffrey W. Baker
+ Copyright (c) 2002 PostgreSQL Global Development Group
+
+You may distribute under the terms of either the GNU General Public
+License or the Artistic License, as specified in the Perl README file.
+
+
+HOW TO GET THE LATEST VERSION:
+------------------------------
+
+Use the following URL to look for new versions of this module:
+
+ http://gborg.postgresql.org/project/dbdpg/projdisplay.php
+
+or
+
+ http://www.perl.com/CPAN/modules/by-module/DBD/
+
+Note, that this request will be redirected automatically to the
+nearest CPAN site.
+
+
+IF YOU HAVE PROBLEMS:
+---------------------
+
+Please send comments and bug-reports to <dbd-general@gborg.postgresql.org>
+
+Please include the output of perl -v and perl -V, the version of PostgreSQL,
+the version of DBD-Pg, the version of DBI, and details about your platform
+in your bug-report.
+
+
+REQUIREMENTS:
+-------------
+
+ build, test, and install Perl 5 (at least 5.005)
+ build, test, and install the DBI module (at least 1.30)
+ build, test, and install PostgreSQL (at least 7.3)
+ build, test, and install Test::Simple (at least 0.17)
+
+INSTALLATION:
+-------------
+
+By default Makefile.PL uses App:Info to find the location of the
+PostgreSQL library and include directories. However, if you want to
+control it yourself, define the environment variables POSTGRES_INCLUDE
+and POSTGRES_LIB, or POSTGRES_HOME.
+
+ 1. perl Makefile.PL
+ 2. make
+ 3. make test
+ 4. make install
+
+Do steps 1 to 3 as normal user, not as root!
+
+
+TESTING:
+--------
+
+The tests are designed to connect to a live database. The following
+environment variables must be set for the tests to run:
+
+ DBI_DSN=dbi:Pg:dbname=<database>
+ DBI_USER=<username>
+ DBI_PASS=<password>
+
+If you are using the shared library libpq.so check if your dynamic
+loader finds libpq.so. With Linux the command /sbin/ldconfig -v should
+tell you, where it finds libpq.so. If ldconfig does not find libpq.so,
+either add an appropriate entry to /etc/ld.so.conf and re-run ldconfig
+or add the path to the environment variable LD_LIBRARY_PATH.
+
+A typical error message resulting from not finding libpq.so is:
+
+ install_driver(Pg) failed: Can't load './blib/arch/auto/DBD/Pg/Pg.so'
+ for module DBD::Pg: File not found at
+
+If you get an error message like:
+
+ perl: error while loading shared libraries:
+ /usr/lib/perl5/site_perl/5.6.0/i386-linux/auto/DBD/Pg/Pg.so: undefined
+ symbol: PQconnectdb
+
+when you call DBI->connect, then your libpq.so was probably not seen at
+build-time. This should have caused 'make test' to fail; did you really
+run it and look at the output? Check the setting of POSTGRES_LIB and
+recompile DBD-Pg.
+
+Some linux distributions have incomplete perl installations. If you have
+compile errors like "XS_VERSION_BOOTCHECK undeclared", do:
+
+ find .../lib/perl5 -name XSUB.h -print
+
+If this file is not present, you need to recompile and re-install perl.
+
+SGI users: if you get segmentation faults make sure, you use the malloc
+which comes with perl when compiling perl (the default is not to).
+"David R. Noble" <drnoble@engsci.sandia.gov>
+
+HP users: if you get error messages like:
+
+ can't open shared library: .../lib/libpq.sl
+ No such file or directory
+
+when running the test script, try to replace the 'shared' option in the
+LDDFLAGS with 'archive'. Dan Lauterbach <danla@dimensional.com>
+
+
+FreeBSD users: if you get during make test the error message:
+
+ 'DBD driver has not implemented the AutoCommit attribute'
+
+recompile the DBI module and the DBD-Pg module and disable optimization.
+This error message is due to the broken optimization in gcc-2.7.2.1.
+
+If you get compiler errors like:
+ In function `XS_DBD__Pg__dr_discon_all_'
+ `sv_yes' undeclared (first use in this function)
+
+It may be because there is a 'patchlevel.h' file from another package
+(such as 'hdf') in your POSTGRES_INCLUDE dir. The presence of this file
+prevents the compiler from finding the perl include file
+'mach/CORE/patchlevel.h'. Do 'pg_config --includedir' to identify the
+POSTGRES_INCLUDE dir. Rename patchlevel.h whilst you build DBD::Pg.
+
+
+Sun Users: if you get compile errors like:
+
+ /usr/include/string.h:57: parse error before `]'
+
+then you need to remove from pgsql/include/libpq-fe.h the define for
+strerror, which clashes with the definition in the standard include
+file.
+
+Win32 Users: Running DBD-Pg scripts on Win32 needs some configuration work
+on the server side:
+
+ o add a postgres user with the same name as the NT-User
+ (eg Administrator)
+ o make sure, that your pg_hba.conf on the server is configured,
+ such that a connection from another host will be accepted
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/README.win32 b/install/5.005/DBD-Pg-1.22-fixvercmp/README.win32
new file mode 100644
index 0000000..3cbe673
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/README.win32
@@ -0,0 +1,63 @@
+
+$Id: README.win32,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+
+Here is a step-by-step procedure for getting DBD-Pg to work on Windows
+NT. This Port has been done by Bob Kline <bkline@rksystems.com>.
+
+
+prerequisites: (older versions might also work, but these are the
+-------------- versions I used)
+
+ o Windows NT4 SP4
+ o Visual Studio 6.0
+ o ActivePerl-5_6_0_613 with DBI-1.13
+ o postgresql-7.0.2
+ o DBD-Pg-0.95
+
+Here we assume, that perl and postgresql have been installed in C:\. Now
+perform the following steps:
+
+
+1. compile libpq
+----------------
+
+set POSTGRES_HOME=C:\postgresql-7.0.2
+cd postgresql-7.0.2
+mkdir lib
+mkdir include
+cd src
+copy include\port\win32.h include\os.h
+edit interfaces\libpq\fe-connect.c and add as first statement in connectDBStart() the following code:
+ #ifdef WIN32
+ static int WeHaveCalledWSAStartup;
+ if (!WeHaveCalledWSAStartup) {
+ WSADATA wsaData;
+ if (WSAStartup(MAKEWORD(1, 1), &wsaData)) {
+ printfPQExpBuffer(&conn->errorMessage, "WSAStartup failed: errno=%d\n", h_errno);
+ goto connect_errReturn;
+ }
+ WeHaveCalledWSAStartup = 1;
+ }
+ #endif
+edit interfaces\libpq\win32.mak and change the flag /ML to /MD: CPP_PROJ=/nologo /MD ...
+nmake /f win32.mak
+cd ..
+copy src\interfaces\libpq\Release\libpq.lib lib
+copy src\interfaces\libpq\libpq-fe.h include
+copy src\include\postgres_ext.h include
+cd ..
+
+
+2. build DBD-Pg
+---------------
+
+cd DBD-Pg
+perl Makefile.PL CAPI=TRUE
+nmake
+set the environment variable PGHOST to the name of the postgresql server: set PGHOST=myserver
+add on the server a postgres user with the same name as the NT-User (eg Administrator)
+make sure, that your pg_hba.conf on the server is configured, such that a connection from another host will be accepted
+mkdir C:\tmp
+nmake test (expect to get errors concerning blobs)
+nmake install
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/dbd-pg.pod b/install/5.005/DBD-Pg-1.22-fixvercmp/dbd-pg.pod
new file mode 100644
index 0000000..ccbbc63
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/dbd-pg.pod
@@ -0,0 +1,411 @@
+
+# $Id: dbd-pg.pod,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+=head1 NAME
+
+DBD::Pg - PostgreSQL database driver for the DBI module
+
+=head1 DESCRIPTION
+
+DBD::Pg is a Perl module which works with the DBI module to provide
+access to PostgreSQL databases.
+
+=head1 DBD::Pg
+
+=begin docbook
+<!-- The following blank =head1 is to allow us to use purely =head2 headings -->
+<!-- This keeps the POD fairly simple with regards to Pod::DocBook -->
+
+=end docbook
+
+=head1
+
+=head2 Version
+
+Version 0.91.
+
+=head2 Author and Contact Details
+
+The driver author is Edmund Mergl. He can be contacted via the
+I<dbi-users> mailing list.
+
+
+=head2 Supported Database Versions and Options
+
+The DBD-Pg-0.92 module supports Postgresql 6.5.
+
+
+=head2 Connect Syntax
+
+The C<DBI-E<gt>connect()> Data Source Name, or I<DSN>, can be one of the
+following:
+
+ dbi:Pg:dbname=$dbname
+ dbi:Pg:dbname=$dbname;host=$host;port=$port;options=$options;tty=$tty
+
+All parameters, including the userid and password parameter of the
+connect command, have a hard-coded default which can be overridden
+by setting appropriate environment variables:
+
+ Parameter Environment Variable Default
+ --------- -------------------- --------------
+ dbname PGDATABASE current userid
+ host PGHOST localhost
+ port PGPORT 5432
+ options PGOPTIONS ""
+ tty PGTTY ""
+ username PGUSER current userid
+ password PGPASSWORD ""
+
+There are no driver specific attributes for the C<DBI->connect()> method.
+
+
+=head2 Numeric Data Handling
+
+Postgresql supports the following numeric types:
+
+ Postgresql Range
+ ---------- --------------------------
+ int2 -32768 to +32767
+ int4 -2147483648 to +2147483647
+ float4 6 decimal places
+ float8 15 decimal places
+
+Some platforms also support the int8 type.
+C<DBD::Pg> always returns all numbers as strings.
+
+
+=head2 String Data Handling
+
+Postgresql supports the following string data types:
+
+ CHAR single character
+ CHAR(size) fixed length blank-padded
+ VARCHAR(size) variable length with limit
+ TEXT variable length
+
+All string data types have a limit of 4096 bytes.
+The CHAR type is fixed length and blank padded.
+
+There is no special handling for data with the 8th bit set. They
+are stored unchanged in the database.
+None of the character types can store embedded nulls and Unicode is
+not formally supported.
+
+Strings can be concatenated using the C<||> operator.
+
+
+=head2 Date Data Handling
+
+Postgresql supports the following date time data types:
+
+ Type Storage Recommendation Description
+ --------- -------- -------------------------- ----------------------------
+ abstime 4 bytes original date and time limited range
+ date 4 bytes SQL92 type wide range
+ datetime 8 bytes best general date and time wide range, high precision
+ interval 12 bytes SQL92 type equivalent to timespan
+ reltime 4 bytes original time interval limited range, low precision
+ time 4 bytes SQL92 type wide range
+ timespan 12 bytes best general time interval wide range, high precision
+ timestamp 4 bytes SQL92 type limited range
+
+ Data Type Range Resolution
+ ---------- ---------------------------------- -----------
+ abstime 1901-12-14 2038-01-19 1 sec
+ timestamp 1901-12-14 2038-01-19 1 sec
+ reltime -68 years +68 years 1 sec
+ tinterval -178000000 years +178000000 years 1 microsec
+ timespan -178000000 years 178000000 years 1 microsec
+ date 4713 BC 32767 AD 1 day
+ datetime 4713 BC 1465001 AD 1 microsec
+ time 00:00:00:00 23:59:59:99 1 microsec
+
+Postgresql supports a range of date formats:
+
+ Name Example
+ ----------- ----------------------
+ ISO 1997-12-17 0:37:16-08
+ SQL 12/17/1997 07:37:16.00 PST
+ Postgres Wed Dec 17 07:37:16 1997 PST
+ European 17/12/1997 15:37:16.00 MET
+ NonEuropean 12/17/1997 15:37:16.00 MET
+ US 12/17/1997 07:37:16.00 MET
+
+The default output format does not depend on the client/server locale.
+It depends on, in increasing priority: the PGDATESTYLE environment
+variable at the server, the PGDATESTYLE environment variable at the client, and
+the C<SET DATESTYLE> SQL command.
+
+All of the formats described above can be used for input. A great many
+others can also be used. There is no specific default input format.
+If the format of a date input is ambiguous then the current DATESTYLE
+is used to help disambiguate.
+
+If you specify a date/time value without a time component, the default
+time is 00:00:00 (midnight). To specify a date/time value without a date
+is not allowed.
+If a date with a two digit year is input then if the year was less than
+70, add 2000; otherwise, add 1900.
+
+The currect date/time is returned by the keyword C<'now'> or C<'current'>,
+which has to be casted to a valid data type. For example:
+
+ SELECT 'now'::datetime
+
+Postgresql supports a range of date time functions for converting
+between types, extracting parts of a date time value, truncating to a
+given unit, etc. The usual arithmetic can be performed on date and
+interval values, e.g., date-date=interval, etc.
+
+The following SQL expression can be used to convert an integer "seconds
+since 1-jan-1970 GMT" value to the corresponding database date time:
+
+ DATETIME(unixtime_field)
+
+and to do the reverse:
+
+ DATE_PART('epoch', datetime_field)
+
+The server stores all dates internally in GMT. Times are converted to
+local time on the database server before being sent to the client
+frontend, hence by default are in the server time zone.
+
+The TZ environment variable is used by the server as default time
+zone. The PGTZ environment variable on the client side is used to send
+the time zone information to the backend upon connection. The SQL C<SET
+TIME ZONE> command can set the time zone for the current session.
+
+
+=head2 LONG/BLOB Data Handling
+
+Postgresql handles BLOBS using a so called "large objects" type. The
+handling of this type differs from all other data types. The data are
+broken into chunks, which are stored in tuples in the database. Access
+to large objects is given by an interface which is modelled closely
+after the UNIX file system. The maximum size is limited by the file
+size of the operating system.
+
+
+If you just select the field, you get a "large object identifier" and
+not the data itself. The I<LongReadLen> and I<LongTruncOk> attributes are
+not implemented because they don't make sense in this case. The only
+method implemented by the driver is the undocumented DBI method
+C<blob_read()>.
+
+
+=head2 Other Data Handling issues
+
+The C<DBD::Pg> driver supports the C<type_info()> method.
+
+Postgresql supports automatic conversions between data types wherever
+it's reasonable.
+
+=head2 Transactions, Isolation and Locking
+
+Postgresql supports transactions.
+The current default isolation transaction level is "Serializable" and
+is currently implemented using table level locks. Both may change.
+No other isolation levels for transactions are supported.
+
+With AutoCommit on, a query never places a lock on a table. Readers
+never block writers and writers never block readers. This behavior
+changes whenever a transaction is started (AutoCommit off). Then a
+query induces a shared lock on a table and blocks anyone else
+until the transaction has been finished.
+
+The C<LOCK TABLE table_name> statement can be used to apply an explicit
+lock on a table. This only works inside a transaction (AutoCommit off).
+
+To ensure that a table being selected does not change before you make
+an update later in the transaction, you must explicitly lock it with a
+C<LOCK TABLE> statement before executing the select.
+
+
+=head2 No-Table Expression Select Syntax
+
+To select a constant expression, that is, an expression that doesn't involve
+data from a database table or view, just omit the "from" clause.
+Here's an example that selects the current time as a datetime:
+
+ SELECT 'now'::datetime;
+
+=head2 Table Join Syntax
+
+Outer joins are not supported. Inner joins use the traditional syntax.
+
+=head2 Table and Column Names
+
+The max size of table and column names cannot exceed 31 charaters in
+length.
+Only alphanumeric characters can be used; the first character must
+be a letter.
+
+If an identifier is enclosed by double quotation marks (C<">), it can
+contain any combination of characters except double quotation marks.
+
+Postgresql converts all identifiers to lower-case unless enclosed in
+double quotation marks.
+National character set characters can be used, if enclosed in quotation
+marks.
+
+
+=head2 Case Sensitivity of LIKE Operator
+
+Postgresql has the following string matching operators:
+
+ Glyph Description Example
+ ----- ---------------------------------------- -----------------------------
+ ~~ Same as SQL "LIKE" operator 'scrappy,marc' ~~ '%scrappy%'
+ !~~ Same as SQL "NOT LIKE" operator 'bruce' !~~ '%al%'
+ ~ Match (regex), case sensitive 'thomas' ~ '.*thomas.*'
+ ~* Match (regex), case insensitive 'thomas' ~* '.*Thomas.*'
+ !~ Does not match (regex), case sensitive 'thomas' !~ '.*Thomas.*'
+ !~* Does not match (regex), case insensitive 'thomas' !~ '.*vadim.*'
+
+
+=head2 Row ID
+
+The Postgresql "row id" pseudocolumn is called I<oid>, object identifier.
+It can be treated as a string and used to rapidly (re)select rows.
+
+
+=head2 Automatic Key or Sequence Generation
+
+Postgresql does not support automatic key generation such as "auto
+increment" or "system generated" keys.
+
+However, Postgresql does support "sequence generators". Any number of
+named sequence generators can be created in a database. Sequences
+are used via functions called C<NEXTVAL> and C<CURRVAL>. Typical usage:
+
+ INSERT INTO table (k, v) VALUES (nextval('seq_name'), ?);
+
+To get the value just inserted, you can use the corresponding C<currval()>
+SQL function in the same session, or
+
+ SELECT last_value FROM seq_name
+
+
+=head2 Automatic Row Numbering and Row Count Limiting
+
+Postgresql does not support any way of automatically numbering returned rows.
+
+
+=head2 Parameter Binding
+
+Parameter binding is emulated by the driver.
+Both the C<?> and C<:1> style of placeholders are supported.
+
+The TYPE attribute of the C<bind_param()> method may be used to
+influence how parameters are treated. These SQL types are bound as
+VARCHAR: SQL_NUMERIC, SQL_DECIMAL, SQL_INTEGER, SQL_SMALLINT,
+SQL_FLOAT, SQL_REAL, SQL_DOUBLE, SQL_VARCHAR.
+
+The SQL_CHAR type is bound as a CHAR thus enabling fixed-width blank
+padded comparison semantics.
+
+Unsupported values of the TYPE attribute generate a warning.
+
+
+=head2 Stored Procedures
+
+C<DBD::Pg> does not support stored procedures.
+
+
+=head2 Table Metadata
+
+C<DBD::Pg> supports the C<table_info()> method.
+
+The I<pg_attribute> table contains detailed information about all columns
+of all the tables in the database, one row per table.
+
+The I<pg_index> table contains detailed information about all indexes in
+the database, one row per index.
+
+Primary keys are implemented as unique indexes. See I<pg_index> above.
+
+
+=head2 Driver-specific Attributes and Methods
+
+There are no significant C<DBD::Pg> driver-specific database handle attributes.
+
+C<DBD::Pg> has the following driver-specific statement handle attributes:
+
+=over 8
+
+=item I<pg_size>
+
+Returns a reference to an array of integer values for each column. The
+integer shows the storage (not display) size of the column in bytes.
+Variable length columns are indicated by -1.
+
+=item I<pg_type>
+
+Returns a reference to an array of strings for each column. The string
+shows the name of the data type.
+
+=item I<pg_oid_status>
+
+Returns the OID of the last INSERT command.
+
+=item I<pg_cmd_status>
+
+Returns the name of the last command type. Possible types are: INSERT,
+DELETE, UPDATE, SELECT.
+
+=back
+
+
+C<DBD::Pg> has no private methods.
+
+
+=head2 Positioned updates and deletes
+
+Postgresql does not support positioned updates or deletes.
+
+
+=head2 Differences from the DBI Specification
+
+C<DBD::Pg> has no significant differences in behavior from the
+current DBI specification.
+
+Note that C<DBD::Pg> does not fully parse the statement until
+it's executed. Thus attributes like I<$sth-E<gt>{NUM_OF_FIELDS}> are not
+available until after C<$sth-E<gt>execute> has been called. This is valid
+behaviour but is important to note when porting applications
+originally written for other drivers.
+
+
+=head2 URLs to More Database/Driver Specific Information
+
+ http://www.postgresql.org
+
+
+=head2 Concurrent use of Multiple Handles
+
+C<DBD::Pg> supports an unlimited number of concurrent database
+connections to one or more databases.
+
+It also supports the preparation and execution of a new statement
+handle while still fetching data from another statement handle,
+provided it is
+associated with the same database handle.
+
+
+=head2 Other Significant Database or Driver Features
+
+Postgres offers substantial additional power by incorporating the
+following four additional basic concepts in such a way that users can
+easily extend the system: classes, inheritance, types, and functions.
+
+Other features provide additional power and flexibility: constraints,
+triggers, rules, transaction integrity, procedural languages, and large objects.
+
+It's also free Open Source Software with an active community of developers.
+
+=cut
+
+# This driver summary for DBD::Pg is Copyright (c) 1999 Tim Bunce
+# and Edmund Mergl.
+# $Id: dbd-pg.pod,v 1.1 2004-04-29 09:21:28 ivan Exp $
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.c b/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.c
new file mode 100644
index 0000000..55f4ee7
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.c
@@ -0,0 +1,2024 @@
+/*
+ $Id: dbdimp.c,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+ Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+ Copyright (c) 2002 Jeffrey W. Baker
+ Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+
+ You may distribute under the terms of either the GNU General Public
+ License or the Artistic License, as specified in the Perl README file.
+
+*/
+
+
+/*
+ hard-coded OIDs: (here we need the postgresql types)
+ pg_sql_type() 1042 (bpchar), 1043 (varchar)
+ ddb_st_fetch() 1042 (bpchar), 16 (bool)
+ ddb_preparse() 1043 (varchar)
+ pgtype_bind_ok()
+*/
+
+#include "Pg.h"
+
+/* XXX DBI should provide a better version of this */
+#define IS_DBI_HANDLE(h) (SvROK(h) && SvTYPE(SvRV(h)) == SVt_PVHV && SvRMAGICAL(SvRV(h)) && (SvMAGIC(SvRV(h)))->mg_type == 'P')
+
+DBISTATE_DECLARE;
+
+/* hard-coded array delimiter */
+static char* array_delimiter = ",";
+
+static void dbd_preparse (imp_sth_t *imp_sth, char *statement);
+
+
+void
+dbd_init (dbistate)
+ dbistate_t *dbistate;
+{
+ DBIS = dbistate;
+}
+
+
+int
+dbd_discon_all (drh, imp_drh)
+ SV *drh;
+ imp_drh_t *imp_drh;
+{
+ dTHR;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_discon_all\n"); }
+
+ /* The disconnect_all concept is flawed and needs more work */
+ if (!dirty && !SvTRUE(perl_get_sv("DBI::PERL_ENDING",0))) {
+ sv_setiv(DBIc_ERR(imp_drh), (IV)1);
+ sv_setpv(DBIc_ERRSTR(imp_drh),
+ (char*)"disconnect_all not implemented");
+ DBIh_EVENT2(drh, ERROR_event,
+ DBIc_ERR(imp_drh), DBIc_ERRSTR(imp_drh));
+ return FALSE;
+ }
+ if (perl_destruct_level) {
+ perl_destruct_level = 0;
+ }
+ return FALSE;
+}
+
+
+/* Database specific error handling. */
+
+void
+pg_error (h, error_num, error_msg)
+ SV *h;
+ int error_num;
+ char *error_msg;
+{
+ D_imp_xxh(h);
+ char *err, *src, *dst;
+ int len = strlen(error_msg);
+
+ err = (char *)malloc(len + 1);
+ if (!err) {
+ return;
+ }
+ src = error_msg;
+ dst = err;
+
+ /* copy error message without trailing newlines */
+ while (*src != '\0' && *src != '\n') {
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+
+ sv_setiv(DBIc_ERR(imp_xxh), (IV)error_num); /* set err early */
+ sv_setpv(DBIc_ERRSTR(imp_xxh), (char*)err);
+ DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), DBIc_ERRSTR(imp_xxh));
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "%s error %d recorded: %s\n", err, error_num, SvPV(DBIc_ERRSTR(imp_xxh),na)); }
+ free(err);
+}
+
+static int
+pgtype_bind_ok (dbtype)
+ int dbtype;
+{
+ /* basically we support types that can be returned as strings */
+ switch(dbtype) {
+ case 16: /* bool */
+ case 17: /* bytea */
+ case 18: /* char */
+ case 20: /* int8 */
+ case 21: /* int2 */
+ case 23: /* int4 */
+ case 25: /* text */
+ case 26: /* oid */
+ case 700: /* float4 */
+ case 701: /* float8 */
+ case 702: /* abstime */
+ case 703: /* reltime */
+ case 704: /* tinterval */
+ case 1042: /* bpchar */
+ case 1043: /* varchar */
+ case 1082: /* date */
+ case 1083: /* time */
+ case 1184: /* datetime */
+ case 1186: /* timespan */
+ case 1296: /* timestamp */
+ return 1;
+ }
+ return 0;
+}
+
+
+/* ================================================================== */
+
+int
+pg_db_login (dbh, imp_dbh, dbname, uid, pwd)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+ char *dbname;
+ char *uid;
+ char *pwd;
+{
+ dTHR;
+
+ char *conn_str;
+ char *src;
+ char *dest;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "pg_db_login\n"); }
+
+ /* build connect string */
+ /* DBD-Pg syntax: 'dbname=dbname;host=host;port=port' */
+ /* pgsql syntax: 'dbname=dbname host=host port=port user=uid password=pwd' */
+
+ conn_str = (char *)malloc(strlen(dbname) + strlen(uid) + strlen(pwd) + 16 + 1);
+ if (! conn_str) {
+ return 0;
+ }
+
+ src = dbname;
+ dest = conn_str;
+ while (*src) {
+ if (*src != ';') {
+ *dest++ = *src++;
+ continue;
+ }
+ *dest++ = ' ';
+ src++;
+ }
+ *dest = '\0';
+
+ if (strlen(uid)) {
+ strcat(conn_str, " user=");
+ strcat(conn_str, uid);
+ }
+ if (strlen(uid) && strlen(pwd)) {
+ strcat(conn_str, " password=");
+ strcat(conn_str, pwd);
+ }
+
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "pg_db_login: conn_str = >%s<\n", conn_str); }
+
+ /* make a connection to the database */
+ imp_dbh->conn = PQconnectdb(conn_str);
+ free(conn_str);
+
+ /* check to see that the backend connection was successfully made */
+ if (PQstatus(imp_dbh->conn) != CONNECTION_OK) {
+ pg_error(dbh, PQstatus(imp_dbh->conn), PQerrorMessage(imp_dbh->conn));
+ PQfinish(imp_dbh->conn);
+ return 0;
+ }
+
+ imp_dbh->init_commit = 1; /* initialize AutoCommit */
+ imp_dbh->pg_auto_escape = 1; /* initialize pg_auto_escape */
+ imp_dbh->pg_bool_tf = 0; /* initialize pg_bool_tf */
+
+ DBIc_IMPSET_on(imp_dbh); /* imp_dbh set up now */
+ DBIc_ACTIVE_on(imp_dbh); /* call disconnect before freeing */
+ return 1;
+}
+
+
+int
+dbd_db_getfd (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ char id;
+ SV* retsv;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_getfd\n"); }
+
+ return PQsocket(imp_dbh->conn);
+}
+
+SV *
+dbd_db_pg_notifies (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ char id;
+ PGnotify* notify;
+ AV* ret;
+ SV* retsv;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_pg_notifies\n"); }
+
+ PQconsumeInput(imp_dbh->conn);
+
+ notify = PQnotifies(imp_dbh->conn);
+
+ if (!notify) return &sv_undef;
+
+ ret=newAV();
+
+ av_push(ret, newSVpv(notify->relname,0) );
+ av_push(ret, newSViv(notify->be_pid) );
+
+ /* Should free notify memory with PQfreemem() */
+
+ retsv = newRV(sv_2mortal((SV*)ret));
+
+ return retsv;
+}
+
+int
+dbd_db_ping (dbh)
+ SV *dbh;
+{
+ char id;
+ D_imp_dbh(dbh);
+ PGresult* result;
+ ExecStatusType status;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_ping\n"); }
+
+ if (NULL != imp_dbh->conn) {
+ result = PQexec(imp_dbh->conn, " ");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+
+ if (PGRES_EMPTY_QUERY != status) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int
+dbd_db_commit (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_commit\n"); }
+
+ /* no commit if AutoCommit = on */
+ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) != FALSE) {
+ return 0;
+ }
+
+ if (NULL != imp_dbh->conn) {
+ PGresult* result = 0;
+ ExecStatusType commitstatus, beginstatus;
+
+ /* execute commit */
+ result = PQexec(imp_dbh->conn, "commit");
+ commitstatus = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+
+ /* check result */
+ if (commitstatus != PGRES_COMMAND_OK) {
+ /* Only put the error message in DBH->errstr */
+ pg_error(dbh, commitstatus, PQerrorMessage(imp_dbh->conn));
+ }
+
+ /* start new transaction. AutoCommit must be FALSE, ref. 20 lines up */
+ result = PQexec(imp_dbh->conn, "begin");
+ beginstatus = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (beginstatus != PGRES_COMMAND_OK) {
+ /* Maybe add some loud barf here? Raising some very high error? */
+ pg_error(dbh, beginstatus, "begin failed\n");
+ return 0;
+ }
+
+ /* if the initial COMMIT failed, return 0 now */
+ if (commitstatus != PGRES_COMMAND_OK) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int
+dbd_db_rollback (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_rollback\n"); }
+
+ /* no rollback if AutoCommit = on */
+ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) != FALSE) {
+ return 0;
+ }
+
+ if (NULL != imp_dbh->conn) {
+ PGresult* result = 0;
+ ExecStatusType status;
+
+ /* execute rollback */
+ result = PQexec(imp_dbh->conn, "rollback");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+
+ /* check result */
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(dbh, status, "rollback failed\n");
+ return 0;
+ }
+
+ /* start new transaction. AutoCommit must be FALSE, ref. 20 lines up */
+ result = PQexec(imp_dbh->conn, "begin");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(dbh, status, "begin failed\n");
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int
+dbd_db_disconnect (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ dTHR;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_disconnect\n"); }
+
+ /* We assume that disconnect will always work */
+ /* since most errors imply already disconnected. */
+ DBIc_ACTIVE_off(imp_dbh);
+
+ if (NULL != imp_dbh->conn) {
+ /* rollback if AutoCommit = off */
+ if (DBIc_has(imp_dbh, DBIcf_AutoCommit) == FALSE) {
+ PGresult* result = 0;
+ ExecStatusType status;
+ result = PQexec(imp_dbh->conn, "rollback");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(dbh, status, "rollback failed\n");
+ return 0;
+ }
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "dbd_db_disconnect: AutoCommit=off -> rollback\n"); }
+ }
+
+ PQfinish(imp_dbh->conn);
+
+ imp_dbh->conn = NULL;
+ }
+
+ /* We don't free imp_dbh since a reference still exists */
+ /* The DESTROY method is the only one to 'free' memory. */
+ /* Note that statement objects may still exists for this dbh! */
+ return 1;
+}
+
+
+void
+dbd_db_destroy (dbh, imp_dbh)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_destroy\n"); }
+
+ if (DBIc_ACTIVE(imp_dbh)) {
+ dbd_db_disconnect(dbh, imp_dbh);
+ }
+
+ /* Nothing in imp_dbh to be freed */
+ DBIc_IMPSET_off(imp_dbh);
+}
+
+
+int
+dbd_db_STORE_attrib (dbh, imp_dbh, keysv, valuesv)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+ SV *keysv;
+ SV *valuesv;
+{
+ STRLEN kl;
+ char *key = SvPV(keysv,kl);
+ int newval = SvTRUE(valuesv);
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_STORE\n"); }
+
+ if (kl==10 && strEQ(key, "AutoCommit")) {
+ int oldval = DBIc_has(imp_dbh, DBIcf_AutoCommit);
+ DBIc_set(imp_dbh, DBIcf_AutoCommit, newval);
+ if (oldval == FALSE && newval != FALSE && imp_dbh->init_commit) {
+ /* do nothing, fall through */
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "dbd_db_STORE: initialize AutoCommit to on\n"); }
+ } else if (oldval == FALSE && newval != FALSE) {
+ if (NULL != imp_dbh->conn) {
+ /* commit any outstanding changes */
+ PGresult* result = 0;
+ ExecStatusType status;
+ result = PQexec(imp_dbh->conn, "commit");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(dbh, status, "commit failed\n");
+ return 0;
+ }
+ }
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "dbd_db_STORE: switch AutoCommit to on: commit\n"); }
+ } else if ((oldval != FALSE && newval == FALSE) || (oldval == FALSE && newval == FALSE && imp_dbh->init_commit)) {
+ if (NULL != imp_dbh->conn) {
+ /* start new transaction */
+ PGresult* result = 0;
+ ExecStatusType status;
+ result = PQexec(imp_dbh->conn, "begin");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(dbh, status, "begin failed\n");
+ return 0;
+ }
+ }
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "dbd_db_STORE: switch AutoCommit to off: begin\n"); }
+ }
+ /* only needed once */
+ imp_dbh->init_commit = 0;
+ return 1;
+ } else if (kl==14 && strEQ(key, "pg_auto_escape")) {
+ imp_dbh->pg_auto_escape = newval;
+ } else if (kl==10 && strEQ(key, "pg_bool_tf")) {
+ imp_dbh->pg_bool_tf = newval;
+#ifdef SvUTF8_off
+ } else if (kl==14 && strEQ(key, "pg_enable_utf8")) {
+ imp_dbh->pg_enable_utf8 = newval;
+#endif
+ } else {
+ return 0;
+ }
+}
+
+
+SV *
+dbd_db_FETCH_attrib (dbh, imp_dbh, keysv)
+ SV *dbh;
+ imp_dbh_t *imp_dbh;
+ SV *keysv;
+{
+ STRLEN kl;
+ char *key = SvPV(keysv,kl);
+ SV *retsv = Nullsv;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_db_FETCH\n"); }
+
+ if (kl==10 && strEQ(key, "AutoCommit")) {
+ retsv = boolSV(DBIc_has(imp_dbh, DBIcf_AutoCommit));
+ } else if (kl==14 && strEQ(key, "pg_auto_escape")) {
+ retsv = newSViv((IV)imp_dbh->pg_auto_escape);
+ } else if (kl==10 && strEQ(key, "pg_bool_tf")) {
+ retsv = newSViv((IV)imp_dbh->pg_bool_tf);
+#ifdef SvUTF8_off
+ } else if (kl==14 && strEQ(key, "pg_enable_utf8")) {
+ retsv = newSViv((IV)imp_dbh->pg_enable_utf8);
+#endif
+ } else if (kl==11 && strEQ(key, "pg_INV_READ")) {
+ retsv = newSViv((IV)INV_READ);
+ } else if (kl==12 && strEQ(key, "pg_INV_WRITE")) {
+ retsv = newSViv((IV)INV_WRITE);
+ }
+
+ if (!retsv) {
+ return Nullsv;
+ }
+ if (retsv == &sv_yes || retsv == &sv_no) {
+ return retsv; /* no need to mortalize yes or no */
+ }
+ return sv_2mortal(retsv);
+}
+
+
+/* driver specific functins */
+
+
+int
+pg_db_lo_open (dbh, lobjId, mode)
+ SV *dbh;
+ unsigned int lobjId;
+ int mode;
+{
+ D_imp_dbh(dbh);
+ return lo_open(imp_dbh->conn, lobjId, mode);
+}
+
+
+int
+pg_db_lo_close (dbh, fd)
+ SV *dbh;
+ int fd;
+{
+ D_imp_dbh(dbh);
+ return lo_close(imp_dbh->conn, fd);
+}
+
+
+int
+pg_db_lo_read (dbh, fd, buf, len)
+ SV *dbh;
+ int fd;
+ char *buf;
+ int len;
+{
+ D_imp_dbh(dbh);
+ return lo_read(imp_dbh->conn, fd, buf, len);
+}
+
+
+int
+pg_db_lo_write (dbh, fd, buf, len)
+ SV *dbh;
+ int fd;
+ char *buf;
+ int len;
+{
+ D_imp_dbh(dbh);
+ return lo_write(imp_dbh->conn, fd, buf, len);
+}
+
+
+int
+pg_db_lo_lseek (dbh, fd, offset, whence)
+ SV *dbh;
+ int fd;
+ int offset;
+ int whence;
+{
+ D_imp_dbh(dbh);
+ return lo_lseek(imp_dbh->conn, fd, offset, whence);
+}
+
+
+unsigned int
+pg_db_lo_creat (dbh, mode)
+ SV *dbh;
+ int mode;
+{
+ D_imp_dbh(dbh);
+ return lo_creat(imp_dbh->conn, mode);
+}
+
+
+int
+pg_db_lo_tell (dbh, fd)
+ SV *dbh;
+ int fd;
+{
+ D_imp_dbh(dbh);
+ return lo_tell(imp_dbh->conn, fd);
+}
+
+
+int
+pg_db_lo_unlink (dbh, lobjId)
+ SV *dbh;
+ unsigned int lobjId;
+{
+ D_imp_dbh(dbh);
+ return lo_unlink(imp_dbh->conn, lobjId);
+}
+
+
+unsigned int
+pg_db_lo_import (dbh, filename)
+ SV *dbh;
+ char *filename;
+{
+ D_imp_dbh(dbh);
+ return lo_import(imp_dbh->conn, filename);
+}
+
+
+int
+pg_db_lo_export (dbh, lobjId, filename)
+ SV *dbh;
+ unsigned int lobjId;
+ char *filename;
+{
+ D_imp_dbh(dbh);
+ return lo_export(imp_dbh->conn, lobjId, filename);
+}
+
+
+int
+pg_db_putline (dbh, buffer)
+ SV *dbh;
+ char *buffer;
+{
+ D_imp_dbh(dbh);
+ return PQputline(imp_dbh->conn, buffer);
+}
+
+
+int
+pg_db_getline (dbh, buffer, length)
+ SV *dbh;
+ char *buffer;
+ int length;
+{
+ D_imp_dbh(dbh);
+ return PQgetline(imp_dbh->conn, buffer, length);
+}
+
+
+int
+pg_db_endcopy (dbh)
+ SV *dbh;
+{
+ D_imp_dbh(dbh);
+ return PQendcopy(imp_dbh->conn);
+}
+
+
+/* ================================================================== */
+
+
+int
+dbd_st_prepare (sth, imp_sth, statement, attribs)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ char *statement;
+ SV *attribs;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_prepare: statement = >%s<\n", statement); }
+
+ /* scan statement for '?', ':1' and/or ':foo' style placeholders */
+ dbd_preparse(imp_sth, statement);
+
+ /* initialize new statement handle */
+ imp_sth->result = 0;
+ imp_sth->cur_tuple = 0;
+
+ DBIc_IMPSET_on(imp_sth);
+ return 1;
+}
+
+
+static void
+dbd_preparse (imp_sth, statement)
+ imp_sth_t *imp_sth;
+ char *statement;
+{
+ bool in_literal = FALSE;
+ char in_comment = '\0';
+ char *src, *start, *dest;
+ phs_t phs_tpl;
+ SV *phs_sv;
+ int idx=0;
+ char *style="", *laststyle=Nullch;
+ STRLEN namelen;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_preparse: statement = >%s<\n", statement); }
+
+ /* allocate room for copy of statement with spare capacity */
+ /* for editing '?' or ':1' into ':p1'. */
+ /* */
+ /* Note: the calculated length used here for the safemalloc */
+ /* isn't related in any way to the actual worst case length */
+ /* of the translated statement, but allowing for 3 times */
+ /* the length of the original statement should be safe... */
+ imp_sth->statement = (char*)safemalloc(strlen(statement) * 3 + 1);
+
+ /* initialise phs ready to be cloned per placeholder */
+ memset(&phs_tpl, 0, sizeof(phs_tpl));
+ phs_tpl.ftype = 1043; /* VARCHAR */
+
+ src = statement;
+ dest = imp_sth->statement;
+ while(*src) {
+
+ if (in_comment) {
+ /* SQL-style and C++-style */
+ if ((in_comment == '-' || in_comment == '/') && *src == '\n') {
+ in_comment = '\0';
+ }
+ /* C-style */
+ else if (in_comment == '*' && *src == '*' && *(src+1) == '/') {
+ *dest++ = *src++; /* avoids asterisk-slash-asterisk issues */
+ in_comment = '\0';
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ if (in_literal) {
+ /* check if literal ends but keep quotes in literal */
+ if (*src == in_literal) {
+ int bs=0;
+ char *str;
+ str = src-1;
+ while (*(str-bs) == '\\')
+ bs++;
+ if (!(bs & 1))
+ in_literal = 0;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* Look for comments: SQL-style or C++-style or C-style */
+ if ((*src == '-' && *(src+1) == '-') ||
+ (*src == '/' && *(src+1) == '/') ||
+ (*src == '/' && *(src+1) == '*'))
+ {
+ in_comment = *(src+1);
+ /* We know *src & the next char are to be copied, so do */
+ /* it. In the case of C-style comments, it happens to */
+ /* help us avoid slash-asterisk-slash oddities. */
+ *dest++ = *src++;
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* check if no placeholders */
+ if (*src != ':' && *src != '?') {
+ if (*src == '\'' || *src == '"') {
+ in_literal = *src;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* check for cast operator */
+ if (*src == ':' && (*(src-1) == ':' || *(src+1) == ':')) {
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* only here for : or ? outside of a comment or literal and no cast */
+
+ start = dest; /* save name inc colon */
+ *dest++ = *src++;
+ if (*start == '?') { /* X/Open standard */
+ sprintf(start,":p%d", ++idx); /* '?' -> ':p1' (etc) */
+ dest = start+strlen(start);
+ style = "?";
+
+ } else if (isDIGIT(*src)) { /* ':1' */
+ idx = atoi(src);
+ *dest++ = 'p'; /* ':1'->':p1' */
+ if (idx <= 0) {
+ croak("Placeholder :%d invalid, placeholders must be >= 1", idx);
+ }
+ while(isDIGIT(*src)) {
+ *dest++ = *src++;
+ }
+ style = ":1";
+
+ } else if (isALNUM(*src)) { /* ':foo' */
+ while(isALNUM(*src)) { /* includes '_' */
+ *dest++ = *src++;
+ }
+ style = ":foo";
+ } else { /* perhaps ':=' PL/SQL construct */
+ continue;
+ }
+ *dest = '\0'; /* handy for debugging */
+ namelen = (dest-start);
+ if (laststyle && style != laststyle) {
+ croak("Can't mix placeholder styles (%s/%s)",style,laststyle);
+ }
+ laststyle = style;
+ if (imp_sth->all_params_hv == NULL) {
+ imp_sth->all_params_hv = newHV();
+ }
+ phs_tpl.sv = &sv_undef;
+ phs_sv = newSVpv((char*)&phs_tpl, sizeof(phs_tpl)+namelen+1);
+ hv_store(imp_sth->all_params_hv, start, namelen, phs_sv, 0);
+ strcpy( ((phs_t*)(void*)SvPVX(phs_sv))->name, start);
+ }
+ *dest = '\0';
+ if (imp_sth->all_params_hv) {
+ DBIc_NUM_PARAMS(imp_sth) = (int)HvKEYS(imp_sth->all_params_hv);
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, " dbd_preparse scanned %d distinct placeholders\n", (int)DBIc_NUM_PARAMS(imp_sth)); }
+ }
+}
+
+
+/* if it LOOKS like a string, this function will determine whether the type needs to be surrounded in single quotes */
+static int pg_sql_needquote (sql_type)
+ int sql_type;
+{
+ if (sql_type > 1000 || sql_type == 17 || sql_type == 25 ) {
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static int
+pg_sql_type (imp_sth, name, sql_type)
+ imp_sth_t *imp_sth;
+ char *name;
+ int sql_type;
+{
+ switch (sql_type) {
+ case SQL_CHAR:
+ return 1042; /* bpchar */
+ case SQL_NUMERIC:
+ return 700; /* float4 */
+ case SQL_DECIMAL:
+ return 700; /* float4 */
+ case SQL_INTEGER:
+ return 23; /* int4 */
+ case SQL_SMALLINT:
+ return 21; /* int2 */
+ case SQL_FLOAT:
+ return 700; /* float4 */
+ case SQL_REAL:
+ return 701; /* float8 */
+ case SQL_DOUBLE:
+ return 20; /* int8 */
+ case SQL_VARCHAR:
+ return 1043; /* varchar */
+ case SQL_BINARY:
+ return 17; /* bytea */
+ default:
+ if (DBIc_WARN(imp_sth) && imp_sth && name) {
+ warn("SQL type %d for '%s' is not fully supported, bound as VARCHAR instead",
+ sql_type, name);
+ }
+ return pg_sql_type(imp_sth, name, SQL_VARCHAR);
+ }
+}
+
+static int
+sql_pg_type (imp_sth, name, sql_type)
+ imp_sth_t *imp_sth;
+ char *name;
+ int sql_type;
+{
+ if (dbis->debug >= 1) {
+ PerlIO_printf(DBILOGFP, "sql_pg_type name '%s' type '%d'\n", name, sql_type );
+ }
+
+ switch (sql_type) {
+ case 17: /* bytea */
+ return SQL_BINARY;
+ case 20: /* int8 */
+ return SQL_DOUBLE;
+ case 21: /* int2 */
+ return SQL_SMALLINT;
+ case 23: /* int4 */
+ return SQL_INTEGER;
+ case 700: /* float4 */
+ return SQL_NUMERIC;
+ case 701: /* float8 */
+ return SQL_REAL;
+ case 1042: /* bpchar */
+ return SQL_CHAR;
+ case 1043: /* varchar */
+ return SQL_VARCHAR;
+ case 1082: /* date */
+ return SQL_DATE;
+ case 1083: /* time */
+ return SQL_TIME;
+ case 1296: /* date */
+ return SQL_TIMESTAMP;
+
+ default:
+ return sql_type;
+ }
+}
+
+
+static int
+dbd_rebind_ph (sth, imp_sth, phs)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ phs_t *phs;
+{
+ STRLEN value_len;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_rebind\n"); }
+
+ /* convert to a string ASAP */
+ if (!SvPOK(phs->sv) && SvOK(phs->sv)) {
+ sv_2pv(phs->sv, &na);
+ }
+
+ if (dbis->debug >= 2) {
+ char *val = neatsvpv(phs->sv,0);
+ PerlIO_printf(DBILOGFP, " bind %s <== %.1000s (", phs->name, val);
+ if (SvOK(phs->sv)) {
+ PerlIO_printf(DBILOGFP, "size %ld/%ld/%ld, ", (long)SvCUR(phs->sv),(long)SvLEN(phs->sv),phs->maxlen);
+ } else {
+ PerlIO_printf(DBILOGFP, "NULL, ");
+ }
+ PerlIO_printf(DBILOGFP, "ptype %d, otype %d%s)\n", (int)SvTYPE(phs->sv), phs->ftype, (phs->is_inout) ? ", inout" : "");
+ }
+
+ /* At the moment we always do sv_setsv() and rebind. */
+ /* Later we may optimise this so that more often we can */
+ /* just copy the value & length over and not rebind. */
+
+ if (phs->is_inout) { /* XXX */
+ if (SvREADONLY(phs->sv)) {
+ croak(no_modify);
+ }
+ /* phs->sv _is_ the real live variable, it may 'mutate' later */
+ /* pre-upgrade high to reduce risk of SvPVX realloc/move */
+ (void)SvUPGRADE(phs->sv, SVt_PVNV);
+ /* ensure room for result, 28 is magic number (see sv_2pv) */
+ SvGROW(phs->sv, (phs->maxlen < 28) ? 28 : phs->maxlen+1);
+ }
+ else {
+ /* phs->sv is copy of real variable, upgrade to at least string */
+ (void)SvUPGRADE(phs->sv, SVt_PV);
+ }
+
+ /* At this point phs->sv must be at least a PV with a valid buffer, */
+ /* even if it's undef (null) */
+ /* Here we set phs->progv, phs->indp, and value_len. */
+ if (SvOK(phs->sv)) {
+ phs->progv = SvPV(phs->sv, value_len);
+ phs->indp = 0;
+ }
+ else { /* it's null but point to buffer in case it's an out var */
+ phs->progv = SvPVX(phs->sv);
+ phs->indp = -1;
+ value_len = 0;
+ }
+ phs->sv_type = SvTYPE(phs->sv); /* part of mutation check */
+ phs->maxlen = SvLEN(phs->sv)-1; /* avail buffer space */
+ if (phs->maxlen < 0) { /* can happen with nulls */
+ phs->maxlen = 0;
+ }
+
+ phs->alen = value_len + phs->alen_incnull;
+
+ imp_sth->all_params_len += SvOK(phs->sv) ? phs->alen : 4; /* NULL */
+
+ if (dbis->debug >= 3) {
+ PerlIO_printf(DBILOGFP, " bind %s <== '%.*s' (size %ld/%ld, otype %d, indp %d)\n",
+ phs->name,
+ (int)(phs->alen>SvIV(DBIS->neatsvpvlen) ? SvIV(DBIS->neatsvpvlen) : phs->alen),
+ (phs->progv) ? phs->progv : "",
+ (long)phs->alen, (long)phs->maxlen, phs->ftype, phs->indp);
+ }
+
+ return 1;
+}
+
+
+void dereference(value)
+SV** value;
+{
+ AV* buf;
+ SV* val;
+ char *src;
+ int is_ref;
+ STRLEN len;
+
+ if (SvTYPE(SvRV(*value)) != SVt_PVAV)
+ croak("Not an array reference (%s)", neatsvpv(*value,0));
+
+ buf = (AV *) SvRV(*value);
+ sv_setpv(*value, "{");
+ while ( SvOK(val = av_shift(buf)) ) {
+ is_ref = SvROK(val);
+ if (is_ref)
+ dereference(&val);
+ else
+ sv_catpv(*value, "\"");
+ /* Quote */
+ src = SvPV(val, len);
+ while (len--) {
+ if (!is_ref && *src == '\"')
+ sv_catpv(*value, "\\");
+ sv_catpvn(*value, src++, 1);
+ }
+ /* End of quote */
+ if (!is_ref)
+ sv_catpv(*value, "\"");
+ if (av_len(buf) > -1)
+ sv_catpv(*value, array_delimiter);
+ }
+ sv_catpv(*value, "}");
+ av_clear(buf);
+}
+
+int
+dbd_bind_ph (sth, imp_sth, ph_namesv, newvalue, sql_type, attribs, is_inout, maxlen)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ SV *ph_namesv;
+ SV *newvalue;
+ IV sql_type;
+ SV *attribs;
+ int is_inout;
+ IV maxlen;
+{
+ SV **phs_svp;
+ STRLEN name_len;
+ char *name;
+ char namebuf[30];
+ phs_t *phs;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_bind_ph\n"); }
+
+ /* check if placeholder was passed as a number */
+
+ if (SvGMAGICAL(ph_namesv)) { /* eg if from tainted expression */
+ mg_get(ph_namesv);
+ }
+ if (!SvNIOKp(ph_namesv)) {
+ name = SvPV(ph_namesv, name_len);
+ }
+ if (SvNIOKp(ph_namesv) || (name && isDIGIT(name[0]))) {
+ sprintf(namebuf, ":p%d", (int)SvIV(ph_namesv));
+ name = namebuf;
+ name_len = strlen(name);
+ }
+ assert(name != Nullch);
+
+ if (SvTYPE(newvalue) > SVt_PVLV) { /* hook for later array logic */
+ croak("Can't bind a non-scalar value (%s)", neatsvpv(newvalue,0));
+ }
+ if (SvROK(newvalue) && !IS_DBI_HANDLE(newvalue)) {
+ /* dbi handle allowed for cursor variables */
+ dereference(&newvalue);
+ }
+ if (SvTYPE(newvalue) == SVt_PVLV && is_inout) { /* may allow later */
+ croak("Can't bind ``lvalue'' mode scalar as inout parameter (currently)");
+ }
+
+ if (dbis->debug >= 2) {
+ PerlIO_printf(DBILOGFP, " bind %s <== %s (type %ld", name, neatsvpv(newvalue,0), (long)sql_type);
+ if (is_inout) {
+ PerlIO_printf(DBILOGFP, ", inout 0x%lx, maxlen %ld", (long)newvalue, (long)maxlen);
+ }
+ if (attribs) {
+ PerlIO_printf(DBILOGFP, ", attribs: %s", neatsvpv(attribs,0));
+ }
+ PerlIO_printf(DBILOGFP, ")\n");
+ }
+
+ phs_svp = hv_fetch(imp_sth->all_params_hv, name, name_len, 0);
+ if (phs_svp == NULL) {
+ croak("Can't bind unknown placeholder '%s' (%s)", name, neatsvpv(ph_namesv,0));
+ }
+ phs = (phs_t*)(void*)SvPVX(*phs_svp); /* placeholder struct */
+
+ if (phs->sv == &sv_undef) { /* first bind for this placeholder */
+ phs->ftype = 1043; /* our default type VARCHAR */
+ phs->is_inout = is_inout;
+ if (is_inout) {
+ /* phs->sv assigned in the code below */
+ ++imp_sth->has_inout_params;
+ /* build array of phs's so we can deal with out vars fast */
+ if (!imp_sth->out_params_av) {
+ imp_sth->out_params_av = newAV();
+ }
+ av_push(imp_sth->out_params_av, SvREFCNT_inc(*phs_svp));
+ }
+
+ if (attribs) { /* only look for pg_type on first bind of var */
+ SV **svp;
+ /* Setup / Clear attributes as defined by attribs. */
+ /* XXX If attribs is EMPTY then reset attribs to default? */
+ if ( (svp = hv_fetch((HV*)SvRV(attribs), "pg_type", 7, 0)) != NULL) {
+ int pg_type = SvIV(*svp);
+ if (!pgtype_bind_ok(pg_type)) {
+ croak("Can't bind %s, pg_type %d not supported by DBD::Pg", phs->name, pg_type);
+ }
+ if (sql_type) {
+ croak("Can't specify both TYPE (%d) and pg_type (%d) for %s", sql_type, pg_type, phs->name);
+ }
+ phs->ftype = pg_type;
+ }
+ }
+ if (sql_type) {
+ /* SQL_BINARY (-2) is deprecated. */
+ if (sql_type == -2 && DBIc_WARN(imp_sth)) {
+ warn("Use of SQL type SQL_BINARY (%d) is deprecated. Use { pg_type => DBD::Pg::PG_BYTEA } instead.", sql_type);
+ }
+ phs->ftype = pg_sql_type(imp_sth, phs->name, sql_type);
+ }
+ } /* was first bind for this placeholder */
+
+ /* check later rebinds for any changes */
+ else if (is_inout || phs->is_inout) {
+ croak("Can't rebind or change param %s in/out mode after first bind (%d => %d)", phs->name, phs->is_inout , is_inout);
+ }
+ else if (sql_type && phs->ftype != pg_sql_type(imp_sth, phs->name, sql_type)) {
+ croak("Can't change TYPE of param %s to %d after initial bind", phs->name, sql_type);
+ }
+
+ phs->maxlen = maxlen; /* 0 if not inout */
+
+ if (!is_inout) { /* normal bind to take a (new) copy of current value */
+ if (phs->sv == &sv_undef) { /* (first time bind) */
+ phs->sv = newSV(0);
+ }
+ sv_setsv(phs->sv, newvalue);
+ } else if (newvalue != phs->sv) {
+ if (phs->sv) {
+ SvREFCNT_dec(phs->sv);
+ }
+ phs->sv = SvREFCNT_inc(newvalue); /* point to live var */
+ }
+
+ return dbd_rebind_ph(sth, imp_sth, phs);
+}
+
+
+int
+dbd_st_execute (sth, imp_sth) /* <= -2:error, >=0:ok row count, (-1=unknown count) */
+ SV *sth;
+ imp_sth_t *imp_sth;
+{
+ dTHR;
+
+ D_imp_dbh_from_sth;
+ ExecStatusType status = -1;
+ char *cmdStatus;
+ char *cmdTuples;
+ char *statement;
+ int ret = -2;
+ int num_fields;
+ int i;
+ STRLEN len;
+ bool in_literal = FALSE;
+ char in_comment = '\0';
+ char *src;
+ char *dest;
+ char *val;
+ char namebuf[30];
+ phs_t *phs;
+ SV **svp;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_execute\n"); }
+
+ /*
+ here we get the statement from the statement handle where
+ it has been stored when creating a blank sth during prepare
+ svp = hv_fetch((HV *)SvRV(sth), "Statement", 9, FALSE);
+ statement = SvPV(*svp, na);
+ */
+
+ if (NULL == imp_dbh->conn) {
+ pg_error(sth, -1, "execute on disconnected handle");
+ return -2;
+ }
+
+ statement = imp_sth->statement;
+ if (! statement) {
+ /* are we prepared ? */
+ pg_error(sth, -1, "statement not prepared\n");
+ return -2;
+ }
+
+ /* do we have input parameters ? */
+ if ((int)DBIc_NUM_PARAMS(imp_sth) > 0) {
+ /*
+ we have to allocate some additional memory for possible escaping
+ quotes and backslashes:
+ max_len = length of statement
+ + total length of all params allowing for worst case all
+ characters binary-escaped (\\xxx)
+ + null terminator
+ Note: parameters look like :p1 at this point, so there's no
+ need to explicitly allow for surrounding quotes because '' is
+ shorter than :p1
+ */
+ int max_len = strlen(imp_sth->statement) + imp_sth->all_params_len * 5 + 1;
+ statement = (char*)safemalloc( max_len );
+ dest = statement;
+ src = imp_sth->statement;
+ /* scan statement for ':p1' style placeholders */
+ while(*src) {
+
+ if (in_comment) {
+ /* SQL-style and C++-style */
+ if ((in_comment == '-' || in_comment == '/') && *src == '\n') {
+ in_comment = '\0';
+ }
+ /* C-style */
+ else if (in_comment == '*' && *src == '*' && *(src+1) == '/') {
+ *dest++ = *src++; /* avoids asterisk-slash-asterisk issues */
+ in_comment = '\0';
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ if (in_literal) {
+ /* check if literal ends but keep quotes in literal */
+ if (*src == in_literal) {
+ int bs=0;
+ char *str;
+ str = src-1;
+ while (*(str-bs) == '\\')
+ bs++;
+ if (!(bs & 1))
+ in_literal = 0;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* Look for comments: SQL-style or C++-style or C-style */
+ if ((*src == '-' && *(src+1) == '-') ||
+ (*src == '/' && *(src+1) == '/') ||
+ (*src == '/' && *(src+1) == '*'))
+ {
+ in_comment = *(src+1);
+ /* We know *src & the next char are to be copied, so do */
+ /* it. In the case of C-style comments, it happens to */
+ /* help us avoid slash-asterisk-slash oddities. */
+ *dest++ = *src++;
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* check if no placeholders */
+ if (*src != ':' && *src != '?') {
+ if (*src == '\'' || *src == '"') {
+ in_literal = *src;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* check for cast operator */
+ if (*src == ':' && (*(src-1) == ':' || *(src+1) == ':')) {
+ *dest++ = *src++;
+ continue;
+ }
+
+
+ i = 0;
+ namebuf[i++] = *src++; /* ':' */
+ namebuf[i++] = *src++; /* 'p' */
+
+ while (isDIGIT(*src) && i < (sizeof(namebuf)-1) ) {
+ namebuf[i++] = *src++;
+ }
+ if ( i == (sizeof(namebuf) - 1)) {
+ pg_error(sth, -1, "namebuf buffer overrun\n");
+ return -2;
+ }
+ namebuf[i] = '\0';
+ svp = hv_fetch(imp_sth->all_params_hv, namebuf, i, 0);
+ if (svp == NULL) {
+ pg_error(sth, -1, "parameter unknown\n");
+ return -2;
+ }
+ /* get attribute */
+ phs = (phs_t*)(void*)SvPVX(*svp);
+ /* replace undef with NULL */
+ if(!SvOK(phs->sv)) {
+ val = "NULL";
+ len = 4;
+ } else {
+ val = SvPV(phs->sv, len);
+ }
+ /* quote string attribute */
+ if(!SvNIOK(phs->sv) && SvOK(phs->sv) && pg_sql_needquote(phs->ftype)) { /* avoid quoting NULL, tpf: bind_param as numeric */
+ *dest++ = '\'';
+ }
+ while (len--) {
+ if (imp_dbh->pg_auto_escape) {
+ /* if the parameter was bound as PG_BYTEA, escape nonprintables */
+ if (phs->ftype == 17 && !isPRINT(*val)) { /* escape null character */
+ dest+=snprintf(dest, (statement + max_len) - dest, "\\\\%03o", *((unsigned char *)val));
+ if (dest > statement + max_len) {
+ pg_error(sth, -1, "statement buffer overrun\n");
+ return -2;
+ }
+ val++;
+ continue; /* do not copy the null */
+ }
+ /* escape quote */
+ if (*val == '\'') {
+ *dest++ = '\'';
+ }
+ /* escape backslash */
+ if (*val == '\\') {
+ if (phs->ftype == 17) { /* four backslashes. really. */
+ *dest++ = '\\';
+ *dest++ = '\\';
+ *dest++ = '\\';
+ } else {
+ *dest++ = '\\';
+ }
+ }
+ }
+ /* copy attribute to statement */
+ *dest++ = *val++;
+ }
+ /* quote string attribute */
+ if(!SvNIOK(phs->sv) && SvOK(phs->sv) && pg_sql_needquote(phs->ftype)) { /* avoid quoting NULL, tpf: bind_param as numeric */
+ *dest++ = '\'';
+ }
+ }
+ *dest = '\0';
+ }
+
+ if (dbis->debug >= 2) { PerlIO_printf(DBILOGFP, "dbd_st_execute: statement = >%s<\n", statement); }
+
+ /* clear old result (if any) */
+ if (imp_sth->result) {
+ PQclear(imp_sth->result);
+ }
+
+ /* execute statement */
+ imp_sth->result = PQexec(imp_dbh->conn, statement);
+
+ /* free statement string in case of input parameters */
+ if ((int)DBIc_NUM_PARAMS(imp_sth) > 0) {
+ Safefree(statement);
+ }
+
+ /* check status */
+ status = imp_sth->result ? PQresultStatus(imp_sth->result) : -1;
+ cmdStatus = imp_sth->result ? (char *)PQcmdStatus(imp_sth->result) : "";
+ cmdTuples = imp_sth->result ? (char *)PQcmdTuples(imp_sth->result) : "";
+
+ if (PGRES_TUPLES_OK == status) {
+ /* select statement */
+ num_fields = PQnfields(imp_sth->result);
+ imp_sth->cur_tuple = 0;
+ DBIc_NUM_FIELDS(imp_sth) = num_fields;
+ DBIc_ACTIVE_on(imp_sth);
+ ret = PQntuples(imp_sth->result);
+ } else if (PGRES_COMMAND_OK == status) {
+ /* non-select statement */
+ if (! strncmp(cmdStatus, "DELETE", 6) || ! strncmp(cmdStatus, "INSERT", 6) || ! strncmp(cmdStatus, "UPDATE", 6)) {
+ ret = atoi(cmdTuples);
+ } else {
+ ret = -1;
+ }
+ } else if (PGRES_COPY_OUT == status || PGRES_COPY_IN == status) {
+ /* Copy Out/In data transfer in progress */
+ ret = -1;
+ } else {
+ pg_error(sth, status, PQerrorMessage(imp_dbh->conn));
+ ret = -2;
+ }
+
+ /* store the number of affected rows */
+ imp_sth->rows = ret;
+
+ return ret;
+}
+
+
+int
+is_high_bit_set(val)
+ char *val;
+{
+ while (*val++)
+ if (*val & 0x80) return 1;
+ return 0;
+}
+
+AV *
+dbd_st_fetch (sth, imp_sth)
+ SV *sth;
+ imp_sth_t *imp_sth;
+{
+ D_imp_dbh_from_sth;
+ int num_fields;
+ int i;
+ AV *av;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_fetch\n"); }
+
+ /* Check that execute() was executed sucessfully */
+ if ( !DBIc_ACTIVE(imp_sth) ) {
+ pg_error(sth, 1, "no statement executing\n");
+
+ return Nullav;
+ }
+
+ if ( imp_sth->cur_tuple == PQntuples(imp_sth->result) ) {
+ imp_sth->cur_tuple = 0;
+ DBIc_ACTIVE_off(imp_sth);
+ return Nullav; /* we reached the last tuple */
+ }
+
+ av = DBIS->get_fbav(imp_sth);
+ num_fields = AvFILL(av)+1;
+
+ for(i = 0; i < num_fields; ++i) {
+
+ SV *sv = AvARRAY(av)[i];
+ if (PQgetisnull(imp_sth->result, imp_sth->cur_tuple, i)) {
+ sv_setsv(sv, &sv_undef);
+ } else {
+ char *val = (char*)PQgetvalue(imp_sth->result, imp_sth->cur_tuple, i);
+ int val_len = strlen(val);
+ int type = PQftype(imp_sth->result, i); /* hopefully these hard coded values will not change */
+ if (16 == type && ! imp_dbh->pg_bool_tf) {
+ *val = (*val == 'f') ? '0' : '1'; /* bool: translate postgres into perl */
+ }
+ if (17 == type) { /* decode \001 -> chr(1), etc, in-place */
+ char *p = val; /* points to next available pos */
+ char *s = val; /* points to current scanning pos */
+ int c1,c2,c3;
+ while (*s) {
+ if (*s == '\\') {
+ if (*(s+1) == '\\') { /* double backslash */
+ *p++ = '\\';
+ s += 2;
+ continue;
+ }
+ else if ( isdigit(c1=(*(s+1))) &&
+ isdigit(c2=(*(s+2))) &&
+ isdigit(c3=(*(s+3))) ) {
+ *p++ = (c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0');
+ s += 4;
+ continue;
+ }
+ }
+ *p++ = *s++;
+ }
+ val_len = (p - val);
+ }
+ else if (1042 == type && DBIc_has(imp_sth,DBIcf_ChopBlanks)) {
+ char *str = val;
+ while((val_len > 0) && (str[val_len-1] == ' ')) {
+ val_len--;
+ }
+ val[val_len] = '\0';
+ }
+ sv_setpvn(sv, val, val_len);
+#ifdef SvUTF8_off
+ if (imp_dbh->pg_enable_utf8) {
+ SvUTF8_off(sv);
+ /* XXX Is this all the character data types? */
+ if (18 == type || 25 == type || 1042 ==type || 1043 == type) {
+ if (is_high_bit_set(val) && is_utf8_string(val, val_len))
+ SvUTF8_on(sv);
+ }
+ }
+#endif
+ }
+ }
+
+ imp_sth->cur_tuple += 1;
+
+ return av;
+}
+
+
+int
+dbd_st_blob_read (sth, imp_sth, lobjId, offset, len, destrv, destoffset)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ int lobjId;
+ long offset;
+ long len;
+ SV *destrv;
+ long destoffset;
+{
+ D_imp_dbh_from_sth;
+ int ret, lobj_fd, nbytes, nread;
+ PGresult* result;
+ ExecStatusType status;
+ SV *bufsv;
+ char *tmp;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_blob_read\n"); }
+ /* safety check */
+ if (lobjId <= 0) {
+ pg_error(sth, -1, "dbd_st_blob_read: lobjId <= 0");
+ return 0;
+ }
+ if (offset < 0) {
+ pg_error(sth, -1, "dbd_st_blob_read: offset < 0");
+ return 0;
+ }
+ if (len < 0) {
+ pg_error(sth, -1, "dbd_st_blob_read: len < 0");
+ return 0;
+ }
+ if (! SvROK(destrv)) {
+ pg_error(sth, -1, "dbd_st_blob_read: destrv not a reference");
+ return 0;
+ }
+ if (destoffset < 0) {
+ pg_error(sth, -1, "dbd_st_blob_read: destoffset < 0");
+ return 0;
+ }
+
+ /* dereference destination and ensure it's writable string */
+ bufsv = SvRV(destrv);
+ if (! destoffset) {
+ sv_setpvn(bufsv, "", 0);
+ }
+
+ /* execute begin
+ result = PQexec(imp_dbh->conn, "begin");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(sth, status, PQerrorMessage(imp_dbh->conn));
+ return 0;
+ }
+ */
+
+ /* open large object */
+ lobj_fd = lo_open(imp_dbh->conn, lobjId, INV_READ);
+ if (lobj_fd < 0) {
+ pg_error(sth, -1, PQerrorMessage(imp_dbh->conn));
+ return 0;
+ }
+
+ /* seek on large object */
+ if (offset > 0) {
+ ret = lo_lseek(imp_dbh->conn, lobj_fd, offset, SEEK_SET);
+ if (ret < 0) {
+ pg_error(sth, -1, PQerrorMessage(imp_dbh->conn));
+ return 0;
+ }
+ }
+
+ /* read from large object */
+ nread = 0;
+ SvGROW(bufsv, destoffset + nread + BUFSIZ + 1);
+ tmp = (SvPVX(bufsv)) + destoffset + nread;
+ while ((nbytes = lo_read(imp_dbh->conn, lobj_fd, tmp, BUFSIZ)) > 0) {
+ nread += nbytes;
+ /* break if user wants only a specified chunk */
+ if (len > 0 && nread > len) {
+ nread = len;
+ break;
+ }
+ SvGROW(bufsv, destoffset + nread + BUFSIZ + 1);
+ tmp = (SvPVX(bufsv)) + destoffset + nread;
+ }
+
+ /* terminate string */
+ SvCUR_set(bufsv, destoffset + nread);
+ *SvEND(bufsv) = '\0';
+
+ /* close large object */
+ ret = lo_close(imp_dbh->conn, lobj_fd);
+ if (ret < 0) {
+ pg_error(sth, -1, PQerrorMessage(imp_dbh->conn));
+ return 0;
+ }
+
+ /* execute end
+ result = PQexec(imp_dbh->conn, "end");
+ status = result ? PQresultStatus(result) : -1;
+ PQclear(result);
+ if (status != PGRES_COMMAND_OK) {
+ pg_error(sth, status, PQerrorMessage(imp_dbh->conn));
+ return 0;
+ }
+ */
+
+ return nread;
+}
+
+
+int
+dbd_st_rows (sth, imp_sth)
+ SV *sth;
+ imp_sth_t *imp_sth;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_rows\n"); }
+
+ return imp_sth->rows;
+}
+
+
+int
+dbd_st_finish (sth, imp_sth)
+ SV *sth;
+ imp_sth_t *imp_sth;
+{
+ dTHR;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_finish\n"); }
+
+ if (DBIc_ACTIVE(imp_sth) && imp_sth->result) {
+ PQclear(imp_sth->result);
+ imp_sth->result = 0;
+ imp_sth->rows = 0;
+ }
+
+ DBIc_ACTIVE_off(imp_sth);
+ return 1;
+}
+
+
+void
+dbd_st_destroy (sth, imp_sth)
+ SV *sth;
+ imp_sth_t *imp_sth;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_destroy\n"); }
+
+ /* Free off contents of imp_sth */
+
+ Safefree(imp_sth->statement);
+ if (imp_sth->result) {
+ PQclear(imp_sth->result);
+ imp_sth->result = 0;
+ }
+
+ if (imp_sth->out_params_av)
+ sv_free((SV*)imp_sth->out_params_av);
+
+ if (imp_sth->all_params_hv) {
+ HV *hv = imp_sth->all_params_hv;
+ SV *sv;
+ char *key;
+ I32 retlen;
+ hv_iterinit(hv);
+ while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) {
+ if (sv != &sv_undef) {
+ phs_t *phs_tpl = (phs_t*)(void*)SvPVX(sv);
+ sv_free(phs_tpl->sv);
+ }
+ }
+ sv_free((SV*)imp_sth->all_params_hv);
+ }
+
+ DBIc_IMPSET_off(imp_sth); /* let DBI know we've done it */
+}
+
+
+int
+dbd_st_STORE_attrib (sth, imp_sth, keysv, valuesv)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ SV *keysv;
+ SV *valuesv;
+{
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_STORE\n"); }
+
+ return FALSE;
+}
+
+
+SV *
+dbd_st_FETCH_attrib (sth, imp_sth, keysv)
+ SV *sth;
+ imp_sth_t *imp_sth;
+ SV *keysv;
+{
+ STRLEN kl;
+ char *key = SvPV(keysv,kl);
+ int i, sz;
+ SV *retsv = Nullsv;
+
+ if (dbis->debug >= 1) { PerlIO_printf(DBILOGFP, "dbd_st_FETCH\n"); }
+
+ if (! imp_sth->result) {
+ return Nullsv;
+ }
+
+ i = DBIc_NUM_FIELDS(imp_sth);
+
+ if (kl == 4 && strEQ(key, "NAME")) {
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ av_store(av, i, newSVpv(PQfname(imp_sth->result, i),0));
+ }
+ } else if ( kl== 4 && strEQ(key, "TYPE")) {
+ /* Need to convert the Pg type to ANSI/SQL type. */
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ av_store(av, i, newSViv(sql_pg_type( imp_sth,
+ PQfname(imp_sth->result, i),
+ PQftype(imp_sth->result, i))));
+ }
+ } else if (kl==9 && strEQ(key, "PRECISION")) {
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ sz = PQfsize(imp_sth->result, i);
+ av_store(av, i, sz > 0 ? newSViv(sz) : &sv_undef);
+ }
+ } else if (kl==5 && strEQ(key, "SCALE")) {
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ av_store(av, i, &sv_undef);
+ }
+ } else if (kl==8 && strEQ(key, "NULLABLE")) {
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ av_store(av, i, newSViv(2));
+ }
+ } else if (kl==10 && strEQ(key, "CursorName")) {
+ retsv = &sv_undef;
+ } else if (kl==11 && strEQ(key, "RowsInCache")) {
+ retsv = &sv_undef;
+ } else if (kl==7 && strEQ(key, "pg_size")) {
+ AV *av = newAV();
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ av_store(av, i, newSViv(PQfsize(imp_sth->result, i)));
+ }
+ } else if (kl==7 && strEQ(key, "pg_type")) {
+ AV *av = newAV();
+ char *type_nam;
+ retsv = newRV(sv_2mortal((SV*)av));
+ while(--i >= 0) {
+ switch (PQftype(imp_sth->result, i)) {
+ case 16:
+ type_nam = "bool";
+ break;
+ case 17:
+ type_nam = "bytea";
+ break;
+ case 18:
+ type_nam = "char";
+ break;
+ case 19:
+ type_nam = "name";
+ break;
+ case 20:
+ type_nam = "int8";
+ break;
+ case 21:
+ type_nam = "int2";
+ break;
+ case 22:
+ type_nam = "int28";
+ break;
+ case 23:
+ type_nam = "int4";
+ break;
+ case 24:
+ type_nam = "regproc";
+ break;
+ case 25:
+ type_nam = "text";
+ break;
+ case 26:
+ type_nam = "oid";
+ break;
+ case 27:
+ type_nam = "tid";
+ break;
+ case 28:
+ type_nam = "xid";
+ break;
+ case 29:
+ type_nam = "cid";
+ break;
+ case 30:
+ type_nam = "oid8";
+ break;
+ case 32:
+ type_nam = "SET";
+ break;
+ case 210:
+ type_nam = "smgr";
+ break;
+ case 600:
+ type_nam = "point";
+ break;
+ case 601:
+ type_nam = "lseg";
+ break;
+ case 602:
+ type_nam = "path";
+ break;
+ case 603:
+ type_nam = "box";
+ break;
+ case 604:
+ type_nam = "polygon";
+ break;
+ case 605:
+ type_nam = "filename";
+ break;
+ case 628:
+ type_nam = "line";
+ break;
+ case 629:
+ type_nam = "_line";
+ break;
+ case 700:
+ type_nam = "float4";
+ break;
+ case 701:
+ type_nam = "float8";
+ break;
+ case 702:
+ type_nam = "abstime";
+ break;
+ case 703:
+ type_nam = "reltime";
+ break;
+ case 704:
+ type_nam = "tinterval";
+ break;
+ case 705:
+ type_nam = "unknown";
+ break;
+ case 718:
+ type_nam = "circle";
+ break;
+ case 719:
+ type_nam = "_circle";
+ break;
+ case 790:
+ type_nam = "money";
+ break;
+ case 791:
+ type_nam = "_money";
+ break;
+ case 810:
+ type_nam = "oidint2";
+ break;
+ case 910:
+ type_nam = "oidint4";
+ break;
+ case 911:
+ type_nam = "oidname";
+ break;
+ case 1000:
+ type_nam = "_bool";
+ break;
+ case 1001:
+ type_nam = "_bytea";
+ break;
+ case 1002:
+ type_nam = "_char";
+ break;
+ case 1003:
+ type_nam = "_name";
+ break;
+ case 1005:
+ type_nam = "_int2";
+ break;
+ case 1006:
+ type_nam = "_int28";
+ break;
+ case 1007:
+ type_nam = "_int4";
+ break;
+ case 1008:
+ type_nam = "_regproc";
+ break;
+ case 1009:
+ type_nam = "_text";
+ break;
+ case 1028:
+ type_nam = "_oid";
+ break;
+ case 1010:
+ type_nam = "_tid";
+ break;
+ case 1011:
+ type_nam = "_xid";
+ break;
+ case 1012:
+ type_nam = "_cid";
+ break;
+ case 1013:
+ type_nam = "_oid8";
+ break;
+ case 1014:
+ type_nam = "_lock";
+ break;
+ case 1015:
+ type_nam = "_stub";
+ break;
+ case 1016:
+ type_nam = "_ref";
+ break;
+ case 1017:
+ type_nam = "_point";
+ break;
+ case 1018:
+ type_nam = "_lseg";
+ break;
+ case 1019:
+ type_nam = "_path";
+ break;
+ case 1020:
+ type_nam = "_box";
+ break;
+ case 1021:
+ type_nam = "_float4";
+ break;
+ case 1022:
+ type_nam = "_float8";
+ break;
+ case 1023:
+ type_nam = "_abstime";
+ break;
+ case 1024:
+ type_nam = "_reltime";
+ break;
+ case 1025:
+ type_nam = "_tinterval";
+ break;
+ case 1026:
+ type_nam = "_filename";
+ break;
+ case 1027:
+ type_nam = "_polygon";
+ break;
+ case 1033:
+ type_nam = "aclitem";
+ break;
+ case 1034:
+ type_nam = "_aclitem";
+ break;
+ case 1042:
+ type_nam = "bpchar";
+ break;
+ case 1043:
+ type_nam = "varchar";
+ break;
+ case 1082:
+ type_nam = "date";
+ break;
+ case 1083:
+ type_nam = "time";
+ break;
+ case 1182:
+ type_nam = "_date";
+ break;
+ case 1183:
+ type_nam = "_time";
+ break;
+ case 1184:
+ type_nam = "datetime";
+ break;
+ case 1185:
+ type_nam = "_datetime";
+ break;
+ case 1186:
+ type_nam = "timespan";
+ break;
+ case 1187:
+ type_nam = "_timespan";
+ break;
+ case 1231:
+ type_nam = "_numeric";
+ break;
+ case 1296:
+ type_nam = "timestamp";
+ break;
+ case 1700:
+ type_nam = "numeric";
+ break;
+
+ default:
+ type_nam = "unknown";
+
+ }
+ av_store(av, i, newSVpv(type_nam, 0));
+ }
+ } else if (kl==13 && strEQ(key, "pg_oid_status")) {
+ retsv = newSVpv((char *)PQoidStatus(imp_sth->result), 0);
+ } else if (kl==13 && strEQ(key, "pg_cmd_status")) {
+ retsv = newSVpv((char *)PQcmdStatus(imp_sth->result), 0);
+ } else {
+ return Nullsv;
+ }
+
+ return sv_2mortal(retsv);
+}
+
+
+/* end of dbdimp.c */
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.h b/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.h
new file mode 100644
index 0000000..58c105b
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/dbdimp.h
@@ -0,0 +1,81 @@
+/*
+ $Id: dbdimp.h,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+ Copyright (c) 1997,1998,1999,2000 Edmund Mergl
+ Portions Copyright (c) 1994,1995,1996,1997 Tim Bunce
+
+ You may distribute under the terms of either the GNU General Public
+ License or the Artistic License, as specified in the Perl README file.
+*/
+
+#ifdef WIN32
+#define snprintf _snprintf
+#endif
+
+/* Define drh implementor data structure */
+struct imp_drh_st {
+ dbih_drc_t com; /* MUST be first element in structure */
+};
+
+/* Define dbh implementor data structure */
+struct imp_dbh_st {
+ dbih_dbc_t com; /* MUST be first element in structure */
+
+ PGconn * conn; /* connection structure */
+ int init_commit; /* initialize AutoCommit */
+ int pg_auto_escape; /* initialize AutoEscape */
+ int pg_bool_tf; /* do bools return 't'/'f' */
+#ifdef SvUTF8_off
+ int pg_enable_utf8; /* should we attempt to make utf8 strings? */
+#endif
+};
+
+/* Define sth implementor data structure */
+struct imp_sth_st {
+ dbih_stc_t com; /* MUST be first element in structure */
+
+ PGresult* result; /* result structure */
+ int cur_tuple; /* current tuple */
+ int rows; /* number of affected rows */
+
+ /* Input Details */
+ char *statement; /* sql (see sth_scan) */
+ HV *all_params_hv; /* all params, keyed by name */
+ AV *out_params_av; /* quick access to inout params */
+ int pg_pad_empty; /* convert ""->" " when binding */
+ int all_params_len; /* length-sum of all params */
+
+ /* (In/)Out Parameter Details */
+ bool has_inout_params;
+};
+
+
+#define sword signed int
+#define sb2 signed short
+#define ub2 unsigned short
+
+typedef struct phs_st phs_t; /* scalar placeholder */
+
+struct phs_st { /* scalar placeholder EXPERIMENTAL */
+ sword ftype; /* external OCI field type */
+
+ SV *sv; /* the scalar holding the value */
+ int sv_type; /* original sv type at time of bind */
+ bool is_inout;
+
+ IV maxlen; /* max possible len (=allocated buffer) */
+
+ /* these will become an array */
+ sb2 indp; /* null indicator */
+ char *progv;
+ ub2 arcode;
+ IV alen; /* effective length ( <= maxlen ) */
+
+ int alen_incnull; /* 0 or 1 if alen should include null */
+ char name[1]; /* struct is malloc'd bigger as needed */
+};
+
+
+SV * dbd_db_pg_notifies (SV *dbh, imp_dbh_t *imp_dbh);
+
+/* end of dbdimp.h */
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/eg/ApacheDBI.pl b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/ApacheDBI.pl
new file mode 100755
index 0000000..b084f70
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/ApacheDBI.pl
@@ -0,0 +1,70 @@
+#!/usr/local/bin/perl
+
+# $Id: ApacheDBI.pl,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+# don't forget to create in postgres the user who is running
+# the httpd, eg 'createuser nobody' !
+#
+# demo script, tested with:
+# - PostgreSQL-7.1.1
+# - apache_1.3.12
+# - mod_perl-1.23
+# - perl5.6.0
+# - DBI-1.14
+
+use CGI;
+use DBI;
+use strict;
+
+my $query = new CGI;
+
+print $query->header,
+ $query->start_html(-title=>'A Simple Example'),
+ $query->startform,
+ "<CENTER><H3>Testing Module DBI</H3></CENTER>",
+ "<P><CENTER><TABLE CELLPADDING=4 CELLSPACING=2 BORDER=1>",
+ "<TR><TD>Enter the data source: </TD>",
+ "<TD>", $query->textfield(-name=>'data_source', -size=>40, -default=>'dbi:Pg:dbname=template1'), "</TD>",
+ "</TR>",
+ "<TR><TD>Enter the user name: </TD>",
+ "<TD>", $query->textfield(-name=>'username'), "</TD>",
+ "</TR>",
+ "<TR><TD>Enter the password: </TD>",
+ "<TD>", $query->textfield(-name=>'auth'), "</TD>",
+ "</TR>",
+ "<TR><TD>Enter the select command: </TD>",
+ "<TD>", $query->textfield(-name=>'cmd', -size=>40), "</TD>",
+ "</TR>",
+ "</TABLE></CENTER><P>",
+ "<CENTER>", $query->submit(-value=>'Submit'), "</CENTER>",
+ $query->endform;
+
+if ($query->param) {
+
+ my $data_source = $query->param('data_source');
+ my $username = $query->param('username');
+ my $auth = $query->param('auth');
+ my $cmd = $query->param('cmd');
+ my $dbh = DBI->connect($data_source, $username, $auth);
+ if ($dbh) {
+ my $sth = $dbh->prepare($cmd);
+ my $ret = $sth->execute;
+ if ($ret) {
+ my($i, $ary_ref);
+ print "<P><CENTER><TABLE CELLPADDING=4 CELLSPACING=2 BORDER=1>\n";
+ while ($ary_ref = $sth->fetchrow_arrayref) {
+ print "<TR><TD>", join("</TD><TD>", @$ary_ref), "</TD></TR>\n";
+ }
+ print "</TABLE></CENTER><P>\n";
+ $sth->finish;
+ } else {
+ print "<CENTER><H2>", $DBI::errstr, "</H2></CENTER>\n";
+ }
+ $dbh->disconnect;
+ } else {
+ print "<CENTER><H2>", $DBI::errstr, "</H2></CENTER>\n";
+ }
+}
+
+print $query->end_html;
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/eg/lotest.pl b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/lotest.pl
new file mode 100644
index 0000000..6192c49
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/lotest.pl
@@ -0,0 +1,74 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use DBI;
+use DBD::Pg;
+
+my $dsn = "dbname=p1";
+my $dbh = DBI->connect('dbi:Pg:dbname=p1', undef, undef, { AutoCommit => 1 });
+
+my $buf = 'abcdefghijklmnopqrstuvwxyz' x 400;
+
+my $id = write_blob($dbh, undef, $buf);
+
+my $dat = read_blob($dbh, $id);
+
+print "Done\n";
+
+sub write_blob {
+ my ($dbh, $lobj_id, $data) = @_;
+
+ # begin transaction
+ $dbh->{AutoCommit} = 0;
+
+ # Create a new lo if we are not passed an lo object ID.
+ unless ($lobj_id) {
+ # Create the object.
+ $lobj_id = $dbh->func($dbh->{'pg_INV_WRITE'}, 'lo_creat');
+ }
+
+ # Open it to get a file descriptor.
+ my $lobj_fd = $dbh->func($lobj_id, $dbh->{'pg_INV_WRITE'}, 'lo_open');
+
+ $dbh->func($lobj_fd, 0, 0, 'lo_lseek');
+
+ # Write some data to it.
+ my $len = $dbh->func($lobj_fd, $data, length($data), 'lo_write');
+
+ die "Errors writing lo\n" if $len != length($data);
+
+ # Close 'er up.
+ $dbh->func($lobj_fd, 'lo_close') or die "Problems closing lo object\n";
+
+ # end transaction
+ $dbh->{AutoCommit} = 1;
+
+ return $lobj_id;
+}
+
+sub read_blob {
+ my ($dbh, $lobj_id) = @_;
+ my $data = '';
+ my $read_len = 256;
+ my $chunk = '';
+
+ # begin transaction
+ $dbh->{AutoCommit} = 0;
+
+ my $lobj_fd = $dbh->func($lobj_id, $dbh->{'pg_INV_READ'}, 'lo_open');
+
+ $dbh->func($lobj_fd, 0, 0, 'lo_lseek');
+
+ # Pull out all the data.
+ while ($dbh->func($lobj_fd, $chunk, $read_len, 'lo_read')) {
+ $data .= $chunk;
+ }
+
+ $dbh->func($lobj_fd, 'lo_close') or die "Problems closing lo object\n";
+
+ # end transaction
+ $dbh->{AutoCommit} = 1;
+
+ return $data;
+}
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/eg/notify_test.patch b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/notify_test.patch
new file mode 100644
index 0000000..6f8acf8
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/eg/notify_test.patch
@@ -0,0 +1,82 @@
+diff -r --unified DBD-Pg-1.00/test.pl DBD-Pg-1.00.alex/test.pl
+--- DBD-Pg-1.00/test.pl Sun May 27 10:10:13 2001
++++ DBD-Pg-1.00.alex/test.pl Sun Jun 10 15:38:09 2001
+@@ -40,7 +40,7 @@
+ my $dsn_main = "dbi:Pg:dbname=$dbmain";
+ my $dsn_test = "dbi:Pg:dbname=$dbtest";
+
+-my ($dbh0, $dbh, $sth);
++my ($dbh0, $dbh, $dbh1, $sth);
+
+ #DBI->trace(3); # make your choice
+
+@@ -445,16 +445,56 @@
+ # end transaction
+ $dbh->{AutoCommit} = 1;
+
++# compare large objects
++
+ ( $dbh->func($lobjId, 'lo_unlink') )
+ and print "\$dbh->func(lo_unlink) ...... ok\n"
+ or print "\$dbh->func(lo_unlink) ...... not ok\n";
+
+-# compare large objects
+-
+ ( $pgin cmp $buf and $pgin cmp $blob )
+ and print "compare blobs .............. not ok\n"
+ or print "compare blobs .............. ok\n";
+
++my $fd;
++( $fd=$dbh->func( 'getfd') )
++ and print "\$dbh->func(getfd) .......... ok\n"
++ or print "\$dbh->func(getfd) .......... not ok\n";
++
++( $dbh->do( 'LISTEN test ') )
++ and print "\$dbh->do('LISTEN test') .... ok\n"
++ or print "\$dbh->do('LISTEN test') .... not ok\n";
++
++( $dbh1 = DBI->connect("$dsn_test", '', '', { AutoCommit => 1 }) )
++ and print "DBI->connect (for notify)... ok\n"
++ or die "DBI->connect (for notify)... not ok: ", $DBI::errstr;
++
++# there should be no data for read on $fd , until we send a notify
++
++ my $rout;
++ my $rin = '';
++ vec($rin,$fd,1) = 1;
++ my $nfound = select( $rout=$rin, undef, undef, 0);
++
++( $nfound==0 )
++ and print "select(\$fd) returns no data. ok\n"
++ or die "select(\$fd) returns no data. not ok\n";
++
++( $dbh1->do( 'NOTIFY test ') )
++ and print "\$dbh1->do('NOTIFY test') ... ok\n"
++ or print "\$dbh1->do('NOTIFY test') ... not ok\n";
++
++ my $nfound = select( $rout=$rin, undef, undef, 1);
++
++( $nfound==1 )
++ and print "select(\$fd) returns data.... ok\n"
++ or die "select(\$fd) returns data.... not ok\n";
++
++my $notify_r;
++
++( $notify_r = $dbh->func('notifies') )
++ and print "\$dbh->func('notifies')...... ok\n"
++ or die "\$dbh->func('notifies')...... not ok\n";
++
+ ######################### disconnect and drop test database
+
+ # disconnect
+@@ -462,6 +502,10 @@
+ ( $dbh->disconnect )
+ and print "\$dbh->disconnect ........... ok\n"
+ or die "\$dbh->disconnect ........... not ok: ", $DBI::errstr;
++
++( $dbh1->disconnect )
++ and print "\$dbh1->disconnect .......... ok\n"
++ or die "\$dbh1->disconnect .......... not ok: ", $DBI::errstr;
+
+ $dbh0->do("DROP DATABASE $dbtest");
+ $dbh0->disconnect;
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/00basic.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/00basic.t
new file mode 100644
index 0000000..1c0cb28
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/00basic.t
@@ -0,0 +1,10 @@
+print "1..1\n";
+
+use DBI;
+use DBD::Pg;
+
+if ($DBD::Pg::VERSION) {
+ print "ok 1\n";
+} else {
+ print "not ok 1\n";
+}
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/01connect.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01connect.t
new file mode 100644
index 0000000..be17b50
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01connect.t
@@ -0,0 +1,26 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 2;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+
+ok((defined $dbh and $dbh->disconnect()),
+ 'connect with transaction'
+ );
+
+undef $dbh;
+$dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 1});
+
+ok((defined $dbh and $dbh->disconnect()),
+ 'connect without transaction'
+ );
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/01constants.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01constants.t
new file mode 100644
index 0000000..09907e9
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01constants.t
@@ -0,0 +1,25 @@
+use strict;
+use Test::More tests => 20;
+
+use DBD::Pg qw(:pg_types);
+
+ok(PG_BOOL == 16, 'PG_BOOL');
+ok(PG_BYTEA == 17, 'PG_BYTEA');
+ok(PG_CHAR == 18, 'PG_CHAR');
+ok(PG_INT8 == 20, 'PG_INT8');
+ok(PG_INT2 == 21, 'PG_INT2');
+ok(PG_INT4 == 23, 'PG_INT4');
+ok(PG_TEXT == 25, 'PG_TEXT');
+ok(PG_OID == 26, 'PG_OID');
+ok(PG_FLOAT4 == 700, 'PG_FLOAT4');
+ok(PG_FLOAT8 == 701, 'PG_FLOAT8');
+ok(PG_ABSTIME == 702, 'PG_ABSTIME');
+ok(PG_RELTIME == 703, 'PG_RELTIME');
+ok(PG_TINTERVAL == 704, 'PG_TINTERVAL');
+ok(PG_BPCHAR == 1042, 'PG_BPCHAR');
+ok(PG_VARCHAR == 1043, 'PG_VARCHAR');
+ok(PG_DATE == 1082, 'PG_DATE');
+ok(PG_TIME == 1083, 'PG_TIME');
+ok(PG_DATETIME == 1184, 'PG_DATETIME');
+ok(PG_TIMESPAN == 1186, 'PG_TIMESPAN');
+ok(PG_TIMESTAMP == 1296, 'PG_TIMESTAMP');
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/01setup.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01setup.t
new file mode 100644
index 0000000..d0b57a3
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/01setup.t
@@ -0,0 +1,38 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 3;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 1});
+ok(defined $dbh,'connect without transaction');
+{
+ local $dbh->{PrintError} = 0;
+ local $dbh->{RaiseError} = 0;
+ $dbh->do(q{DROP TABLE test});
+}
+
+my $sql = <<SQL;
+CREATE TABLE test (
+ id int,
+ name text,
+ val text,
+ score float,
+ date timestamp default 'now()',
+ array text[][]
+)
+SQL
+
+ok($dbh->do($sql),
+ 'create table'
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/02prepare.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/02prepare.t
new file mode 100644
index 0000000..373aca2
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/02prepare.t
@@ -0,0 +1,84 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 8;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my $sql = <<SQL;
+ SELECT *
+ FROM test
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+$sql = <<SQL;
+ SELECT id
+ FROM test
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = 1
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+$sql = <<SQL;
+ SELECT *
+ FROM test
+ WHERE id = ?
+ AND name = ?
+ AND value = ?
+ AND score = ?
+ and data = ?
+SQL
+
+ok($dbh->prepare($sql),
+ "prepare: $sql"
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/03bind.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/03bind.t
new file mode 100644
index 0000000..df7c884
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/03bind.t
@@ -0,0 +1,85 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 11;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my $sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+SQL
+my $sth = $dbh->prepare($sql);
+ok(defined $sth,
+ "prepare: $sql"
+ );
+
+ok($sth->bind_param(1, 'foo'),
+ 'bind int column with string'
+ );
+
+ok($sth->bind_param(1, 1),
+ 'rebind int column with int'
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+ AND name = ?
+SQL
+$sth = $dbh->prepare($sql);
+ok(defined $sth,
+ "prepare: $sql"
+ );
+
+ok($sth->bind_param(1, 'foo'),
+ 'bind int column with string',
+ );
+ok($sth->bind_param(2, 'bar'),
+ 'bind string column with text'
+ );
+ok($sth->bind_param(2, 'baz'),
+ 'rebind string column with text'
+ );
+
+ok($sth->finish(),
+ 'finish'
+ );
+
+# Make sure that we get warnings when we try to use SQL_BINARY.
+{
+ local $SIG{__WARN__} =
+ sub { ok($_[0] =~ /^Use of SQL type SQL_BINARY/,
+ 'warning with SQL_BINARY'
+ );
+ };
+
+ $sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+ AND name = ?
+SQL
+ $sth = $dbh->prepare($sql);
+
+ $sth->bind_param(1, 'foo', DBI::SQL_BINARY);
+}
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/04execute.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/04execute.t
new file mode 100644
index 0000000..9643878
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/04execute.t
@@ -0,0 +1,113 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 13;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my $sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+SQL
+my $sth = $dbh->prepare($sql);
+ok(defined $sth,
+ "prepare: $sql"
+ );
+
+$sth->bind_param(1, 1);
+ok($sth->execute(),
+ 'exectute with one bind param'
+ );
+
+$sth->bind_param(1, 2);
+ok($sth->execute(),
+ 'exectute with rebinding one param'
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+ AND name = ?
+SQL
+$sth = $dbh->prepare($sql);
+ok(defined $sth,
+ "prepare: $sql"
+ );
+
+$sth->bind_param(1, 2);
+$sth->bind_param(2, 'foo');
+ok($sth->execute(),
+ 'exectute with two bind params'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth = $dbh->prepare($sql);
+ $sth->bind_param(1, 2);
+ $sth->execute();
+};
+ok(!$@,
+ 'execute with only first of two params bound'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth = $dbh->prepare($sql);
+ $sth->bind_param(2, 'foo');
+ $sth->execute();
+};
+ok(!$@,
+ 'execute with only second of two params bound'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth = $dbh->prepare($sql);
+ $sth->execute();
+};
+ok(!$@,
+ 'execute with neither of two params bound'
+ );
+
+$sth = $dbh->prepare($sql);
+ok($sth->execute(1, 'foo'),
+ 'execute with both params bound in execute'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth = $dbh->prepare(q{
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+ AND name = ?
+ });
+ $sth->execute(1);
+};
+ok($@,
+ 'execute with only one of two params bound in execute'
+ );
+
+
+ok($sth->finish(),
+ 'finish'
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/05fetch.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/05fetch.t
new file mode 100644
index 0000000..b6f8f66
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/05fetch.t
@@ -0,0 +1,131 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 10;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+$dbh->do(q{INSERT INTO test (id, name, val) VALUES (1, 'foo', 'horse')});
+$dbh->do(q{INSERT INTO test (id, name, val) VALUES (2, 'bar', 'chicken')});
+$dbh->do(q{INSERT INTO test (id, name, val) VALUES (3, 'baz', 'pig')});
+ok($dbh->commit(),
+ 'commit'
+ );
+
+my $sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+SQL
+my $sth = $dbh->prepare($sql);
+$sth->execute();
+
+my $rows = 0;
+while (my ($id, $name) = $sth->fetchrow_array()) {
+ if (defined($id) && defined($name)) {
+ $rows++;
+ }
+}
+$sth->finish();
+ok($rows == 3,
+ 'fetch three rows'
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE 1 = 0
+SQL
+$sth = $dbh->prepare($sql);
+$sth->execute();
+
+$rows = 0;
+while (my ($id, $name) = $sth->fetchrow_array()) {
+ $rows++;
+}
+$sth->finish();
+
+ok($rows == 0,
+ 'fetch zero rows'
+ );
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE id = ?
+SQL
+$sth = $dbh->prepare($sql);
+$sth->execute(1);
+
+$rows = 0;
+while (my ($id, $name) = $sth->fetchrow_array()) {
+ if (defined($id) && defined($name)) {
+ $rows++;
+ }
+}
+$sth->finish();
+
+ok($rows == 1,
+ 'fetch one row on id'
+ );
+
+# Attempt to test whether or not we can get unicode out of the database
+# correctly. Reuse the previous sth.
+SKIP: {
+ eval "use Encode";
+ skip "need Encode module for unicode tests", 3 if $@;
+ local $dbh->{pg_enable_utf8} = 1;
+ $dbh->do("INSERT INTO test (id, name, val) VALUES (4, '\001\000dam', 'cow')");
+ $sth->execute(4);
+ my ($id, $name) = $sth->fetchrow_array();
+ ok(Encode::is_utf8($name),
+ 'returned data has utf8 bit set'
+ );
+ is(length($name), 4,
+ 'returned utf8 data is not corrupted'
+ );
+ $sth->finish();
+ $sth->execute(1);
+ my ($id2, $name2) = $sth->fetchrow_array();
+ ok(! Encode::is_utf8($name2),
+ 'returned ASCII data has not got utf8 bit set'
+ );
+ $sth->finish();
+}
+
+$sql = <<SQL;
+ SELECT id
+ , name
+ FROM test
+ WHERE name = ?
+SQL
+$sth = $dbh->prepare($sql);
+$sth->execute('foo');
+
+$rows = 0;
+while (my ($id, $name) = $sth->fetchrow_array()) {
+ if (defined($id) && defined($name)) {
+ $rows++;
+ }
+}
+$sth->finish();
+
+ok($rows == 1,
+ 'fetch one row on name'
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/06disconnect.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/06disconnect.t
new file mode 100644
index 0000000..5d76bc0
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/06disconnect.t
@@ -0,0 +1,31 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 3;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
+
+$dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+
+$dbh->disconnect();
+$dbh->disconnect();
+$dbh->disconnect();
+ok($dbh->disconnect(),
+ 'disconnect on already disconnected dbh'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/07reuse.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/07reuse.t
new file mode 100644
index 0000000..d09dfc0
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/07reuse.t
@@ -0,0 +1,28 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 3;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, PrintError => 0, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my $sth = $dbh->prepare(q{SELECT * FROM test});
+ok($dbh->disconnect(),
+ 'disconnect with un-finished statement'
+ );
+
+eval {
+ $sth->execute();
+};
+ok($@,
+ 'execute on disconnected statement'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/08txn.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/08txn.t
new file mode 100644
index 0000000..467aa31
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/08txn.t
@@ -0,0 +1,102 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 18;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh1 = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh1,
+ 'connect first dbh'
+ );
+
+my $dbh2 = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh2,
+ 'connect second dbh'
+ );
+
+$dbh1->do(q{DELETE FROM test});
+ok($dbh1->commit(),
+ 'delete'
+ );
+
+my $rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on empty table from dbh1'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on empty table from dbh2'
+ );
+
+$dbh1->do(q{INSERT INTO test (id, name, val) VALUES (1, 'foo', 'horse')});
+$dbh1->do(q{INSERT INTO test (id, name, val) VALUES (2, 'bar', 'chicken')});
+$dbh1->do(q{INSERT INTO test (id, name, val) VALUES (3, 'baz', 'pig')});
+
+$rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch three rows on dbh1'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on dbh2 before commit'
+ );
+
+ok($dbh1->commit(),
+ 'commit work'
+ );
+
+$rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch on dbh1 after commit'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch on dbh2 after commit'
+ );
+
+ok($dbh1->do(q{DELETE FROM test}),
+ 'delete'
+ );
+
+$rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on empty table from dbh1'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch on from dbh2 without commit'
+ );
+
+ok($dbh1->rollback(),
+ 'rollback'
+ );
+
+$rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch on from dbh1 after rollback'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 3,
+ 'fetch on from dbh2 after rollback'
+ );
+
+ok($dbh1->disconnect(),
+ 'disconnect on dbh1'
+);
+
+ok($dbh2->disconnect(),
+ 'disconnect on dbh2'
+);
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/09autocommit.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/09autocommit.t
new file mode 100644
index 0000000..9b1b69f
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/09autocommit.t
@@ -0,0 +1,68 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 12;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh1 = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 1}
+ );
+ok(defined $dbh1,
+ 'connect first dbh'
+ );
+
+my $dbh2 = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 1}
+ );
+ok(defined $dbh2,
+ 'connect second dbh'
+ );
+
+ok($dbh1->do(q{DELETE FROM test}),
+ 'delete'
+ );
+
+my $rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on empty table from dbh1'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 0,
+ 'fetch on empty table from dbh2'
+ );
+
+ok($dbh1->do(q{INSERT INTO test (id, name, val) VALUES (1, 'foo', 'horse')}),
+ 'insert'
+ );
+
+$rows = ($dbh1->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 1,
+ 'fetch one row from dbh1'
+ );
+
+$rows = ($dbh2->selectrow_array(q{SELECT COUNT(*) FROM test}))[0];
+ok($rows == 1,
+ 'fetch one row from dbh1'
+ );
+
+local $SIG{__WARN__} = sub {};
+ok(!$dbh1->commit(),
+ 'commit'
+ );
+
+ok(!$dbh1->rollback(),
+ 'rollback'
+ );
+
+ok($dbh1->disconnect(),
+ 'disconnect on dbh1'
+);
+
+ok($dbh2->disconnect(),
+ 'disconnect on dbh2'
+);
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/11quoting.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/11quoting.t
new file mode 100644
index 0000000..afec963
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/11quoting.t
@@ -0,0 +1,50 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 8;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my %tests = (
+ one=>["'", "'\\" . sprintf("%03o", ord("'")) . "'"],
+ two=>["''", "'" . ("\\" . sprintf("%03o", ord("'")))x2 . "'"],
+ three=>["\\", "'\\" . sprintf("%03o", ord("\\")) . "'"],
+ four=>["\\'", sprintf("'\\%03o\\%03o'", ord("\\"), ord("'"))],
+ five=>["\\'?:", sprintf("'\\%03o\\%03o?:'", ord("\\"), ord("'"))],
+ );
+
+foreach my $test (keys %tests) {
+ my ($unq, $quo, $ref);
+
+ $unq = $tests{$test}->[0];
+ $ref = $tests{$test}->[1];
+ $quo = $dbh->quote($unq);
+
+ ok($quo eq $ref,
+ "$test: $unq -> expected $quo got $ref"
+ );
+}
+
+# Make sure that SQL_BINARY doesn't work.
+# eval { $dbh->quote('foo', { TYPE => DBI::SQL_BINARY })};
+eval {
+ local $dbh->{PrintError} = 0;
+ $dbh->quote('foo', DBI::SQL_BINARY);
+};
+ok($@ && $@ =~ /Use of SQL_BINARY invalid in quote/,
+ 'SQL_BINARY'
+);
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/12placeholders.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/12placeholders.t
new file mode 100644
index 0000000..bd79ea7
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/12placeholders.t
@@ -0,0 +1,125 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 9;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+my $quo = $dbh->quote("\\'?:");
+my $sth = $dbh->prepare(qq{
+ INSERT INTO test (name) VALUES ($quo)
+ });
+$sth->execute();
+
+my $sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = $quo;
+SQL
+$sth = $dbh->prepare($sql);
+$sth->execute();
+
+my ($retr) = $sth->fetchrow_array();
+ok((defined($retr) && $retr eq "\\'?:"),
+ 'fetch'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth->execute('foo');
+};
+ok($@,
+ 'execute with one bind param where none expected'
+ );
+
+$sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = ?
+SQL
+$sth = $dbh->prepare($sql);
+
+$sth->execute("\\'?:");
+
+($retr) = $sth->fetchrow_array();
+ok((defined($retr) && $retr eq "\\'?:"),
+ 'execute with ? placeholder'
+ );
+
+$sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = :1
+SQL
+$sth = $dbh->prepare($sql);
+
+$sth->execute("\\'?:");
+
+($retr) = $sth->fetchrow_array();
+ok((defined($retr) && $retr eq "\\'?:"),
+ 'execute with :1 placeholder'
+ );
+
+$sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = '?'
+SQL
+$sth = $dbh->prepare($sql);
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth->execute('foo');
+};
+ok($@,
+ 'execute with quoted ?'
+ );
+
+$sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = ':1'
+SQL
+$sth = $dbh->prepare($sql);
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $sth->execute('foo');
+};
+ok($@,
+ 'execute with quoted :1'
+ );
+
+$sql = <<SQL;
+ SELECT name
+ FROM test
+ WHERE name = '\\\\'
+ AND name = '?'
+SQL
+$sth = $dbh->prepare($sql);
+
+eval {
+ local $dbh->{PrintError} = 0;
+ local $sth->{PrintError} = 0;
+ $sth->execute('foo');
+};
+ok($@,
+ 'execute with quoted ?'
+ );
+
+$sth->finish();
+$dbh->rollback();
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/13pgtype.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/13pgtype.t
new file mode 100644
index 0000000..8db819e
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/13pgtype.t
@@ -0,0 +1,43 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 3;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+eval {
+ local $dbh->{PrintError} = 0;
+ $dbh->do(q{DROP TABLE tt});
+ $dbh->commit();
+};
+$dbh->rollback();
+
+$dbh->do(q{CREATE TABLE tt (blah numeric(5,2), foo text)});
+my $sth = $dbh->prepare(qq{
+ SELECT * FROM tt WHERE FALSE
+ });
+$sth->execute();
+
+my @types = @{$sth->{pg_type}};
+
+ok($types[0] eq 'numeric',
+ 'type numeric'
+ );
+
+ok($types[1] eq 'text',
+ 'type text'
+ );
+
+$sth->finish();
+$dbh->rollback();
+$dbh->disconnect();
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/15funct.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/15funct.t
new file mode 100644
index 0000000..1bc2cf9
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/15funct.t
@@ -0,0 +1,353 @@
+#!/usr/bin/perl -w -I./t
+$| = 1;
+
+# vim:ts=2:sw=2:ai:aw:nu:
+use DBI qw(:sql_types);
+use Data::Dumper;
+use strict;
+use Test::More;
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 59;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+#
+# Test the different methods, so are expected to fail.
+#
+
+my $sth;
+
+# foreach (@{ $DBI::EXPORT_TAGS{sql_types} }) {
+# no strict 'refs';
+# printf "%s=%d\n", $_, &{"DBI::$_"};
+# }
+
+my $get_info = {
+ SQL_DBMS_NAME => 17
+ , SQL_DBMS_VER => 18
+ , SQL_IDENTIFIER_QUOTE_CHAR => 29
+ , SQL_CATALOG_NAME_SEPARATOR => 41
+ , SQL_CATALOG_LOCATION => 114
+};
+
+# Ping
+ eval {
+ ok( $dbh->ping(), "Testing Ping" );
+ };
+ok ( !$@, "Ping Tested" );
+
+# Get Info
+ eval {
+ $sth = $dbh->get_info();
+ };
+ok ($@, "Call to get_info with 0 arguements, error expected: $@" );
+$sth = undef;
+
+# Table Info
+ eval {
+ $sth = $dbh->table_info();
+ };
+ok ((!$@ and defined $sth), "table_info tested" );
+$sth = undef;
+
+# Column Info
+ eval {
+ $sth = $dbh->column_info();
+ };
+ok ((!$@ and defined $sth), "column_info tested" );
+#ok ($@, "Call to column_info with 0 arguements, error expected: $@" );
+$sth = undef;
+
+
+# Tables
+ eval {
+ $sth = $dbh->tables();
+ };
+ok ((!$@ and defined $sth), "tables tested" );
+$sth = undef;
+
+# Type Info All
+ eval {
+ $sth = $dbh->type_info_all();
+ };
+ok ((!$@ and defined $sth), "type_info_all tested" );
+$sth = undef;
+
+# Type Info
+ eval {
+ my @types = $dbh->type_info();
+ die unless @types;
+ };
+ok (!$@, "type_info(undef)");
+$sth = undef;
+
+# Quote
+ eval {
+ my $val = $dbh->quote();
+ die unless $val;
+ };
+ok ($@, "quote error expected: $@");
+
+$sth = undef;
+# Tests for quote:
+my @qt_vals = (1, 2, undef, 'NULL', "ThisIsAString", "This is Another String");
+my @expt_vals = (q{'1'}, q{'2'}, "NULL", q{'NULL'}, q{'ThisIsAString'}, q{'This is Another String'});
+for (my $x = 0; $x <= $#qt_vals; $x++) {
+ local $^W = 0;
+ my $val = $dbh->quote( $qt_vals[$x] );
+ is( $val, $expt_vals[$x], "$x: quote on $qt_vals[$x] returned $val" );
+}
+
+is( $dbh->quote( 1, SQL_INTEGER() ), 1, "quote(1, SQL_INTEGER)" );
+
+
+# Quote Identifier
+ eval {
+ my $val = $dbh->quote_identifier();
+ die unless $val;
+ };
+
+ok ($@, "quote_identifier error expected: $@");
+$sth = undef;
+
+SKIP: {
+ skip("get_info() not yet implemented", 1);
+ # , SQL_IDENTIFIER_QUOTE_CHAR => 29
+ # , SQL_CATALOG_NAME_SEPARATOR => 41
+ my $qt = $dbh->get_info( $get_info->{SQL_IDENTIFIER_QUOTE_CHAR} );
+ my $sep = $dbh->get_info( $get_info->{SQL_CATALOG_NAME_SEPARATOR} );
+
+ # Uncomment this line and remove the next line when get_info() is implemented.
+# my $cmp_str = qq{${qt}link${qt}${sep}${qt}schema${qt}${sep}${qt}table${qt}};
+ my $cmp_str = '';
+ is( $dbh->quote_identifier( "link", "schema", "table" )
+ , $cmp_str
+ , q{quote_identifier( "link", "schema", "table" )}
+ );
+}
+
+# Test ping
+
+ok ($dbh->ping, "Ping the current connection ..." );
+
+# Test Get Info.
+
+# SQL_KEYWORDS
+# SQL_CATALOG_TERM
+# SQL_DATA_SOURCE_NAME
+# SQL_DBMS_NAME
+# SQL_DBMS_VERSION
+# SQL_DRIVER_NAME
+# SQL_DRIVER_VER
+# SQL_PROCEDURE_TERM
+# SQL_SCHEMA_TERM
+# SQL_TABLE_TERM
+# SQL_USER_NAME
+
+SKIP: {
+ skip("get_info() not yet implemented", 5);
+ foreach my $info (sort keys %$get_info) {
+ my $type = $dbh->get_info($get_info->{$info});
+ ok( defined $type, "get_info($info) ($get_info->{$info}) " .
+ ($type || '') );
+ }
+}
+
+# Test Table Info
+$sth = $dbh->table_info( undef, undef, undef );
+ok( defined $sth, "table_info(undef, undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->table_info( undef, undef, undef, "VIEW" );
+ok( defined $sth, "table_info(undef, undef, undef, \"VIEW\") tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+# Test Table Info Rule 19a
+$sth = $dbh->table_info( '%', '', '');
+ok( defined $sth, "table_info('%', '', '',) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+# Test Table Info Rule 19b
+$sth = $dbh->table_info( '', '%', '');
+ok( defined $sth, "table_info('', '%', '',) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+# Test Table Info Rule 19c
+$sth = $dbh->table_info( '', '', '', '%');
+ok( defined $sth, "table_info('', '', '', '%',) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+# Test to see if this database contains any of the defined table types.
+$sth = $dbh->table_info( '', '', '', '%');
+ok( defined $sth, "table_info('', '', '', '%',) tested" );
+if ($sth) {
+ my $ref = $sth->fetchall_hashref( 'TABLE_TYPE' );
+ foreach my $type ( sort keys %$ref ) {
+ my $tsth = $dbh->table_info( undef, undef, undef, $type );
+ ok( defined $tsth, "table_info(undef, undef, undef, $type) tested" );
+ DBI::dump_results($tsth) if defined $tsth;
+ $tsth->finish;
+ }
+ $sth->finish;
+}
+$sth = undef;
+
+# Test Column Info
+$sth = $dbh->column_info( undef, undef, undef, undef );
+ok( defined $sth, "column_info(undef, undef, undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", undef, undef );
+ok( defined $sth, "column_info(undef, 'auser', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'ause%'", undef, undef );
+ok( defined $sth, "column_info(undef, 'ause%', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser','replicator'", undef, undef );
+ok( defined $sth, "column_info(undef, 'auser','replicator', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser','repl%'", undef, undef );
+ok( defined $sth, "column_info(undef, 'auser','repl%', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'fred','repl%'", undef, undef );
+ok( defined $sth, "column_info(undef, 'fred','repl%', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'fred','jim'", undef, undef );
+ok( defined $sth, "column_info(undef, 'fred','jim', undef, undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", "'pga_schema'", undef );
+ok( defined $sth, "column_info(undef, 'auser', 'pga_schema', undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", "'pga_%'", undef );
+ok( defined $sth, "column_info(undef, 'auser', 'pga_%', undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'ause%'", "'pga_%'", undef );
+ok( defined $sth, "column_info(undef, 'ause%', 'pga_%', undef) tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", "'pga_schema'", "'schemaname'" );
+ok( defined $sth, "column_info(undef, 'auser', 'pga_schema', 'schemaname') tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", "'pga_schema'", "'schema%'" );
+ok( defined $sth, "column_info(undef, 'auser', 'pga_schema', 'schema%') tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'auser'", "'pga_%'", "'schema%'" );
+ok( defined $sth, "column_info(undef, 'auser', 'pga_%', 'schema%') tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+$sth = $dbh->column_info( undef, "'ause%'", "'pga_%'", "'schema%'" );
+ok( defined $sth, "column_info(undef, 'ause%', 'pga_%', 'schema%') tested" );
+DBI::dump_results($sth) if defined $sth;
+$sth = undef;
+
+# Test call to primary_key_info
+local ($dbh->{Warn}, $dbh->{PrintError});
+$dbh->{PrintError} = $dbh->{Warn} = 0;
+
+# Primary Key Info
+eval {
+ $sth = $dbh->primary_key_info();
+ die unless $sth;
+};
+ok ($@, "Call to primary_key_info with 0 arguements, error expected: $@" );
+$sth = undef;
+
+# Primary Key
+eval {
+ $sth = $dbh->primary_key();
+ die unless $sth;
+};
+ok ($@, "Call to primary_key with 0 arguements, error expected: $@" );
+$sth = undef;
+
+$sth = $dbh->primary_key_info(undef, undef, undef );
+
+ok( defined $sth, "Statement handle defined for primary_key_info()" );
+
+if ( defined $sth ) {
+ while( my $row = $sth->fetchrow_arrayref ) {
+ local $^W = 0;
+ # print join( ", ", @$row, "\n" );
+ }
+
+ undef $sth;
+
+}
+
+$sth = $dbh->primary_key_info(undef, undef, undef );
+ok( defined $sth, "Statement handle defined for primary_key_info()" );
+
+my ( %catalogs, %schemas, %tables);
+
+my $cnt = 0;
+while( my ($catalog, $schema, $table) = $sth->fetchrow_array ) {
+ local $^W = 0;
+ $catalogs{$catalog}++ if $catalog;
+ $schemas{$schema}++ if $schema;
+ $tables{$table}++ if $table;
+ $cnt++;
+}
+ok( $cnt > 0, "At least one table has a primary key." );
+
+$sth = $dbh->primary_key_info(undef, qq{'$ENV{DBI_USER}'}, undef );
+ok(
+ defined $sth
+ , "Getting primary keys for tables owned by $ENV{DBI_USER}");
+DBI::dump_results($sth) if defined $sth;
+
+undef $sth;
+
+SKIP: {
+ # foreign_key_info
+ local ($dbh->{Warn}, $dbh->{PrintError});
+ $dbh->{PrintError} = $dbh->{Warn} = 0;
+ eval {
+ $sth = $dbh->foreign_key_info();
+ die unless $sth;
+ };
+ skip "foreign_key_info not supported by driver", 1 if $@;
+ ok( defined $sth, "Statement handle defined for foreign_key_info()" );
+ DBI::dump_results($sth) if defined $sth;
+ $sth = undef;
+}
+
+ok( $dbh->disconnect, "Disconnect from database" );
+
+exit(0);
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/99cleanup.t b/install/5.005/DBD-Pg-1.22-fixvercmp/t/99cleanup.t
new file mode 100644
index 0000000..e7563ab
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/99cleanup.t
@@ -0,0 +1,24 @@
+use strict;
+use DBI;
+use Test::More;
+
+if (defined $ENV{DBI_DSN}) {
+ plan tests => 3;
+} else {
+ plan skip_all => 'cannot test without DB info';
+}
+
+my $dbh = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS},
+ {RaiseError => 1, AutoCommit => 0}
+ );
+ok(defined $dbh,
+ 'connect with transaction'
+ );
+
+ok($dbh->do(q{DROP TABLE test}),
+ 'drop'
+ );
+
+ok($dbh->disconnect(),
+ 'disconnect'
+ );
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info.pm
new file mode 100644
index 0000000..417247f
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info.pm
@@ -0,0 +1,1167 @@
+package App::Info;
+
+# $Id: Info.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+=head1 NAME
+
+App::Info - Information about software packages on a system
+
+=head1 SYNOPSIS
+
+ use App::Info::Category::FooApp;
+
+ my $app = App::Info::Category::FooApp->new;
+
+ if ($app->installed) {
+ print "App name: ", $app->name, "\n";
+ print "Version: ", $app->version, "\n";
+ print "Bin dir: ", $app->bin_dir, "\n";
+ } else {
+ print "App not installed on your system. :-(\n";
+ }
+
+=head1 DESCRIPTION
+
+App::Info is an abstract base class designed to provide a generalized
+interface for subclasses that provide metadata about software packages
+installed on a system. The idea is that these classes can be used in Perl
+application installers in order to determine whether software dependencies
+have been fulfilled, and to get necessary metadata about those software
+packages.
+
+App::Info provides an event model for handling events triggered by App::Info
+subclasses. The events are classified as "info", "error", "unknown", and
+"confirm" events, and multiple handlers may be specified to handle any or all
+of these event types. This allows App::Info clients to flexibly handle events
+in any way they deem necessary. Implementing new event handlers is
+straight-forward, and use the triggering of events by App::Info subclasses is
+likewise kept easy-to-use.
+
+A few L<sample subclasses|"SEE ALSO"> are provided with the distribution, but
+others are invited to write their own subclasses and contribute them to the
+CPAN. Contributors are welcome to extend their subclasses to provide more
+information relevant to the application for which data is to be provided (see
+L<App::Info::HTTPD::Apache|App::Info::HTTPD::Apache> for an example), but are
+encouraged to, at a minimum, implement the abstract methods defined here and
+in the category abstract base classes (e.g.,
+L<App::Info::HTTPD|App::Info::HTTPD> and L<App::Info::Lib|App::Info::Lib>).
+See L<Subclassing|"SUBCLASSING"> for more information on implementing new
+subclasses.
+
+=cut
+
+use strict;
+use Carp ();
+use App::Info::Handler;
+use App::Info::Request;
+use vars qw($VERSION);
+
+$VERSION = '0.23';
+
+##############################################################################
+##############################################################################
+# This code ref is used by the abstract methods to throw an exception when
+# they're called directly.
+my $croak = sub {
+ my ($caller, $meth) = @_;
+ $caller = ref $caller || $caller;
+ if ($caller eq __PACKAGE__) {
+ $meth = __PACKAGE__ . '::' . $meth;
+ Carp::croak(__PACKAGE__ . " is an abstract base class. Attempt to " .
+ " call non-existent method $meth");
+ } else {
+ Carp::croak("Class $caller inherited from the abstract base class " .
+ __PACKAGE__ . ", but failed to redefine the $meth() " .
+ "method. Attempt to call non-existent method " .
+ "${caller}::$meth");
+ }
+};
+
+##############################################################################
+# This code reference is used by new() and the on_* error handler methods to
+# set the error handlers.
+my $set_handlers = sub {
+ my $on_key = shift;
+ # Default is to do nothing.
+ return [] unless $on_key;
+ my $ref = ref $on_key;
+ if ($ref) {
+ $on_key = [$on_key] unless $ref eq 'ARRAY';
+ # Make sure they're all handlers.
+ foreach my $h (@$on_key) {
+ if (my $r = ref $h) {
+ Carp::croak("$r object is not an App::Info::Handler")
+ unless UNIVERSAL::isa($h, 'App::Info::Handler');
+ } else {
+ # Look up the handler.
+ $h = App::Info::Handler->new( key => $h);
+ }
+ }
+ # Return 'em!
+ return $on_key;
+ } else {
+ # Look up the handler.
+ return [ App::Info::Handler->new( key => $on_key) ];
+ }
+};
+
+##############################################################################
+##############################################################################
+
+=head1 INTERFACE
+
+This section documents the public interface of App::Info.
+
+=head2 Constructor
+
+=head3 new
+
+ my $app = App::Info::Category::FooApp->new(@params);
+
+Constructs an App::Info object and returns it. The @params arguments define
+how the App::Info object will respond to certain events, and correspond to
+their like-named methods. See the L<"Event Handler Object Methods"> section
+for more information on App::Info events and how to handle them. The
+parameters to C<new()> for the different types of App::Info events are:
+
+=over 4
+
+=item on_info
+
+=item on_error
+
+=item on_unknown
+
+=item on_confirm
+
+=back
+
+When passing event handlers to C<new()>, the list of handlers for each type
+should be an anonymous array, for example:
+
+ my $app = App::Info::Category::FooApp->new( on_info => \@handlers );
+
+=cut
+
+sub new {
+ my ($pkg, %p) = @_;
+ my $class = ref $pkg || $pkg;
+ # Fail if the method isn't overridden.
+ $croak->($pkg, 'new') if $class eq __PACKAGE__;
+
+ # Set up handlers.
+ for (qw(on_error on_unknown on_info on_confirm)) {
+ $p{$_} = $set_handlers->($p{$_});
+ }
+
+ # Do it!
+ return bless \%p, $class;
+}
+
+##############################################################################
+##############################################################################
+
+=head2 Metadata Object Methods
+
+These are abstract methods in App::Info and must be provided by its
+subclasses. They provide the essential metadata of the software package
+supported by the App::Info subclass.
+
+=head3 key_name
+
+ my $key_name = $app->key_name;
+
+Returns a string that uniquely identifies the software for which the App::Info
+subclass provides data. This value should be unique across all App::Info
+classes. Typically, it's simply the name of the software.
+
+=cut
+
+sub key_name { $croak->(shift, 'key_name') }
+
+=head3 installed
+
+ if ($app->installed) {
+ print "App is installed.\n"
+ } else {
+ print "App is not installed.\n"
+ }
+
+Returns a true value if the application is installed, and a false value if it
+is not.
+
+=cut
+
+sub installed { $croak->(shift, 'installed') }
+
+##############################################################################
+
+=head3 name
+
+ my $name = $app->name;
+
+Returns the name of the application.
+
+=cut
+
+sub name { $croak->(shift, 'name') }
+
+##############################################################################
+
+=head3 version
+
+ my $version = $app->version;
+
+Returns the full version number of the application.
+
+=cut
+
+##############################################################################
+
+sub version { $croak->(shift, 'version') }
+
+=head3 major_version
+
+ my $major_version = $app->major_version;
+
+Returns the major version number of the application. For example, if
+C<version()> returns "7.1.2", then this method returns "7".
+
+=cut
+
+sub major_version { $croak->(shift, 'major_version') }
+
+##############################################################################
+
+=head3 minor_version
+
+ my $minor_version = $app->minor_version;
+
+Returns the minor version number of the application. For example, if
+C<version()> returns "7.1.2", then this method returns "1".
+
+=cut
+
+sub minor_version { $croak->(shift, 'minor_version') }
+
+##############################################################################
+
+=head3 patch_version
+
+ my $patch_version = $app->patch_version;
+
+Returns the patch version number of the application. For example, if
+C<version()> returns "7.1.2", then this method returns "2".
+
+=cut
+
+sub patch_version { $croak->(shift, 'patch_version') }
+
+##############################################################################
+
+=head3 bin_dir
+
+ my $bin_dir = $app->bin_dir;
+
+Returns the full path the application's bin directory, if it exists.
+
+=cut
+
+sub bin_dir { $croak->(shift, 'bin_dir') }
+
+##############################################################################
+
+=head3 inc_dir
+
+ my $inc_dir = $app->inc_dir;
+
+Returns the full path the application's include directory, if it exists.
+
+=cut
+
+sub inc_dir { $croak->(shift, 'inc_dir') }
+
+##############################################################################
+
+=head3 lib_dir
+
+ my $lib_dir = $app->lib_dir;
+
+Returns the full path the application's lib directory, if it exists.
+
+=cut
+
+sub lib_dir { $croak->(shift, 'lib_dir') }
+
+##############################################################################
+
+=head3 so_lib_dir
+
+ my $so_lib_dir = $app->so_lib_dir;
+
+Returns the full path the application's shared library directory, if it
+exists.
+
+=cut
+
+sub so_lib_dir { $croak->(shift, 'so_lib_dir') }
+
+##############################################################################
+
+=head3 home_url
+
+ my $home_url = $app->home_url;
+
+The URL for the software's home page.
+
+=cut
+
+sub home_url { $croak->(shift, 'home_url') }
+
+##############################################################################
+
+=head3 download_url
+
+ my $download_url = $app->download_url;
+
+The URL for the software's download page.
+
+=cut
+
+sub download_url { $croak->(shift, 'download_url') }
+
+##############################################################################
+##############################################################################
+
+=head2 Event Handler Object Methods
+
+These methods provide control over App::Info event handling. Events can be
+handled by one or more objects of subclasses of App::Info::Handler. The first
+to return a true value will be the last to execute. This approach allows
+handlers to be stacked, and makes it relatively easy to create new handlers.
+L<App::Info::Handler|App::Info::Handler> for information on writing event
+handlers.
+
+Each of the event handler methods takes a list of event handlers as its
+arguments. If none are passed, the existing list of handlers for the relevant
+event type will be returned. If new handlers are passed in, they will be
+returned.
+
+The event handlers may be specified as one or more objects of the
+App::Info::Handler class or subclasses, as one or more strings that tell
+App::Info construct such handlers itself, or a combination of the two. The
+strings can only be used if the relevant App::Info::Handler subclasses have
+registered strings with App::Info. For example, the App::Info::Handler::Print
+class included in the App::Info distribution registers the strings "stderr"
+and "stdout" when it starts up. These strings may then be used to tell
+App::Info to construct App::Info::Handler::Print objects that print to STDERR
+or to STDOUT, respectively. See the App::Info::Handler subclasses for what
+strings they register with App::Info.
+
+=head3 on_info
+
+ my @handlers = $app->on_info;
+ $app->on_info(@handlers);
+
+Info events are triggered when the App::Info subclass wants to send an
+informational status message. By default, these events are ignored, but a
+common need is for such messages to simply print to STDOUT. Use the
+L<App::Info::Handler::Print|App::Info::Handler::Print> class included with the
+App::Info distribution to have info messages print to STDOUT:
+
+ use App::Info::Handler::Print;
+ $app->on_info('stdout');
+ # Or:
+ my $stdout_handler = App::Info::Handler::Print->new('stdout');
+ $app->on_info($stdout_handler);
+
+=cut
+
+sub on_info {
+ my $self = shift;
+ $self->{on_info} = $set_handlers->(\@_) if @_;
+ return @{ $self->{on_info} };
+}
+
+=head3 on_error
+
+ my @handlers = $app->on_error;
+ $app->on_error(@handlers);
+
+Error events are triggered when the App::Info subclass runs into an unexpected
+but not fatal problem. (Note that fatal problems will likely throw an
+exception.) By default, these events are ignored. A common way of handling
+these events is to print them to STDERR, once again using the
+L<App::Info::Handler::Print|App::Info::Handler::Print> class included with the
+App::Info distribution:
+
+ use App::Info::Handler::Print;
+ my $app->on_error('stderr');
+ # Or:
+ my $stderr_handler = App::Info::Handler::Print->new('stderr');
+ $app->on_error($stderr_handler);
+
+Another approach might be to turn such events into fatal exceptions. Use the
+included L<App::Info::Handler::Carp|App::Info::Handler::Carp> class for this
+purpose:
+
+ use App::Info::Handler::Carp;
+ my $app->on_error('croak');
+ # Or:
+ my $croaker = App::Info::Handler::Carp->new('croak');
+ $app->on_error($croaker);
+
+=cut
+
+sub on_error {
+ my $self = shift;
+ $self->{on_error} = $set_handlers->(\@_) if @_;
+ return @{ $self->{on_error} };
+}
+
+=head3 on_unknown
+
+ my @handlers = $app->on_unknown;
+ $app->on_uknown(@handlers);
+
+Unknown events are trigged when the App::Info subclass cannot find the value
+to be returned by a method call. By default, these events are ignored. A
+common way of handling them is to have the application prompt the user for the
+relevant data. The App::Info::Handler::Prompt class included with the
+App::Info distribution can do just that:
+
+ use App::Info::Handler::Prompt;
+ my $app->on_unknown('prompt');
+ # Or:
+ my $prompter = App::Info::Handler::Prompt;
+ $app->on_unknown($prompter);
+
+See L<App::Info::Handler::Prompt|App::Info::Handler::Prompt> for information
+on how it works.
+
+=cut
+
+sub on_unknown {
+ my $self = shift;
+ $self->{on_unknown} = $set_handlers->(\@_) if @_;
+ return @{ $self->{on_unknown} };
+}
+
+=head3 on_confirm
+
+ my @handlers = $app->on_confirm;
+ $app->on_confirm(@handlers);
+
+Confirm events are triggered when the App::Info subclass has found an
+important piece of information (such as the location of the executable it'll
+use to collect information for the rest of its methods) and wants to confirm
+that the information is correct. These events will most often be triggered
+during the App::Info subclass object construction. Here, too, the
+App::Info::Handler::Prompt class included with the App::Info distribution can
+help out:
+
+ use App::Info::Handler::Prompt;
+ my $app->on_confirm('prompt');
+ # Or:
+ my $prompter = App::Info::Handler::Prompt;
+ $app->on_confirm($prompter);
+
+=cut
+
+sub on_confirm {
+ my $self = shift;
+ $self->{on_confirm} = $set_handlers->(\@_) if @_;
+ return @{ $self->{on_confirm} };
+}
+
+##############################################################################
+##############################################################################
+
+=head1 SUBCLASSING
+
+As an abstract base class, App::Info is not intended to be used directly.
+Instead, you'll use concrete subclasses that implement the interface it
+defines. These subclasses each provide the metadata necessary for a given
+software package, via the interface outlined above (plus any additional
+methods the class author deems sensible for a given application).
+
+This section describes the facilities App::Info provides for subclassing. The
+goal of the App::Info design has been to make subclassing straight-forward, so
+that developers can focus on gathering the data they need for their
+application and minimize the work necessary to handle unknown values or to
+confirm values. As a result, there are essentially three concepts that
+developers need to understand when subclassing App::Info: organization,
+utility methods, and events.
+
+=head2 Organization
+
+The organizational idea behind App::Info is to name subclasses by broad
+software categories. This approach allows the categories themselves to
+function as abstract base classes that extend App::Info, so that they can
+specify more methods for all of their base classes to implement. For example,
+App::Info::HTTPD has specified the C<httpd_root()> abstract method that its
+subclasses must implement. So as you get ready to implement your own subclass,
+think about what category of software you're gathering information about.
+New categories can be added as necessary.
+
+=head2 Utility Methods
+
+Once you've decided on the proper category, you can start implementing your
+App::Info concrete subclass. As you do so, take advantage of App::Info::Util,
+wherein I've tried to encapsulate common functionality to make subclassing
+easier. I found that most of what I was doing repetitively was looking for
+files and directories, and searching through files. Thus, App::Info::Util
+subclasses L<File::Spec|File::Spec> in order to offer easy access to
+commonly-used methods from that class, e.g., C<path()>. Plus, it has several
+of its own methods to assist you in finding files and directories in lists of
+files and directories, as well as methods for searching through files and
+returning the values found in those files. See
+L<App::Info::Util|App::Info::Util> for more information, and the App::Info
+subclasses in this distribution for usage examples.
+
+I recommend the use of a package-scoped lexical App::Info::Util object. That
+way it's nice and handy when you need to carry out common tasks. If you find
+you're doing something over and over that's not already addressed by an
+App::Info::Util method, consider submitting a patch to App::Info::Util to add
+the functionality you need.
+
+=head2 Events
+
+Use the methods described below to trigger events. Events are designed to
+provide a simple way for App::Info subclass developers to send status messages
+and errors, to confirm data values, and to request a value when the class
+caonnot determine a value itself. Events may optionally be handled by module
+users who assign App::Info::Handler subclass objects to your App::Info
+subclass object using the event handling methods described in the L<"Event
+Handler Object Methods"> section.
+
+=cut
+
+##############################################################################
+# This code reference is used by the event methods to manage the stack of
+# event handlers that may be available to handle each of the events.
+my $handler = sub {
+ my ($self, $meth, $params) = @_;
+
+ # Sanity check. We really want to keep control over this.
+ Carp::croak("Cannot call protected method $meth()")
+ unless UNIVERSAL::isa($self, scalar caller(1));
+
+ # Create the request object.
+ $params->{type} ||= $meth;
+ my $req = App::Info::Request->new(%$params);
+
+ # Do the deed. The ultimate handling handler may die.
+ foreach my $eh (@{$self->{"on_$meth"}}) {
+ last if $eh->handler($req);
+ }
+
+ # Return the requst.
+ return $req;
+};
+
+##############################################################################
+
+=head3 info
+
+ $self->info(@message);
+
+Use this method to display status messages for the user. You may wish to use
+it to inform users that you're searching for a particular file, or attempting
+to parse a file or some other resource for the data you need. For example, a
+common use might be in the object constructor: generally, when an App::Info
+object is created, some important initial piece of information is being
+sought, such as an executable file. That file may be in one of many locations,
+so it makes sense to let the user know that you're looking for it:
+
+ $self->info("Searching for executable");
+
+Note that, due to the nature of App::Info event handlers, your informational
+message may be used or displayed any number of ways, or indeed not at all (as
+is the default behavior).
+
+The C<@message> will be joined into a single string and stored in the
+C<message> attribute of the App::Info::Request object passed to info event
+handlers.
+
+=cut
+
+sub info {
+ my $self = shift;
+ # Execute the handler sequence.
+ my $req = $handler->($self, 'info', { message => join '', @_ });
+}
+
+##############################################################################
+
+=head3 error
+
+ $self->error(@error);
+
+Use this method to inform the user that something unexpected has happened. An
+example might be when you invoke another program to parse its output, but it's
+output isn't what you expected:
+
+ $self->error("Unable to parse version from `/bin/myapp -c`");
+
+As with all events, keep in mind that error events may be handled in any
+number of ways, or not at all.
+
+The C<@erorr> will be joined into a single string and stored in the C<message>
+attribute of the App::Info::Request object passed to error event handlers. If
+that seems confusing, think of it as an "error message" rather than an "error
+error." :-)
+
+=cut
+
+sub error {
+ my $self = shift;
+ # Execute the handler sequence.
+ my $req = $handler->($self, 'error', { message => join '', @_ });
+}
+
+##############################################################################
+
+=head3 unknown
+
+ my $val = $self->unknown(@params);
+
+Use this method when a value is unknown. This will give the user the option --
+assuming the appropriate handler handles the event -- to provide the needed
+data. The value entered will be returned by C<unknown()>. The parameters are
+as follows:
+
+=over 4
+
+=item key
+
+The C<key> parameter uniquely identifies the data point in your class, and is
+used by App::Info to ensure that an unknown event is handled only once, no
+matter how many times the method is called. The same value will be returned by
+subsequent calls to C<unknown()> as was returned by the first call, and no
+handlers will be activated. Typical values are "version" and "lib_dir".
+
+=item prompt
+
+The C<prompt> parameter is the prompt to be displayed should an event handler
+decide to prompt for the appropriate value. Such a prompt might be something
+like "Path to your httpd executable?". If this parameter is not provided,
+App::Info will construct one for you using your class' C<key_name()> method
+and the C<key> parameter. The result would be something like "Enter a valid
+FooApp version". The C<prompt> parameter value will be stored in the
+C<message> attribute of the App::Info::Request object passed to event
+handlers.
+
+=item callback
+
+Assuming a handler has collected a value for your unknown data point, it might
+make sense to validate the value. For example, if you prompt the user for a
+directory location, and the user enters one, it makes sense to ensure that the
+directory actually exists. The C<callback> parameter allows you to do this. It
+is a code reference that takes the new value or values as its arguments, and
+returns true if the value is valid, and false if it is not. For the sake of
+convenience, the first argument to the callback code reference is also stored
+in C<$_> .This makes it easy to validate using functions or operators that,
+er, operate on C<$_> by default, but still allows you to get more information
+from C<@_> if necessary. For the directory example, a good callback might be
+C<sub { -d }>. The C<callback> parameter code reference will be stored in the
+C<callback> attribute of the App::Info::Request object passed to event
+handlers.
+
+=item error
+
+The error parameter is the error message to display in the event that the
+C<callback> code reference returns false. This message may then be used by the
+event handler to let the user know what went wrong with the data she entered.
+For example, if the unknown value was a directory, and the user entered a
+value that the C<callback> identified as invalid, a message to display might
+be something like "Invalid directory path". Note that if the C<error>
+parameter is not provided, App::Info will supply the generic error message
+"Invalid value". This value will be stored in the C<error> attribute of the
+App::Info::Request object passed to event handlers.
+
+=back
+
+This may be the event method you use most, as it should be called in every
+metadata method if you cannot provide the data needed by that method. It will
+typically be the last part of the method. Here's an example demonstrating each
+of the above arguments:
+
+ my $dir = $self->unknown( key => 'lib_dir',
+ prompt => "Enter lib directory path",
+ callback => sub { -d },
+ error => "Not a directory");
+
+=cut
+
+sub unknown {
+ my ($self, %params) = @_;
+ my $key = delete $params{key}
+ or Carp::croak("No key parameter passed to unknown()");
+ # Just return the value if we've already handled this value. Ideally this
+ # shouldn't happen.
+ return $self->{__unknown__}{$key} if exists $self->{__unknown__}{$key};
+
+ # Create a prompt and error message, if necessary.
+ $params{message} = delete $params{prompt} ||
+ "Enter a valid " . $self->key_name . " $key";
+ $params{error} ||= 'Invalid value';
+
+ # Execute the handler sequence.
+ my $req = $handler->($self, "unknown", \%params);
+
+ # Mark that we've provided this value and then return it.
+ $self->{__unknown__}{$key} = $req->value;
+ return $self->{__unknown__}{$key};
+}
+
+##############################################################################
+
+=head3 confirm
+
+ my $val = $self->confirm(@params);
+
+This method is very similar to C<unknown()>, but serves a different purpose.
+Use this method for significant data points where you've found an appropriate
+value, but want to ensure it's really the correct value. A "significant data
+point" is usually a value essential for your class to collect metadata values.
+For example, you might need to locate an executable that you can then call to
+collect other data. In general, this will only happen once for an object --
+during object construction -- but there may be cases in which it is needed
+more than that. But hopefully, once you've confirmed in the constructor that
+you've found what you need, you can use that information to collect the data
+needed by all of the metadata methods and can assume that they'll be right
+because that first, significant data point has been confirmed.
+
+Other than where and how often to call C<confirm()>, its use is quite similar
+to that of C<unknown()>. Its parameters are as follows:
+
+=over
+
+=item key
+
+Same as for C<unknown()>, a string that uniquely identifies the data point in
+your class, and ensures that the event is handled only once for a given key.
+The same value will be returned by subsequent calls to C<confirm()> as was
+returned by the first call for a given key.
+
+=item prompt
+
+Same as for C<unknown()>. Although C<confirm()> is called to confirm a value,
+typically the prompt should request the relevant value, just as for
+C<unknown()>. The difference is that the handler I<should> use the C<value>
+parameter as the default should the user not provide a value. The C<prompt>
+parameter will be stored in the C<message> attribute of the App::Info::Request
+object passed to event handlers.
+
+=item value
+
+The value to be confirmed. This is the value you've found, and it will be
+provided to the user as the default option when they're prompted for a new
+value. This value will be stored in the C<value> attribute of the
+App::Info::Request object passed to event handlers.
+
+=item callback
+
+Same as for C<unknown()>. Because the user can enter data to replace the
+default value provided via the C<value> parameter, you might want to validate
+it. Use this code reference to do so. The callback will be stored in the
+C<callback> attribute of the App::Info::Request object passed to event
+handlers.
+
+=item error
+
+Same as for C<unknown()>: an error message to display in the event that a
+value entered by the user isn't validated by the C<callback> code reference.
+This value will be stored in the C<error> attribute of the App::Info::Request
+object passed to event handlers.
+
+=back
+
+Here's an example usage demonstrating all of the above arguments:
+
+ my $exe = $self->confirm( key => 'shell',
+ prompt => 'Path to your shell?',
+ value => '/bin/sh',
+ callback => sub { -x },
+ error => 'Not an executable');
+
+
+=cut
+
+sub confirm {
+ my ($self, %params) = @_;
+ my $key = delete $params{key}
+ or Carp::croak("No key parameter passed to confirm()");
+ return $self->{__confirm__}{$key} if exists $self->{__confirm__}{$key};
+
+ # Create a prompt and error message, if necessary.
+ $params{message} = delete $params{prompt} ||
+ "Enter a valid " . $self->key_name . " $key";
+ $params{error} ||= 'Invalid value';
+
+ # Execute the handler sequence.
+ my $req = $handler->($self, "confirm", \%params);
+
+ # Mark that we've confirmed this value.
+ $self->{__confirm__}{$key} = $req->value;
+
+ return $self->{__confirm__}{$key}
+}
+
+1;
+__END__
+
+=head2 Event Examples
+
+Below I provide some examples demonstrating the use of the event methods.
+These are meant to emphasize the contexts in which it's appropriate to use
+them.
+
+Let's start with the simplest, first. Let's say that to find the version
+number for an application, you need to search a file for the relevant data.
+Your App::Info concrete subclass might have a private method that handles this
+work, and this method is the appropriate place to use the C<info()> and, if
+necessary, C<error()> methods.
+
+ sub _find_version {
+ my $self = shift;
+
+ # Try to find the revelant file. We cover this method below.
+ # Just return if we cant' find it.
+ my $file = $self->_find_file('version.conf') or return;
+
+ # Send a status message.
+ $self->info("Searching '$file' file for version");
+
+ # Search the file. $util is an App::Info::Util object.
+ my $ver = $util->search_file($file, qr/^Version\s+(.*)$/);
+
+ # Trigger an error message, if necessary. We really think we'll have the
+ # value, but we have to cover our butts in the unlikely event that we're
+ # wrong.
+ $self->error("Unable to find version in file '$file'") unless $ver;
+
+ # Return the version number.
+ return $ver;
+ }
+
+Here we've used the C<info()> method to display a status message to let the
+user know what we're doing. Then we used the C<error()> method when something
+unexpected happened, which in this case was that we weren't able to find the
+version number in the file.
+
+Note the C<_find_file()> method we've thrown in. This might be a method that
+we call whenever we need to find a file that might be in one of a list of
+directories. This method, too, will be an appropriate place for an C<info()>
+method call. But rather than call the C<error()> method when the file can't be
+found, you might want to give an event handler a chance to supply that value
+for you. Use the C<unknown()> method for a case such as this:
+
+ sub _find_file {
+ my ($self, $file) = @_;
+
+ # Send a status message.
+ $self->info("Searching for '$file' file");
+
+ # Look for the file. See App::Info:Utility for its interface.
+ my @paths = qw(/usr/conf /etc/conf /foo/conf);
+ my $found = $util->first_cat_path($file, @paths);
+
+ # If we didn't find it, trigger an unknown event to
+ # give a handler a chance to get the value.
+ $found ||= $self->unknown( key => "file_$file",
+ prompt => "Location of '$file' file?",
+ callback => sub { -f },
+ error => "Not a file");
+
+ # Now return the file name, regardless of whether we found it or not.
+ return $found;
+ }
+
+Note how in this method, we've tried to locate the file ourselves, but if we
+can't find it, we trigger an unknown event. This allows clients of our
+App::Info subclass to try to establish the value themselves by having an
+App::Info::Handler subclass handle the event. If a value is found by an
+App::Info::Handler subclass, it will be returned by C<unknown()> and we can
+continue. But we can't assume that the unknown event will even be handled, and
+thus must expect that an unknown value may remain unknown. This is why the
+C<_find_version()> method above simply returns if C<_find_file()> doesn't
+return a file name; there's no point in searching through a file that doesn't
+exist.
+
+Attentive readers may be left to wonder how to decide when to use C<error()>
+and when to use C<unknown()>. To a large extent, this decision must be based
+on one's own understanding of what's most appropriate. Nevertheless, I offer
+the following simple guidelines: Use C<error()> when you expect something to
+work and then it just doesn't (as when a file exists and should contain the
+information you seek, but then doesn't). Use C<unknown()> when you're less
+sure of your processes for finding the value, and also for any of the values
+that should be returned by any of the L<metadata object methods|"Metadata
+Object Methods">. And of course, C<error()> would be more appropriate when you
+encounter an unexpected condition and don't think that it could be handled in
+any other way.
+
+Now, more than likely, a method such C<_find_version()> would be called by the
+C<version()> method, which is a metadata method mandated by the App::Info
+abstract base class. This is an appropriate place to handle an unknown version
+value. Indeed, every one of your metadata methods should make use of the
+C<unknown()> method. The C<version()> method then should look something like
+this:
+
+ sub version {
+ my $self = shift;
+
+ unless (exists $self->{version}) {
+ # Try to find the version number.
+ $self->{version} = $self->_find_version ||
+ $self->unknown( key => 'version',
+ prompt => "Enter the version number");
+ }
+
+ # Now return the version number.
+ return $self->{version};
+ }
+
+Note how this method only tries to find the version number once. Any
+subsequent calls to C<version()> will return the same value that was returned
+the first time it was called. Of course, thanks to the C<key> parameter in the
+call to C<unknown()>, we could have have tried to enumerate the version number
+every time, as C<unknown()> will return the same value every time it is called
+(as, indeed, should C<_find_version()>. But by checking for the C<version> key
+in C<$self> ourselves, we save some of the overhead.
+
+But as I said before, every metadata method should make use of the
+C<unknown()> method. Thus, the C<major()> method might looks something like
+this:
+
+ sub major {
+ my $self = shift;
+
+ unless (exists $self->{major}) {
+ # Try to get the major version from the full version number.
+ ($self->{major}) = $self->version =~ /^(\d+)\./;
+ # Handle an unknown value.
+ $self->{major} = $self->unknown( key => 'major',
+ prompt => "Enter major version",
+ callback => sub { /^\d+$/ },
+ error => "Not a number")
+ unless defined $self->{major};
+ }
+
+ return $self->{version};
+ }
+
+Finally, the C<confirm()> method should be used to verify core pieces of data
+that significant numbers of other methods rely on. Typically such data are
+executables or configuration files from which will be drawn other metadata.
+Most often, such major data points will be sought in the object constructor.
+Here's an example:
+
+ sub new {
+ # Construct the object so that handlers will work properly.
+ my $self = shift->SUPER::new(@_);
+
+ # Try to find the executable.
+ $self->info("Searching for executable");
+ if (my $exe = $util->first_exe('/bin/myapp', '/usr/bin/myapp')) {
+ # Confirm it.
+ $self->{exe} =
+ $self->confirm( key => 'binary',
+ prompt => 'Path to your executable?',
+ value => $exe,
+ callback => sub { -x },
+ error => 'Not an executable');
+ } else {
+ # Handle an unknown value.
+ $self->{exe} =
+ $self->unknown( key => 'binary',
+ prompt => 'Path to your executable?',
+ callback => sub { -x },
+ error => 'Not an executable');
+ }
+
+ # We're done.
+ return $self;
+ }
+
+By now, most of what's going on here should be quite familiar. The use of the
+C<confirm()> method is quite similar to that of C<unknown()>. Really the only
+difference is that the value is known, but we need verification or a new value
+supplied if the value we found isn't correct. Such may be the case when
+multiple copies of the executable have been installed on the system, we found
+F</bin/myapp>, but the user may really be interested in F</usr/bin/myapp>.
+Thus the C<confirm()> event gives the user the chance to change the value if
+the confirm event is handled.
+
+The final thing to note about this constructor is the first line:
+
+ my $self = shift->SUPER::new(@_);
+
+The first thing an App::Info subclass should do is execute this line to allow
+the super class to construct the object first. Doing so allows any event
+handling arguments to set up the event handlers, so that when we call
+C<confirm()> or C<unknown()> the event will be handled as the client expects.
+
+If we needed our subclass constructor to take its own parameter argumente, the
+approach is to specify the same C<key => $arg> syntax as is used by
+App::Info's C<new()> method. Say we wanted to allow clients of our App::Info
+subclass to pass in a list of alternate executable locations for us to search.
+Such an argument would most make sense as an array reference. So we specify
+that the key be C<alt_paths> and allow the user to construct an object like
+this:
+
+ my $app = App::Info::Category::FooApp->new( alt_paths => \@paths );
+
+This approach allows the super class constructor arguments to pass unmolested
+(as long as we use unique keys!):
+
+ my $app = App::Info::Category::FooApp->new( on_error => \@handlers,
+ alt_paths => \@paths );
+
+Then, to retrieve these paths inside our C<new()> constructor, all we need do
+is access them directly from the object:
+
+ my $self = shift->SUPER::new(@_);
+ my $alt_paths = $self->{alt_paths};
+
+=head2 Subclassing Guidelines
+
+To summarize, here are some guidelines for subclassing App::Info.
+
+=over 4
+
+=item *
+
+Always subclass an App::Info category subclass. This will help to keep the
+App::Info namespace well-organized. New categories can be added as needed.
+
+=item *
+
+When you create the C<new()> constructor, always call C<SUPER::new(@_)>. This
+ensures that the event handling methods methods defined by the App::Info base
+classes (e.g., C<error()>) will work properly.
+
+=item *
+
+Use a package-scoped lexical App::Info::Util object to carry out common tasks.
+If you find you're doing something over and over that's not already addressed
+by an App::Info::Util method, and you think that others might find your
+solution useful, consider submitting a patch to App::Info::Util to add the
+functionality you need. See L<App::Info::Util|App::Info::Util> for complete
+documentation of its interface.
+
+=item *
+
+Use the C<info()> event triggering method to send messages to users of your
+subclass.
+
+=item *
+
+Use the C<error()> event triggering method to alert users of unexpected
+conditions. Fatal errors should still be fatal; use C<Carp::croak()> to throw
+exceptions for fatal errors.
+
+=item *
+
+Use the C<unknown()> event triggering method when a metadata or other
+important value is unknown and you want to give any event handlers the chance
+to provide the data.
+
+=item *
+
+Use the C<confirm()> event triggering method when a core piece of data is
+known (such as the location of an executable in the C<new()> constructor) and
+you need to make sure that you have the I<correct> information.
+
+=item *
+
+Be sure to implement B<all> of the abstract methods defined by App::Info and
+by your category abstract base class -- even if they don't do anything. Doing
+so ensures that all App::Info subclasses share a common interface, and can, if
+necessary, be used without regard to subclass. Any method not implemented but
+called on an object will generate a fatal exception.
+
+=back
+
+Otherwise, have fun! There are a lot of software packages for which relevant
+information might be collected and aggregated into an App::Info concrete
+subclass (witness all of the Automake macros in the world!), and folks who are
+knowledgeable about particular software packages or categories of software are
+warmly invited to contribute. As more subclasses are implemented, it will make
+sense, I think, to create separate distributions based on category -- or even,
+when necessary, on a single software package. Broader categories can then be
+aggregated in Bundle distributions.
+
+But I get ahead of myself...
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+The following classes define a few software package categories in which
+App::Info subclasses can be placed. Check them out for ideas on how to
+create new category subclasses.
+
+=over 4
+
+=item L<App::Info::HTTP|App::Info::HTTPD>
+
+=item L<App::Info::RDBMS|App::Info::RDBMS>
+
+=item L<App::Info::Lib|App::Info::Lib>
+
+=back
+
+The following classes implement the App::Info interface for various software
+packages. Check them out for examples of how to implement new App::Info
+concrete subclasses.
+
+=over
+
+=item L<App::Info::HTTPD::Apache|App::Info::HTTPD::Apache>
+
+=item L<App::Info::RDBMS::PostgreSQL|App::Info::RDBMS::PostgreSQL>
+
+=item L<App::Info::Lib::Expat|App::Info::Lib::Expat>
+
+=item L<App::Info::Lib::Iconv|App::Info::Lib::Iconv>
+
+=back
+
+L<App::Info::Util|App::Info::Util> provides utility methods for App::Info
+subclasses.
+
+L<App::Info::Handler|App::Info::Handler> defines an interface for event
+handlers to subclass. Consult its documentation for information on creating
+custom event handlers.
+
+The following classes implement the App::Info::Handler interface to offer some
+simple event handling. Check them out for examples of how to implement new
+App::Info::Handler subclasses.
+
+=over 4
+
+=item L<App::Info::Handler::Print|App::Info::Handler::Print>
+
+=item L<App::Info::Handler::Carp|App::Info::Handler::Carp>
+
+=item L<App::Info::Handler::Prompt|App::Info::Handler::Prompt>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler.pm
new file mode 100644
index 0000000..65416a8
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler.pm
@@ -0,0 +1,305 @@
+package App::Info::Handler;
+
+# $Id: Handler.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+=head1 NAME
+
+App::Info::Handler - App::Info event handler base class
+
+=head1 SYNOPSIS
+
+ use App::Info::Category::FooApp;
+ use App::Info::Handler;
+
+ my $app = App::Info::Category::FooApp->new( on_info => ['default'] );
+
+=head1 DESCRIPTION
+
+This class defines the interface for subclasses that wish to handle events
+triggered by App::Info concrete subclasses. The different types of events
+triggered by App::Info can all be handled by App::Info::Handler (indeed, by
+default they're all handled by a single App::Info::Handler object), and
+App::Info::Handler subclasses may be designed to handle whatever events they
+wish.
+
+If you're interested in I<using> an App::Info event handler, this is probably
+not the class you should look at, since all it does is define a simple handler
+that does nothing with an event. Look to the L<App::Info::Handler
+subclasses|"SEE ALSO"> included in this distribution to do more interesting
+things with App::Info events.
+
+If, on the other hand, you're interested in implementing your own event
+handlers, read on!
+
+=cut
+
+use strict;
+use vars qw($VERSION);
+$VERSION = '0.22';
+
+my %handlers;
+
+=head1 INTERFACE
+
+This section documents the public interface of App::Info::Handler.
+
+=head2 Class Method
+
+=head3 register_handler
+
+ App::Info::Handler->register_handler( $key => $code_ref );
+
+This class method may be used by App::Info::Handler subclasses to register
+themselves with App::Info::Handler. Multiple registrations are supported. The
+idea is that a subclass can define different functionality by specifying
+different strings that represent different modes of constructing an
+App::Info::Handler subclass object. The keys are case-sensitve, and should be
+unique across App::Info::Handler subclasses so that many subclasses can be
+loaded and used separately. If the C<$key> is already registered,
+C<register_handler()> will throw an exception. The values are code references
+that, when executed, return the appropriate App::Info::Handler subclass
+object.
+
+=cut
+
+sub register_handler {
+ my ($pkg, $key, $code) = @_;
+ Carp::croak("Handler '$key' already exists")
+ if $handlers{$key};
+ $handlers{$key} = $code;
+}
+
+# Register ourself.
+__PACKAGE__->register_handler('default', sub { __PACKAGE__->new } );
+
+##############################################################################
+
+=head2 Constructor
+
+=head3 new
+
+ my $handler = App::Info::Handler->new;
+ $handler = App::Info::Handler->new( key => $key);
+
+Constructs an App::Info::Handler object and returns it. If the key parameter
+is provided and has been registered by an App::Info::Handler subclass via the
+C<register_handler()> class method, then the relevant code reference will be
+executed and the resulting App::Info::Handler subclass object returned. This
+approach provides a handy shortcut for having C<new()> behave as an abstract
+factory method, returning an object of the subclass appropriate to the key
+parameter.
+
+=cut
+
+sub new {
+ my ($pkg, %p) = @_;
+ my $class = ref $pkg || $pkg;
+ $p{key} ||= 'default';
+ if ($class eq __PACKAGE__ && $p{key} ne 'default') {
+ # We were called directly! Handle it.
+ Carp::croak("No such handler '$p{key}'") unless $handlers{$p{key}};
+ return $handlers{$p{key}}->();
+ } else {
+ # A subclass called us -- just instantiate and return.
+ return bless \%p, $class;
+ }
+}
+
+=head2 Instance Method
+
+=head3 handler
+
+ $handler->handler($req);
+
+App::Info::Handler defines a single instance method that must be defined by
+its subclasses, C<handler()>. This is the method that will be executed by an
+event triggered by an App::Info concrete subclass. It takes as its single
+argument an App::Info::Request object, and returns a true value if it has
+handled the event request. Returning a false value declines the request, and
+App::Info will then move on to the next handler in the chain.
+
+The C<handler()> method implemented in App::Info::Handler itself does nothing
+more than return a true value. It thus acts as a very simple default event
+handler. See the App::Info::Handler subclasses for more interesting handling
+of events, or create your own!
+
+=cut
+
+sub handler { 1 }
+
+1;
+__END__
+
+=head1 SUBCLASSING
+
+I hatched the idea of the App::Info event model with its subclassable handlers
+as a way of separating the aggregation of application metadata from writing a
+user interface for handling certain conditions. I felt it a better idea to
+allow people to create their own user interfaces, and instead to provide only
+a few examples. The App::Info::Handler class defines the API interface for
+handling these conditions, which App::Info refers to as "events".
+
+There are various types of events defined by App::Info ("info", "error",
+"unknown", and "confirm"), but the App::Info::Handler interface is designed to
+be flexible enough to handle any and all of them. If you're interested in
+creating your own App::Info event handler, this is the place to learn how.
+
+=head2 The Interface
+
+To create an App::Info event handler, all one need do is subclass
+App::Info::Handler and then implement the C<new()> constructor and the
+C<handler()> method. The C<new()> constructor can do anything you like, and
+take any arguments you like. However, I do recommend that the first thing
+you do in your implementation is to call the super constructor:
+
+ sub new {
+ my $pkg = shift;
+ my $self = $pkg->SUPER::new(@_);
+ # ... other stuff.
+ return $self;
+ }
+
+Although the default C<new()> constructor currently doesn't do much, that may
+change in the future, so this call will keep you covered. What it does do is
+take the parameterized arguments and assign them to the App::Info::Handler
+object. Thus if you've specified a "mode" argument, where clients can
+construct objects of you class like this:
+
+ my $handler = FooHandler->new( mode => 'foo' );
+
+You can access the mode parameter directly from the object, like so:
+
+ sub new {
+ my $pkg = shift;
+ my $self = $pkg->SUPER::new(@_);
+ if ($self->{mode} eq 'foo') {
+ # ...
+ }
+ return $self;
+ }
+
+Just be sure not to use a parameter key name required by App::Info::Handler
+itself. At the moment, the only parameter accepted by App::Info::Handler is
+"key", so in general you'll be pretty safe.
+
+Next, I recommend that you take advantage of the C<register_handler()> method
+to create some shortcuts for creating handlers of your class. For example, say
+we're creating a handler subclass FooHandler. It has two modes, a default
+"foo" mode and an advanced "bar" mode. To allow both to be constructed by
+stringified shortcuts, the FooHandler class implementation might start like
+this:
+
+ package FooHandler;
+
+ use strict;
+ use App::Info::Handler;
+ use vars qw(@ISA);
+ @ISA = qw(App::Info::Handler);
+
+ foreach my $c (qw(foo bar)) {
+ App::Info::Handler->register_handler
+ ( $c => sub { __PACKAGE__->new( mode => $c) } );
+ }
+
+The strings "foo" and "bar" can then be used by clients as shortcuts to have
+App::Info objects automatically create and use handlers for certain events.
+For example, if a client wanted to use a "bar" event handler for its info
+events, it might do this:
+
+ use App::Info::Category::FooApp;
+ use FooHandler;
+
+ my $app = App::Info::Category::FooApp->new(on_info => ['bar']);
+
+Take a look at App::Info::Handler::Print and App::Info::Handler::Carp to see
+concrete examples of C<register_handler()> usage.
+
+The final step in creating a new App::Info event handler is to implement the
+C<handler()> method itself. This method takes a single argument, an
+App::Info::Request object, and is expected to return true if it handled the
+request, and false if it did not. The App::Info::Request object contains all
+the metadata relevant to a request, including the type of event that triggered
+it; see L<App::Info::Request|App::Info::Request> for its documentation.
+
+Use the App::Info::Request object however you like to handle the request
+however you like. You are, however, expected to abide by a a few guidelines:
+
+=over 4
+
+=item *
+
+For error and info events, you are expected (but not required) to somehow
+display the info or error message for the user. How your handler chooses to do
+so is up to you and the handler.
+
+=item *
+
+For unknown and confirm events, you are expected to prompt the user for a
+value. If it's a confirm event, offer the known value (found in
+C<$req-E<gt>value>) as a default.
+
+=item *
+
+For unknown and confirm events, you are expected to call C<$req-E<gt>callback>
+and pass in the new value. If C<$req-E<gt>callback> returns a false value, you
+are expected to display the error message in C<$req-E<gt>error> and prompt the
+user again. Note that C<$req-E<gt>value> calls C<$req-E<gt>callback>
+internally, and thus assigns the value and returns true if
+C<$req-E<gt>callback> returns true, and does not assign the value and returns
+false if C<$req-E<gt>callback> returns false.
+
+=item *
+
+For unknown and confirm events, if you've collected a new value and
+C<$req-E<gt>callback> returns true for that value, you are expected to assign
+the value by passing it to C<$req-E<gt>value>. This allows App::Info to give
+the value back to the calling App::Info concrete subclass.
+
+=back
+
+Probably the easiest way to get started creating new App::Info event handlers
+is to check out the simple handlers provided with the distribution and follow
+their logical examples. Consult the App::Info documentation of the L<event
+methods|App::Info/"Events"> for details on how App::Info constructs the
+App::Info::Request object for each event type.
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info> thoroughly documents the client interface for setting
+event handlers, as well as the event triggering interface for App::Info
+concrete subclasses.
+
+L<App::Info::Request|App::Info::Request> documents the interface for the
+request objects passed to App::Info::Handler C<handler()> methods.
+
+The following App::Info::Handler subclasses offer examples for event handler
+authors, and, of course, provide actual event handling functionality for
+App::Info clients.
+
+=over 4
+
+=item L<App::Info::Handler::Carp|App::Info::Handler::Carp>
+
+=item L<App::Info::Handler::Print|App::Info::Handler::Print>
+
+=item L<App::Info::Handler::Prompt|App::Info::Handler::Prompt>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler/Prompt.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler/Prompt.pm
new file mode 100644
index 0000000..47edd78
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Handler/Prompt.pm
@@ -0,0 +1,170 @@
+package App::Info::Handler::Prompt;
+
+# $Id: Prompt.pm,v 1.1 2004-04-29 09:21:29 ivan Exp $
+
+=head1 NAME
+
+App::Info::Handler::Prompt - Prompting App::Info event handler
+
+=head1 SYNOPSIS
+
+ use App::Info::Category::FooApp;
+ use App::Info::Handler::Print;
+
+ my $prompter = App::Info::Handler::Print->new;
+ my $app = App::Info::Category::FooApp->new( on_unknown => $prompter );
+
+ # Or...
+ my $app = App::Info::Category::FooApp->new( on_confirm => 'prompt' );
+
+=head1 DESCRIPTION
+
+App::Info::Handler::Prompt objects handle App::Info events by printing their
+messages to C<STDOUT> and then accepting a new value from C<STDIN>. The new
+value is validated by any callback supplied by the App::Info concrete subclass
+that triggered the event. If the value is valid, App::Info::Handler::Prompt
+assigns the new value to the event request. If it isn't it prints the error
+message associated with the event request, and then prompts for the data
+again.
+
+Although designed with unknown and confirm events in mind,
+App::Info::Handler::Prompt handles info and error events as well. It will
+simply print info event messages to C<STDOUT> and print error event messages
+to C<STDERR>. For more interesting info and error event handling, see
+L<App::Info::Handler::Print|App::Info::Handler::Print> and
+L<App::Info::Handler::Carp|App::Info::Handler::Carp>.
+
+Upon loading, App::Info::Handler::Print registers itself with
+App::Info::Handler, setting up a single string, "prompt", that can be passed
+to an App::Info concrete subclass constructor. This string is a shortcut that
+tells App::Info how to create an App::Info::Handler::Print object for handling
+events.
+
+=cut
+
+use strict;
+use App::Info::Handler;
+use vars qw($VERSION @ISA);
+$VERSION = '0.22';
+@ISA = qw(App::Info::Handler);
+
+# Register ourselves.
+App::Info::Handler->register_handler
+ ('prompt' => sub { __PACKAGE__->new('prompt') } );
+
+=head1 INTERFACE
+
+=head2 Constructor
+
+=head3 new
+
+ my $prompter = App::Info::Handler::Prompt->new;
+
+Constructs a new App::Info::Handler::Prompt object and returns it. No special
+arguments are required.
+
+=cut
+
+sub new {
+ my $pkg = shift;
+ my $self = $pkg->SUPER::new(@_);
+ $self->{tty} = -t STDIN && ( -t STDOUT || !( -f STDOUT || -c STDOUT ) );
+ # We're done!
+ return $self;
+}
+
+my $get_ans = sub {
+ my ($prompt, $tty, $def) = @_;
+ # Print the message.
+ local $| = 1;
+ local $\;
+ print $prompt;
+
+ # Collect the answer.
+ my $ans;
+ if ($tty) {
+ $ans = <STDIN>;
+ if (defined $ans ) {
+ chomp $ans;
+ } else { # user hit ctrl-D
+ print "\n";
+ }
+ } else {
+ print "$def\n" if defined $def;
+ }
+ return $ans;
+};
+
+sub handler {
+ my ($self, $req) = @_;
+ my $ans;
+ my $type = $req->type;
+ if ($type eq 'unknown' || $type eq 'confirm') {
+ # We'll want to prompt for a new value.
+ my $val = $req->value;
+ my ($def, $dispdef) = defined $val ? ($val, " [$val] ") : ('', ' ');
+ my $msg = $req->message or Carp::croak("No message in request");
+ $msg .= $dispdef;
+
+ # Get the answer.
+ $ans = $get_ans->($msg, $self->{tty}, $def);
+ # Just return if they entered an empty string or we couldnt' get an
+ # answer.
+ return 1 unless defined $ans && $ans ne '';
+
+ # Validate the answer.
+ my $err = $req->error;
+ while (!$req->value($ans)) {
+ print "$err: '$ans'\n";
+ $ans = $get_ans->($msg, $self->{tty}, $def);
+ return 1 unless defined $ans && $ans ne '';
+ }
+
+ } elsif ($type eq 'info') {
+ # Just print the message.
+ print STDOUT $req->message, "\n";
+ } elsif ($type eq 'error') {
+ # Just print the message.
+ print STDERR $req->message, "\n";
+ } else {
+ # This shouldn't happen.
+ Carp::croak("Invalid request type '$type'");
+ }
+
+ # Return true to indicate that we've handled the request.
+ return 1;
+}
+
+1;
+__END__
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info> documents the event handling interface.
+
+L<App::Info::Handler::Carp|App::Info::Handler::Carp> handles events by
+passing their messages Carp module functions.
+
+L<App::Info::Handler::Print|App::Info::Handler::Print> handles events by
+printing their messages to a file handle.
+
+L<App::Info::Handler|App::Info::Handler> describes how to implement custom
+App::Info event handlers.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS.pm
new file mode 100644
index 0000000..504d570
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS.pm
@@ -0,0 +1,55 @@
+package App::Info::RDBMS;
+
+# $Id: RDBMS.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+use strict;
+use App::Info;
+use vars qw(@ISA $VERSION);
+@ISA = qw(App::Info);
+$VERSION = '0.22';
+
+1;
+__END__
+
+=head1 NAME
+
+App::Info::RDBMS - Information about databases on a system
+
+=head1 DESCRIPTION
+
+This class is an abstract base class for App::Info subclasses that provide
+information about relational databases. Its subclasses are required to
+implement its interface. See L<App::Info|App::Info> for a complete description
+and L<App::Info::RDBMS::PostgreSQL|App::Info::RDBMS::PostgreSQL> for an example
+implementation.
+
+=head1 INTERFACE
+
+Currently, App::Info::RDBMS adds no more methods than those from its parent
+class, App::Info.
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info>,
+L<App::Info::RDBMS::PostgreSQL|App::Info::RDBMS::PostgreSQL>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
+
+
+
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS/PostgreSQL.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS/PostgreSQL.pm
new file mode 100644
index 0000000..aef326c
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/RDBMS/PostgreSQL.pm
@@ -0,0 +1,730 @@
+package App::Info::RDBMS::PostgreSQL;
+
+# $Id: PostgreSQL.pm,v 1.1 2004-04-29 09:21:29 ivan Exp $
+
+=head1 NAME
+
+App::Info::RDBMS::PostgreSQL - Information about PostgreSQL
+
+=head1 SYNOPSIS
+
+ use App::Info::RDBMS::PostgreSQL;
+
+ my $pg = App::Info::RDBMS::PostgreSQL->new;
+
+ if ($pg->installed) {
+ print "App name: ", $pg->name, "\n";
+ print "Version: ", $pg->version, "\n";
+ print "Bin dir: ", $pg->bin_dir, "\n";
+ } else {
+ print "PostgreSQL is not installed. :-(\n";
+ }
+
+=head1 DESCRIPTION
+
+App::Info::RDBMS::PostgreSQL supplies information about the PostgreSQL
+database server installed on the local system. It implements all of the
+methods defined by App::Info::RDBMS. Methods that trigger events will trigger
+them only the first time they're called (See L<App::Info|App::Info> for
+documentation on handling events). To start over (after, say, someone has
+installed PostgreSQL) construct a new App::Info::RDBMS::PostgreSQL object to
+aggregate new metadata.
+
+Some of the methods trigger the same events. This is due to cross-calling of
+shared subroutines. However, any one event should be triggered no more than
+once. For example, although the info event "Executing `pg_config --version`"
+is documented for the methods C<name()>, C<version()>, C<major_version()>,
+C<minor_version()>, and C<patch_version()>, rest assured that it will only be
+triggered once, by whichever of those four methods is called first.
+
+=cut
+
+use strict;
+use App::Info::RDBMS;
+use App::Info::Util;
+use vars qw(@ISA $VERSION);
+@ISA = qw(App::Info::RDBMS);
+$VERSION = '0.22';
+
+my $u = App::Info::Util->new;
+
+=head1 INTERFACE
+
+=head2 Constructor
+
+=head3 new
+
+ my $pg = App::Info::RDBMS::PostgreSQL->new(@params);
+
+Returns an App::Info::RDBMS::PostgreSQL object. See L<App::Info|App::Info> for
+a complete description of argument parameters.
+
+When it called, C<new()> searches the file system for the F<pg_config>
+application. If found, F<pg_config> will be called by the object methods below
+to gather the data necessary for each. If F<pg_config> cannot be found, then
+PostgreSQL is assumed not to be installed, and each of the object methods will
+return C<undef>.
+
+App::Info::RDBMS::PostgreSQL searches for F<pg_config> along your path, as
+defined by C<File::Spec-E<gt>path>. Failing that, it searches the following
+directories:
+
+=over 4
+
+=item /usr/local/pgsql/bin
+
+=item /usr/local/postgres/bin
+
+=item /opt/pgsql/bin
+
+=item /usr/local/bin
+
+=item /usr/local/sbin
+
+=item /usr/bin
+
+=item /usr/sbin
+
+=item /bin
+
+=back
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Looking for pg_config
+
+=item confirm
+
+Path to pg_config?
+
+=item unknown
+
+Path to pg_config?
+
+=back
+
+=cut
+
+sub new {
+ # Construct the object.
+ my $self = shift->SUPER::new(@_);
+
+ # Find pg_config.
+ $self->info("Looking for pg_config");
+ my @paths = ($u->path,
+ qw(/usr/local/pgsql/bin
+ /usr/local/postgres/bin
+ /opt/pgsql/bin
+ /usr/local/bin
+ /usr/local/sbin
+ /usr/bin
+ /usr/sbin
+ /bin));
+
+ if (my $cfg = $u->first_cat_exe('pg_config', @paths)) {
+ # We found it. Confirm.
+ $self->{pg_config} = $self->confirm( key => 'pg_config',
+ prompt => 'Path to pg_config?',
+ value => $cfg,
+ callback => sub { -x },
+ error => 'Not an executable');
+ } else {
+ # Handle an unknown value.
+ $self->{pg_config} = $self->unknown( key => 'pg_config',
+ prompt => 'Path to pg_config?',
+ callback => sub { -x },
+ error => 'Not an executable');
+ }
+
+ return $self;
+}
+
+# We'll use this code reference as a common way of collecting data.
+my $get_data = sub {
+ return unless $_[0]->{pg_config};
+ $_[0]->info("Executing `$_[0]->{pg_config} $_[1]`");
+ my $info = `$_[0]->{pg_config} $_[1]`;
+ chomp $info;
+ return $info;
+};
+
+##############################################################################
+
+=head2 Class Method
+
+=head3 key_name
+
+ my $key_name = App::Info::RDBMS::PostgreSQL->key_name;
+
+Returns the unique key name that describes this class. The value returned is
+the string "PostgreSQL".
+
+=cut
+
+sub key_name { 'PostgreSQL' }
+
+##############################################################################
+
+=head2 Object Methods
+
+=head3 installed
+
+ print "PostgreSQL is ", ($pg->installed ? '' : 'not '), "installed.\n";
+
+Returns true if PostgreSQL is installed, and false if it is not.
+App::Info::RDBMS::PostgreSQL determines whether PostgreSQL is installed based
+on the presence or absence of the F<pg_config> application on the file system
+as found when C<new()> constructed the object. If PostgreSQL does not appear
+to be installed, then all of the other object methods will return empty
+values.
+
+=cut
+
+sub installed { return $_[0]->{pg_config} ? 1 : undef }
+
+##############################################################################
+
+=head3 name
+
+ my $name = $pg->name;
+
+Returns the name of the application. App::Info::RDBMS::PostgreSQL parses the
+name from the system call C<`pg_config --version`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --version`
+
+=item error
+
+Failed to find PostgreSQL version with `pg_config --version`
+
+Unable to parse name from string
+
+Unable to parse version from string
+
+Failed to parse PostgreSQL version parts from string
+
+=item unknown
+
+Enter a valid PostgreSQL name
+
+=back
+
+=cut
+
+# This code reference is used by name(), version(), major_version(),
+# minor_version(), and patch_version() to aggregate the data they need.
+my $get_version = sub {
+ my $self = shift;
+ $self->{'--version'} = 1;
+ my $data = $get_data->($self, '--version');
+ unless ($data) {
+ $self->error("Failed to find PostgreSQL version with ".
+ "`$self->{pg_config} --version");
+ return;
+ }
+
+ chomp $data;
+ my ($name, $version) = split /\s+/, $data, 2;
+
+ # Check for and assign the name.
+ $name ?
+ $self->{name} = $name :
+ $self->error("Unable to parse name from string '$data'");
+
+ # Parse the version number.
+ if ($version) {
+ my ($x, $y, $z) = $version =~ /(\d+)\.(\d+).(\d+)/;
+ if (defined $x and defined $y and defined $z) {
+ @{$self}{qw(version major minor patch)} =
+ ($version, $x, $y, $z);
+ } else {
+ $self->error("Failed to parse PostgreSQL version parts from " .
+ "string '$version'");
+ }
+ } else {
+ $self->error("Unable to parse version from string '$data'");
+ }
+};
+
+sub name {
+ my $self = shift;
+ return unless $self->{pg_config};
+
+ # Load data.
+ $get_version->($self) unless $self->{'--version'};
+
+ # Handle an unknown name.
+ $self->{name} ||= $self->unknown( key => 'name' );
+
+ # Return the name.
+ return $self->{name};
+}
+
+##############################################################################
+
+=head3 version
+
+ my $version = $pg->version;
+
+Returns the PostgreSQL version number. App::Info::RDBMS::PostgreSQL parses the
+version number from the system call C<`pg_config --version`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --version`
+
+=item error
+
+Failed to find PostgreSQL version with `pg_config --version`
+
+Unable to parse name from string
+
+Unable to parse version from string
+
+Failed to parse PostgreSQL version parts from string
+
+=item unknown
+
+Enter a valid PostgreSQL version number
+
+=back
+
+=cut
+
+sub version {
+ my $self = shift;
+ return unless $self->{pg_config};
+
+ # Load data.
+ $get_version->($self) unless $self->{'--version'};
+
+ # Handle an unknown value.
+ unless ($self->{version}) {
+ # Create a validation code reference.
+ my $chk_version = sub {
+ # Try to get the version number parts.
+ my ($x, $y, $z) = /^(\d+)\.(\d+).(\d+)$/;
+ # Return false if we didn't get all three.
+ return unless $x and defined $y and defined $z;
+ # Save all three parts.
+ @{$self}{qw(major minor patch)} = ($x, $y, $z);
+ # Return true.
+ return 1;
+ };
+ $self->{version} = $self->unknown( key => 'version number',
+ callback => $chk_version);
+ }
+
+ return $self->{version};
+}
+
+##############################################################################
+
+=head3 major version
+
+ my $major_version = $pg->major_version;
+
+Returns the PostgreSQL major version number. App::Info::RDBMS::PostgreSQL
+parses the major version number from the system call C<`pg_config --version`>.
+For example, C<version()> returns "7.1.2", then this method returns "7".
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --version`
+
+=item error
+
+Failed to find PostgreSQL version with `pg_config --version`
+
+Unable to parse name from string
+
+Unable to parse version from string
+
+Failed to parse PostgreSQL version parts from string
+
+=item unknown
+
+Enter a valid PostgreSQL major version number
+
+=back
+
+=cut
+
+# This code reference is used by major_version(), minor_version(), and
+# patch_version() to validate a version number entered by a user.
+my $is_int = sub { /^\d+$/ };
+
+sub major_version {
+ my $self = shift;
+ return unless $self->{pg_config};
+ # Load data.
+ $get_version->($self) unless exists $self->{'--version'};
+ # Handle an unknown value.
+ $self->{major} = $self->unknown( key => 'major version number',
+ callback => $is_int)
+ unless $self->{major};
+ return $self->{major};
+}
+
+##############################################################################
+
+=head3 minor version
+
+ my $minor_version = $pg->minor_version;
+
+Returns the PostgreSQL minor version number. App::Info::RDBMS::PostgreSQL
+parses the minor version number from the system call C<`pg_config --version`>.
+For example, if C<version()> returns "7.1.2", then this method returns "2".
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --version`
+
+=item error
+
+Failed to find PostgreSQL version with `pg_config --version`
+
+Unable to parse name from string
+
+Unable to parse version from string
+
+Failed to parse PostgreSQL version parts from string
+
+=item unknown
+
+Enter a valid PostgreSQL minor version number
+
+=back
+
+=cut
+
+sub minor_version {
+ my $self = shift;
+ return unless $self->{pg_config};
+ # Load data.
+ $get_version->($self) unless exists $self->{'--version'};
+ # Handle an unknown value.
+ $self->{minor} = $self->unknown( key => 'minor version number',
+ callback => $is_int)
+ unless defined $self->{minor};
+ return $self->{minor};
+}
+
+##############################################################################
+
+=head3 patch version
+
+ my $patch_version = $pg->patch_version;
+
+Returns the PostgreSQL patch version number. App::Info::RDBMS::PostgreSQL
+parses the patch version number from the system call C<`pg_config --version`>.
+For example, if C<version()> returns "7.1.2", then this method returns "1".
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --version`
+
+=item error
+
+Failed to find PostgreSQL version with `pg_config --version`
+
+Unable to parse name from string
+
+Unable to parse version from string
+
+Failed to parse PostgreSQL version parts from string
+
+=item unknown
+
+Enter a valid PostgreSQL minor version number
+
+=back
+
+=cut
+
+sub patch_version {
+ my $self = shift;
+ return unless $self->{pg_config};
+ # Load data.
+ $get_version->($self) unless exists $self->{'--version'};
+ # Handle an unknown value.
+ $self->{patch} = $self->unknown( key => 'patch version number',
+ callback => $is_int)
+ unless defined $self->{patch};
+ return $self->{patch};
+}
+
+##############################################################################
+
+=head3 bin_dir
+
+ my $bin_dir = $pg->bin_dir;
+
+Returns the PostgreSQL binary directory path. App::Info::RDBMS::PostgreSQL
+gathers the path from the system call C<`pg_config --bindir`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --bindir`
+
+=item error
+
+Cannot find bin directory
+
+=item unknown
+
+Enter a valid PostgreSQL bin directory
+
+=back
+
+=cut
+
+# This code reference is used by bin_dir(), lib_dir(), and so_lib_dir() to
+# validate a directory entered by the user.
+my $is_dir = sub { -d };
+
+sub bin_dir {
+ my $self = shift;
+ return unless $self->{pg_config};
+ unless (exists $self->{bin_dir} ) {
+ if (my $dir = $get_data->($self, '--bindir')) {
+ $self->{bin_dir} = $dir;
+ } else {
+ # Handle an unknown value.
+ $self->error("Cannot find bin directory");
+ $self->{bin_dir} = $self->unknown( key => 'bin directory',
+ callback => $is_dir)
+ }
+ }
+
+ return $self->{bin_dir};
+}
+
+##############################################################################
+
+=head3 inc_dir
+
+ my $inc_dir = $pg->inc_dir;
+
+Returns the PostgreSQL include directory path. App::Info::RDBMS::PostgreSQL
+gathers the path from the system call C<`pg_config --includedir`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --includedir`
+
+=item error
+
+Cannot find include directory
+
+=item unknown
+
+Enter a valid PostgreSQL include directory
+
+=back
+
+=cut
+
+sub inc_dir {
+ my $self = shift;
+ return unless $self->{pg_config};
+ unless (exists $self->{inc_dir} ) {
+ if (my $dir = $get_data->($self, '--includedir')) {
+ $self->{inc_dir} = $dir;
+ } else {
+ # Handle an unknown value.
+ $self->error("Cannot find include directory");
+ $self->{inc_dir} = $self->unknown( key => 'include directory',
+ callback => $is_dir)
+ }
+ }
+
+ return $self->{inc_dir};
+}
+
+##############################################################################
+
+=head3 lib_dir
+
+ my $lib_dir = $pg->lib_dir;
+
+Returns the PostgreSQL library directory path. App::Info::RDBMS::PostgreSQL
+gathers the path from the system call C<`pg_config --libdir`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --libdir`
+
+=item error
+
+Cannot find library directory
+
+=item unknown
+
+Enter a valid PostgreSQL library directory
+
+=back
+
+=cut
+
+sub lib_dir {
+ my $self = shift;
+ return unless $self->{pg_config};
+ unless (exists $self->{lib_dir} ) {
+ if (my $dir = $get_data->($self, '--libdir')) {
+ $self->{lib_dir} = $dir;
+ } else {
+ # Handle an unknown value.
+ $self->error("Cannot find library directory");
+ $self->{lib_dir} = $self->unknown( key => 'library directory',
+ callback => $is_dir)
+ }
+ }
+
+ return $self->{lib_dir};
+}
+
+##############################################################################
+
+=head3 so_lib_dir
+
+ my $so_lib_dir = $pg->so_lib_dir;
+
+Returns the PostgreSQL shared object library directory path.
+App::Info::RDBMS::PostgreSQL gathers the path from the system call
+C<`pg_config --pkglibdir`>.
+
+B<Events:>
+
+=over 4
+
+=item info
+
+Executing `pg_config --pkglibdir`
+
+=item error
+
+Cannot find shared object library directory
+
+=item unknown
+
+Enter a valid PostgreSQL shared object library directory
+
+=back
+
+=cut
+
+# Location of dynamically loadable modules.
+sub so_lib_dir {
+ my $self = shift;
+ return unless $self->{pg_config};
+ unless (exists $self->{so_lib_dir} ) {
+ if (my $dir = $get_data->($self, '--pkglibdir')) {
+ $self->{so_lib_dir} = $dir;
+ } else {
+ # Handle an unknown value.
+ $self->error("Cannot find shared object library directory");
+ $self->{so_lib_dir} =
+ $self->unknown( key => 'shared object library directory',
+ callback => $is_dir)
+ }
+ }
+
+ return $self->{so_lib_dir};
+}
+
+##############################################################################
+
+=head3 home_url
+
+ my $home_url = $pg->home_url;
+
+Returns the PostgreSQL home page URL.
+
+=cut
+
+sub home_url { "http://www.postgresql.org/" }
+
+##############################################################################
+
+=head3 download_url
+
+ my $download_url = $pg->download_url;
+
+Returns the PostgreSQL download URL.
+
+=cut
+
+sub download_url { "http://www.ca.postgresql.org/sitess.html" }
+
+1;
+__END__
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">> based on code by Sam
+Tregar <L<sam@tregar.com|"sam@tregar.com">>.
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info> documents the event handling interface.
+
+L<App::Info::RDBMS|App::Info::RDBMS> is the App::Info::RDBMS::PostgreSQL
+parent class.
+
+L<DBD::Pg|DBD::Pg> is the L<DBI|DBI> driver for connecting to PostgreSQL
+databases.
+
+L<http://www.postgresql.org/> is the PostgreSQL home page.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Request.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Request.pm
new file mode 100644
index 0000000..c02c97b
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Request.pm
@@ -0,0 +1,287 @@
+package App::Info::Request;
+
+# $Id: Request.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+=head1 NAME
+
+App::Info::Request - App::Info event handler request object
+
+=head1 SYNOPSIS
+
+ # In an App::Info::Handler subclass:
+ sub handler {
+ my ($self, $req) = @_;
+ print "Event Type: ", $req->type;
+ print "Message: ", $req->message;
+ print "Error: ", $req->error;
+ print "Value: ", $req->value;
+ }
+
+=head1 DESCRIPTION
+
+Objects of this class are passed to the C<handler()> method of App::Info event
+handlers. Generally, this class will be of most interest to App::Info::Handler
+subclass implementers.
+
+The L<event triggering methods|App::Info/"Events"> in App::Info each construct
+a new App::Info::Request object and initialize it with their arguments. The
+App::Info::Request object is then the sole argument passed to the C<handler()>
+method of any and all App::Info::Handler objects in the event handling chain.
+Thus, if you'd like to create your own App::Info event handler, this is the
+object you need to be familiar with. Consult the
+L<App::Info::Handler|App::Info::Handler> documentation for details on creating
+custom event handlers.
+
+Each of the App::Info event triggering methods constructs an
+App::Info::Request object with different attribute values. Be sure to consult
+the documentation for the L<event triggering methods|App::Info/"Events"> in
+App::Info, where the values assigned to the App::Info::Request object are
+documented. Then, in your event handler subclass, check the value returned by
+the C<type()> method to determine what type of event request you're handling
+to handle the request appropriately.
+
+=cut
+
+use strict;
+use vars qw($VERSION);
+$VERSION = '0.23';
+
+##############################################################################
+
+=head1 INTERFACE
+
+The following sections document the App::Info::Request interface.
+
+=head2 Constructor
+
+=head3 new
+
+ my $req = App::Info::Request->new(%params);
+
+This method is used internally by App::Info to construct new
+App::Info::Request objects to pass to event handler objects. Generally, you
+won't need to use it, other than perhaps for testing custom App::Info::Handler
+classes.
+
+The parameters to C<new()> are passed as a hash of named parameters that
+correspond to their like-named methods. The supported parameters are:
+
+=over 4
+
+=item type
+
+=item message
+
+=item error
+
+=item value
+
+=item callback
+
+=back
+
+See the object methods documentation below for details on these object
+attributes.
+
+=cut
+
+sub new {
+ my $pkg = shift;
+
+ # Make sure we've got a hash of arguments.
+ Carp::croak("Odd number of parameters in call to " . __PACKAGE__ .
+ "->new() when named parameters expected" ) if @_ % 2;
+ my %params = @_;
+
+ # Validate the callback.
+ if ($params{callback}) {
+ Carp::croak("Callback parameter '$params{callback}' is not a code ",
+ "reference")
+ unless UNIVERSAL::isa($params{callback}, 'CODE');
+ } else {
+ # Otherwise just assign a default approve callback.
+ $params{callback} = sub { 1 };
+ }
+
+ # Validate type parameter.
+ if (my $t = $params{type}) {
+ Carp::croak("Invalid handler type '$t'")
+ unless $t eq 'error' or $t eq 'info' or $t eq 'unknown'
+ or $t eq 'confirm';
+ } else {
+ $params{type} = 'info';
+ }
+
+ # Return the request object.
+ bless \%params, ref $pkg || $pkg;
+}
+
+##############################################################################
+
+=head2 Object Methods
+
+=head3 message
+
+ my $message = $req->message;
+
+Returns the message stored in the App::Info::Request object. The message is
+typically informational, or an error message, or a prompt message.
+
+=cut
+
+sub message { $_[0]->{message} }
+
+##############################################################################
+
+=head3 error
+
+ my $error = $req->error;
+
+Returns any error message associated with the App::Info::Request object. The
+error message is typically there to display for users when C<callback()>
+returns false.
+
+=cut
+
+sub error { $_[0]->{error} }
+
+##############################################################################
+
+=head3 type
+
+ my $type = $req->type;
+
+Returns a string representing the type of event that triggered this request.
+The types are the same as the event triggering methods defined in App::Info.
+As of this writing, the supported types are:
+
+=over
+
+=item info
+
+=item error
+
+=item unknown
+
+=item confirm
+
+=back
+
+Be sure to consult the App::Info documentation for more details on the event
+types.
+
+=cut
+
+sub type { $_[0]->{type} }
+
+##############################################################################
+
+=head3 callback
+
+ if ($req->callback($value)) {
+ print "Value '$value' is valid.\n";
+ } else {
+ print "Value '$value' is not valid.\n";
+ }
+
+Executes the callback anonymous subroutine supplied by the App::Info concrete
+base class that triggered the event. If the callback returns false, then
+C<$value> is invalid. If the callback returns true, then C<$value> is valid
+and can be assigned via the C<value()> method.
+
+Note that the C<value()> method itself calls C<callback()> if it was passed a
+value to assign. See its documentation below for more information.
+
+=cut
+
+sub callback {
+ my $self = shift;
+ my $code = $self->{callback};
+ local $_ = $_[0];
+ $code->(@_);
+}
+
+##############################################################################
+
+=head3 value
+
+ my $value = $req->value;
+ if ($req->value($value)) {
+ print "Value '$value' successfully assigned.\n";
+ } else {
+ print "Value '$value' not successfully assigned.\n";
+ }
+
+When called without an argument, C<value()> simply returns the value currently
+stored by the App::Info::Request object. Typically, the value is the default
+value for a confirm event, or a value assigned to an unknown event.
+
+When passed an argument, C<value()> attempts to store the the argument as a
+new value. However, C<value()> calls C<callback()> on the new value, and if
+C<callback()> returns false, then C<value()> returns false and does not store
+the new value. If C<callback()> returns true, on the other hand, then
+C<value()> goes ahead and stores the new value and returns true.
+
+=cut
+
+sub value {
+ my $self = shift;
+ if ($#_ >= 0) {
+ # grab the value.
+ my $value = shift;
+ # Validate the value.
+ if ($self->callback($value)) {
+ # The value is good. Assign it and return true.
+ $self->{value} = $value;
+ return 1;
+ } else {
+ # Invalid value. Return false.
+ return;
+ }
+ }
+ # Just return the value.
+ return $self->{value};
+}
+
+1;
+__END__
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info> documents the event triggering methods and how they
+construct App::Info::Request objects to pass to event handlers.
+
+L<App::Info::Handler:|App::Info::Handler> documents how to create custom event
+handlers, which must make use of the App::Info::Request object passed to their
+C<handler()> object methods.
+
+The following classes subclass App::Info::Handler, and thus offer good
+exemplars for using App::Info::Request objects when handling events.
+
+=over 4
+
+=item L<App::Info::Handler::Carp|App::Info::Handler::Carp>
+
+=item L<App::Info::Handler::Print|App::Info::Handler::Print>
+
+=item L<App::Info::Handler::Prompt|App::Info::Handler::Prompt>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Util.pm b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Util.pm
new file mode 100644
index 0000000..55bb333
--- /dev/null
+++ b/install/5.005/DBD-Pg-1.22-fixvercmp/t/lib/App/Info/Util.pm
@@ -0,0 +1,456 @@
+package App::Info::Util;
+
+# $Id: Util.pm,v 1.1 2004-04-29 09:21:28 ivan Exp $
+
+=head1 NAME
+
+App::Info::Util - Utility class for App::Info subclasses
+
+=head1 SYNOPSIS
+
+ use App::Info::Util;
+
+ my $util = App::Info::Util->new;
+
+ # Subclasses File::Spec.
+ my @paths = $util->paths;
+
+ # First directory that exists in a list.
+ my $dir = $util->first_dir(@paths);
+
+ # First directory that exists in a path.
+ $dir = $util->first_path($ENV{PATH});
+
+ # First file that exists in a list.
+ my $file = $util->first_file('this.txt', '/that.txt', 'C:\\foo.txt');
+
+ # First file found among file base names and directories.
+ my $files = ['this.txt', 'that.txt'];
+ $file = $util->first_cat_file($files, @paths);
+
+=head1 DESCRIPTION
+
+This class subclasses L<File::Spec|File::Spec> and adds its own methods in
+order to offer utility methods to L<App::Info|App::Info> classes. Although
+intended to be used by App::Info subclasses, in truth App::Info::Util's
+utility may be considered more general, so feel free to use it elsewhere.
+
+The methods added in addition to the usual File::Spec suspects are designed to
+facilitate locating files and directories on the file system, as well as
+searching those files. The assumption is that, in order to provide useful
+metadata about a given software package, an App::Info subclass must find
+relevant files and directories and parse them with regular expressions. This
+class offers methods that simplify those tasks.
+
+=cut
+
+use strict;
+use File::Spec ();
+use vars qw(@ISA $VERSION);
+@ISA = qw(File::Spec);
+$VERSION = '0.22';
+
+my %path_dems = (MacOS => qr',',
+ MSWin32 => qr';',
+ os2 => qr';',
+ VMS => undef,
+ epoc => undef);
+
+my $path_dem = exists $path_dems{$^O} ? $path_dems{$^O} : qr':';
+
+=head1 CONSTRUCTOR
+
+=head2 new
+
+ my $util = App::Info::Util->new;
+
+This is a very simple constructor that merely returns an App::Info::Util
+object. Since, like its File::Spec super class, App::Info::Util manages no
+internal data itself, all methods may be used as class methods, if one prefers
+to. The constructor here is provided merely as a convenience.
+
+=cut
+
+sub new { bless {}, ref $_[0] || $_[0] }
+
+=head1 OBJECT METHODS
+
+In addition to all of the methods offered by its super class,
+L<File::Spec|File::Spec>, App::Info::Util offers the following methods.
+
+=head2 first_dir
+
+ my @paths = $util->paths;
+ my $dir = $util->first_dir(@dirs);
+
+Returns the first file system directory in @paths that exists on the local
+file system. Only the first item in @paths that exists as a directory will be
+returned; any other paths leading to non-directories will be ignored.
+
+=cut
+
+sub first_dir {
+ shift;
+ foreach (@_) { return $_ if -d }
+ return;
+}
+
+=head2 first_path
+
+ my $path = $ENV{PATH};
+ $dir = $util->first_path($path);
+
+Takes the $path string and splits it into a list of directory paths, based on
+the path demarcator on the local file system. Then calls C<first_dir()> to
+return the first directoy in the path list that exists on the local file
+system. The path demarcator is specified for the following file systems:
+
+=over 4
+
+=item MacOS: ","
+
+=item MSWin32: ";"
+
+=item os2: ";"
+
+=item VMS: undef
+
+This method always returns undef on VMS. Patches welcome.
+
+=item epoc: undef
+
+This method always returns undef on epoch. Patches welcome.
+
+=item Unix: ":"
+
+All other operating systems are assumed to be Unix-based.
+
+=back
+
+=cut
+
+sub first_path {
+ return unless $path_dem;
+ shift->first_dir(split /$path_dem/, shift)
+}
+
+=head2 first_file
+
+ my $file = $util->first_file(@filelist);
+
+Examines each of the files in @filelist and returns the first one that exists
+on the file system. The file must be a regular file -- directories will be
+ignored.
+
+=cut
+
+sub first_file {
+ shift;
+ foreach (@_) { return $_ if -f }
+ return;
+}
+
+=head2 first_exe
+
+ my $exe = $util->first_exe(@exelist);
+
+Examines each of the files in @exelist and returns the first one that exists
+on the file system as an executable file. Directories will be ignored.
+
+=cut
+
+sub first_exe {
+ shift;
+ foreach (@_) { return $_ if -f && -x }
+ return;
+}
+
+=head2 first_cat_path
+
+ my $file = $util->first_cat_path('ick.txt', @paths);
+ $file = $util->first_cat_path(['this.txt', 'that.txt'], @paths);
+
+The first argument to this method may be either a file or directory base name
+(that is, a file or directory name without a full path specification), or a
+reference to an array of file or directory base names. The remaining arguments
+constitute a list of directory paths. C<first_cat_path()> processes each of
+these directory paths, concatenates (by the method native to the local
+operating system) each of the file or directory base names, and returns the
+first one that exists on the file system.
+
+For example, let us say that we were looking for a file called either F<httpd>
+or F<apache>, and it could be in any of the following paths:
+F</usr/local/bin>, F</usr/bin/>, F</bin>. The method call looks like this:
+
+ my $httpd = $util->first_cat_path(['httpd', 'apache'], '/usr/local/bin',
+ '/usr/bin/', '/bin');
+
+If the OS is a Unix variant, C<first_cat_path()> will then look for the first
+file that exists in this order:
+
+=over 4
+
+=item /usr/local/bin/httpd
+
+=item /usr/local/bin/apache
+
+=item /usr/bin/httpd
+
+=item /usr/bin/apache
+
+=item /bin/httpd
+
+=item /bin/apache
+
+=back
+
+The first of these complete paths to be found will be returned. If none are
+found, then undef will be returned.
+
+=cut
+
+sub first_cat_path {
+ my $self = shift;
+ my $files = ref $_[0] ? shift() : [shift()];
+ foreach my $p (@_) {
+ foreach my $f (@$files) {
+ my $path = $self->catfile($p, $f);
+ return $path if -e $path;
+ }
+ }
+ return;
+}
+
+=head2 first_cat_dir
+
+ my $dir = $util->first_cat_dir('ick.txt', @paths);
+ $dir = $util->first_cat_dir(['this.txt', 'that.txt'], @paths);
+
+Funtionally identical to C<first_cat_path()>, except that it returns the
+directory path in which the first file was found, rather than the full
+concatenated path. Thus, in the above example, if the file found was
+F</usr/bin/httpd>, while C<first_cat_path()> would return that value,
+C<first_cat_dir()> would return F</usr/bin> instead.
+
+=cut
+
+sub first_cat_dir {
+ my $self = shift;
+ my $files = ref $_[0] ? shift() : [shift()];
+ foreach my $p (@_) {
+ foreach my $f (@$files) {
+ my $path = $self->catfile($p, $f);
+ return $p if -e $path;
+ }
+ }
+ return;
+}
+
+=head2 first_cat_exe
+
+ my $exe = $util->first_cat_exe('ick.txt', @paths);
+ $exe = $util->first_cat_exe(['this.txt', 'that.txt'], @paths);
+
+Funtionally identical to C<first_cat_path()>, except that it returns the full
+path to the first executable file found, rather than simply the first file
+found.
+
+=cut
+
+sub first_cat_exe {
+ my $self = shift;
+ my $files = ref $_[0] ? shift() : [shift()];
+ foreach my $p (@_) {
+ foreach my $f (@$files) {
+ my $path = $self->catfile($p, $f);
+ return $path if -f $path && -x $path;
+ }
+ }
+ return;
+}
+
+=head2 search_file
+
+ my $file = 'foo.txt';
+ my $regex = qr/(text\s+to\s+find)/;
+ my $value = $util->search_file($file, $regex);
+
+Opens C<$file> and executes the C<$regex> regular expression against each line
+in the file. Once the line matches and one or more values is returned by the
+match, the file is closed and the value or values returned.
+
+For example, say F<foo.txt> contains the line "Version 6.5, patch level 8",
+and you need to grab each of the three version parts. All three parts can
+be grabbed like this:
+
+ my $regex = qr/Version\s+(\d+)\.(\d+),[^\d]*(\d+)/;
+ my @nums = $util->search_file($file, $regex);
+
+Now C<@nums> will contain the values C<(6, 5, 8)>. Note that in a scalar
+context, the above search would yeild an array reference:
+
+ my $regex = qr/Version\s+(\d+)\.(\d+),[^\d]*(\d+)/;
+ my $nums = $util->search_file($file, $regex);
+
+So now C<$nums> contains C<[6, 5, 8]>. The same does not hold true if the
+match returns only one value, however. Say F<foo.txt> contains the line
+"king of the who?", and you wish to know who the king is king of. Either
+of the following two calls would get you the data you need:
+
+ my $minions = $util->search_file($file, qr/King\s+of\s+(.*)/);
+ my @minions = $util->search_file($file, qr/King\s+of\s+(.*)/);
+
+In the first case, because the regular expression contains only one set of
+parentheses, C<search_file()> will simply return that value: C<$minions>
+contains the string "the who?". In the latter case, C<@minions> of course
+contains a single element: C<("the who?")>.
+
+Note that a regular expression without parentheses -- that is, one that
+doesn't grab values and put them into $1, $2, etc., will never successfully
+match a line in this method. You must include something to parentetically
+match. If you just want to know the value of what was matched, parenthesize
+the whole thing and if the value returns, you have a match. Also, if you need
+to match patterns across lines, try using multiple regular expressions with
+C<multi_search_file()>, instead.
+
+=cut
+
+sub search_file {
+ my ($self, $file, $regex) = @_;
+ return unless $file && $regex;
+ open F, "<$file" or Carp::croak "Cannot open $file: $!\n";
+ my @ret;
+ while (<F>) {
+ # If we find a match, we're done.
+ (@ret) = /$regex/ and last;
+ }
+ close F;
+ # If the match returned an more than one value, always return the full
+ # array. Otherwise, return just the first value in a scalar context.
+ return unless @ret;
+ return wantarray ? @ret : $#ret <= 0 ? $ret[0] : \@ret;
+}
+
+=head2 multi_search_file
+
+ my @regexen = (qr/(one)/, qr/(two)\s+(three)/);
+ my @matches = $util->multi_search_file($file, @regexen);
+
+Like C<search_file()>, this mehod opens C<$file> and parses it for regular
+expresion matches. This method, however, can take a list of regular
+expressions to look for, and will return the values found for all of them.
+Regular expressions that match and return multiple values will be returned as
+array referernces, while those that match and return a single value will
+return just that single value.
+
+For example, say you are parsing a file with lines like the following:
+
+ #define XML_MAJOR_VERSION 1
+ #define XML_MINOR_VERSION 95
+ #define XML_MICRO_VERSION 2
+
+You need to get each of these numbers, but calling C<search_file()> for each
+of them would be wasteful, as each call to C<search_file()> opens the file and
+parses it. With C<multi_search_file()>, on the other hand, the file will be
+opened only once, and, once all of the regular expressions have returned
+matches, the file will be closed and the matches returned.
+
+Thus the above values can be collected like this:
+
+ my @regexen = ( qr/XML_MAJOR_VERSION\s+(\d+)$/,
+ qr/XML_MINOR_VERSION\s+(\d+)$/,
+ qr/XML_MICRO_VERSION\s+(\d+)$/ );
+
+ my @nums = $file->multi_search_file($file, @regexen);
+
+The result will be that C<@nums> contains C<(1, 95, 2)>. Note that
+C<multi_file_search()> tries to do the right thing by only parsing the file
+until all of the regular expressions have been matched. Thus, a large file
+with the values you need near the top can be parsed very quickly.
+
+As with C<search_file()>, C<multi_search_file()> can take regular expressions
+that match multiple values. These will be returned as array references. For
+example, say the file you're parsing has files like this:
+
+ FooApp Version 4
+ Subversion 2, Microversion 6
+
+To get all of the version numbers, you can either use three regular
+expressions, as in the previous example:
+
+ my @regexen = ( qr/FooApp\s+Version\s+(\d+)$/,
+ qr/Subversion\s+(\d+),/,
+ qr/Microversion\s+(\d$)$/ );
+
+ my @nums = $file->multi_search_file($file, @regexen);
+
+In which case C<@nums> will contain C<(4, 2, 6)>. Or, you can use just two
+regular expressions:
+
+ my @regexen = ( qr/FooApp\s+Version\s+(\d+)$/,
+ qr/Subversion\s+(\d+),\s+Microversion\s+(\d$)$/ );
+
+ my @nums = $file->multi_search_file($file, @regexen);
+
+In which case C<@nums> will contain C<(4, [2, 6])>. Note that the two
+parentheses that return values in the second regular expression cause the
+matches to be returned as an array reference.
+
+=cut
+
+sub multi_search_file {
+ my ($self, $file, @regexen) = @_;
+ return unless $file && @regexen;
+ my @each = @regexen;
+ open F, "<$file" or Carp::croak "Cannot open $file: $!\n";
+ my %ret;
+ while (my $line = <F>) {
+ my @splice;
+ # Process each of the regular expresssions.
+ for (my $i = 0; $i < @each; $i++) {
+ if ((my @ret) = $line =~ /$each[$i]/) {
+ # We have a match! If there's one match returned, just grab
+ # it. If there's more than one, keep it as an array ref.
+ $ret{$each[$i]} = $#ret > 0 ? \@ret : $ret[0];
+ # We got values for this regex, so not its place in the @each
+ # array.
+ push @splice, $i;
+ }
+ }
+ # Remove any regexen that have already found a match.
+ for (@splice) { splice @each, $_, 1 }
+ # If there are no more regexes, we're done -- no need to keep
+ # processing lines in the file!
+ last unless @each;
+ }
+ close F;
+ return unless %ret;
+ return wantarray ? @ret{@regexen} : \@ret{@regexen};
+}
+
+1;
+__END__
+
+=head1 BUGS
+
+Report all bugs via the CPAN Request Tracker at
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-Info>.
+
+=head1 AUTHOR
+
+David Wheeler <L<david@wheeler.net|"david@wheeler.net">>
+
+=head1 SEE ALSO
+
+L<App::Info|App::Info>, L<File::Spec|File::Spec>,
+L<App::Info::HTTPD::Apache|App::Info::HTTPD::Apache>
+L<App::Info::RDBMS::PostgreSQL|App::Info::RDBMS::PostgreSQL>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2002, David Wheeler. All Rights Reserved.
+
+This module is free software; you can redistribute it and/or modify it under the
+same terms as Perl itself.
+
+=cut
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Changes b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Changes
new file mode 100644
index 0000000..f413bd9
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Changes
@@ -0,0 +1,62 @@
+Revision history for Perl extension DBIx::DBSchema.
+
+0.23 Mon Feb 16 17:35:54 PST 2004
+ - Update Pg dependancy to 1.32
+ - Update the simple load test so it skips DBIx::DBSchema::DBD::Pg if
+ DBD::Pg 1.32 is not installed.
+
+0.22 Thu Oct 23 15:18:21 PDT 2003
+ - Pg reverse-engineering fix: varchar with no limit
+ - Pg needs (unreleased) DBD::Pg 1.30 (or deb 1.22-2... interesting)
+
+0.21 Thu Sep 19 05:04:18 PDT 2002
+ - Pg reverse-engineering fix: now sets default
+
+0.20 Mon Mar 4 04:58:34 2002
+ - documentation updates
+ - fix Column->new when using named params
+ - fix Pg driver reverse-engineering length of numeric columns:
+ translate 655362 to 10,2, etc.
+ - fix Pg driver reverse-engineering of text columns (don't have a
+ length)
+
+0.19 Tue Oct 23 08:49:12 2001
+ - documentation for %typemap
+ - preliminary Sybase driver from Charles Shapiro
+ <charles.shapiro@numethods.com> and Mitchell J. Friedman
+ <mitchell.friedman@numethods.com>.
+ - Fix Column::line to return a scalar as documented, not a list.
+ - Should finally eliminate the Use of uninitialized value at
+ ... DBIx/DBSchema/Column.pm line 251
+
+0.18 Fri Aug 10 17:07:28 2001
+ - Added Table::delcolumn
+ - patch from Charles Shapiro <cshapiro@numethods.com> to add
+ `ORDER BY a.attnum' to the SQL in DBIx::DBSchema::DBD::Pg::columns
+
+0.17 Sat Jul 7 17:55:33 2001
+ - Rework Table->new interface for named params
+ - Fixes for Pg blobs, yay!
+ - MySQL doesn't need non-standard index syntax anymore (since 3.22).
+ - patch from Mark Ethan Trostler <mark@zzo.com> for generating
+ tables without indices.
+
+0.16 Fri Jan 5 15:55:50 2001
+ - Don't overflow index names.
+
+0.15 Fri Nov 24 23:39:16 2000
+ - MySQL handling of BOOL type (change to TINYINT)
+
+0.14 Tue Oct 24 14:43:16 2000
+ - MySQL handling of SERIAL type (change to INTEGER AUTO_INCREMENT)
+
+0.13 Wed Oct 11 10:47:13 2000
+ - fixed up type mapping foo, added default values, added named
+ parameters to Column->new, fixed quoting of default values
+
+0.11 Sun Sep 28 02:16:25 2000
+ - oops, original verison got 0.10, so this one will get 0.11
+
+0.01 Sun Sep 17 07:57:35 2000
+ - original version; created by h2xs 1.19
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema.pm
new file mode 100644
index 0000000..fc4916d
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema.pm
@@ -0,0 +1,367 @@
+package DBIx::DBSchema;
+
+use strict;
+use vars qw(@ISA $VERSION);
+#use Exporter;
+use Carp qw(confess);
+use DBI;
+use FreezeThaw qw(freeze thaw cmpStr);
+use DBIx::DBSchema::Table;
+use DBIx::DBSchema::Column;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+
+#@ISA = qw(Exporter);
+@ISA = ();
+
+$VERSION = "0.23";
+
+=head1 NAME
+
+DBIx::DBSchema - Database-independent schema objects
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema;
+
+ $schema = new DBIx::DBSchema @dbix_dbschema_table_objects;
+ $schema = new_odbc DBIx::DBSchema $dbh;
+ $schema = new_odbc DBIx::DBSchema $dsn, $user, $pass;
+ $schema = new_native DBIx::DBSchema $dbh;
+ $schema = new_native DBIx::DBSchema $dsn, $user, $pass;
+
+ $schema->save("filename");
+ $schema = load DBIx::DBSchema "filename";
+
+ $schema->addtable($dbix_dbschema_table_object);
+
+ @table_names = $schema->tables;
+
+ $DBIx_DBSchema_table_object = $schema->table("table_name");
+
+ @sql = $schema->sql($dbh);
+ @sql = $schema->sql($dsn, $username, $password);
+ @sql = $schema->sql($dsn); #doesn't connect to database - less reliable
+
+ $perl_code = $schema->pretty_print;
+ %hash = eval $perl_code;
+ use DBI qw(:sql_types); $schema = pretty_read DBIx::DBSchema \%hash;
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema objects are collections of DBIx::DBSchema::Table objects and
+represent a database schema.
+
+This module implements an OO-interface to database schemas. Using this module,
+you can create a database schema with an OO Perl interface. You can read the
+schema from an existing database. You can save the schema to disk and restore
+it a different process. Most importantly, DBIx::DBSchema can write SQL
+CREATE statements statements for different databases from a single source.
+
+Currently supported databases are MySQL and PostgreSQL. Sybase support is
+partially implemented. DBIx::DBSchema will attempt to use generic SQL syntax
+for other databases. Assistance adding support for other databases is
+welcomed. See L<DBIx::DBSchema::DBD>, "Driver Writer's Guide and Base Class".
+
+=head1 METHODS
+
+=over 4
+
+=item new TABLE_OBJECT, TABLE_OBJECT, ...
+
+Creates a new DBIx::DBSchema object.
+
+=cut
+
+sub new {
+ my($proto, @tables) = @_;
+ my %tables = map { $_->name, $_ } @tables; #check for duplicates?
+
+ my $class = ref($proto) || $proto;
+ my $self = {
+ 'tables' => \%tables,
+ };
+
+ bless ($self, $class);
+
+}
+
+=item new_odbc DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
+
+Creates a new DBIx::DBSchema object from an existing data source, which can be
+specified by passing an open DBI database handle, or by passing the DBI data
+source name, username, and password. This uses the experimental DBI type_info
+method to create a schema with standard (ODBC) SQL column types that most
+closely correspond to any non-portable column types. Use this to import a
+schema that you wish to use with many different database engines. Although
+primary key and (unique) index information will only be read from databases
+with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
+column names and attributes *should* work for any database. Note that this
+method only uses "ODBC" column types; it does not require or use an ODBC
+driver.
+
+=cut
+
+sub new_odbc {
+ my($proto, $dbh) = (shift, shift);
+ $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr unless ref($dbh);
+ $proto->new(
+ map { new_odbc DBIx::DBSchema::Table $dbh, $_ } _tables_from_dbh($dbh)
+ );
+}
+
+=item new_native DATABASE_HANDLE | DATA_SOURCE USERNAME PASSWORD [ ATTR ]
+
+Creates a new DBIx::DBSchema object from an existing data source, which can be
+specified by passing an open DBI database handle, or by passing the DBI data
+source name, username and password. This uses database-native methods to read
+the schema, and will preserve any non-portable column types. The method is
+only available if there is a DBIx::DBSchema::DBD for the corresponding database engine (currently, MySQL and PostgreSQL).
+
+=cut
+
+sub new_native {
+ my($proto, $dbh) = (shift, shift);
+ $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr unless ref($dbh);
+ $proto->new(
+ map { new_native DBIx::DBSchema::Table ( $dbh, $_ ) } _tables_from_dbh($dbh)
+ );
+}
+
+=item load FILENAME
+
+Loads a DBIx::DBSchema object from a file.
+
+=cut
+
+sub load {
+ my($proto,$file)=@_; #use $proto ?
+ open(FILE,"<$file") or die "Can't open $file: $!";
+ my($string)=join('',<FILE>); #can $string have newlines? pry not?
+ close FILE or die "Can't close $file: $!";
+ my($self)=thaw $string;
+ #no bless needed?
+ $self;
+}
+
+=item save FILENAME
+
+Saves a DBIx::DBSchema object to a file.
+
+=cut
+
+sub save {
+ my($self,$file)=@_;
+ my($string)=freeze $self;
+ open(FILE,">$file") or die "Can't open $file: $!";
+ print FILE $string;
+ close FILE or die "Can't close file: $!";
+ my($check_self)=thaw $string;
+ die "Verify error: Can't freeze and thaw dbdef $self"
+ if (cmpStr($self,$check_self));
+}
+
+=item addtable TABLE_OBJECT
+
+Adds the given DBIx::DBSchema::Table object to this DBIx::DBSchema.
+
+=cut
+
+sub addtable {
+ my($self,$table)=@_;
+ $self->{'tables'}->{$table->name} = $table; #check for dupliates?
+}
+
+=item tables
+
+Returns a list of the names of all tables.
+
+=cut
+
+sub tables {
+ my($self)=@_;
+ keys %{$self->{'tables'}};
+}
+
+=item table TABLENAME
+
+Returns the specified DBIx::DBSchema::Table object.
+
+=cut
+
+sub table {
+ my($self,$table)=@_;
+ $self->{'tables'}->{$table};
+}
+
+=item sql [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
+
+Returns a list of SQL `CREATE' statements for this schema.
+
+The data source can be specified by passing an open DBI database handle, or by
+passing the DBI data source name, username and password.
+
+Although the username and password are optional, it is best to call this method
+with a database handle or data source including a valid username and password -
+a DBI connection will be opened and the quoting and type mapping will be more
+reliable.
+
+If passed a DBI data source (or handle) such as `DBI:mysql:database' or
+`DBI:Pg:dbname=database', will use syntax specific to that database engine.
+Currently supported databases are MySQL and PostgreSQL.
+
+If not passed a data source (or handle), or if there is no driver for the
+specified database, will attempt to use generic SQL syntax.
+
+=cut
+
+sub sql {
+ my($self, $dbh) = (shift, shift);
+ my $created_dbh = 0;
+ unless ( ref($dbh) || ! @_ ) {
+ $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
+ $created_dbh = 1;
+ }
+ my @r = map { $self->table($_)->sql_create_table($dbh); } $self->tables;
+ $dbh->disconnect if $created_dbh;
+ @r;
+}
+
+=item pretty_print
+
+Returns the data in this schema as Perl source, suitable for assigning to a
+hash.
+
+=cut
+
+sub pretty_print {
+ my($self) = @_;
+ join("},\n\n",
+ map {
+ my $table = $_;
+ "'$table' => {\n".
+ " 'columns' => [\n".
+ join("", map {
+ #cant because -w complains about , in qw()
+ # (also biiiig problems with empty lengths)
+ #" qw( $_ ".
+ #$self->table($table)->column($_)->type. " ".
+ #( $self->table($table)->column($_)->null ? 'NULL' : 0 ). " ".
+ #$self->table($table)->column($_)->length. " ),\n"
+ " '$_', ".
+ "'". $self->table($table)->column($_)->type. "', ".
+ "'". $self->table($table)->column($_)->null. "', ".
+ "'". $self->table($table)->column($_)->length. "', ".
+ "'". $self->table($table)->column($_)->default. "', ".
+ "'". $self->table($table)->column($_)->local. "',\n"
+ } $self->table($table)->columns
+ ).
+ " ],\n".
+ " 'primary_key' => '". $self->table($table)->primary_key. "',\n".
+ " 'unique' => [ ". join(', ',
+ map { "[ '". join("', '", @{$_}). "' ]" }
+ @{$self->table($table)->unique->lol_ref}
+ ). " ],\n".
+ " 'index' => [ ". join(', ',
+ map { "[ '". join("', '", @{$_}). "' ]" }
+ @{$self->table($table)->index->lol_ref}
+ ). " ],\n"
+ #" 'index' => [ ". " ],\n"
+ } $self->tables
+ ), "}\n";
+}
+
+=cut
+
+=item pretty_read HASHREF
+
+Creates a schema as specified by a data structure such as that created by
+B<pretty_print> method.
+
+=cut
+
+sub pretty_read {
+ my($proto, $href) = @_;
+ my $schema = $proto->new( map {
+ my(@columns);
+ while ( @{$href->{$_}{'columns'}} ) {
+ push @columns, DBIx::DBSchema::Column->new(
+ splice @{$href->{$_}{'columns'}}, 0, 6
+ );
+ }
+ DBIx::DBSchema::Table->new(
+ $_,
+ $href->{$_}{'primary_key'},
+ DBIx::DBSchema::ColGroup::Unique->new($href->{$_}{'unique'}),
+ DBIx::DBSchema::ColGroup::Index->new($href->{$_}{'index'}),
+ @columns,
+ );
+ } (keys %{$href}) );
+}
+
+# private subroutines
+
+sub _load_driver {
+ my($dbh) = @_;
+ my $driver;
+ if ( ref($dbh) ) {
+ $driver = $dbh->{Driver}->{Name};
+ } else {
+ $dbh =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i #nicked from DBI->connect
+ or '' =~ /()/; # ensure $1 etc are empty if match fails
+ $driver = $1 or confess "can't parse data source: $dbh";
+ }
+
+ #require "DBIx/DBSchema/DBD/$driver.pm";
+ #$driver;
+ eval 'require "DBIx/DBSchema/DBD/$driver.pm"' and $driver or die $@;
+}
+
+sub _tables_from_dbh {
+ my($dbh) = @_;
+ my $sth = $dbh->table_info or die $dbh->errstr;
+ #map { $_->{TABLE_NAME} } grep { $_->{TABLE_TYPE} eq 'TABLE' }
+ # @{ $sth->fetchall_arrayref({ TABLE_NAME=>1, TABLE_TYPE=>1}) };
+ map { $_->[0] } grep { $_->[1] =~ /^TABLE$/i }
+ @{ $sth->fetchall_arrayref([2,3]) };
+}
+
+=back
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+Charles Shapiro <charles.shapiro@numethods.com> and Mitchell Friedman
+<mitchell.friedman@numethods.com> contributed the start of a Sybase driver.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+Each DBIx::DBSchema object should have a name which corresponds to its name
+within the SQL database engine (DBI data source).
+
+pretty_print is actually pretty ugly.
+
+Perhaps pretty_read should eval column types so that we can use DBI
+qw(:sql_types) here instead of externally.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema::Table>, L<DBIx::DBSchema::ColGroup>,
+L<DBIx::DBSchema::ColGroup::Unique>, L<DBIx::DBSchema::ColGroup::Index>,
+L<DBIx::DBSchema::Column>, L<DBIx::DBSchema::DBD>,
+L<DBIx::DBSchema::DBD::mysql>, L<DBIx::DBSchema::DBD::Pg>, L<FS::Record>,
+L<DBI>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup.pm
new file mode 100644
index 0000000..ceeb223
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup.pm
@@ -0,0 +1,141 @@
+package DBIx::DBSchema::ColGroup;
+
+use strict;
+use vars qw(@ISA);
+#use Exporter;
+
+#@ISA = qw(Exporter);
+@ISA = qw();
+
+=head1 NAME
+
+DBIx::DBSchema::ColGroup - Column group objects
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema::ColGroup;
+
+ $colgroup = new DBIx::DBSchema::ColGroup ( $lol_ref );
+ $colgroup = new DBIx::DBSchema::ColGroup ( \@lol );
+ $colgroup = new DBIx::DBSchema::ColGroup (
+ [
+ [ 'single_column' ],
+ [ 'multiple_columns', 'another_column', ],
+ ]
+ );
+
+ $lol_ref = $colgroup->lol_ref;
+
+ @sql_lists = $colgroup->sql_list;
+
+ @singles = $colgroup->singles;
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema::ColGroup objects represent sets of sets of columns. (IOW a
+"list of lists" - see L<perllol>.)
+
+=head1 METHODS
+
+=over 4
+
+=item new [ LOL_REF ]
+
+Creates a new DBIx::DBSchema::ColGroup object. Pass a reference to a list of
+lists of column names.
+
+=cut
+
+sub new {
+ my($proto, $lol) = @_;
+
+ my $class = ref($proto) || $proto;
+ my $self = {
+ 'lol' => $lol,
+ };
+
+ bless ($self, $class);
+
+}
+
+=item lol_ref
+
+Returns a reference to a list of lists of column names.
+
+=cut
+
+sub lol_ref {
+ my($self) = @_;
+ $self->{'lol'};
+}
+
+=item sql_list
+
+Returns a flat list of comma-separated values, for SQL statements.
+
+For example:
+
+ @lol = (
+ [ 'single_column' ],
+ [ 'multiple_columns', 'another_column', ],
+ );
+
+ $colgroup = new DBIx::DBSchema::ColGroup ( \@lol );
+
+ print join("\n", $colgroup->sql_list), "\n";
+
+Will print:
+
+ single_column
+ multiple_columns, another_column
+
+=cut
+
+sub sql_list { #returns a flat list of comman-separates lists (for sql)
+ my($self)=@_;
+ grep $_ ne '', map join(', ', @{$_}), @{$self->{'lol'}};
+}
+
+=item singles
+
+Returns a flat list of all single item lists.
+
+=cut
+
+sub singles { #returns single-field groups as a flat list
+ my($self)=@_;
+ #map ${$_}[0], grep scalar(@{$_}) == 1, @{$self->{'lol'}};
+ map {
+ ${$_}[0] =~ /^(\w+)$/
+ #aah!
+ or die "Illegal column ", ${$_}[0], " in colgroup!";
+ $1;
+ } grep scalar(@{$_}) == 1, @{$self->{'lol'}};
+}
+
+=back
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema::Table>, L<DBIx::DBSchema::ColGroup::Unique>,
+L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema>, L<perllol>, L<perldsc>,
+L<DBI>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Index.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Index.pm
new file mode 100644
index 0000000..1a92baa
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Index.pm
@@ -0,0 +1,37 @@
+package DBIx::DBSchema::ColGroup::Index;
+
+use strict;
+use vars qw(@ISA);
+use DBIx::DBSchema::ColGroup;
+
+@ISA=qw(DBIx::DBSchema::ColGroup);
+
+=head1 NAME
+
+DBIx::DBSchema::ColGroup::Index - Index column group object
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema::ColGroup::Index;
+
+ # see DBIx::DBSchema::ColGroup methods
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema::ColGroup::Index objects represent the (non-unique) indices of a
+database table (L<DBIx::DBSchema::Table>). DBIx::DBSchema::ColGroup::Index
+inherits from DBIx::DBSchema::ColGroup.
+
+=head1 BUGS
+
+Is this empty subclass needed?
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema::ColGroup>, L<DBIx::DBSchema::ColGroup::Unique>,
+L<DBIx::DBSchema::Table>, L<DBIx::DBSchema>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Unique.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Unique.pm
new file mode 100644
index 0000000..450043f
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/ColGroup/Unique.pm
@@ -0,0 +1,38 @@
+package DBIx::DBSchema::ColGroup::Unique;
+
+use strict;
+use vars qw(@ISA);
+use DBIx::DBSchema::ColGroup;
+
+@ISA=qw(DBIx::DBSchema::ColGroup);
+
+=head1 NAME
+
+DBIx::DBSchema::ColGroup::Unique - Unique column group object
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema::ColGroup::Unique;
+
+ # see DBIx::DBSchema::ColGroup methods
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema::ColGroup::Unique objects represent the unique indices of a
+database table (L<DBIx::DBSchema::Table>). DBIx::DBSchema::ColGroup:Unique
+inherits from DBIx::DBSchema::ColGroup.
+
+=head1 BUGS
+
+Is this empty subclass needed?
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema::ColGroup>, L<DBIx::DBSchema::ColGroup::Index>,
+L<DBIx::DBSchema::Table>, L<DBIx::DBSchema>, L<FS::Record>
+
+=cut
+
+1;
+
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Column.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Column.pm
new file mode 100644
index 0000000..4e26646
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Column.pm
@@ -0,0 +1,300 @@
+package DBIx::DBSchema::Column;
+
+use strict;
+use vars qw(@ISA $VERSION);
+#use Carp;
+#use Exporter;
+
+#@ISA = qw(Exporter);
+@ISA = qw();
+
+$VERSION = '0.02';
+
+=head1 NAME
+
+DBIx::DBSchema::Column - Column objects
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema::Column;
+
+ #named params with a hashref (preferred)
+ $column = new DBIx::DBSchema::Column ( {
+ 'name' => 'column_name',
+ 'type' => 'varchar'
+ 'null' => 'NOT NULL',
+ 'length' => 64,
+ 'default' => '
+ 'local' => '',
+ } );
+
+ #list
+ $column = new DBIx::DBSchema::Column ( $name, $sql_type, $nullability, $length, $default, $local );
+
+ $name = $column->name;
+ $column->name( 'name' );
+
+ $sql_type = $column->type;
+ $column->type( 'sql_type' );
+
+ $null = $column->null;
+ $column->null( 'NULL' );
+ $column->null( 'NOT NULL' );
+ $column->null( '' );
+
+ $length = $column->length;
+ $column->length( '10' );
+ $column->length( '8,2' );
+
+ $default = $column->default;
+ $column->default( 'Roo' );
+
+ $sql_line = $column->line;
+ $sql_line = $column->line($datasrc);
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema::Column objects represent columns in tables (see
+L<DBIx::DBSchema::Table>).
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+=item new [ name [ , type [ , null [ , length [ , default [ , local ] ] ] ] ] ]
+
+Creates a new DBIx::DBSchema::Column object. Takes a hashref of named
+parameters, or a list. B<name> is the name of the column. B<type> is the SQL
+data type. B<null> is the nullability of the column (intrepreted using Perl's
+rules for truth, with one exception: `NOT NULL' is false). B<length> is the
+SQL length of the column. B<default> is the default value of the column.
+B<local> is reserved for database-specific information.
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+
+ my $self;
+ if ( ref($_[0]) ) {
+ $self = shift;
+ } else {
+ $self = { map { $_ => shift } qw(name type null length default local) };
+ }
+
+ #croak "Illegal name: ". $self->{'name'}
+ # if grep $self->{'name'} eq $_, @reserved_words;
+
+ $self->{'null'} =~ s/^NOT NULL$//i;
+ $self->{'null'} = 'NULL' if $self->{'null'};
+
+ bless ($self, $class);
+
+}
+
+=item name [ NAME ]
+
+Returns or sets the column name.
+
+=cut
+
+sub name {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ #croak "Illegal name: $name" if grep $name eq $_, @reserved_words;
+ $self->{'name'} = $value;
+ } else {
+ $self->{'name'};
+ }
+}
+
+=item type [ TYPE ]
+
+Returns or sets the column type.
+
+=cut
+
+sub type {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{'type'} = $value;
+ } else {
+ $self->{'type'};
+ }
+}
+
+=item null [ NULL ]
+
+Returns or sets the column null flag (the empty string is equivalent to
+`NOT NULL')
+
+=cut
+
+sub null {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $value =~ s/^NOT NULL$//i;
+ $value = 'NULL' if $value;
+ $self->{'null'} = $value;
+ } else {
+ $self->{'null'};
+ }
+}
+
+=item length [ LENGTH ]
+
+Returns or sets the column length.
+
+=cut
+
+sub length {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{'length'} = $value;
+ } else {
+ $self->{'length'};
+ }
+}
+
+=item default [ LOCAL ]
+
+Returns or sets the default value.
+
+=cut
+
+sub default {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{'default'} = $value;
+ } else {
+ $self->{'default'};
+ }
+}
+
+
+=item local [ LOCAL ]
+
+Returns or sets the database-specific field.
+
+=cut
+
+sub local {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{'local'} = $value;
+ } else {
+ $self->{'local'};
+ }
+}
+
+=item line [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
+
+Returns an SQL column definition.
+
+The data source can be specified by passing an open DBI database handle, or by
+passing the DBI data source name, username and password.
+
+Although the username and password are optional, it is best to call this method
+with a database handle or data source including a valid username and password -
+a DBI connection will be opened and the quoting and type mapping will be more
+reliable.
+
+If passed a DBI data source (or handle) such as `DBI:mysql:database' or
+`DBI:Pg:dbname=database', will use syntax specific to that database engine.
+Currently supported databases are MySQL and PostgreSQL. Non-standard syntax
+for other engines (if applicable) may also be supported in the future.
+
+=cut
+
+sub line {
+ my($self,$dbh) = (shift, shift);
+
+ my $created_dbh = 0;
+ unless ( ref($dbh) || ! @_ ) {
+ $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
+ my $gratuitous = $DBI::errstr; #surpress superfluous `used only once' error
+ $created_dbh = 1;
+ }
+
+ my $driver = DBIx::DBSchema::_load_driver($dbh);
+ my %typemap;
+ %typemap = eval "\%DBIx::DBSchema::DBD::${driver}::typemap" if $driver;
+ my $type = defined( $typemap{uc($self->type)} )
+ ? $typemap{uc($self->type)}
+ : $self->type;
+
+ my $null = $self->null;
+
+ my $default;
+ if ( defined($self->default) && $self->default ne ''
+ && ref($dbh)
+ # false laziness: nicked from FS::Record::_quote
+ && ( $self->default !~ /^\-?\d+(\.\d+)?$/
+ || $type =~ /(char|binary|blob|text)$/i
+ )
+ ) {
+ $default = $dbh->quote($self->default);
+ } else {
+ $default = $self->default;
+ }
+
+ #this should be a callback into the driver
+ if ( $driver eq 'mysql' ) { #yucky mysql hack
+ $null ||= "NOT NULL";
+ $self->local('AUTO_INCREMENT') if uc($self->type) eq 'SERIAL';
+ } elsif ( $driver eq 'Pg' ) { #yucky Pg hack
+ $null ||= "NOT NULL";
+ $null =~ s/^NULL$//;
+ }
+
+ my $r = join(' ',
+ $self->name,
+ $type. ( ( defined($self->length) && $self->length )
+ ? '('.$self->length.')'
+ : ''
+ ),
+ $null,
+ ( ( defined($default) && $default ne '' )
+ ? 'DEFAULT '. $default
+ : ''
+ ),
+ ( ( $driver eq 'mysql' && defined($self->local) )
+ ? $self->local
+ : ''
+ ),
+ );
+ $dbh->disconnect if $created_dbh;
+ $r;
+
+}
+
+=back
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+line() has database-specific foo that probably ought to be abstracted into
+the DBIx::DBSchema:DBD:: modules.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema::Table>, L<DBIx::DBSchema>, L<DBIx::DBSchema::DBD>, L<DBI>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD.pm
new file mode 100644
index 0000000..a4c6000
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD.pm
@@ -0,0 +1,113 @@
+package DBIx::DBSchema::DBD;
+
+use strict;
+use vars qw($VERSION);
+
+$VERSION = '0.02';
+
+=head1 NAME
+
+DBIx::DBSchema::DBD - DBIx::DBSchema Driver Writer's Guide and Base Class
+
+=head1 SYNOPSIS
+
+ perldoc DBIx::DBSchema::DBD
+
+ package DBIx::DBSchema::DBD::FooBase
+ use DBIx::DBSchmea::DBD;
+ @ISA = qw(DBIx::DBSchema::DBD);
+
+=head1 DESCRIPTION
+
+Drivers should be named DBIx::DBSchema::DBD::DatabaseName, where DatabaseName
+is the same as the DBD:: driver for this database. Drivers should implement the
+following class methods:
+
+=over 4
+
+=item columns CLASS DBI_DBH TABLE
+
+Given an active DBI database handle, return a listref of listrefs (see
+L<perllol>), each containing six elements: column name, column type,
+nullability, column length, column default, and a field reserved for
+driver-specific use.
+
+=item column CLASS DBI_DBH TABLE COLUMN
+
+Same as B<columns> above, except return the listref for a single column. You
+can inherit from DBIx::DBSchema::DBD to provide this function.
+
+=cut
+
+sub column {
+ my($proto, $dbh, $table, $column) = @_;
+ #@a = grep { $_->[0] eq $column } @{ $proto->columns( $dbh, $table ) };
+ #$a[0];
+ @{ [
+ grep { $_->[0] eq $column } @{ $proto->columns( $dbh, $table ) }
+ ] }[0]; #force list context on grep, return scalar of first element
+}
+
+=item primary_key CLASS DBI_DBH TABLE
+
+Given an active DBI database handle, return the primary key for the specified
+table.
+
+=item unique CLASS DBI_DBH TABLE
+
+Given an active DBI database handle, return a hashref of unique indices. The
+keys of the hashref are index names, and the values are arrayrefs which point
+a list of column names for each. See L<perldsc/"HASHES OF LISTS"> and
+L<DBIx::DBSchema::ColGroup>.
+
+=item index CLASS DBI_DBH TABLE
+
+Given an active DBI database handle, return a hashref of (non-unique) indices.
+The keys of the hashref are index names, and the values are arrayrefs which
+point a list of column names for each. See L<perldsc/"HASHES OF LISTS"> and
+L<DBIx::DBSchema::ColGroup>.
+
+=back
+
+=head1 TYPE MAPPING
+
+You can define a %typemap array for your driver to map "standard" data
+types to database-specific types. For example, the MySQL TIMESTAMP field
+has non-standard auto-updating semantics; the MySQL DATETIME type is
+what other databases and the ODBC standard call TIMESTAMP, so one of the
+entries in the MySQL %typemap is:
+
+ 'TIMESTAMP' => 'DATETIME',
+
+Another example is the Pg %typemap which maps the standard types BLOB and
+LONG VARBINARY to the Pg-specific BYTEA:
+
+ 'BLOB' => 'BYTEA',
+ 'LONG VARBINARY' => 'BYTEA',
+
+Make sure you use all uppercase-keys.
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<DBIx::DBSchema::DBD::mysql>, L<DBIx::DBSchema::DBD::Pg>,
+L<DBIx::DBSchema::ColGroup>, L<DBI>, L<DBI::DBD>, L<perllol>,
+L<perldsc/"HASHES OF LISTS">
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Pg.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Pg.pm
new file mode 100644
index 0000000..018b890
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Pg.pm
@@ -0,0 +1,175 @@
+package DBIx::DBSchema::DBD::Pg;
+
+use strict;
+use vars qw($VERSION @ISA %typemap);
+use DBD::Pg 1.22;
+use DBIx::DBSchema::DBD;
+
+$VERSION = '0.08';
+@ISA = qw(DBIx::DBSchema::DBD);
+
+%typemap = (
+ 'BLOB' => 'BYTEA',
+ 'LONG VARBINARY' => 'BYTEA',
+);
+
+=head1 NAME
+
+DBIx::DBSchema::DBD::Pg - PostgreSQL native driver for DBIx::DBSchema
+
+=head1 SYNOPSIS
+
+use DBI;
+use DBIx::DBSchema;
+
+$dbh = DBI->connect('dbi:Pg:dbname=database', 'user', 'pass');
+$schema = new_native DBIx::DBSchema $dbh;
+
+=head1 DESCRIPTION
+
+This module implements a PostgreSQL-native driver for DBIx::DBSchema.
+
+=cut
+
+sub columns {
+ my($proto, $dbh, $table) = @_;
+ my $sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull,
+ a.atthasdef, a.attnum
+ FROM pg_class c, pg_attribute a, pg_type t
+ WHERE c.relname = '$table'
+ AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid
+ ORDER BY a.attnum
+END
+ $sth->execute or die $sth->errstr;
+
+ map {
+
+ my $default = '';
+ if ( $_->{atthasdef} ) {
+ my $attnum = $_->{attnum};
+ my $d_sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c
+ WHERE c.relname = '$table' AND c.oid = d.adrelid AND d.adnum = $attnum
+END
+ $d_sth->execute or die $d_sth->errstr;
+
+ $default = $d_sth->fetchrow_arrayref->[0];
+ };
+
+ my $len = '';
+ if ( $_->{attlen} == -1 && $_->{atttypmod} != -1
+ && $_->{typname} ne 'text' ) {
+ $len = $_->{atttypmod} - 4;
+ if ( $_->{typname} eq 'numeric' ) {
+ $len = ($len >> 16). ','. ($len & 0xffff);
+ }
+ }
+
+ my $type = $_->{'typname'};
+ $type = 'char' if $type eq 'bpchar';
+
+ [
+ $_->{'attname'},
+ $type,
+ ! $_->{'attnotnull'},
+ $len,
+ $default,
+ '' #local
+ ];
+
+ } @{ $sth->fetchall_arrayref({}) };
+}
+
+sub primary_key {
+ my($proto, $dbh, $table) = @_;
+ my $sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT a.attname, a.attnum
+ FROM pg_class c, pg_attribute a, pg_type t
+ WHERE c.relname = '${table}_pkey'
+ AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid
+END
+ $sth->execute or die $sth->errstr;
+ my $row = $sth->fetchrow_hashref or return '';
+ $row->{'attname'};
+}
+
+sub unique {
+ my($proto, $dbh, $table) = @_;
+ my $gratuitous = { map { $_ => [ $proto->_index_fields($dbh, $_ ) ] }
+ grep { $proto->_is_unique($dbh, $_ ) }
+ $proto->_all_indices($dbh, $table)
+ };
+}
+
+sub index {
+ my($proto, $dbh, $table) = @_;
+ my $gratuitous = { map { $_ => [ $proto->_index_fields($dbh, $_ ) ] }
+ grep { ! $proto->_is_unique($dbh, $_ ) }
+ $proto->_all_indices($dbh, $table)
+ };
+}
+
+sub _all_indices {
+ my($proto, $dbh, $table) = @_;
+ my $sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT c2.relname
+ FROM pg_class c, pg_class c2, pg_index i
+ WHERE c.relname = '$table' AND c.oid = i.indrelid AND i.indexrelid = c2.oid
+END
+ $sth->execute or die $sth->errstr;
+ map { $_->{'relname'} }
+ grep { $_->{'relname'} !~ /_pkey$/ }
+ @{ $sth->fetchall_arrayref({}) };
+}
+
+sub _index_fields {
+ my($proto, $dbh, $index) = @_;
+ my $sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT a.attname, a.attnum
+ FROM pg_class c, pg_attribute a, pg_type t
+ WHERE c.relname = '$index'
+ AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid
+END
+ $sth->execute or die $sth->errstr;
+ map { $_->{'attname'} } @{ $sth->fetchall_arrayref({}) };
+}
+
+sub _is_unique {
+ my($proto, $dbh, $index) = @_;
+ my $sth = $dbh->prepare(<<END) or die $dbh->errstr;
+ SELECT i.indisunique
+ FROM pg_index i, pg_class c, pg_am a
+ WHERE i.indexrelid = c.oid AND c.relname = '$index' AND c.relam = a.oid
+END
+ $sth->execute or die $sth->errstr;
+ my $row = $sth->fetchrow_hashref or die 'guru meditation #420';
+ $row->{'indisunique'};
+}
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+Yes.
+
+columns doesn't return column default information.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<DBIx::DBSchema::DBD>, L<DBI>, L<DBI::DBD>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Sybase.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Sybase.pm
new file mode 100755
index 0000000..4a74069
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/Sybase.pm
@@ -0,0 +1,141 @@
+package DBIx::DBSchema::DBD::Sybase;
+
+use strict;
+use vars qw($VERSION @ISA %typemap);
+use DBIx::DBSchema::DBD;
+
+$VERSION = '0.03';
+@ISA = qw(DBIx::DBSchema::DBD);
+
+%typemap = (
+# 'empty' => 'empty'
+);
+
+=head1 NAME
+
+DBIx::DBSchema::DBD::Sybase - Sybase database driver for DBIx::DBSchema
+
+=head1 SYNOPSIS
+
+use DBI;
+use DBIx::DBSchema;
+
+$dbh = DBI->connect('dbi:Sybase:dbname=database', 'user', 'pass');
+$schema = new_native DBIx::DBSchema $dbh;
+
+=head1 DESCRIPTION
+
+This module implements a Sybase driver for DBIx::DBSchema.
+
+=cut
+
+sub columns {
+ my($proto, $dbh, $table) = @_;
+
+ my $sth = $dbh->prepare("sp_columns \@table_name=$table")
+ or die $dbh->errstr;
+
+ $sth->execute or die $sth->errstr;
+ my @cols = map {
+ [
+ $_->{'column_name'},
+ $_->{'type_name'},
+ ($_->{'nullable'} ? 1 : ''),
+ $_->{'length'},
+ '', #default
+ '' #local
+ ]
+ } @{ $sth->fetchall_arrayref({}) };
+ $sth->finish;
+
+ @cols;
+}
+
+sub primary_key {
+ return("StubbedPrimaryKey");
+}
+
+
+sub unique {
+ my($proto, $dbh, $table) = @_;
+ my $gratuitous = { map { $_ => [ $proto->_index_fields($dbh, $table, $_ ) ] }
+ grep { $proto->_is_unique($dbh, $_ ) }
+ $proto->_all_indices($dbh, $table)
+ };
+}
+
+sub index {
+ my($proto, $dbh, $table) = @_;
+ my $gratuitous = { map { $_ => [ $proto->_index_fields($dbh, $table, $_ ) ] }
+ grep { ! $proto->_is_unique($dbh, $_ ) }
+ $proto->_all_indices($dbh, $table)
+ };
+}
+
+sub _all_indices {
+ my($proto, $dbh, $table) = @_;
+
+ my $sth = $dbh->prepare_cached(<<END) or die $dbh->errstr;
+ SELECT name
+ FROM sysindexes
+ WHERE id = object_id('$table') and indid between 1 and 254
+END
+ $sth->execute or die $sth->errstr;
+ my @indices = map { $_->[0] } @{ $sth->fetchall_arrayref() };
+ $sth->finish;
+ $sth = undef;
+ @indices;
+}
+
+sub _index_fields {
+ my($proto, $dbh, $table, $index) = @_;
+
+ my @keys;
+
+ my ($indid) = $dbh->selectrow_array("select indid from sysindexes where id = object_id('$table') and name = '$index'");
+ for (1..30) {
+ push @keys, $dbh->selectrow_array("select index_col('$table', $indid, $_)") || ();
+ }
+
+ return @keys;
+}
+
+sub _is_unique {
+ my($proto, $dbh, $table, $index) = @_;
+
+ my ($isunique) = $dbh->selectrow_array("select status & 2 from sysindexes where id = object_id('$table') and name = '$index'");
+
+ return $isunique;
+}
+
+=head1 AUTHOR
+
+Charles Shapiro <charles.shapiro@numethods.com>
+(courtesy of Ivan Kohler <ivan-dbix-dbschema@420.am>)
+
+Mitchell Friedman <mitchell.friedman@numethods.com>
+
+Bernd Dulfer <bernd@widd.de>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2001 Charles Shapiro, Mitchell J. Friedman
+Copyright (c) 2001 nuMethods LLC.
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+Yes.
+
+The B<primary_key> method does not yet work.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<DBIx::DBSchema::DBD>, L<DBI>, L<DBI::DBD>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/mysql.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/mysql.pm
new file mode 100644
index 0000000..f3804dd
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/DBD/mysql.pm
@@ -0,0 +1,126 @@
+package DBIx::DBSchema::DBD::mysql;
+
+use strict;
+use vars qw($VERSION @ISA %typemap);
+use DBIx::DBSchema::DBD;
+
+$VERSION = '0.03';
+@ISA = qw(DBIx::DBSchema::DBD);
+
+%typemap = (
+ 'TIMESTAMP' => 'DATETIME',
+ 'SERIAL' => 'INTEGER',
+ 'BOOL' => 'TINYINT',
+ 'LONG VARBINARY' => 'LONGBLOB',
+);
+
+=head1 NAME
+
+DBIx::DBSchema::DBD::mysql - MySQL native driver for DBIx::DBSchema
+
+=head1 SYNOPSIS
+
+use DBI;
+use DBIx::DBSchema;
+
+$dbh = DBI->connect('dbi:mysql:database', 'user', 'pass');
+$schema = new_native DBIx::DBSchema $dbh;
+
+=head1 DESCRIPTION
+
+This module implements a MySQL-native driver for DBIx::DBSchema.
+
+=cut
+
+sub columns {
+ my($proto, $dbh, $table ) = @_;
+ my $sth = $dbh->prepare("SHOW COLUMNS FROM $table") or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ map {
+ $_->{'Type'} =~ /^(\w+)\(?([\d\,]+)?\)?( unsigned)?$/
+ or die "Illegal type: ". $_->{'Type'}. "\n";
+ my($type, $length) = ($1, $2);
+ [
+ $_->{'Field'},
+ $type,
+ $_->{'Null'},
+ $length,
+ $_->{'Default'},
+ $_->{'Extra'}
+ ]
+ } @{ $sth->fetchall_arrayref( {} ) };
+}
+
+#sub primary_key {
+# my($proto, $dbh, $table ) = @_;
+# my $primary_key = '';
+# my $sth = $dbh->prepare("SHOW INDEX FROM $table")
+# or die $dbh->errstr;
+# $sth->execute or die $sth->errstr;
+# my @pkey = map { $_->{'Column_name'} } grep {
+# $_->{'Key_name'} eq "PRIMARY"
+# } @{ $sth->fetchall_arrayref( {} ) };
+# scalar(@pkey) ? $pkey[0] : '';
+#}
+
+sub primary_key {
+ my($proto, $dbh, $table) = @_;
+ my($pkey, $unique_href, $index_href) = $proto->_show_index($dbh, $table);
+ $pkey;
+}
+
+sub unique {
+ my($proto, $dbh, $table) = @_;
+ my($pkey, $unique_href, $index_href) = $proto->_show_index($dbh, $table);
+ $unique_href;
+}
+
+sub index {
+ my($proto, $dbh, $table) = @_;
+ my($pkey, $unique_href, $index_href) = $proto->_show_index($dbh, $table);
+ $index_href;
+}
+
+sub _show_index {
+ my($proto, $dbh, $table ) = @_;
+ my $sth = $dbh->prepare("SHOW INDEX FROM $table")
+ or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ my $pkey = '';
+ my(%index, %unique);
+ foreach my $row ( @{ $sth->fetchall_arrayref({}) } ) {
+ if ( $row->{'Key_name'} eq 'PRIMARY' ) {
+ $pkey = $row->{'Column_name'};
+ } elsif ( $row->{'Non_unique'} ) { #index
+ push @{ $index{ $row->{'Key_name'} } }, $row->{'Column_name'};
+ } else { #unique
+ push @{ $unique{ $row->{'Key_name'} } }, $row->{'Column_name'};
+ }
+ }
+
+ ( $pkey, \%unique, \%index );
+}
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<DBIx::DBSchema::DBD>, L<DBI>, L<DBI::DBD>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Table.pm b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Table.pm
new file mode 100644
index 0000000..2d6272e
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/DBSchema/Table.pm
@@ -0,0 +1,471 @@
+package DBIx::DBSchema::Table;
+
+use strict;
+use vars qw(@ISA %create_params);
+#use Carp;
+#use Exporter;
+use DBIx::DBSchema::Column 0.02;
+use DBIx::DBSchema::ColGroup::Unique;
+use DBIx::DBSchema::ColGroup::Index;
+
+#@ISA = qw(Exporter);
+@ISA = qw();
+
+=head1 NAME
+
+DBIx::DBSchema::Table - Table objects
+
+=head1 SYNOPSIS
+
+ use DBIx::DBSchema::Table;
+
+ #old style (depriciated)
+ $table = new DBIx::DBSchema::Table (
+ "table_name",
+ "primary_key",
+ $dbix_dbschema_colgroup_unique_object,
+ $dbix_dbschema_colgroup_index_object,
+ @dbix_dbschema_column_objects,
+ );
+
+ #new style (preferred), pass a hashref of parameters
+ $table = new DBIx::DBSchema::Table (
+ {
+ name => "table_name",
+ primary_key => "primary_key",
+ unique => $dbix_dbschema_colgroup_unique_object,
+ 'index' => $dbix_dbschema_colgroup_index_object,
+ columns => \@dbix_dbschema_column_objects,
+ }
+ );
+
+ $table->addcolumn ( $dbix_dbschema_column_object );
+
+ $table_name = $table->name;
+ $table->name("table_name");
+
+ $primary_key = $table->primary_key;
+ $table->primary_key("primary_key");
+
+ $dbix_dbschema_colgroup_unique_object = $table->unique;
+ $table->unique( $dbix_dbschema__colgroup_unique_object );
+
+ $dbix_dbschema_colgroup_index_object = $table->index;
+ $table->index( $dbix_dbschema_colgroup_index_object );
+
+ @column_names = $table->columns;
+
+ $dbix_dbschema_column_object = $table->column("column");
+
+ #preferred
+ @sql_statements = $table->sql_create_table( $dbh );
+ @sql_statements = $table->sql_create_table( $datasrc, $username, $password );
+
+ #possible problems
+ @sql_statements = $table->sql_create_table( $datasrc );
+ @sql_statements = $table->sql_create_table;
+
+=head1 DESCRIPTION
+
+DBIx::DBSchema::Table objects represent a single database table.
+
+=head1 METHODS
+
+=over 4
+
+=item new [ TABLE_NAME [ , PRIMARY_KEY [ , UNIQUE [ , INDEX [ , COLUMN... ] ] ] ] ]
+
+=item new HASHREF
+
+Creates a new DBIx::DBSchema::Table object. The preferred usage is to pass a
+hash reference of named parameters.
+
+ {
+ name => TABLE_NAME,
+ primary_key => PRIMARY_KEY,
+ unique => UNIQUE,
+ 'index' => INDEX,
+ columns => COLUMNS
+ }
+
+TABLE_NAME is the name of the table. PRIMARY_KEY is the primary key (may be
+empty). UNIQUE is a DBIx::DBSchema::ColGroup::Unique object (see
+L<DBIx::DBSchema::ColGroup::Unique>). INDEX is a
+DBIx::DBSchema::ColGroup::Index object (see
+L<DBIx::DBSchema::ColGroup::Index>). COLUMNS is a reference to an array of
+DBIx::DBSchema::Column objects (see L<DBIx::DBSchema::Column>).
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+
+ my $self;
+ if ( ref($_[0]) ) {
+
+ $self = shift;
+ $self->{column_order} = [ map { $_->name } @{$self->{columns}} ];
+ $self->{columns} = { map { $_->name, $_ } @{$self->{columns}} };
+
+ } else {
+
+ my($name,$primary_key,$unique,$index,@columns) = @_;
+
+ my %columns = map { $_->name, $_ } @columns;
+ my @column_order = map { $_->name } @columns;
+
+ $self = {
+ 'name' => $name,
+ 'primary_key' => $primary_key,
+ 'unique' => $unique,
+ 'index' => $index,
+ 'columns' => \%columns,
+ 'column_order' => \@column_order,
+ };
+
+ }
+
+ #check $primary_key, $unique and $index to make sure they are $columns ?
+ # (and sanity check?)
+
+ bless ($self, $class);
+
+}
+
+=item new_odbc DATABASE_HANDLE TABLE_NAME
+
+Creates a new DBIx::DBSchema::Table object from the supplied DBI database
+handle for the specified table. This uses the experimental DBI type_info
+method to create a table with standard (ODBC) SQL column types that most
+closely correspond to any non-portable column types. Use this to import a
+schema that you wish to use with many different database engines. Although
+primary key and (unique) index information will only be imported from databases
+with DBIx::DBSchema::DBD drivers (currently MySQL and PostgreSQL), import of
+column names and attributes *should* work for any database.
+
+Note: the _odbc refers to the column types used and nothing else - you do not
+have to have ODBC installed or connect to the database via ODBC.
+
+=cut
+
+%create_params = (
+# undef => sub { '' },
+ '' => sub { '' },
+ 'max length' => sub { $_[0]->{PRECISION}->[$_[1]]; },
+ 'precision,scale' =>
+ sub { $_[0]->{PRECISION}->[$_[1]]. ','. $_[0]->{SCALE}->[$_[1]]; }
+);
+
+sub new_odbc {
+ my( $proto, $dbh, $name) = @_;
+ my $driver = DBIx::DBSchema::_load_driver($dbh);
+ my $sth = _null_sth($dbh, $name);
+ my $sthpos = 0;
+ $proto->new (
+ $name,
+ scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
+ DBIx::DBSchema::ColGroup::Unique->new(
+ $driver
+ ? [values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"}]
+ : []
+ ),
+ DBIx::DBSchema::ColGroup::Index->new(
+ $driver
+ ? [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
+ : []
+ ),
+ map {
+ my $type_info = scalar($dbh->type_info($sth->{TYPE}->[$sthpos]))
+ or die "DBI::type_info ". $dbh->{Driver}->{Name}. " driver ".
+ "returned no results for type ". $sth->{TYPE}->[$sthpos];
+ new DBIx::DBSchema::Column
+ $_,
+ $type_info->{'TYPE_NAME'},
+ #"SQL_". uc($type_info->{'TYPE_NAME'}),
+ $sth->{NULLABLE}->[$sthpos],
+ &{ $create_params{ $type_info->{CREATE_PARAMS} } }( $sth, $sthpos++ ), $driver && #default
+ ${ [
+ eval "DBIx::DBSchema::DBD::$driver->column(\$dbh, \$name, \$_)"
+ ] }[4]
+ # DB-local
+ } @{$sth->{NAME}}
+ );
+}
+
+=item new_native DATABASE_HANDLE TABLE_NAME
+
+Creates a new DBIx::DBSchema::Table object from the supplied DBI database
+handle for the specified table. This uses database-native methods to read the
+schema, and will preserve any non-portable column types. The method is only
+available if there is a DBIx::DBSchema::DBD for the corresponding database
+engine (currently, MySQL and PostgreSQL).
+
+=cut
+
+sub new_native {
+ my( $proto, $dbh, $name) = @_;
+ my $driver = DBIx::DBSchema::_load_driver($dbh);
+ $proto->new (
+ $name,
+ scalar(eval "DBIx::DBSchema::DBD::$driver->primary_key(\$dbh, \$name)"),
+ DBIx::DBSchema::ColGroup::Unique->new(
+ [ values %{eval "DBIx::DBSchema::DBD::$driver->unique(\$dbh, \$name)"} ]
+ ),
+ DBIx::DBSchema::ColGroup::Index->new(
+ [ values %{eval "DBIx::DBSchema::DBD::$driver->index(\$dbh, \$name)"} ]
+ ),
+ map {
+ DBIx::DBSchema::Column->new( @{$_} )
+ } eval "DBIx::DBSchema::DBD::$driver->columns(\$dbh, \$name)"
+ );
+}
+
+=item addcolumn COLUMN
+
+Adds this DBIx::DBSchema::Column object.
+
+=cut
+
+sub addcolumn {
+ my($self,$column)=@_;
+ ${$self->{'columns'}}{$column->name}=$column; #sanity check?
+ push @{$self->{'column_order'}}, $column->name;
+}
+
+=item delcolumn COLUMN_NAME
+
+Deletes this column. Returns false if no column of this name was found to
+remove, true otherwise.
+
+=cut
+
+sub delcolumn {
+ my($self,$column) = @_;
+ return 0 unless exists $self->{'columns'}{$column};
+ delete $self->{'columns'}{$column};
+ @{$self->{'column_order'}}= grep { $_ ne $column } @{$self->{'column_order'}}; 1;
+}
+
+=item name [ TABLE_NAME ]
+
+Returns or sets the table name.
+
+=cut
+
+sub name {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{name} = $value;
+ } else {
+ $self->{name};
+ }
+}
+
+=item primary_key [ PRIMARY_KEY ]
+
+Returns or sets the primary key.
+
+=cut
+
+sub primary_key {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{primary_key} = $value;
+ } else {
+ #$self->{primary_key};
+ #hmm. maybe should untaint the entire structure when it comes off disk
+ # cause if you don't trust that, ?
+ $self->{primary_key} =~ /^(\w*)$/
+ #aah!
+ or die "Illegal primary key: ", $self->{primary_key};
+ $1;
+ }
+}
+
+=item unique [ UNIQUE ]
+
+Returns or sets the DBIx::DBSchema::ColGroup::Unique object.
+
+=cut
+
+sub unique {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{unique} = $value;
+ } else {
+ $self->{unique};
+ }
+}
+
+=item index [ INDEX ]
+
+Returns or sets the DBIx::DBSchema::ColGroup::Index object.
+
+=cut
+
+sub index {
+ my($self,$value)=@_;
+ if ( defined($value) ) {
+ $self->{'index'} = $value;
+ } else {
+ $self->{'index'};
+ }
+}
+
+=item columns
+
+Returns a list consisting of the names of all columns.
+
+=cut
+
+sub columns {
+ my($self)=@_;
+ #keys %{$self->{'columns'}};
+ #must preserve order
+ @{ $self->{'column_order'} };
+}
+
+=item column COLUMN_NAME
+
+Returns the column object (see L<DBIx::DBSchema::Column>) for the specified
+COLUMN_NAME.
+
+=cut
+
+sub column {
+ my($self,$column)=@_;
+ $self->{'columns'}->{$column};
+}
+
+=item sql_create_table [ DATABASE_HANDLE | DATA_SOURCE [ USERNAME PASSWORD [ ATTR ] ] ]
+
+Returns a list of SQL statments to create this table.
+
+The data source can be specified by passing an open DBI database handle, or by
+passing the DBI data source name, username and password.
+
+Although the username and password are optional, it is best to call this method
+with a database handle or data source including a valid username and password -
+a DBI connection will be opened and the quoting and type mapping will be more
+reliable.
+
+If passed a DBI data source (or handle) such as `DBI:mysql:database', will use
+MySQL- or PostgreSQL-specific syntax. Non-standard syntax for other engines
+(if applicable) may also be supported in the future.
+
+=cut
+
+sub sql_create_table {
+ my($self, $dbh) = (shift, shift);
+
+ my $created_dbh = 0;
+ unless ( ref($dbh) || ! @_ ) {
+ $dbh = DBI->connect( $dbh, @_ ) or die $DBI::errstr;
+ my $gratuitous = $DBI::errstr; #surpress superfluous `used only once' error
+ $created_dbh = 1;
+ }
+ #false laziness: nicked from DBSchema::_load_driver
+ my $driver;
+ if ( ref($dbh) ) {
+ $driver = $dbh->{Driver}->{Name};
+ } else {
+ my $discard = $dbh;
+ $discard =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i #nicked from DBI->connect
+ or '' =~ /()/; # ensure $1 etc are empty if match fails
+ $driver = $1 or die "can't parse data source: $dbh";
+ }
+ #eofalse
+
+#should be in the DBD somehwere :/
+# my $saved_pkey = '';
+# if ( $driver eq 'Pg' && $self->primary_key ) {
+# my $pcolumn = $self->column( (
+# grep { $self->column($_)->name eq $self->primary_key } $self->columns
+# )[0] );
+##AUTO-INCREMENT# $pcolumn->type('serial') if lc($pcolumn->type) eq 'integer';
+# $pcolumn->local( $pcolumn->local. ' PRIMARY KEY' );
+# #my $saved_pkey = $self->primary_key;
+# #$self->primary_key('');
+# #change it back afterwords :/
+# }
+
+ my @columns = map { $self->column($_)->line($dbh) } $self->columns;
+
+ push @columns, "PRIMARY KEY (". $self->primary_key. ")"
+ #if $self->primary_key && $driver ne 'Pg';
+ if $self->primary_key;
+
+ my $indexnum = 1;
+
+ my @r = (
+ "CREATE TABLE ". $self->name. " (\n ". join(",\n ", @columns). "\n)\n"
+ );
+
+ push @r, map {
+ #my($index) = $self->name. "__". $_ . "_idx";
+ #$index =~ s/,\s*/_/g;
+ my $index = $self->name. $indexnum++;
+ "CREATE UNIQUE INDEX $index ON ". $self->name. " ($_)\n"
+ } $self->unique->sql_list
+ if $self->unique;
+
+ push @r, map {
+ #my($index) = $self->name. "__". $_ . "_idx";
+ #$index =~ s/,\s*/_/g;
+ my $index = $self->name. $indexnum++;
+ "CREATE INDEX $index ON ". $self->name. " ($_)\n"
+ } $self->index->sql_list
+ if $self->index;
+
+ #$self->primary_key($saved_pkey) if $saved_pkey;
+ $dbh->disconnect if $created_dbh;
+ @r;
+}
+
+#
+
+sub _null_sth {
+ my($dbh, $table) = @_;
+ my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0")
+ or die $dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ $sth;
+}
+
+=back
+
+=head1 AUTHOR
+
+Ivan Kohler <ivan-dbix-dbschema@420.am>
+
+Thanks to Mark Ethan Trostler <mark@zzo.com> for a patch to allow tables
+with no indices.
+
+=head1 COPYRIGHT
+
+Copyright (c) 2000 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=head1 BUGS
+
+sql_create_table() has database-specific foo that probably ought to be
+abstracted into the DBIx::DBSchema::DBD:: modules.
+
+sql_create_table may change or destroy the object's data. If you need to use
+the object after sql_create_table, make a copy beforehand.
+
+Some of the logic in new_odbc might be better abstracted into Column.pm etc.
+
+=head1 SEE ALSO
+
+L<DBIx::DBSchema>, L<DBIx::DBSchema::ColGroup::Unique>,
+L<DBIx::DBSchema::ColGroup::Index>, L<DBIx::DBSchema::Column>, L<DBI>
+
+=cut
+
+1;
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST
new file mode 100644
index 0000000..b04de25
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST
@@ -0,0 +1,19 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+README
+TODO
+Makefile.PL
+DBSchema.pm
+t/load.t
+t/load-mysql.t
+t/load-pg.t
+DBSchema/Table.pm
+DBSchema/ColGroup.pm
+DBSchema/ColGroup/Index.pm
+DBSchema/ColGroup/Unique.pm
+DBSchema/Column.pm
+DBSchema/DBD.pm
+DBSchema/DBD/mysql.pm
+DBSchema/DBD/Pg.pm
+DBSchema/DBD/Sybase.pm
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST.SKIP b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST.SKIP
new file mode 100644
index 0000000..ae335e7
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/MANIFEST.SKIP
@@ -0,0 +1 @@
+CVS/
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Makefile.PL b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Makefile.PL
new file mode 100644
index 0000000..a10e4da
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/Makefile.PL
@@ -0,0 +1,11 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'DBIx::DBSchema',
+ 'VERSION_FROM' => 'DBSchema.pm', # finds $VERSION
+ 'PREREQ_PM' => {
+ 'DBI' => 0,
+ 'FreezeThaw' => 0,
+ },
+);
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/README b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/README
new file mode 100644
index 0000000..8911ea4
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/README
@@ -0,0 +1,42 @@
+DBIx::DBSchema
+
+Copyright (c) 2000-2002 Ivan Kohler
+Copyright (c) 2000 Mail Abuse Prevention System LLC
+All rights reserved.
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+This module implements an OO-interface to database schemas. Using this module,
+you can create a database schema with an OO Perl interface. You can read the
+schema from an existing database. You can save the schema to disk and restore
+it from different process. Most importantly, DBIx::DBSchema can write SQL
+CREATE statements for different databases from a single source.
+
+Currently supported databases are MySQL, PostgreSQL and Sybase.
+DBIx::DBSchema will attempt to use generic SQL syntax for other databases.
+Assistance adding support for other databases is welcomed. See the
+DBIx::DBSchema::DBD manpage, "Driver Writer's Guide and Base Class".
+
+To install:
+ perl Makefile.PL
+ make
+ make test # nothing substantial yet
+ make install
+
+Documentation will then be available via `man DBIx::DBSchema' or
+`perldoc DBIx::DBSchema'.
+
+Anonymous CVS access is available:
+ $ export CVSROOT=":pserver:anonymous@cleanwhisker.420.am:/home/cvs/cvsroot"
+ $ cvs login
+ (Logging in to anonymous@cleanwhisker.420.am)
+ CVS password: anonymous
+ $ cvs checkout DBIx-DBSchema
+as well as <http://www.420.am/cgi-bin/cvsweb/DBIx-DBSchema>.
+
+A mailing list is available. Send a blank message to
+<ivan-dbix-dbschema-users-subscribe@420.am>.
+
+Homepage: <http://www.420.am/dbix-dbschema>
+
+$Id: README,v 1.1 2004-04-29 09:21:27 ivan Exp $
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/TODO b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/TODO
new file mode 100644
index 0000000..e75850b
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/TODO
@@ -0,0 +1,6 @@
+port and test with additional databases
+
+sql CREATE TABLE output should convert integers
+(i.e. use DBI qw(:sql_types);) to local types using DBI->type_info plus a hash
+to fudge things
+
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-mysql.t b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-mysql.t
new file mode 100644
index 0000000..78818c1
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-mysql.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use DBIx::DBSchema::DBD::mysql;
+$loaded = 1;
+print "ok 1\n";
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-pg.t b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-pg.t
new file mode 100644
index 0000000..93fcf4a
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load-pg.t
@@ -0,0 +1,12 @@
+print "1..1\n";
+eval "use DBD::Pg 1.32";
+if ( length($@) ) {
+ print "ok 1 # Skipped: DBD::Pg 1.32 required for Pg";
+} else {
+ eval "use DBIx::DBSchema::DBD::Pg;";
+ if ( length($@) ) {
+ print "not ok 1\n";
+ } else {
+ print "ok 1\n";
+ }
+}
diff --git a/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load.t b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load.t
new file mode 100644
index 0000000..67ea44b
--- /dev/null
+++ b/install/5.005/DBIx-DBSchema-0.23-5.005kludge/t/load.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use DBIx::DBSchema;
+$loaded = 1;
+print "ok 1\n";
diff --git a/install/debian/3.0/INSTALL b/install/debian/3.0/INSTALL
new file mode 100644
index 0000000..96991e8
--- /dev/null
+++ b/install/debian/3.0/INSTALL
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+echo "deb http://pouncequick.420.am/~ivan/freeside-woody/ ./" >>/etc/apt/sources.list
+
+apt-get update
+apt-get install screen zsh libapache-mod-ssl libapache-mod-perl rsync \
+ postgresql cvs fsh \
+ liburi-perl libhtml-tagset-perl libnet-perl liblocale-codes-perl \
+ libnet-whois-perl libwww-perl libbusiness-creditcard-perl \
+ libmailtools-perl libtimedate-perl libdate-manip-perl \
+ libfile-counterfile-perl libfreezethaw-perl libstring-approx-perl \
+ libtext-template-perl libdbi-perl libdbd-pg-perl \
+ libdbix-datasource-perl libdbix-dbschema-perl libnet-ssh-perl \
+ libstring-shellquote-perl libnet-scp-perl libapache-asp-perl \
+ libtie-ixhash-perl libtime-duration-perl \
+ libhtml-widgets-selectlayers-perl libstorable-perl \
+ libapache-dbi-perl libcache-cache-perl libdbd-mysql-perl
+
+useradd freeside
+su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
diff --git a/install/fedora/fc1/INSTALL b/install/fedora/fc1/INSTALL
new file mode 100755
index 0000000..c347609
--- /dev/null
+++ b/install/fedora/fc1/INSTALL
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+wget --passive-ftp --continue http://download.fedora.us/fedora/fedora/1/i386/RPMS.stable/apt-0.5.15cnc5-0.fdr.10.1.i386.rpm
+rpm -i apt*i386.rpm
+
+cp sources.list /etc/apt/sources.list
+
+apt-get update
+
+apt-get install perl-Devel-Symdump perl-BSD-Resource
+
+wget --passive-ftp --continue http://linux.reb00t.com/fedora-current/RPMS/apache-1.3.29-1.n0i.2.MPSSL.i686.rpm http://linux.reb00t.com/fedora-current/RPMS/mm-1.3.0-0.n0i.2.i686.rpm http://mirrors.kernel.org/fedora.us/fedora/fedora/1.91/i386/RPMS.os/db4-4.2.52-3.1.i386.rpm
+
+apt-get remove httpd mod_perl
+
+rpm -i mm-1.3.0-0.n0i.2.i686.rpm db4-4.2.52-3.1.i386.rpm apache-1.3.29-1.n0i.2.MPSSL.i686.rpm
+
+/sbin/chkconfig httpd on
+
+#edit /etc/httpd/conf/httpd.conf, remove mod_auth_db LoadMoudle and AddModule
+
+echo 'OPTIONS="-DHAVE_PERL -DHAVE_SSL"' >>/etc/sysconfig/apache
+
+/etc/init.d/httpd start
+
+echo 'RPM::Allow-Duplicated { "^db4$"; };' >>/etc/apt/apt.conf
+
+wget --continue http://atrpms.physik.fu-berlin.de/RPM-GPG-KEY.atrpms
+rpm --import RPM-GPG-KEY.atrpms
+wget --continue http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt
+rpm --import RPM-GPG-KEY.dag.txt
+
+apt-get install perl-DBD-MySQL perl-DBI perl-DateManip perl-HTML-Parser perl-HTML-Tagset perl-TimeDate perl-URI perl-libwww-perl perl-suidperl rsync postgresql postgresql-docs postgresql-libs postgresql-server postgresql-devel screen zsh lftp cvs gcc gd perl-GD perl-MailTools perl-FreezeThaw perl-NetAddr-IP perl-Chart
+
+perl -MCPAN -e"install Net::Whois::Raw, Business::CreditCard, \
+ File::CounterFile, String::Approx, Text::Template, \
+ DBIx::DataSource, DBIx::DBSchema, Net::SSH, \
+ String::ShellQuote, Net::SCP, Apache::ASP, \
+ Tie::IxHash, Time::Duration, \
+ HTML::Widgets::SelectLayers, Apache::DBI, \
+ Cache::Cache, IPC::ShareLite, Locale::SubCountry, \
+ DBD::Pg, Crypt::PasswdMD5 "
+
+
+#remove perl & ssl LoadModule lines from /etc/httpd/conf/httpd.conf
+#as they're statically linked (?)
+
+/usr/sbin/useradd freeside
+chsh freeside -s /bin/bash
+
+/sbin/chkconfig postgresql on
+/etc/init.d/postgresql start
+
+echo -e '\n\ny\nn" | su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
+
diff --git a/install/fedora/fc1/sources.list b/install/fedora/fc1/sources.list
new file mode 100644
index 0000000..9b36242
--- /dev/null
+++ b/install/fedora/fc1/sources.list
@@ -0,0 +1,12 @@
+# Fedora Core (Kernel.org, San Francisco California, USA)
+rpm http://mirrors.kernel.org/fedora.us/fedora fedora/1/i386 os updates
+rpm-src http://mirrors.kernel.org/fedora.us/fedora fedora/1/i386 os updates
+
+# Fedora Extras (Kernel.org, San Francisco California, USA)
+rpm http://mirrors.kernel.org/fedora.us/fedora fedora/1/i386 stable
+rpm-src http://mirrors.kernel.org/fedora.us/fedora fedora/1/i386 stable
+
+### Dag Apt Repository for Red Hat Fedora Core 1 (rhfc1)
+rpm http://apt.sw.be redhat/fc1/en/i386 dag
+
+rpm http://apt.physik.fu-berlin.de redhat/9/en/i386 at-testing
diff --git a/install/freebsd/INSTALL b/install/freebsd/INSTALL
new file mode 100755
index 0000000..53fc613
--- /dev/null
+++ b/install/freebsd/INSTALL
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+( cd /usr/ports/sysutils/portupgrade
+ make install
+)
+
+pkgdb -u
+
+portinstall -PR cvsup-without-gui
+
+cp /usr/share/examples/cvsup/ports-supfile /root
+perl -pi -e 's/CHANGE_THIS/cvsup1/;' /root/ports-supfile
+cvsup /root/ports-supfile
+
+for port in `grep -v '^ *#' ports`; do
+ #cd /usr/ports/$port
+ #make install || exit
+ portinstall -P -R $port || exit
+done
+
+for a in Net::SSH DBIx::DBSchema HTML::Widgets::SelectLayers Time::Duration Business::CreditCard; do perl -MCPAN -e"install $a"; done
+
+su -l pgsql -c initdb
+
+/usr/local/etc/rc.d/010.pgsql.sh start
+
+pw user add freeside -m
+
+su -l pgsql -c 'createuser -P freeside'
+
+su -l freeside -c 'createdb freeside'
+
+#?
+cd ../..
+make install-perl-modules
+make create-config
+make deploy
+
+#edit apache config, etc.
+
diff --git a/install/freebsd/ports b/install/freebsd/ports
new file mode 100644
index 0000000..019c5e1
--- /dev/null
+++ b/install/freebsd/ports
@@ -0,0 +1,44 @@
+shells/zsh
+misc/screen
+ftp/lftp
+www/apache13-modssl
+www/mod_perl
+net/rsync
+databases/postgresql7
+misc/p5-Array-PrintCols
+devel/p5-Term-Query
+converters/p5-MIME-Base64
+security/p5-Digest-MD5
+security/p5-MD5
+net/p5-URI
+www/p5-HTML-Tagset
+www/p5-HTML-Parser
+net/p5-Net
+misc/p5-Locale-Codes
+net/p5-Net-Whois
+www/p5-libwww
+ #misc/p5-Business-CreditCard
+devel/p5-Data-ShowTable
+mail/p5-Mail-Tools
+devel/p5-TimeDate
+devel/p5-Date-Manip
+misc/p5-File-CounterFile
+devel/p5-FreezeThaw
+devel/p5-String-Approx
+textproc/p5-Text-Template
+databases/p5-DBI
+databases/p5-DBD-Pg
+#databases/p5-DBD-mysql
+databases/p5-DBIx-DataSource
+ #database/p5-DBIx-DBSchema
+ #net/p5-Net-SSH
+textproc/p5-String-ShellQuote
+net/p5-Net-SCP
+www/p5-Apache-ASP
+ #www/p5-HTML-Mason
+devel/p5-Tie-IxHash
+ #devel/p5-Time-Duration
+ #www/p5-HTML-Widgets-SelectLayers
+devel/p5-Storable
+www/p5-Apache-DBI
+devel/p5-Cache-Cache
diff --git a/install/openbsd/INSTALL b/install/openbsd/INSTALL
new file mode 100644
index 0000000..1beef92
--- /dev/null
+++ b/install/openbsd/INSTALL
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+DIR=`pwd`
+
+#cd /usr/ports
+#cvs -q -d anoncvs@anoncvs6.usa.openbsd.org:/cvs up -r OPENBSD_`uname -r | perl -pe 's/\./_/g;'` -Pd
+
+for a in `grep -v '^ *#' $DIR/ports`
+do cd /usr/ports/$a
+ make install
+done
+
+for a in `grep -v '^ *#' $DIR/cpan`
+do perl -MCPAN -e "install $a"
+done
+
+#from /usr/local/share/doc/postgresql/README.OpenBSD
+useradd -c "PostgreSQL Admin User" -g =uid -m -d /var/postgresql -s /bin/sh postgresql
+
+su -l postgresql -c 'mkdir /var/postgresql/data'
+su -l postgresql -c 'initdb -D /var/postgresql/data'
+
+cat <<END >>/etc/rc.local
+if [ -x /usr/local/bin/pg_ctl ]; then
+ su -l postgresql -c "/usr/local/bin/pg_ctl start \
+ -D /var/postgresql/data -l /var/postgresql/logfile \
+ -o '-D /var/postgresql/data'"
+ echo -n ' postgresql'
+fi
+END
+
+cat <<END >>/etc/rc.shutdown
+if [ -f /var/postgresql/data/postmaster.pid ]; then
+ su -l postgresql -c "/usr/local/bin/pg_ctl stop -m fast \
+ -D /var/postgresql/data"
+ rm -f /var/postgresql/data/postmaster.pid
+fi
+
+su -l postgresql -c "/usr/local/bin/pg_ctl start \
+ -D /var/postgresql/data -l /var/postgresql/logfile \
+ -o '-D /var/postgresql/data'"
+
+useradd -c "Freeside" -g =uid -m freeside
+su -l postgresql -c 'createuser -P freeside'
+su -l freeside -c 'createdb freeside'
+
+#?
+cd ../..
+make install-perl-modules
+make create-config
+make deploy
+
+#edit apache config, etc.
+
diff --git a/install/openbsd/cpan b/install/openbsd/cpan
new file mode 100644
index 0000000..4304b72
--- /dev/null
+++ b/install/openbsd/cpan
@@ -0,0 +1,15 @@
+DBIx::DBSchema
+Time::Duration
+Business::CreditCard
+String::ShellQuote
+Net::SSH
+HTML::Mason
+HTML::Widgets::SelectLayers
+DBIx::DataSource
+Date::Manip
+String::Approx
+Tie::IxHash
+Date::Parse
+File::CounterFile
+Net::SCP
+Mail::Internet
diff --git a/install/openbsd/ports b/install/openbsd/ports
new file mode 100644
index 0000000..3e17d82
--- /dev/null
+++ b/install/openbsd/ports
@@ -0,0 +1,24 @@
+shells/zsh
+misc/screen
+#www/apache13-modssl
+www/mod_perl
+net/rsync
+databases/postgresql
+converters/p5-MIME-Base64
+security/p5-Digest-MD5
+security/p5-MD5
+www/p5-HTML-Tagset
+www/p5-HTML-Parser
+net/p5-libnet
+misc/p5-Locale-Codes
+net/p5-Net-Whois
+www/p5-libwww
+#mail/p5-Mail-Tools
+devel/p5-FreezeThaw
+textproc/p5-Text-Template
+databases/p5-DBI
+databases/p5-DBD-Pg
+#databases/p5-DBD-Msql-Mysql
+www/p5-Apache-ASP
+devel/p5-Storable
+www/p5-Apache-DBI
diff --git a/install/redhat/7.3/INSTALL b/install/redhat/7.3/INSTALL
new file mode 100644
index 0000000..d2f60cb
--- /dev/null
+++ b/install/redhat/7.3/INSTALL
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+wget --passive-ftp ftp://apt-rpm.tuxfamily.org/apt/redhat/7.3/en/i386/RPMS.extra/apt-*i386.rpm
+rpm -i apt*i386.rpm
+cp sources.list /etc/apt/
+apt-get update; apt-get update
+apt-get install apache mod_ssl mod_perl perl-CGI perl-CPAN perl-DBD-MySQL perl-DBD-Pg perl-DBI perl-DateManip perl-Digest-MD5 perl-HTML-Parser perl-HTML-Tagset perl-MIME-Base64 perl-Storable perl-TimeDate perl-URI perl-libnet perl-libwww-perl perl-suidperl rsync postgresql postgresql-docs postgresql-libs postgresql-server screen zsh lftp cvs #openssh
+
+wget --passive-ftp --continue http://download.atrpms.net/production/packages/redhat-7.3-i386/atrpms/atrpms-51-1.rh7.3.at.noarch.rpm http://download.atrpms.net/production/packages/redhat-7.3-i386/atrpms/atrpms-package-config-51-1.rh7.3.at.noarch.rpm http://download.atrpms.net/production/packages/redhat-7.3-i386/atrpms/gd-2.0.15-1_6.rh7.3.at.i386.rpm http://download.atrpms.net/production/packages/redhat-7.3-i386/atrpms/gd-devel-2.0.15-1_6.rh7.3.at.i386.rpm http://download.atrpms.net/production/packages/redhat-7.3-i386/atrpms/perl-GD-2.11-7.rh7.3.at.i386.rpm
+
+#rpm -i atrpms-package-config-51-1.rh7.3.at.noarch.rpm
+rpm -i atrpms-51-1.rh7.3.at.noarch.rpm
+rpm -i gd-2.0.15-1_6.rh7.3.at.i386.rpm
+rpm -i perl-GD-2.11-7.rh7.3.at.i386.rpm
+
+perl -MCPAN -e"install Locale::Country, Net::Whois, Business::CreditCard, \
+ Mail::Internet, File::CounterFile, FreezeThaw, \
+ String::Approx, Text::Template, DBIx::DataSource, \
+ DBIx::DBSchema, Net::SSH, String::ShellQuote, \
+ Net::SCP, Apache::ASP, Tie::IxHash, Time::Duration, \
+ HTML::Widgets::SelectLayers, Apache::DBI, Cache::Cache"
+
+useradd freeside
+
+chkconfig postgresql on
+/etc/init.d/postgresql start
+
+su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
+
diff --git a/install/redhat/7.3/sources.list b/install/redhat/7.3/sources.list
new file mode 100644
index 0000000..9a9ad5c
--- /dev/null
+++ b/install/redhat/7.3/sources.list
@@ -0,0 +1,2 @@
+rpm ftp://apt-rpm.tuxfamily.org/apt redhat/7.3/en/i386 os updates extra
+rpm-src ftp://apt-rpm.tuxfamily.org/apt redhat/7.3/en/i386 os updates extra
diff --git a/install/redhat/8/INSTALL b/install/redhat/8/INSTALL
new file mode 100755
index 0000000..e6107d9
--- /dev/null
+++ b/install/redhat/8/INSTALL
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+rpm -Fvh http://redhat.usu.edu/mirrors/apt4rpm/apt-0.5.4cnc8-fr1.i386.rpm
+
+cp sources.list /etc/apt/
+apt-get update; apt-get update
+#apt-get install apache mod_ssl mod_perl perl-CGI perl-CPAN perl-DBD-MySQL perl-DBD-Pg perl-DBI perl-DateManip perl-Digest-MD5 perl-HTML-Parser perl-HTML-Tagset perl-MIME-Base64 perl-Storable perl-TimeDate perl-URI perl-libnet perl-libwww-perl perl-suidperl rsync postgresql postgresql-docs postgresql-libs postgresql-server screen zsh lftp cvs #openssh
+
+#ftp://ftp.pbone.net/mirror/www.aucs.org/rpmcenter/packages/apache13-php43-mods-rh8
+
+apt-get install perl-CGI perl-CPAN perl-DBD-MySQL perl-DateManip perl-HTML-Parser perl-HTML-Tagset perl-TimeDate perl-URI perl-libwww-perl perl-suidperl rsync krb5-libs postgresql-server postgresql postgresql-docs postgresql-libs postgresql-devel screen zsh lftp cvs gcc tetex-fonts tetex-latex tetex tetex-dvips ghostscript ghostscript-fonts libpng-devel freetype-devel libjpeg-devel #gd openssh
+
+#lftpget http://download.atrpms.net/testing/packages/redhat-8.0-i386/atrpms/gd-2.0.28-0_9.rh8.0.at.i386.rpm
+lftpget http://download.atrpms.net/testing/packages/redhat-8.0-i386/atrpms/libgd2-2.0.28-0_9.rh8.0.at.i386.rpm http://download.atrpms.net/testing/packages/redhat-8.0-i386/atrpms/gd-devel-2.0.28-0_9.rh8.0.at.i386.rpm
+
+rpm -i libgd2-2.0.28-0_9.rh8.0.at.i386.rpm gd-devel-2.0.28-0_9.rh8.0.at.i386.rpm
+
+perl -MCPAN -e"install Locale::Country, Net::Whois, Business::CreditCard,
+ Mail::Internet, File::CounterFile, FreezeThaw,
+ String::Approx, Text::Template, DBIx::DataSource,
+ DBIx::DBSchema, Net::SSH, String::ShellQuote,
+ Net::SCP, Apache::ASP, Tie::IxHash, Time::Duration,
+ HTML::Widgets::SelectLayers, Apache::DBI, Cache::Cache,
+ Test::Pod, NetAddr::IP, IPC::ShareLite,
+ Chart::LinesPoints, DBI, DBD::Pg, HTML::Mason,
+ Net::Whois::Raw, Crypt::PasswdMD5, File::Temp, Storable"
+
+#apachetoolbox i guess
+
+/usr/sbin/useradd freeside
+
+/sbin/chkconfig postgresql on
+/etc/init.d/postgresql start
+
+su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
+
diff --git a/install/redhat/8/README.insecure b/install/redhat/8/README.insecure
new file mode 100644
index 0000000..14f1bd0
--- /dev/null
+++ b/install/redhat/8/README.insecure
@@ -0,0 +1,6 @@
+Red Hat has ceased support for all pre-enterprise releases.
+
+Fedora Legacy (http://www.fedoralegacy.org) is only supporting 7.3 and 9.
+
+Red Hat 8.0 is NOT RECOMMENDED. Please consider using a supported
+distribution, such as 7.3, 9, or Fedora core.
diff --git a/install/redhat/8/sources.list b/install/redhat/8/sources.list
new file mode 100644
index 0000000..40a05ca
--- /dev/null
+++ b/install/redhat/8/sources.list
@@ -0,0 +1 @@
+rpm http://download.fedoralegacy.org/apt redhat/8.0/i386 os updates legacy-utils
diff --git a/install/redhat/9/INSTALL b/install/redhat/9/INSTALL
new file mode 100644
index 0000000..c32a235
--- /dev/null
+++ b/install/redhat/9/INSTALL
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+wget --passive-ftp --continue http://redhat.usu.edu/mirrors/apt4rpm/apt-0.5.5cnc4.1-fr1.20030325a.2.i386.rpm
+rpm -i apt*i386.rpm
+cp sources.list /etc/apt/
+apt-get update
+
+apt-get install httpd mod_perl mod_ssl perl-CGI perl-CPAN perl-DBD-MySQL perl-DateManip perl-HTML-Parser perl-HTML-Tagset perl-TimeDate perl-URI perl-libwww-perl perl-suidperl rsync postgresql postgresql-docs postgresql-libs postgresql-server screen zsh lftp cvs gcc # gd openssh
+
+wget --passive-ftp --continue http://atrpms.physik.fu-berlin.de/dist/rh9/perl-GD/perl-GD-2.11-7.rh9.at.i386.rpm http://atrpms.physik.fu-berlin.de/dist/rh9/atrpms/atrpms-54-1.rh9.at.noarch.rpm http://atrpms.physik.fu-berlin.de/dist/rh9/gd/gd-2.0.15-1_6.rh9.at.i386.rpm
+
+cp /etc/apt/apt.conf /etc/apt/apt.conf.real
+
+rpm -i gd-2.0.15-1_6.rh9.at.i386.rpm atrpms-54-1.rh9.at.noarch.rpm perl-GD-2.11-7.rh9.at.i386.rpm
+
+mv /etc/apt/apt.conf.real /etc/apt/apt.conf
+
+perl -MCPAN -e"install Locale::Country, Net::Whois, Business::CreditCard, \
+ Mail::Internet, File::CounterFile, FreezeThaw, \
+ String::Approx, Text::Template, DBIx::DataSource, \
+ DBIx::DBSchema, Net::SSH, String::ShellQuote, \
+ Net::SCP, Apache::ASP, Tie::IxHash, Time::Duration, \
+ HTML::Widgets::SelectLayers, Apache::DBI, \
+ Cache::Cache, Test::Pod, NetAddr::IP, IPC::ShareLite, \
+ Chart::LinesPoints, Net::Whois::Raw, \
+ Locale::SubCountry, Crypt::PasswdMD5, DBI, DBD::Pg"
+
+
+#apachetoolbox i guess
+
+/usr/sbin/useradd freeside
+
+/sbin/chkconfig postgresql on
+/etc/init.d/postgresql start
+
+su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
+
diff --git a/install/redhat/9/sources.list b/install/redhat/9/sources.list
new file mode 100644
index 0000000..6dcb3b4
--- /dev/null
+++ b/install/redhat/9/sources.list
@@ -0,0 +1,2 @@
+#rpm http://download.fedoralegacy.org/apt redhat/9/i386 os updates legacy-util
+rpm http://download.fedoralegacy.org/apt redhat/9/i386 os updates
diff --git a/install/suse/9.0/INSTALL b/install/suse/9.0/INSTALL
new file mode 100644
index 0000000..4e44147
--- /dev/null
+++ b/install/suse/9.0/INSTALL
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# based on install/redhat/9/INSTALL
+
+# apt for SuSE howto: http://linux01.gwdg.de/apt4rpm/
+
+for file in ftp://ftp.gwdg.de/pub/linux/suse/apt/SuSE/9.0-i386/RPMS.suser-rbos/apt-0.5.5cnc6-rb6.i586.rpm ftp://ftp.gwdg.de/pub/linux/suse/apt/SuSE/9.0-i386/RPMS.suser-rbos/apt-libs-0.5.5cnc6-rb6.i586.rpm ftp://ftp.gwdg.de/pub/linux/suse/apt/SuSE/9.0-i386/RPMS.suser-rbos/lua-5.0-rb3.i586.rpm; do
+ curl -C - -o `basename $file` $file
+done
+
+rpm -i lua-5.0-rb3.i586.rpm
+rpm -i apt-libs-0.5.5cnc6-rb6.i586.rpm
+rpm -i apt-0.5.5cnc6-rb6.i586.rpm
+
+perl -pi.bak -e 's/386 update/386 base update/' /etc/apt/sources.list
+
+apt-get update; apt-get update
+
+apt-get install apache mod_ssl mod_perl perl-DBI perl-Msql-Mysql-modules perl-DBD-Pg perl-DateManip perl-HTML-Parser perl-HTML-Tagset perl-TimeDate perl-URI perl-libwww-perl perl-Apache-DBI perl-Apache-ASP perl-GD perl-MailTools perl-Tie-IxHash rsync postgresql postgresql-docs postgresql-libs postgresql-server postgresql-devel screen zsh lftp wget cvs make gcc
+
+perl -MCPAN -e"install DBD::Pg, Net::Whois, Business::CreditCard, \
+ File::CounterFile, FreezeThaw, String::Approx, \
+ Text::Template, DBIx::DataSource, DBIx::DBSchema, \
+ Net::SSH, String::ShellQuote, Net::SCP, \
+ Time::Duration, HTML::Widgets::SelectLayers, \
+ Cache::Cache, Test::Pod, NetAddr::IP, IPC::ShareLite, \
+ Chart::LinesPoints"
+
+/usr/sbin/useradd freeside
+
+/sbin/chkconfig postgresql on
+/etc/init.d/postgresql start
+
+/sbin/chkconfig apache on
+#/etc/init.d/apache start
+
+su postgres -c "createuser -P freeside"
+
+su freeside -c "createdb freeside"
+
+#?
+cd ../../..
+make install-perl-modules
+make create-config
+freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan
+su freeside -c 'freeside-setup ivan'
+su freeside -c '/home/ivan/freeside/bin/populate-msgcat ivan'
+make deploy
+
+
+
+
diff --git a/rt/COPYING b/rt/COPYING
new file mode 100755
index 0000000..e77696a
--- /dev/null
+++ b/rt/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/rt/Changelog b/rt/Changelog
new file mode 100644
index 0000000..0f6bd10
--- /dev/null
+++ b/rt/Changelog
@@ -0,0 +1,2913 @@
+
+
+
+Project "rt.3", Branch 0 Page 1
+Change Log Fri Feb 13 12:31:27 2004
+
+rt.3.D000, C0, jesse, Thu Mar 13 20:43:23 2003, RT: Request Tracker, branch 3.0.
+ RT: Request Tracker, branch 3.0.
+
+ Change Delta Brief Description
+ 10 1 Bumping the version to rt.3.0.0rc0
+ 11 2 Minor CSS update. rollback fix; new database indices;
+ copyright update
+ 15 3 removing the old REST API
+ 16 4 Performance work on 'WhoHaveRight'; bumping to 3.0.0rc2
+ 12 5 fixing indices for postgres
+ 17 6 fixing fastcgi's ability to load webmux.pl on some platforms
+ 18 7 More performance work; backing out 'enhancements' that killed
+ system performance
+ 19 8 Caving in to the masses and making 'notify sender'
+ configurable
+ 20 9 finishing the notify stuff
+ 22 10 Bumping to RC3; fixing the display of 'This user's n highest
+ priority tix'
+ 23 11 New french translation
+ 25 12 Brazilian Portuguese translation
+ 26 13 Postgres fixes
+ 27 14 Postgres schema tweak
+ 28 15 Further postgres tweaks and fixes
+ 29 16 RT should now be less overzealous about opening and then
+ marking a ticket 'new' again
+ 30 17 Bumping to 3.0.0rc4
+ 31 18 Minor fixes - Bumped to 3.0.0;
+ 2266 - postgres scrip creation bug
+ 2267 - Tickets not reopened after being stalled
+ 2265 - Fixed css nits from blaise
+ Unreported - Fixed quick-search bug
+ 32 19 Added better error checking for failed ticket creation
+ 33 20 A tiny bit of extra data passing for some callbacks
+ 35 21 New czech translation
+ 37 22 Updated dependencies
+ 36 23 Updated Spanish translation
+ 38 24 Updates for RT RPC interface from AMS
+ 39 25 Many users should be able to have a blank address; neew test
+ suite to ensure this
+ 50 26 Integrating rafael's failing test suite and proposed fix from
+ autrijus
+ 51 27 Changing address used in example to not send mail to author
+ 52 28 More I18N testing for rafael
+ 53 29 Added preliminary left to right hebrew translation
+ 55 30 #2365 Removing outdated Mason setup parameter
+ 56 31 Testing fixes for mail authentication/authorization
+ 57 32 Testing and fixing binary attachment corruption
+ 58 33 Better binary attachment handling fix from autrijus
+ 59 34 Bumping to RT 3.0.1pre2
+ 60 35 SMTP and I18N fixes from autrijus tang. Updated chinese
+ translations
+ 61 36 New speedycgi support from vivek khera
+ 62 37 Bumping to version 3.0.1
+ 63 38 Fixing a showmessagestanza bug found in RTIR
+ 65 39 Fixing an untainting bug in 3.0.1
+ 66 40 Quicksearch bug fix from Stan
+ 67 41 updating autrijus' autohandler patch. seems to break lots of
+ stuff
+ 68 42 Bumping to 3.0.2pre1
+ 69 43 make ids clicky
+ 70 44 fixing utf8 tainting issue in autohandler; bumped to 3.0.2pre2
+ 71 45 Another go at fixing the ARRAY() issue; bumping to 3.0.2pre3
+ 72 46 bulk links
+ 73 47 I18N patches from autrijus; bouncing to 3.0.2pre4
+ 75 48 bumped version to 3.0.2pre5; attachments performance fixes;
+ utf-8 mailgateway fixes; more extension hooks; template
+ updates for approvals
+ 76 49 [#2437] CanonicalizeEmailAddress fixes; [# 2449] html fixes
+ for right editing; [# 2457] email addresses weren't always
+ being canonicalized
+ 77 50 Fixing bogus anchor tags
+ 78 51 More performance work on WhoHaveRight; removing an extra join
+ 79 52 Cleaning up RT tag processing
+ 80 53 Importing utf8 fixes, _Vendor overlay support from ourinternet
+ 81 54 Bumping the version to 3.0.2pre6
+ 82 55 Bumping the version to RT 3.0.2
+ 83 56 merge from ourinternet; one CreateTickets fix and some utf8
+ updates
+ 85 57 Robert's updated search stuff
+ 86 58 Fix for #2602 - make test fails on _Config.pm
+ 87 59 Including norwegian bokmal translation
+ 14 60 #2539: Re: [rt-users] unexpected usage: change sort order with
+ column headers in search window
+ 88 61 Fix to honor '$LogDir' for LogToFile
+ 89 62 #2603: /opt/rt3/share/doc should not be a file.
+ 90 63 Merging utf8 fixes from autrijus tang
+ 91 64 Fixing an upgrade bug from 3.0.2->3.0.3
+ 92 65 MIME::Words encoding fixes for mail sending
+ 93 66 Additional work on the SQLite port
+ 95 67 Merge from ourinternet: UTF8 fixes; more configurable apache
+ sessions;
+ 96 68 ACL HasRight system replaced with an algorithm that does more
+ looking ahead
+ 97 69 A fix to Tickets_Overlay.pm to make the 'Count' methods
+ actually do a count, not a full SELECT
+ 98 70 Further UTF8-fixed from autrijus
+ 99 71 Bumping the version to 3.0.3pre1
+ 100 72 update layout fix
+ 101 73 #2662 Fixing an overly restrictive ACL check on group creation
+ 102 74 #2657 Web UI Scrip creation bug
+ 103 75 #2652 - de.po updates
+ 105 76 #2658 Cosmetic issue with Scrip menu listing
+ 106 77 fix for FastCGI and SpeedyCGI setgidness with weird
+ environments
+ 107 78 Continued performance improvements for caching
+ 108 79 Log path enhancment to deal more gracefully with absolutely
+ specified logfile paths
+ 109 80 CF defaults; fix Starts set
+ 110 81 ACL cache made postgres safe
+ 111 82 Fixing an acl bug in Principal_Overlay introduced after 3.0.2
+ and a possible 'Deep' transaction issue. now requires
+ DBIx::SearchBuilder 0.83_05 or newer
+ 112 83 #2678 Fixing crit messages in RT::User
+ 113 84 Bumping DBIx::SearchBuilder requirement to 0.84
+ 115 85 The "rt" program, branch 3.0.
+ 117 86 pass title to Header
+ 116 87 Better testing for internationalization of outoging messages;
+ slight refactoring to SendEmail to be more testable; added
+ missing deps to EmailParser
+ 118 88 Bump to 3.0.3pre2; fix a misspelled right in Queue.pm (#2686)
+ 119 89 #2721 - 'id' attribute had mixed casing. this bothers certain
+ databases (Sybase)
+ 120 90 Header & Logout take URL
+ 121 91 Fixes for: Bogus message headers containing high-bit
+ characters; database handle reconnections; postgres test suite
+ failures
+ 123 92 support group & queue acl setting from rt-setup-database
+ 125 93 add TakeTicket, StealTicket rights
+ 122 94 Bumping version to 3.0.3pre3, bumping searchbuilder dependency
+ 126 95 Really bumping the version to 3.0.3pre3
+ 129 96 Various fixes from a pull-up of the ourinternet branch
+ 128 97 #2605 - SpamAssassin Filter returns the wrong codes on
+ success/failure
+ 127 98 Fixes the cascading style sheet to properly reference message
+ bodies
+ 130 99 Attempting to be smarter about guessing encodings for outgoing
+ mail
+ 131 100 Fixing search navigation links (they were made to disappear)
+ 132 101 #2776 - 'new' ACL cache had bad behaviour. rolled back to
+ older cache and added tests
+ 133 102 On postgres, RT didn't previously cope well with multipart
+ messages including non-plain parts containing non-ascii
+ 135 103 Efficiency tweaks for WhoHaveRight
+ 136 104 Bumping version to RT 3.0.3pre4
+ 137 105 #2813 Duplicate tickets created at the same time could cause a
+ user creation race condition
+ 138 106 Importing minor bugfixes from ourinternet
+ 139 107 #2816 new callback to ShowLinks
+ 150 108 #2799: Display URIs instead of HREFs in ticket display
+ 151 109 #2797 Clean up automatich chmodding on installation
+ 152 110 ShowRequestor takes path
+ 153 111 SystemInternal group ACLs in setup
+ 155 112 Better encoding and error checking for message headers
+ 156 113 better handle notification messages containing only text/html
+ content.
+ 157 114 Bumping to 3.0.3pre5
+ 158 115 More I18N fixes from ourinternet
+ 159 116 Bumping version to RC-1
+ 160 117 Another shot at the header encoding fix
+ 161 118 Mitya's failing processing of html email
+ 162 119 Bumping to 3.0.3rc2
+ 163 120 Anonymizing addresses in mitya's submitted testcases
+ 165 121 Better handling of malformed email messages
+ 166 122 Bumping version to 3.0rc3
+ 167 123 #2850 - With some configurations, users could not create new
+ tickets with 'new' requestors - Bumped to 3.0 RC 4
+ 168 124 Scrip data updates weren't propagating to parent Ticket
+ objects; Bumping to 3.0.3
+ 169 125 CustomField rights checking was overly restrictive for users
+ without queue-specific rights
+ 170 126 I18N mail testing was was being cavalier with the state of
+ acls after its testing. (clone of 3.0.C167)
+ 171 127 Ticket Update.html fix to not doubly load content
+ 172 128 Fixing postgres sortorder bug unmased by searchbuilder fix
+ 176 129 Applying POD patches from ourinternet (clone of 3.0.C173)
+ 177 130 UTF8, Custom Field and text message rendering fixes from
+ ourinternet
+ 178 131 #2843 Date relations were too strict in RT::Tickets searches
+ 179 132 #2847: allow URI Resolver to render itself
+ 173 133 ShowMessageHeaders; make headers clicky
+ 180 134 use RTIR callbacks
+ 175 135 Updating rt-setup-database to take acl and schema file names
+ on the commandline
+ 181 136 Refactored Users::WhoHaveRight from Chris Audley at Blackrock
+ 182 137 Download link in ShowTransaction
+ 185 138 Fix for speedycgi disappearing database connections
+ 183 139 #2873: Fix for insufficently agressive loop culling
+ 186 140 Fix for nested email message parsing
+ 187 141 Split the HasRight ACL query into two parts. It's now two
+ small and light SQL queries, instead of one big one that
+ overwhelmed databases
+ 188 142 Stylistic cleanups for HasRight optimizations
+ 189 143 Relationship transactions are recorded and displayed more
+ robustly
+ 190 144 Bumping the version to 3.0.4pre1
+ 191 145 Updated french translation from Blaise Thauvin
+ 13 146 Bumping to 3.0.4RC1
+ 192 147 Attachment display bug fix from autrijus tang
+ 193 148 Bumping the version to 3.0.4RC2
+ 198 149 ShowAttachments had a relative path which hurt extensions
+ 199 150 README updates to indicate deprecated dependencies
+ 200 151 Debugging framework cleanup
+ 201 152 Bumping version to 3.0.4
+ 195 153 #3042: Make max inline body size configurable
+ 196 154 #3029 - better warning message on improper perms on mail in
+ 202 155 Initial commit of new commandline client support code
+ 203 156 More updates to the commandline client
+ 205 157 Removing ancient cli code that was accidentally added to the
+ repository
+ 206 158 Extended ACL edit routines to make it easier to use generic
+ routines in 3rd party apps
+ 24 159 Certain ACL checks could fail on postgres due to a marshalling
+ bug
+ 209 160 #1751: update second page in Bulk update
+ 208 161 #1651: URIs not escaped in ticket display
+ 210 162 A couple of fixes to better deal with creation of 'blank'
+ ticket requestors
+ 207 163 regression tests: use $RT::WebPath and RT_LIB_PATH
+ 211 164 Requestor searches had an extra join that they didn't need
+ 212 165 License tagger was tagging Makefile, not Makefile.in.
+ Reconfigured.
+ 213 166 Bumping to 3.0.5pre1
+ 215 167 Merging internationalization fixes from ourinternet
+ 216 168 #2692: make $Domain an argument for SelectGroups
+ 219 169 #2855: User_Overlay and Template_Overlay fixes
+ 220 170 #2989: regexp changes for Subject and loop-detection
+ 221 171 3158: user can delete only with DeleteTicket right
+ 222 172 fixes for the importer
+ 223 173 Adding the RT coding style guide to the distribution
+ 225 174 One I18N 'fix' from ourinternet tainted attachment data,
+ breaking tests
+ 226 175 Code to catch execution problems within RT's web app server
+ was made more robust
+ 34 176 Failed user creation didn't always properly roll-back the
+ database
+ 227 177 [fsck.com #2378] personal permissions for installation
+ 228 178 #3199: normalize custom fields searching syntax - Global CF's
+ previously didn't allow the { }
+ 229 179 #3201: Perform more clever joining to enhance custom field
+ search results
+ 40 180 #3200 - AND MultipleSelect CFs together - OR all other CFs
+ together.
+ 42 181 Bumping version to 3.0.5-pre2
+ 41 182 #3022: Update to German translation
+ 43 183 #3068: Better setting of Due dates via the web ui
+ 44 184 #3131: Preliminary support for Oracle from Brook Schonfield
+ 45 185 #3152: Updated russian .po file
+ 46 186 #2792: When finding out if someone is a queue watcher, check
+ groups recursively
+ 47 187 Bumping to 3.0.5pre3
+ 49 188 Dependencies updated; performance and memory usage fixes for
+ ticket creation memory usage
+ 231 189 #3237: Queue-specific templates with the same name as global
+ templates will now override the globals for queue-related
+ scrips
+ 54 190 #3279: Make fsck.com-rt: URIs case insensitive
+ 230 191 #3230: Parser patch to make watchers searches more efficient
+ 218 192 #2955: wrapping in messagebox
+ 232 193 Old relationship update transactions weren't properly
+ displayed
+ 237 194 #2672: custom field values ordering
+ 235 195 #2653: Email.pm patch
+ 238 196 #3114: allow longer subject lines for postgres
+ 233 197 #3242: cannonicalize addresses in comments
+ 236 198 #3278: occasional internal server error in RT.pm
+ 239 199 #3309: switch lines in User/Prefs.html
+ 250 200 #3329: Email.pm patch
+ 252 201 #2687: add ticket subject to resolved template
+ 255 202 #2268: align fields in User/Prefs.html
+ 256 203 #2160: clarify that box deletes scrips
+ 259 204 #2773: don't allow searching for deleted tickets
+ 257 205 #2700: configurable home page ticket list length
+ 258 206 #2409: colons after labels in Create.html
+ 261 207 #3240: DeleteWatcher, not DelWatcher
+ 262 208 #3143: Italian translation
+ 251 209 #2617: custom field ordering
+ 260 210 #2558: allow access to CFs with no name
+ 263 211 #3281: form actions must not be paths
+ 266 212 #2693: show proper id in menu after creation
+ 265 213 #3118: change default unset mail address
+ 267 214 #3324: Apache::DBI must be 0.92 or newer
+ 253 215 Fixing improperly applied custom field editing patches
+ 268 216 Bumping version to 3.0.5pre4
+ 269 217 #3341: edit comments in SiteConfig
+ 271 218 #3012: vertical alignment in Ticket/Elements/ShowPeople
+ 270 219 #3349: umlauts aren't correct in subject
+ 272 220 #3236: allow attachments without other txn contents
+ 273 221 #3105: CreateTickets doesn't set ticket type
+ 275 222 #3384: recursive merge patches
+ 276 223 #3354: an additional fix for avoiding the morning bug
+ 277 224 #3114: increase subject length in non-Postgres dbs
+ 278 225 Fixes to attempt to stop mysql 'morning bugs' with mysql
+ 280 226 Post 3.0.5pre3 - sometimes silently losing mail. fixed a
+ possible bug, improved testing
+ 281 227 More explicit warning about a lack of perl 5.8
+ 282 228 fixing the new testdeps thing
+ 279 229 #2651: localize die/warn handlers
+ 64 230 Bumping to 3.0.5pre5
+ 283 231 #3399: Message parsing fails for some types of report
+ 285 232 Better handling of apparently bogus email; rationalize mail
+ gateway error codes
+ 286 233 Bumping to 3.0.5pre6
+ 287 234 Bumping to 3.0.5RC1
+ 288 235 Patches to the cli from ams
+ 289 236 Custom field values couldn't be set to '0'; README updated for
+ apache2
+ 290 237 RT 3.0.5
+ 291 238 Fixing a couple bugs related to display of links
+ 292 239 fixing a multiple-signature-inclusion bug
+ 293 240 Bumping to 3.0.6RC1
+ 295 241 Updated documentation for RT CLI tool
+ 296 242 Bumping to RT 3.0.6
+ 297 243 Conditionalizing Text::Quoted display, so as to avoid utf8
+ crashes
+ 299 244 Merging bugfixes from ourinternet
+ 300 245 A bunch of postgres correctness fixes
+ 301 246 #2346: Resolving a deprecation warning
+ 84 247 #3981: Ticket creation syntax fix
+ 298 248 bps #1032: SelfService fixes
+ 302 249 #3889: add/del fixes
+ 303 250 #3822: Fix for cli bug (Inapropriate use of arrayref)
+ 305 251 #3807: CLI example updates
+ 306 252 #3907: New default templates for user, ticket and queue
+ 308 253 More reference weakening
+ 307 254 User objects weren't always destroyed, due to a circular
+ reference
+ 310 255 Slightly better debugging on failure to send mail
+ 94 256 Deep recursion issue on localization handle; missing language
+ selector
+ 313 257 Adding back missing SelectLang
+ 311 258 #3566: EditCustomField supports Default for FreeformSingle
+ 312 259 Initial Informix port from akso.de
+ 316 260 #3765: TicketsSQL is case-sensitive
+ 317 261 #3613: searching on NOT LIKE
+ 318 262 #3877: don't strip multi-line headers
+ 319 263 #3551: Create values corrupted when adding new files
+ 321 264 #3993: warn when installing with mod_perl2
+ 320 265 #4087: allow non-ISO dates in transaction searches
+ 322 266 #3439: custom fields patch
+ 323 267 #3855: require Locale::Maketext::Lexicon 0.31
+ 325 268 #3856: /REST/1.0/search/tickets should work in UTC
+ 326 269 #3827: regression shouldn't drop db
+ 327 270 #3801: note when transaction content is ellided
+ 328 271 #3751: ParseNewMessageForTicketCcs
+ 332 272 #3776: new indices
+ 329 273 #3674: autohandler patch
+ 336 274 New schema relationships diagram in .dot format
+ 337 275 more work on the schema diagram
+ 330 276 #3601: SelectRights patch
+ 331 277 #3583: set Last Contacted date
+ 333 278 #4088: Postgres performance improvements
+ 335 279 fix attachment links in base RT
+ 338 280 Updating storable dependency, to keep redhat 9 users from
+ hurting themselves
+ 339 281 Small fix to ticket searching to cut down on # of joins needed
+ 104 282 Merging ourinternet's changes relative to 3.0.7pre2; UPGRADING
+ update; postgres installation fixes
+ 350 283 Bumping version to 3.0.7pre3
+ 351 284 CLI changes
+ 352 285 CLI usage updates
+ 353 286 Bumping to 3.0.7rc1
+ 361 287 Bumping to 3.0.7; Updated DBIx::SearchBuilder dependency
+ 362 288 Fixes to RT 3.0.7 upgrade instructions; Bumping to 3.0.7_01
+ 355 289 Minor cleanups to RT cli tool
+ 356 290 Fixup to rt-setup-database tool for local schema
+ 357 291 Display.html takes TicketObj; Update.html uses it
+ 358 292 localization for link text, not whole link
+ 359 293 ProcessTicketCustomFieldUpdates takes TicketObj
+ 360 294 Transaction batching
+ 363 295 protect against reentrancy in Ticket::DESTROY
+ 365 296 FastCGI fix to make the CLI work
+ 366 297 #4415: Fix for imporeting merged tickets
+ 368 298 Regression and upgrade cleanups
+ 367 299 Ticket creation and updates via the Web UI were sometimes
+ encoded wrong
+ 369 300 CLI tool should pass through orderby arg
+ 370 301 Bumping to 3.0.8pre1
+ 371 302 none
+ 372 303 Decode uuencoded attachments
+ 373 304 Fixing next/prev ticket navigation (#4461) it sometimes
+ disappeared.
+ 375 305 Ticket counts became inaccurate after repeated web searches
+ 376 306 Certain non-western From: headers were being mangled
+ 377 307 Numerous CLI improvements
+ 378 308 Bumping to 3.0.8pre2
+ 379 309 Switching to new I18NSafe lowercasing behaviour for Pg
+ 380 310 Adding a new index doubles my performance on /index.html
+ 381 311 Importing fixes from ourinternet
+ 382 312 Researching email corruption
+ 197 313 Fixing CreaetTickets documentation
+ 383 314 #4572: Fix searching on links
+ 385 315 #4554: callbacks should be ordered
+ 386 316 #4552 arguments for AddCustomFieldValues
+ 387 317 #4455: Better handling of bad link URIs
+ 388 318 #3725: Making CLI display newly created tickets ids
+ 389 319 #3736: Backport RT 3.1 'inplace' layout
+ 390 320 #3813: the CreateTickets scripaction couldn't handle
+ customfields
+ 391 321 #3608: search by ccs and adminccs in addition to requestors
+ 114 322 #3066: crontool docs
+ 393 323 #4711: search ON Dates
+ 392 324 order SelfService tickets by numeric id
+ 395 325 Bumping to 3.0.8RC1
+ 396 326 AutoOpen should set correct type for Status transaction
+ 397 327 Fixing apparent SQL error on logout. Actually bug in
+ localization
+ 21 328 #2587: turn off autocomplete in RT's search box
+ 309 329 #3660: add a 'timeout' flag to the rt-mailgate
+ 315 330 #3608: fixing quicksearch to work with new watcher search
+ 398 331 Allow RT::CurrentUser to load objects based on RT::User
+ objects passed in; Backported a fix to the Language Selector
+ for non-traditional languages; bumped to 3.0.8
+ 399 332 Searching for role groups generated queries that were way too
+ complex
+ 48 333 First cut at new oracle code from Netzah
+ 501 334 Adding UTF8 support for oracle; Oracle install instructions;
+ searchbuilder dep bumped; bumping to 3.0.9pre2
+ 502 335 Deleted tickets should never be found in searches
+ 503 336 #5212 Unicode issues with incoming mail with a charset or
+ encoding of UTF-8 (caps)
+ 500 337 Reversing our no-cache pragmas, since IE can't cope with them
+ over SSL
+ 506 338 A couple of perf fixes from autrijus which _require_
+ SearchBuilder 0.97. Reduces long ticket display by 30%
+ 508 339 Bumping to 3.0.9pre3
+ 509 340 RT now uses progressive rendering by default AND no longer
+ blocks the load of the CSS sheet; 3.0.9pre4
+ 511 341 Optimizing column loads from autrijus' new column load patches
+ 512 342 Bumping to 3.0.9pre5
+ 513 343 More performance work listing ticket Attachments
+ 515 344 Turning off autoflush for ticket attachments so we can a
+ content type
+ 516 345 #5178: Use less verbose html for ticket history
+ 517 346 Bumping to 3.0.9pre6
+ 518 347 Improved next/prev handling for merged tickets
+ 519 348 Bumping to 3.0.9 - Noting preference for perl 5.8.3
+
+ rt.3.0.D348, C519, jesse, Fri Feb 13 12:30:42 2004, Bumping to 3.0.9 - Noting
+ preference for perl 5.8.3
+ none
+
+ rt.3.0.D347, C518, jesse, Thu Feb 12 00:21:35 2004, Improved next/prev
+ handling for merged tickets
+ From: Jesse <jesse@bitsy>
+ Date: Thu Feb 12 00:20:40 2004
+
+ none
+
+ rt.3.0.D346, C517, jesse, Tue Feb 10 00:22:11 2004, Bumping to 3.0.9pre6
+ From: Jesse <jesse@bitsy>
+ Date: Tue Feb 10 00:21:24 2004
+ Warning: the original change was in the 'being_integrated' state
+
+ none
+
+ rt.3.0.D345, C516, jesse, Tue Feb 10 00:19:50 2004, #5178: Use less verbose
+ html for ticket history
+ From: Jesse <jesse@bitsy>
+ Date: Tue Feb 10 00:18:57 2004
+
+ none
+
+ rt.3.0.D344, C515, jesse, Tue Feb 10 00:07:41 2004, Turning off autoflush for
+ ticket attachments so we can a content type
+ From: Jesse <jesse@bitsy>
+ Date: Tue Feb 10 00:02:40 2004
+
+ none
+
+ rt.3.0.D343, C513, jesse, Mon Feb 9 23:48:40 2004, More performance work
+ listing ticket Attachments
+ From: Jesse <jesse@bitsy>
+ Date: Mon Feb 9 23:48:07 2004
+
+ none
+
+ rt.3.0.D342, C512, jesse, Fri Feb 6 02:05:13 2004, Bumping to 3.0.9pre5
+ none
+
+ rt.3.0.D341, C511, jesse, Thu Feb 5 23:11:20 2004, Optimizing column loads
+ from autrijus' new column load patches
+ none
+
+ rt.3.0.D340, C509, jesse, Wed Feb 4 17:56:57 2004, RT now uses progressive
+ rendering by default AND no longer blocks the load of the CSS sheet; 3.0.9pre4
+ none
+
+ rt.3.0.D339, C508, jesse, Wed Feb 4 16:42:08 2004, Bumping to 3.0.9pre3
+ none
+
+ rt.3.0.D338, C506, jesse, Wed Feb 4 15:06:36 2004, A couple of perf fixes
+ from autrijus which _require_ SearchBuilder 0.97. Reduces long ticket display
+ by 30%
+ none
+
+ rt.3.0.D337, C500, jesse, Sun Feb 1 15:01:39 2004, Reversing our no-cache
+ pragmas, since IE can't cope with them over SSL
+ none
+
+ rt.3.0.D336, C503, jesse, Wed Jan 28 19:56:55 2004, #5212 Unicode issues with
+ incoming mail with a charset or encoding of UTF-8 (caps)
+ none
+
+ rt.3.0.D335, C502, jesse, Wed Jan 28 19:24:48 2004, Deleted tickets should
+ never be found in searches
+ none
+
+ rt.3.0.D334, C501, jesse, Thu Jan 8 15:21:02 2004, Adding UTF8 support for
+ oracle; Oracle install instructions; searchbuilder dep bumped; bumping to
+ 3.0.9pre2
+ none
+
+ rt.3.0.D333, C48, jesse, Sun Jan 4 23:18:16 2004, First cut at new oracle
+ code from Netzah
+ From: Jesse <jesse@bitsy>
+ Date: Sun Jan 4 23:17:21 2004
+
+ none
+
+ rt.3.0.D332, C399, jesse, Sat Jan 3 15:57:44 2004, Searching for role groups
+ generated queries that were way too complex
+ From: Jesse <jesse@bitsy>
+ Date: Sat Jan 3 15:57:50 2004
+
+ none
+
+ rt.3.0.D331, C398, jesse, Fri Jan 2 16:22:47 2004, Allow RT::CurrentUser to
+ load objects based on RT::User objects passed in; Backported a fix to the
+ Language Selector for non-traditional languages; bumped to 3.0.8
+ From: Jesse <jesse@bitsy>
+ Date: Fri Jan 2 16:22:55 2004
+
+ none
+
+ rt.3.0.D330, C315, jesse, Fri Jan 2 15:10:37 2004, #3608: fixing quicksearch
+ to work with new watcher search
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 15:31:06 2003
+
+ none
+
+ rt.3.0.D329, C309, jesse, Fri Jan 2 15:10:02 2004, #3660: add a 'timeout'
+ flag to the rt-mailgate
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 02:12:05 2003
+
+ none
+
+ rt.3.0.D328, C21, jesse, Fri Jan 2 15:09:18 2004, #2587: turn off
+ autocomplete in RT's search box
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 01:57:52 2003
+
+ none
+
+ rt.3.0.D327, C397, jesse, Tue Dec 30 16:19:24 2003, Fixing apparent SQL error
+ on logout. Actually bug in localization
+ none
+
+ rt.3.0.D326, C396, leira, Fri Dec 19 01:11:25 2003, AutoOpen should set
+ correct type for Status transaction
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Thu Dec 18 23:56:15 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D325, C395, jesse, Wed Dec 17 14:12:38 2003, Bumping to 3.0.8RC1
+ From: Jesse <jesse@bitsy>
+ Date: Thu Dec 18 15:00:12 2003
+
+ none
+
+ rt.3.0.D324, C392, leira, Wed Dec 17 14:09:47 2003, order SelfService tickets
+ by numeric id
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Dec 15 04:21:15 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D323, C393, jesse, Mon Dec 15 19:28:46 2003, #4711: search ON Dates
+ From: Jesse <jesse@bitsy>
+ Date: Tue Dec 16 20:30:27 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D322, C114, jesse, Sat Dec 13 01:32:42 2003, #3066: crontool docs
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 00:38:04 2003
+
+ none
+
+ rt.3.0.D321, C391, jesse, Sat Dec 13 01:32:12 2003, #3608: search by ccs and
+ adminccs in addition to requestors
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 01:24:33 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D320, C390, jesse, Sat Dec 13 01:29:24 2003, #3813: the CreateTickets
+ scripaction couldn't handle customfields
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 01:22:56 2003
+
+ none
+
+ rt.3.0.D319, C389, jesse, Sat Dec 13 01:28:24 2003, #3736: Backport RT 3.1
+ 'inplace' layout
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 01:15:39 2003
+
+ none
+
+ rt.3.0.D318, C388, jesse, Sat Dec 13 01:28:13 2003, #3725: Making CLI display
+ newly created tickets ids
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sat Dec 13 01:12:20 2003
+
+ none
+
+ rt.3.0.D317, C387, jesse, Sat Dec 13 01:27:59 2003, #4455: Better handling of
+ bad link URIs
+ From: Jesse Vincent <jesse@hostname>
+ Date: Fri Dec 12 23:33:09 2003
+
+ none
+
+ rt.3.0.D316, C386, jesse, Sat Dec 13 01:27:41 2003, #4552 arguments for
+ AddCustomFieldValues
+ From: Jesse Vincent <jesse@hostname>
+ Date: Fri Dec 12 23:23:08 2003
+
+ none
+
+ rt.3.0.D315, C385, jesse, Sat Dec 13 01:27:17 2003, #4554: callbacks should be
+ ordered
+ From: Jesse Vincent <jesse@hostname>
+ Date: Fri Dec 12 23:14:57 2003
+
+ none
+
+ rt.3.0.D314, C383, jesse, Sat Dec 13 01:27:06 2003, #4572: Fix searching on
+ links
+ From: Jesse Vincent <jesse@hostname>
+ Date: Fri Dec 12 23:11:24 2003
+
+ none
+
+ rt.3.0.D313, C197, jesse, Fri Dec 12 23:18:59 2003, Fixing CreaetTickets
+ documentation
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 8 19:59:48 2003
+
+ none
+
+ rt.3.0.D312, C382, jesse, Thu Dec 11 12:58:27 2003, Researching email
+ corruption
+ From: Jesse <jesse@bitsy>
+ Date: Fri Dec 12 14:02:37 2003
+
+ none
+
+ rt.3.0.D311, C381, jesse, Mon Dec 8 03:06:53 2003, Importing fixes from
+ ourinternet
+ From: Jesse <jesse@bitsy>
+ Date: Tue Dec 9 04:10:35 2003
+
+ none
+
+ rt.3.0.D310, C380, jesse, Mon Dec 8 02:52:37 2003, Adding a new index doubles
+ my performance on /index.html
+ From: Jesse <jesse@bitsy>
+ Date: Tue Dec 9 03:53:40 2003
+
+ none
+
+ rt.3.0.D309, C379, jesse, Sat Dec 6 21:06:31 2003, Switching to new I18NSafe
+ lowercasing behaviour for Pg
+ From: Jesse <jesse@bitsy>
+ Date: Sun Dec 7 22:11:09 2003
+
+ none
+
+ rt.3.0.D308, C378, jesse, Fri Dec 5 16:51:11 2003, Bumping to 3.0.8pre2
+ From: Jesse <jesse@bitsy>
+ Date: Sat Dec 6 17:43:47 2003
+
+ none
+
+ rt.3.0.D307, C377, leira, Fri Dec 5 16:45:20 2003, Numerous CLI improvements
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Dec 5 03:09:04 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D306, C376, jesse, Wed Dec 3 01:15:23 2003, Certain non-western From:
+ headers were being mangled
+ From: Jesse Vincent <jesse@hostname>
+ Date: Wed Dec 3 01:14:25 2003
+
+ none
+
+ rt.3.0.D305, C375, jesse, Tue Dec 2 19:05:41 2003, Ticket counts became
+ inaccurate after repeated web searches
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Dec 2 19:03:18 2003
+
+ none
+
+ rt.3.0.D304, C373, jesse, Tue Dec 2 18:36:05 2003, Fixing next/prev ticket
+ navigation (#4461) it sometimes disappeared.
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Dec 2 18:34:07 2003
+
+ none
+
+ rt.3.0.D303, C372, jesse, Thu Nov 27 12:28:56 2003, Decode uuencoded
+ attachments
+ From: Jesse <jesse@bitsy>
+ Date: Fri Nov 28 13:32:25 2003
+
+ none
+
+ rt.3.0.D302, C371, jesse, Thu Nov 27 02:00:59 2003, none
+ From: Jesse <jesse@bitsy>
+ Date: Fri Nov 28 03:05:10 2003
+
+ none
+
+ rt.3.0.D301, C370, jesse, Tue Nov 25 22:24:45 2003, Bumping to 3.0.8pre1
+ none
+
+ rt.3.0.D300, C369, leira, Fri Nov 21 12:30:08 2003, CLI tool should pass
+ through orderby arg
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Nov 21 12:22:20 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D299, C367, jesse, Fri Nov 21 00:24:44 2003, Ticket creation and
+ updates via the Web UI were sometimes encoded wrong
+ From: Jesse <jesse@bitsy>
+ Date: Sat Nov 22 01:17:10 2003
+
+ none
+
+ rt.3.0.D298, C368, jesse, Fri Nov 21 00:21:32 2003, Regression and upgrade
+ cleanups
+ From: Jesse <jesse@bitsy>
+ Date: Sat Nov 22 01:20:08 2003
+
+ none
+
+ rt.3.0.D297, C366, jesse, Thu Nov 20 17:44:16 2003, #4415: Fix for imporeting
+ merged tickets
+ From: Jesse <jesse@bitsy>
+ Date: Fri Nov 21 18:47:53 2003
+
+ none
+
+ rt.3.0.D296, C365, jesse, Thu Nov 20 17:22:51 2003, FastCGI fix to make the
+ CLI work
+ From: Jesse <jesse@bitsy>
+ Date: Fri Nov 21 18:24:18 2003
+
+ none
+
+ rt.3.0.D295, C363, leira, Thu Nov 20 17:21:32 2003, protect against reentrancy
+ in Ticket::DESTROY
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Wed Nov 19 15:45:47 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D294, C360, leira, Mon Nov 17 23:28:48 2003, Transaction batching
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 18:52:37 2003
+ Warning: the original change was in the 'being_developed' state
+
+ From: Jesse <jesse@bitsy>
+ Date: Sun Nov 16 18:10:00 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D293, C359, leira, Mon Nov 17 23:24:21 2003,
+ ProcessTicketCustomFieldUpdates takes TicketObj
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 17:59:28 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D292, C358, leira, Mon Nov 17 23:21:05 2003, localization for link
+ text, not whole link
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 17:33:58 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D291, C357, leira, Mon Nov 17 23:20:32 2003, Display.html takes
+ TicketObj; Update.html uses it
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 17:19:05 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D290, C356, leira, Mon Nov 17 23:16:11 2003, Fixup to rt-setup-database
+ tool for local schema
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 15:30:46 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ From: Jesse <jesse@bitsy>
+ Date: Sun Nov 16 18:09:16 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D289, C355, leira, Mon Nov 17 23:14:05 2003, Minor cleanups to RT cli
+ tool
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Nov 17 14:59:54 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ From: Jesse <jesse@bitsy>
+ Date: Sun Nov 16 18:09:22 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D288, C362, jesse, Mon Nov 17 22:53:56 2003, Fixes to RT 3.0.7 upgrade
+ instructions; Bumping to 3.0.7_01
+ From: Jesse <jesse@bitsy>
+ Date: Tue Nov 18 23:56:48 2003
+
+ none
+
+ rt.3.0.D287, C361, jesse, Mon Nov 17 19:30:11 2003, Bumping to 3.0.7; Updated
+ DBIx::SearchBuilder dependency
+ From: Jesse <jesse@bitsy>
+ Date: Tue Nov 18 20:25:24 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D286, C353, jesse, Thu Nov 13 03:11:35 2003, Bumping to 3.0.7rc1
+ From: Jesse <jesse@bitsy>
+ Date: Fri Nov 14 04:13:34 2003
+
+ none
+
+ rt.3.0.D285, C352, jesse, Thu Nov 13 02:59:36 2003, CLI usage updates
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Nov 13 02:58:17 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D284, C351, leira, Thu Nov 13 02:41:21 2003, CLI changes
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Nov 13 02:36:17 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D283, C350, jesse, Mon Nov 10 01:26:11 2003, Bumping version to
+ 3.0.7pre3
+ From: Jesse Vincent <jesse@hostname>
+ Date: Mon Nov 10 01:24:27 2003
+
+ none
+
+ rt.3.0.D282, C104, jesse, Mon Nov 10 01:16:01 2003, Merging ourinternet's
+ changes relative to 3.0.7pre2; UPGRADING update; postgres installation fixes
+ From: Jesse Vincent <jesse@hostname>
+ Date: Mon Nov 10 01:10:08 2003
+
+ none
+
+ rt.3.0.D281, C339, jesse, Thu Nov 6 21:11:46 2003, Small fix to ticket
+ searching to cut down on # of joins needed
+ From: Jesse Vincent <jesse@hostname>
+ Date: Thu Nov 6 21:11:21 2003
+
+ none
+
+ rt.3.0.D280, C338, jesse, Thu Nov 6 21:05:17 2003, Updating storable
+ dependency, to keep redhat 9 users from hurting themselves
+ From: Jesse Vincent <jesse@hostname>
+ Date: Thu Nov 6 20:53:44 2003
+
+ none
+
+ rt.3.0.D279, C335, leira, Tue Nov 4 21:11:39 2003, fix attachment links in
+ base RT
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Tue Nov 4 16:42:55 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D278, C333, leira, Tue Nov 4 21:10:18 2003, #4088: Postgres
+ performance improvements
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Tue Nov 4 16:25:57 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D277, C331, leira, Tue Nov 4 21:08:27 2003, #3583: set Last Contacted
+ date
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Mon Nov 3 13:58:57 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D276, C330, leira, Tue Nov 4 21:06:14 2003, #3601: SelectRights patch
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 22:03:23 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D275, C337, jesse, Tue Nov 4 18:18:39 2003, more work on the schema
+ diagram
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Nov 4 18:16:53 2003
+
+ none
+
+ rt.3.0.D274, C336, jesse, Tue Nov 4 18:18:24 2003, New schema relationships
+ diagram in .dot format
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Nov 4 17:51:10 2003
+
+ none
+
+ rt.3.0.D273, C329, leira, Tue Nov 4 17:52:14 2003, #3674: autohandler patch
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 21:49:43 2003
+
+ none
+
+ rt.3.0.D272, C332, leira, Tue Nov 4 17:29:08 2003, #3776: new indices
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Tue Nov 4 15:25:39 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D271, C328, leira, Sun Nov 2 23:07:51 2003, #3751:
+ ParseNewMessageForTicketCcs
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 21:16:07 2003
+
+ none
+
+ rt.3.0.D270, C327, leira, Sun Nov 2 22:49:39 2003, #3801: note when
+ transaction content is ellided
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 20:44:58 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D269, C326, leira, Sun Nov 2 22:31:42 2003, #3827: regression
+ shouldn't drop db
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 20:17:48 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D268, C325, leira, Sun Nov 2 21:15:22 2003, #3856: /REST/1.0/search/
+ tickets should work in UTC
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 19:57:19 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D267, C323, leira, Sun Nov 2 21:14:48 2003, #3855: require
+ Locale::Maketext::Lexicon 0.31
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 19:36:37 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D266, C322, leira, Sun Nov 2 21:14:18 2003, #3439: custom fields patch
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 19:11:11 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D265, C320, leira, Sun Nov 2 20:00:45 2003, #4087: allow non-ISO dates
+ in transaction searches
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 18:28:45 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D264, C321, leira, Sun Nov 2 19:59:21 2003, #3993: warn when
+ installing with mod_perl2
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 18:47:57 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D263, C319, leira, Sun Nov 2 18:51:11 2003, #3551: Create values
+ corrupted when adding new files
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 17:36:44 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D262, C318, leira, Sun Nov 2 18:50:09 2003, #3877: don't strip multi-
+ line headers
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Sun Nov 2 16:32:21 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D261, C317, leira, Sun Nov 2 18:49:15 2003, #3613: searching on NOT
+ LIKE
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Fri Oct 31 01:39:10 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D260, C316, leira, Fri Oct 31 11:59:11 2003, #3765: TicketsSQL is case-
+ sensitive
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Thu Oct 30 16:46:33 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D259, C312, jesse, Fri Oct 31 11:58:43 2003, Initial Informix port from
+ akso.de
+ From: Jesse Vincent <jesse@hostname>
+ Date: Thu Oct 30 13:03:54 2003
+
+ none
+
+ rt.3.0.D258, C311, leira, Fri Oct 31 11:57:33 2003, #3566: EditCustomField
+ supports Default for FreeformSingle
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Fri Oct 24 16:48:36 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D257, C313, jesse, Thu Oct 30 15:33:45 2003, Adding back missing
+ SelectLang
+ From: Jesse Vincent <jesse@hostname>
+ Date: Thu Oct 30 15:31:35 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D256, C94, jesse, Tue Oct 28 16:02:50 2003, Deep recursion issue on
+ localization handle; missing language selector
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Oct 28 16:01:40 2003
+
+ none
+
+ rt.3.0.D255, C310, jesse, Wed Oct 22 00:33:40 2003, Slightly better debugging
+ on failure to send mail
+ From: Jesse Vincent <jesse@hostname>
+ Date: Wed Oct 22 00:25:35 2003
+
+ none
+
+ rt.3.0.D254, C307, jesse, Tue Oct 21 23:43:39 2003, User objects weren't
+ always destroyed, due to a circular reference
+ From: Jesse Vincent <jesse@hostname>
+ Date: Mon Oct 20 19:02:08 2003
+
+ none
+
+ rt.3.0.D253, C308, jesse, Tue Oct 21 23:35:50 2003, More reference weakening
+ From: Jesse Vincent <jesse@hostname>
+ Date: Tue Oct 21 23:34:11 2003
+
+ none
+
+ rt.3.0.D252, C306, jesse, Sun Oct 19 21:06:33 2003, #3907: New default
+ templates for user, ticket and queue
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 21:05:48 2003
+
+ none
+
+ rt.3.0.D251, C305, jesse, Sun Oct 19 21:00:11 2003, #3807: CLI example updates
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 20:59:14 2003
+
+ none
+
+ rt.3.0.D250, C303, jesse, Sun Oct 19 20:55:45 2003, #3822: Fix for cli bug
+ (Inapropriate use of arrayref)
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 20:54:22 2003
+
+ none
+
+ rt.3.0.D249, C302, jesse, Sun Oct 19 20:55:16 2003, #3889: add/del fixes
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 20:25:21 2003
+
+ none
+
+ rt.3.0.D248, C298, leira, Sun Oct 19 20:23:35 2003, bps #1032: SelfService
+ fixes
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Wed Oct 8 18:39:07 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D247, C84, jesse, Sun Oct 19 20:23:21 2003, #3981: Ticket creation
+ syntax fix
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 20:20:46 2003
+
+ none
+
+ rt.3.0.D246, C301, jesse, Sun Oct 19 20:11:09 2003, #2346: Resolving a
+ deprecation warning
+ From: Jesse Vincent <jesse@hostname>
+ Date: Sun Oct 19 20:10:25 2003
+
+ none
+
+ rt.3.0.D245, C300, jesse, Thu Oct 16 20:03:36 2003, A bunch of postgres
+ correctness fixes
+ From: Jesse Vincent <jesse@hostname>
+ Date: Thu Oct 16 20:00:27 2003
+
+ none
+
+ rt.3.0.D244, C299, jesse, Thu Oct 16 20:02:08 2003, Merging bugfixes from
+ ourinternet
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue Oct 14 12:29:53 2003
+
+ none
+
+ rt.3.0.D243, C297, jesse, Wed Oct 8 11:47:30 2003, Conditionalizing
+ Text::Quoted display, so as to avoid utf8 crashes
+ From: Jesse Vincent <jesse@hostname>
+ Date: Wed Oct 8 16:46:31 2003
+
+ none
+
+ rt.3.0.D242, C296, jesse, Thu Sep 25 16:28:46 2003, Bumping to RT 3.0.6
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Thu Sep 25 16:27:53 2003
+
+ none
+
+ rt.3.0.D241, C295, jesse, Thu Sep 25 15:13:08 2003, Updated documentation for
+ RT CLI tool
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Thu Sep 25 15:12:46 2003
+
+ none
+
+ rt.3.0.D240, C293, jesse, Mon Sep 22 16:21:10 2003, Bumping to 3.0.6RC1
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Sep 22 16:20:02 2003
+
+ none
+
+ rt.3.0.D239, C292, jesse, Mon Sep 22 16:21:00 2003, fixing a multiple-
+ signature-inclusion bug
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Sep 22 15:29:40 2003
+
+ none
+
+ rt.3.0.D238, C291, jesse, Mon Sep 22 14:46:52 2003, Fixing a couple bugs
+ related to display of links
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Sep 22 14:44:12 2003
+
+ none
+
+ rt.3.0.D237, C290, jesse, Mon Sep 8 14:18:29 2003, RT 3.0.5
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Sep 8 14:12:19 2003
+
+ none
+
+ rt.3.0.D236, C289, jesse, Mon Sep 8 13:47:05 2003, Custom field values
+ couldn't be set to '0'; README updated for apache2
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Sep 8 13:45:41 2003
+
+ none
+
+ rt.3.0.D235, C288, jesse, Sat Sep 6 01:52:41 2003, Patches to the cli from
+ ams
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sat Sep 6 01:50:41 2003
+
+ none
+
+ rt.3.0.D234, C287, jesse, Tue Sep 2 18:16:15 2003, Bumping to 3.0.5RC1
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Sep 2 18:15:51 2003
+
+ none
+
+ rt.3.0.D233, C286, jesse, Fri Aug 29 21:10:44 2003, Bumping to 3.0.5pre6
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Aug 29 18:01:40 2003
+
+ none
+
+ rt.3.0.D232, C285, jesse, Fri Aug 29 21:10:24 2003, Better handling of
+ apparently bogus email; rationalize mail gateway error codes
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Aug 29 18:01:29 2003
+
+ none
+
+ rt.3.0.D231, C283, jesse, Fri Aug 29 16:26:30 2003, #3399: Message parsing
+ fails for some types of report
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Aug 29 16:25:33 2003
+
+ none
+
+ rt.3.0.D230, C64, jesse, Thu Aug 28 17:40:29 2003, Bumping to 3.0.5pre5
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Aug 28 17:32:38 2003
+
+ none
+
+ rt.3.0.D229, C279, leira, Thu Aug 28 17:39:37 2003, #2651: localize die/warn
+ handlers
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 26 15:38:12 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D228, C282, jesse, Thu Aug 28 17:38:28 2003, fixing the new testdeps
+ thing
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Aug 28 17:38:07 2003
+
+ none
+
+ rt.3.0.D227, C281, jesse, Thu Aug 28 17:34:18 2003, More explicit warning
+ about a lack of perl 5.8
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Aug 28 17:30:47 2003
+
+ none
+
+ rt.3.0.D226, C280, jesse, Thu Aug 28 17:33:15 2003, Post 3.0.5pre3 - sometimes
+ silently losing mail. fixed a possible bug, improved testing
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Aug 28 17:09:51 2003
+
+ none
+
+ rt.3.0.D225, C278, jesse, Tue Aug 26 15:29:24 2003, Fixes to attempt to stop
+ mysql 'morning bugs' with mysql
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Aug 26 15:28:20 2003
+
+ none
+
+ rt.3.0.D224, C277, leira, Tue Aug 26 15:04:20 2003, #3114: increase subject
+ length in non-Postgres dbs
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 26 14:51:25 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D223, C276, leira, Tue Aug 26 15:03:17 2003, #3354: an additional fix
+ for avoiding the morning bug
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 17:58:27 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D222, C275, leira, Tue Aug 26 15:02:16 2003, #3384: recursive merge
+ patches
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 17:02:00 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D221, C273, leira, Mon Aug 25 16:33:53 2003, #3105: CreateTickets
+ doesn't set ticket type
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 16:30:06 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D220, C272, leira, Mon Aug 25 15:53:13 2003, #3236: allow attachments
+ without other txn contents
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 15:47:52 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D219, C270, leira, Mon Aug 25 15:13:31 2003, #3349: umlauts aren't
+ correct in subject
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 13:55:52 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D218, C271, leira, Mon Aug 25 15:13:05 2003, #3012: vertical alignment
+ in Ticket/Elements/ShowPeople
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 14:08:04 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D217, C269, leira, Mon Aug 25 14:45:12 2003, #3341: edit comments in
+ SiteConfig
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Aug 25 13:38:51 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D216, C268, jesse, Sun Aug 24 17:16:04 2003, Bumping version to
+ 3.0.5pre4
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Aug 24 17:15:36 2003
+
+ none
+
+ rt.3.0.D215, C253, jesse, Sun Aug 24 17:12:37 2003, Fixing improperly applied
+ custom field editing patches
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Aug 24 17:11:40 2003
+
+ none
+
+ rt.3.0.D214, C267, leira, Sun Aug 24 14:57:37 2003, #3324: Apache::DBI must be
+ 0.92 or newer
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Aug 22 14:40:27 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D213, C265, leira, Fri Aug 22 13:30:09 2003, #3118: change default
+ unset mail address
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Aug 22 12:13:22 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D212, C266, leira, Fri Aug 22 13:29:46 2003, #2693: show proper id in
+ menu after creation
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Aug 22 12:39:53 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D211, C263, leira, Fri Aug 22 01:18:00 2003, #3281: form actions must
+ not be paths
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Aug 22 01:12:07 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D210, C260, leira, Fri Aug 22 01:13:07 2003, #2558: allow access to CFs
+ with no name
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 22:36:35 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D209, C251, leira, Fri Aug 22 01:07:34 2003, #2617: custom field
+ ordering
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Wed Aug 20 17:33:22 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D208, C262, leira, Fri Aug 22 01:00:21 2003, #3143: Italian translation
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Aug 22 00:52:11 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D207, C261, leira, Thu Aug 21 23:31:22 2003, #3240: DeleteWatcher, not
+ DelWatcher
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 23:22:40 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D206, C258, leira, Thu Aug 21 23:07:01 2003, #2409: colons after labels
+ in Create.html
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 17:23:04 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D205, C257, leira, Thu Aug 21 23:06:12 2003, #2700: configurable home
+ page ticket list length
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 15:08:27 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D204, C259, leira, Thu Aug 21 22:37:27 2003, #2773: don't allow
+ searching for deleted tickets
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 22:15:43 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D203, C256, leira, Thu Aug 21 14:09:59 2003, #2160: clarify that box
+ deletes scrips
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 13:53:00 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D202, C255, leira, Thu Aug 21 14:09:27 2003, #2268: align fields in
+ User/Prefs.html
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Aug 21 13:21:00 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D201, C252, leira, Wed Aug 20 18:13:50 2003, #2687: add ticket subject
+ to resolved template
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Wed Aug 20 17:58:22 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D200, C250, leira, Wed Aug 20 13:33:40 2003, #3329: Email.pm patch
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Wed Aug 20 13:19:58 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D199, C239, leira, Tue Aug 19 23:05:34 2003, #3309: switch lines in
+ User/Prefs.html
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 22:59:28 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D198, C236, leira, Tue Aug 19 22:47:07 2003, #3278: occasional internal
+ server error in RT.pm
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 22:44:16 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D197, C233, leira, Tue Aug 19 22:28:05 2003, #3242: cannonicalize
+ addresses in comments
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 22:21:57 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D196, C238, leira, Tue Aug 19 22:07:37 2003, #3114: allow longer
+ subject lines for postgres
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 22:01:02 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D195, C235, leira, Tue Aug 19 21:54:16 2003, #2653: Email.pm patch
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 19:57:12 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D194, C237, leira, Tue Aug 19 21:51:11 2003, #2672: custom field values
+ ordering
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Tue Aug 19 21:19:44 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D193, C232, jesse, Mon Aug 18 22:16:09 2003, Old relationship update
+ transactions weren't properly displayed
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 18 22:14:33 2003
+
+ none
+
+ rt.3.0.D192, C218, leira, Thu Aug 14 13:37:04 2003, #2955: wrapping in
+ messagebox
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Wed Aug 13 17:28:03 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D191, C230, jesse, Tue Aug 12 02:54:11 2003, #3230: Parser patch to
+ make watchers searches more efficient
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 11 14:41:00 2003
+
+ none
+
+ rt.3.0.D190, C54, jesse, Tue Aug 12 02:49:27 2003, #3279: Make fsck.com-rt:
+ URIs case insensitive
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 11 15:25:07 2003
+
+ none
+
+ rt.3.0.D189, C231, jesse, Tue Aug 12 02:47:19 2003, #3237: Queue-specific
+ templates with the same name as global templates will now override the globals
+ for queue-related scrips
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 11 15:04:37 2003
+
+ none
+
+ rt.3.0.D188, C49, jesse, Fri Aug 8 01:57:57 2003, Dependencies updated;
+ performance and memory usage fixes for ticket creation memory usage
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Aug 8 01:54:16 2003
+
+ none
+
+ rt.3.0.D187, C47, jesse, Mon Aug 4 23:20:33 2003, Bumping to 3.0.5pre3
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 23:20:16 2003
+
+ none
+
+ rt.3.0.D186, C46, jesse, Mon Aug 4 19:15:22 2003, #2792: When finding out if
+ someone is a queue watcher, check groups recursively
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 19:14:50 2003
+
+ none
+
+ rt.3.0.D185, C45, jesse, Mon Aug 4 18:55:13 2003, #3152: Updated russian .po
+ file
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 18:50:35 2003
+
+ none
+
+ rt.3.0.D184, C44, jesse, Mon Aug 4 18:27:40 2003, #3131: Preliminary support
+ for Oracle from Brook Schonfield
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 18:26:08 2003
+
+ none
+
+ rt.3.0.D183, C43, jesse, Mon Aug 4 16:19:07 2003, #3068: Better setting of
+ Due dates via the web ui
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 16:18:50 2003
+
+ none
+
+ rt.3.0.D182, C41, jesse, Mon Aug 4 16:02:01 2003, #3022: Update to German
+ translation
+ From: Jesse Vincent <jesse@localhost>
+ Date: Mon Aug 4 16:01:08 2003
+
+ none
+
+ rt.3.0.D181, C42, jesse, Thu Jul 31 13:34:50 2003, Bumping version to 3.0.5-
+ pre2
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Jul 31 13:28:35 2003
+
+ none
+
+ rt.3.0.D180, C40, jesse, Tue Jul 29 02:24:22 2003, #3200 - AND MultipleSelect
+ CFs together - OR all other CFs together.
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 29 02:21:50 2003
+
+ none
+
+ rt.3.0.D179, C229, jesse, Tue Jul 29 02:09:06 2003, #3201: Perform more clever
+ joining to enhance custom field search results
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 29 02:06:22 2003
+
+ none
+
+ rt.3.0.D178, C228, jesse, Tue Jul 29 01:17:12 2003, #3199: normalize custom
+ fields searching syntax - Global CF's previously didn't allow the { }
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 29 01:16:28 2003
+
+ none
+
+ rt.3.0.D177, C227, jesse, Mon Jul 28 01:39:28 2003, [fsck.com #2378] personal
+ permissions for installation
+ From: Robert <rspier@bear>
+ Date: Sun Jul 27 22:19:40 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ install as current user option for configure
+
+ rt.3.0.D176, C34, jesse, Mon Jul 28 00:00:12 2003, Failed user creation didn't
+ always properly roll-back the database
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 27 23:59:16 2003
+
+ none
+
+ rt.3.0.D175, C226, jesse, Sun Jul 27 23:52:28 2003, Code to catch execution
+ problems within RT's web app server was made more robust
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 27 23:51:50 2003
+
+ none
+
+ rt.3.0.D174, C225, jesse, Sun Jul 27 23:44:44 2003, One I18N 'fix' from
+ ourinternet tainted attachment data, breaking tests
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 27 23:44:17 2003
+
+ none
+
+ rt.3.0.D173, C223, jesse, Sun Jul 27 17:16:20 2003, Adding the RT coding style
+ guide to the distribution
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 27 17:15:32 2003
+
+ none
+
+ rt.3.0.D172, C222, jesse, Fri Jul 25 19:50:19 2003, fixes for the importer
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 25 19:49:06 2003
+
+ none
+
+ rt.3.0.D171, C221, leira, Fri Jul 25 14:02:49 2003, 3158: user can delete only
+ with DeleteTicket right
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Jul 25 02:44:41 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D170, C220, leira, Fri Jul 25 14:01:38 2003, #2989: regexp changes for
+ Subject and loop-detection
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Jul 25 02:02:18 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D169, C219, leira, Fri Jul 25 13:52:47 2003, #2855: User_Overlay and
+ Template_Overlay fixes
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Fri Jul 25 01:08:37 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D168, C216, leira, Fri Jul 25 13:45:37 2003, #2692: make $Domain an
+ argument for SelectGroups
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Thu Jul 24 23:31:35 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D167, C215, jesse, Thu Jul 24 23:25:01 2003, Merging
+ internationalization fixes from ourinternet
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Jul 24 23:22:46 2003
+
+ none
+
+ rt.3.0.D166, C213, jesse, Thu Jul 24 18:39:57 2003, Bumping to 3.0.5pre1
+ From: Jesse Vincent <jesse@localhost>
+ Date: Thu Jul 24 18:38:14 2003
+
+ none
+
+ rt.3.0.D165, C212, jesse, Wed Jul 23 18:06:41 2003, License tagger was tagging
+ Makefile, not Makefile.in. Reconfigured.
+ From: Jesse Vincent <jesse@localhost>
+ Date: Wed Jul 23 18:03:14 2003
+
+ none
+
+ rt.3.0.D164, C211, jesse, Wed Jul 23 15:23:11 2003, Requestor searches had an
+ extra join that they didn't need
+ From: Jesse Vincent <jesse@localhost>
+ Date: Wed Jul 23 15:22:34 2003
+
+ none
+
+ rt.3.0.D163, C207, leira, Tue Jul 22 03:10:34 2003, regression tests: use
+ $RT::WebPath and RT_LIB_PATH
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Mon Jul 21 18:12:56 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D162, C210, jesse, Tue Jul 22 02:52:01 2003, A couple of fixes to
+ better deal with creation of 'blank' ticket requestors
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 22 02:50:09 2003
+
+ none
+
+ rt.3.0.D161, C208, leira, Tue Jul 22 01:56:30 2003, #1651: URIs not escaped in
+ ticket display
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Jul 21 18:33:24 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D160, C209, leira, Tue Jul 22 01:54:49 2003, #1751: update second page
+ in Bulk update
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Jul 21 20:54:02 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D159, C24, jesse, Fri Jul 18 18:59:13 2003, Certain ACL checks could
+ fail on postgres due to a marshalling bug
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 18 17:06:39 2003
+
+ none
+
+ rt.3.0.D158, C206, jesse, Fri Jul 18 18:58:22 2003, Extended ACL edit routines
+ to make it easier to use generic routines in 3rd party apps
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 18 17:04:15 2003
+
+ none
+
+ rt.3.0.D157, C205, jesse, Mon Jul 14 02:51:28 2003, Removing ancient cli code
+ that was accidentally added to the repository
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 13 23:47:51 2003
+
+ none
+
+ rt.3.0.D156, C203, jesse, Sun Jul 13 22:35:44 2003, More updates to the
+ commandline client
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 13 19:32:10 2003
+
+ none
+
+ rt.3.0.D155, C202, jesse, Sun Jul 13 21:33:58 2003, Initial commit of new
+ commandline client support code
+ From: Jesse Vincent <jesse@localhost>
+ Date: Sun Jul 13 18:33:29 2003
+
+ none
+
+ rt.3.0.D154, C196, jesse, Sun Jul 13 18:06:09 2003, #3029 - better warning
+ message on improper perms on mail in
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 8 16:59:59 2003
+
+ none
+
+ rt.3.0.D153, C195, jesse, Sun Jul 13 18:05:41 2003, #3042: Make max inline
+ body size configurable
+ From: Jesse Vincent <jesse@localhost>
+ Date: Tue Jul 8 16:55:28 2003
+
+ none
+
+ rt.3.0.D152, C201, jesse, Sat Jul 12 02:41:34 2003, Bumping version to 3.0.4
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 11 23:35:03 2003
+
+ none
+
+ rt.3.0.D151, C200, jesse, Sat Jul 12 01:34:41 2003, Debugging framework
+ cleanup
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 11 22:31:37 2003
+
+ none
+
+ rt.3.0.D150, C199, jesse, Sat Jul 12 01:17:57 2003, README updates to indicate
+ deprecated dependencies
+ From: Jesse Vincent <jesse@localhost>
+ Date: Fri Jul 11 22:14:37 2003
+
+ none
+
+ rt.3.0.D149, C198, leira, Sat Jul 12 01:17:08 2003, ShowAttachments had a
+ relative path which hurt extensions
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Wed Jul 9 23:33:21 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D148, C193, jesse, Sat Jul 5 17:34:46 2003, Bumping the version to
+ 3.0.4RC2
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Sat Jul 5 17:22:07 2003
+
+ none
+
+ rt.3.0.D147, C192, jesse, Sat Jul 5 17:11:25 2003, Attachment display bug fix
+ from autrijus tang
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Sat Jul 5 17:09:07 2003
+
+ none
+
+ rt.3.0.D146, C13, jesse, Fri Jul 4 14:54:43 2003, Bumping to 3.0.4RC1
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jul 4 14:52:56 2003
+
+ none
+
+ rt.3.0.D145, C191, jesse, Fri Jul 4 14:50:26 2003, Updated french translation
+ from Blaise Thauvin
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jul 4 14:48:56 2003
+
+ none
+
+ rt.3.0.D144, C190, jesse, Thu Jul 3 01:48:45 2003, Bumping the version to
+ 3.0.4pre1
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Thu Jul 3 01:47:25 2003
+
+ none
+
+ rt.3.0.D143, C189, jesse, Thu Jul 3 01:44:51 2003, Relationship transactions
+ are recorded and displayed more robustly
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Thu Jul 3 01:44:08 2003
+
+ none
+
+ rt.3.0.D142, C188, jesse, Wed Jul 2 21:01:44 2003, Stylistic cleanups for
+ HasRight optimizations
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Wed Jul 2 20:58:51 2003
+
+ none
+
+ rt.3.0.D141, C187, jesse, Wed Jul 2 02:48:21 2003, Split the HasRight ACL
+ query into two parts. It's now two small and light SQL queries, instead of one
+ big one that overwhelmed databases
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Wed Jul 2 02:45:49 2003
+
+ none
+
+ rt.3.0.D140, C186, jesse, Wed Jul 2 02:19:47 2003, Fix for nested email
+ message parsing
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Wed Jul 2 02:17:38 2003
+
+ none
+
+ rt.3.0.D139, C183, jesse, Tue Jul 1 23:18:28 2003, #2873: Fix for
+ insufficently agressive loop culling
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 30 21:12:50 2003
+
+ none
+
+ rt.3.0.D138, C185, jesse, Tue Jul 1 19:08:47 2003, Fix for speedycgi
+ disappearing database connections
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue Jul 1 19:05:19 2003
+
+ none
+
+ rt.3.0.D137, C182, leira, Tue Jul 1 16:10:33 2003, Download link in
+ ShowTransaction
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Mon Jun 30 18:04:52 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D136, C181, jesse, Mon Jun 30 15:25:46 2003, Refactored
+ Users::WhoHaveRight from Chris Audley at Blackrock
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 30 15:23:18 2003
+
+ none
+
+ rt.3.0.D135, C175, jesse, Mon Jun 30 15:24:56 2003, Updating rt-setup-database
+ to take acl and schema file names on the commandline
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 30 13:41:12 2003
+
+ none
+
+ rt.3.0.D134, C180, leira, Mon Jun 30 02:19:05 2003, use RTIR callbacks
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Mon Jun 30 02:11:25 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D133, C173, leira, Mon Jun 30 02:17:05 2003, ShowMessageHeaders; make
+ headers clicky
+ From: Linda Julien <leira@hawthorn.local.>
+ Date: Sun Jun 29 23:31:39 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D132, C179, jesse, Sun Jun 29 03:06:36 2003, #2847: allow URI Resolver
+ to render itself
+ From: Jesse Vincent <jesse@eris>
+ Date: Sun Jun 29 03:04:30 2003
+
+ none
+
+ rt.3.0.D131, C178, jesse, Sun Jun 29 02:59:13 2003, #2843 Date relations were
+ too strict in RT::Tickets searches
+ From: Jesse Vincent <jesse@eris>
+ Date: Sun Jun 29 02:57:53 2003
+
+ none
+
+ rt.3.0.D130, C177, jesse, Sat Jun 28 18:17:28 2003, UTF8, Custom Field and
+ text message rendering fixes from ourinternet
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat Jun 28 18:15:09 2003
+
+ none
+
+ rt.3.0.D129, C176, jesse, Sat Jun 28 17:23:01 2003, Applying POD patches from
+ ourinternet (clone of 3.0.C173)
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat Jun 28 17:15:38 2003
+
+ none
+
+ rt.3.0.D128, C172, jesse, Sat Jun 28 17:17:53 2003, Fixing postgres sortorder
+ bug unmased by searchbuilder fix
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 27 01:12:13 2003
+
+ none
+
+ rt.3.0.D127, C171, jesse, Tue Jun 24 16:42:07 2003, Ticket Update.html fix to
+ not doubly load content
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 24 16:28:49 2003
+
+ none
+
+ rt.3.0.D126, C170, jesse, Tue Jun 24 16:23:49 2003, I18N mail testing was was
+ being cavalier with the state of acls after its testing. (clone of 3.0.C167)
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 24 16:22:16 2003
+
+ none
+
+ rt.3.0.D125, C169, jesse, Tue Jun 24 16:23:37 2003, CustomField rights
+ checking was overly restrictive for users without queue-specific rights
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 24 16:18:05 2003
+
+ none
+
+ rt.3.0.D124, C168, jesse, Sat Jun 21 13:01:18 2003, Scrip data updates weren't
+ propagating to parent Ticket objects; Bumping to 3.0.3
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat Jun 21 12:55:04 2003
+
+ none
+
+ rt.3.0.D123, C167, jesse, Thu Jun 19 22:23:01 2003, #2850 - With some
+ configurations, users could not create new tickets with 'new' requestors -
+ Bumped to 3.0 RC 4
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 22:22:20 2003
+
+ none
+
+ rt.3.0.D122, C166, jesse, Thu Jun 19 14:25:03 2003, Bumping version to 3.0rc3
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 14:18:19 2003
+
+ none
+
+ rt.3.0.D121, C165, jesse, Thu Jun 19 12:25:20 2003, Better handling of
+ malformed email messages
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 12:21:56 2003
+
+ none
+
+ rt.3.0.D120, C163, jesse, Thu Jun 19 03:17:20 2003, Anonymizing addresses in
+ mitya's submitted testcases
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 03:16:47 2003
+
+ none
+
+ rt.3.0.D119, C162, jesse, Thu Jun 19 03:03:16 2003, Bumping to 3.0.3rc2
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 03:01:10 2003
+
+ none
+
+ rt.3.0.D118, C161, jesse, Thu Jun 19 02:58:24 2003, Mitya's failing processing
+ of html email
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 19 02:56:48 2003
+
+ none
+
+ rt.3.0.D117, C160, jesse, Wed Jun 18 17:58:34 2003, Another shot at the header
+ encoding fix
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 18 17:58:01 2003
+
+ none
+
+ rt.3.0.D116, C159, jesse, Wed Jun 18 01:27:03 2003, Bumping version to RC-1
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 18 01:25:13 2003
+
+ none
+
+ rt.3.0.D115, C158, jesse, Wed Jun 18 01:01:22 2003, More I18N fixes from
+ ourinternet
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 18 01:00:11 2003
+
+ none
+
+ rt.3.0.D114, C157, jesse, Tue Jun 17 22:24:03 2003, Bumping to 3.0.3pre5
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 17 22:20:51 2003
+
+ none
+
+ rt.3.0.D113, C156, jesse, Tue Jun 17 22:14:35 2003, better handle notification
+ messages containing only text/html content.
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 17 21:55:06 2003
+
+ none
+
+ rt.3.0.D112, C155, jesse, Tue Jun 17 21:55:16 2003, Better encoding and error
+ checking for message headers
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 17 21:26:27 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D111, C153, leira, Tue Jun 17 17:47:18 2003, SystemInternal group ACLs
+ in setup
+ From: Linda L. Julien <leira@starsong.org>
+ Date: Tue Jun 17 18:19:58 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D110, C152, leira, Tue Jun 17 16:46:34 2003, ShowRequestor takes path
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Tue Jun 17 12:53:35 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D109, C151, jesse, Mon Jun 16 23:48:53 2003, #2797 Clean up automatich
+ chmodding on installation
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 16 23:48:19 2003
+
+ none
+
+ rt.3.0.D108, C150, jesse, Mon Jun 16 23:36:29 2003, #2799: Display URIs
+ instead of HREFs in ticket display
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 16 23:35:00 2003
+
+ none
+
+ rt.3.0.D107, C139, jesse, Mon Jun 16 23:16:45 2003, #2816 new callback to
+ ShowLinks
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 16 23:14:05 2003
+
+ none
+
+ rt.3.0.D106, C138, jesse, Mon Jun 16 23:16:29 2003, Importing minor bugfixes
+ from ourinternet
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 16 23:06:00 2003
+
+ none
+
+ rt.3.0.D105, C137, jesse, Mon Jun 16 19:41:13 2003, #2813 Duplicate tickets
+ created at the same time could cause a user creation race condition
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 16 19:39:57 2003
+
+ none
+
+ rt.3.0.D104, C136, jesse, Fri Jun 13 18:27:11 2003, Bumping version to RT
+ 3.0.3pre4
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 13 18:25:57 2003
+
+ none
+
+ rt.3.0.D103, C135, jesse, Fri Jun 13 18:22:33 2003, Efficiency tweaks for
+ WhoHaveRight
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 13 18:19:02 2003
+
+ none
+
+ rt.3.0.D102, C133, jesse, Fri Jun 13 18:18:32 2003, On postgres, RT didn't
+ previously cope well with multipart messages including non-plain parts
+ containing non-ascii
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 13 18:17:37 2003
+
+ none
+
+ rt.3.0.D101, C132, jesse, Fri Jun 13 01:41:24 2003, #2776 - 'new' ACL cache
+ had bad behaviour. rolled back to older cache and added tests
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 13 01:40:26 2003
+
+ none
+
+ rt.3.0.D100, C131, jesse, Thu Jun 12 13:22:10 2003, Fixing search navigation
+ links (they were made to disappear)
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Jun 12 13:19:37 2003
+
+ none
+
+ rt.3.0.D099, C130, jesse, Wed Jun 11 16:44:16 2003, Attempting to be smarter
+ about guessing encodings for outgoing mail
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 11 16:42:50 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D098, C127, jesse, Wed Jun 11 16:39:39 2003, Fixes the cascading style
+ sheet to properly reference message bodies
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 11 15:43:39 2003
+ Warning: the original change was in the 'being_developed' state
+
+ rt.3.0.D097, C128, jesse, Wed Jun 11 16:37:38 2003, #2605 - SpamAssassin
+ Filter returns the wrong codes on success/failure
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 11 15:53:58 2003
+
+ none
+
+ rt.3.0.D096, C129, jesse, Wed Jun 11 16:36:53 2003, Various fixes from a pull-
+ up of the ourinternet branch
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 11 16:17:44 2003
+
+ none
+
+ rt.3.0.D095, C126, jesse, Tue Jun 10 16:17:50 2003, Really bumping the version
+ to 3.0.3pre3
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 10 16:17:08 2003
+
+ none
+
+ rt.3.0.D094, C122, jesse, Tue Jun 10 15:58:32 2003, Bumping version to
+ 3.0.3pre3, bumping searchbuilder dependency
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 10 15:56:41 2003
+
+ none
+
+ rt.3.0.D093, C125, jesse, Tue Jun 10 15:54:52 2003, add TakeTicket,
+ StealTicket rights
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 10 15:41:43 2003
+ Warning: the original change was in the 'being_developed' state
+
+ From: Jesse <jesse@fsck.com>
+ Date: Tue Jun 10 11:01:04 2003
+ Warning: the original change was in the 'being_reviewed' state
+
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Tue Jun 10 11:58:03 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D092, C123, leira, Tue Jun 10 15:54:10 2003, support group & queue acl
+ setting from rt-setup-database
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Tue Jun 10 14:13:46 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D091, C121, jesse, Sun Jun 8 00:41:47 2003, Fixes for: Bogus message
+ headers containing high-bit characters; database handle reconnections;
+ postgres test suite failures
+ From: Jesse Vincent <jesse@eris>
+ Date: Sun Jun 8 00:32:40 2003
+
+ none
+
+ rt.3.0.D090, C120, leira, Sun Jun 8 00:34:40 2003, Header & Logout take URL
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Fri Jun 6 19:06:41 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D089, C119, jesse, Fri Jun 6 17:33:37 2003, #2721 - 'id' attribute had
+ mixed casing. this bothers certain databases (Sybase)
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Jun 6 17:32:16 2003
+
+ none
+
+ rt.3.0.D088, C118, jesse, Wed Jun 4 17:21:24 2003, Bump to 3.0.3pre2; fix a
+ misspelled right in Queue.pm (#2686)
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 4 17:19:39 2003
+ Warning: the original change was in the 'being_developed' state
+
+ The "rt" program, branch 3.0.
+
+ rt.3.0.D087, C116, jesse, Wed Jun 4 17:17:08 2003, Better testing for
+ internationalization of outoging messages; slight refactoring to SendEmail to
+ be more testable; added missing deps to EmailParser
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Jun 4 16:53:07 2003
+
+ none
+
+ rt.3.0.D086, C117, leira, Wed Jun 4 00:23:20 2003, pass title to Header
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Tue Jun 3 18:09:39 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D085, C115, jesse, Tue Jun 3 00:45:12 2003, The "rt" program, branch
+ 3.0.
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 3 00:43:15 2003
+ Warning: the original change was in the 'being_developed' state
+
+ The "rt" program, branch 3.0.
+
+ rt.3.0.D084, C113, jesse, Tue Jun 3 00:20:07 2003, Bumping
+ DBIx::SearchBuilder requirement to 0.84
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 3 00:19:50 2003
+
+ none
+
+ rt.3.0.D083, C112, jesse, Tue Jun 3 00:18:43 2003, #2678 Fixing crit messages
+ in RT::User
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 3 00:18:12 2003
+
+ none
+
+ rt.3.0.D082, C111, jesse, Tue Jun 3 00:04:45 2003, Fixing an acl bug in
+ Principal_Overlay introduced after 3.0.2 and a possible 'Deep' transaction
+ issue. now requires DBIx::SearchBuilder 0.83_05 or newer
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Jun 3 00:02:23 2003
+
+ none
+
+ rt.3.0.D081, C110, jesse, Mon Jun 2 22:40:34 2003, ACL cache made postgres
+ safe
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Jun 2 22:39:03 2003
+
+ none
+
+ rt.3.0.D080, C109, leira, Mon Jun 2 17:10:05 2003, CF defaults; fix Starts
+ set
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Mon Jun 2 17:32:11 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D079, C108, jesse, Sat May 31 00:28:37 2003, Log path enhancment to
+ deal more gracefully with absolutely specified logfile paths
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat May 31 00:27:38 2003
+
+ none
+
+ rt.3.0.D078, C107, jesse, Sat May 31 00:20:53 2003, Continued performance
+ improvements for caching
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat May 31 00:19:32 2003
+
+ none
+
+ rt.3.0.D077, C106, jesse, Sat May 31 00:20:32 2003, fix for FastCGI and
+ SpeedyCGI setgidness with weird environments
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat May 31 00:19:15 2003
+
+ none
+
+ rt.3.0.D076, C105, jesse, Fri May 30 16:21:14 2003, #2658 Cosmetic issue with
+ Scrip menu listing
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 30 16:14:02 2003
+
+ none
+
+ rt.3.0.D075, C103, jesse, Fri May 30 16:20:57 2003, #2652 - de.po updates
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 30 15:57:44 2003
+
+ none
+
+ rt.3.0.D074, C102, jesse, Fri May 30 16:18:57 2003, #2657 Web UI Scrip
+ creation bug
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 30 15:50:16 2003
+
+ none
+
+ rt.3.0.D073, C101, jesse, Fri May 30 16:17:43 2003, #2662 Fixing an overly
+ restrictive ACL check on group creation
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 30 15:46:22 2003
+
+ none
+
+ rt.3.0.D072, C100, leira, Fri May 30 16:17:05 2003, update layout fix
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Thu May 29 14:30:01 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D071, C99, jesse, Wed May 28 17:54:48 2003, Bumping the version to
+ 3.0.3pre1
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 28 17:52:51 2003
+
+ none
+
+ rt.3.0.D070, C98, jesse, Wed May 28 17:54:40 2003, Further UTF8-fixed from
+ autrijus
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 28 17:49:32 2003
+
+ none
+
+ rt.3.0.D069, C97, jesse, Wed May 28 17:07:10 2003, A fix to Tickets_Overlay.pm
+ to make the 'Count' methods actually do a count, not a full SELECT
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 28 17:04:50 2003
+
+ none
+
+ rt.3.0.D068, C96, jesse, Wed May 28 17:06:43 2003, ACL HasRight system
+ replaced with an algorithm that does more looking ahead
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 28 16:36:10 2003
+
+ none
+
+ rt.3.0.D067, C95, jesse, Tue May 27 13:22:19 2003, Merge from ourinternet:
+ UTF8 fixes; more configurable apache sessions;
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue May 27 13:07:41 2003
+
+ none
+
+ rt.3.0.D066, C93, jesse, Sat May 24 18:05:36 2003, Additional work on the
+ SQLite port
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat May 24 18:04:50 2003
+
+ none
+
+ rt.3.0.D065, C92, jesse, Fri May 23 16:45:23 2003, MIME::Words encoding fixes
+ for mail sending
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 23 16:44:28 2003
+
+ none
+
+ rt.3.0.D064, C91, jesse, Fri May 23 16:12:26 2003, Fixing an upgrade bug from
+ 3.0.2->3.0.3
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 23 15:57:50 2003
+
+ none
+
+ rt.3.0.D063, C90, jesse, Fri May 23 15:27:47 2003, Merging utf8 fixes from
+ autrijus tang
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 23 15:25:23 2003
+
+ none
+
+ rt.3.0.D062, C89, jesse, Wed May 21 00:58:34 2003, #2603: /opt/rt3/share/doc
+ should not be a file.
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Wed May 21 00:29:55 2003
+
+ none
+
+ rt.3.0.D061, C88, jesse, Wed May 21 00:57:45 2003, Fix to honor '$LogDir' for
+ LogToFile
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Wed May 21 00:17:18 2003
+
+ none
+
+ rt.3.0.D060, C14, jesse, Wed May 21 00:56:31 2003, #2539: Re: [rt-users]
+ unexpected usage: change sort order with column headers in search window
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue May 20 23:19:46 2003
+
+ none
+
+ rt.3.0.D059, C87, jesse, Wed May 21 00:55:08 2003, Including norwegian bokmal
+ translation
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue May 20 23:05:17 2003
+
+ none
+
+ rt.3.0.D058, C86, jesse, Wed May 21 00:54:06 2003, Fix for #2602 - make test
+ fails on _Config.pm
+ From: Jesse Vincent <jesse@Jesse-Vincents-Computer.local.>
+ Date: Tue May 20 22:32:52 2003
+
+ none
+
+ rt.3.0.D057, C85, leira, Tue May 20 12:15:11 2003, Robert's updated search
+ stuff
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Mon May 19 18:19:14 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D056, C83, jesse, Sat May 17 17:34:32 2003, merge from ourinternet; one
+ CreateTickets fix and some utf8 updates
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat May 17 17:31:43 2003
+
+ none
+
+ rt.3.0.D055, C82, jesse, Mon May 12 20:30:45 2003, Bumping the version to RT
+ 3.0.2
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue May 13 02:29:44 2003
+
+ none
+
+ rt.3.0.D054, C81, jesse, Wed May 7 09:18:28 2003, Bumping the version to
+ 3.0.2pre6
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 7 15:16:55 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D053, C80, jesse, Wed May 7 09:02:24 2003, Importing utf8 fixes, _
+ Vendor overlay support from ourinternet
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 7 14:52:58 2003
+
+ none
+
+ rt.3.0.D052, C79, jesse, Wed May 7 07:05:14 2003, Cleaning up RT tag
+ processing
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 7 13:04:17 2003
+
+ none
+
+ rt.3.0.D051, C78, jesse, Wed May 7 07:03:27 2003, More performance work on
+ WhoHaveRight; removing an extra join
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed May 7 13:00:43 2003
+
+ none
+
+ rt.3.0.D050, C77, jesse, Fri May 2 11:23:23 2003, Fixing bogus anchor tags
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 2 17:19:54 2003
+
+ none
+
+ rt.3.0.D049, C76, jesse, Fri May 2 10:26:30 2003, [#2437]
+ CanonicalizeEmailAddress fixes; [# 2449] html fixes for right editing; [#
+ 2457] email addresses weren't always being canonicalized
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 2 16:24:40 2003
+
+ none
+
+ rt.3.0.D048, C75, jesse, Fri May 2 08:35:38 2003, bumped version to
+ 3.0.2pre5; attachments performance fixes; utf-8 mailgateway fixes; more
+ extension hooks; template updates for approvals
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri May 2 14:32:57 2003
+
+ none
+
+ rt.3.0.D047, C73, jesse, Sun Apr 27 19:06:46 2003, I18N patches from autrijus;
+ bouncing to 3.0.2pre4
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Apr 28 01:02:18 2003
+
+ none
+
+ rt.3.0.D046, C72, leira, Sun Apr 27 19:06:04 2003, bulk links
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Thu Apr 24 01:23:01 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D045, C71, jesse, Sat Apr 19 03:42:39 2003, Another go at fixing the
+ ARRAY() issue; bumping to 3.0.2pre3
+ From: Jesse Vincent <jesse@eris>
+ Date: Sat Apr 19 08:39:19 2003
+
+ none
+
+ rt.3.0.D044, C70, jesse, Fri Apr 18 16:37:00 2003, fixing utf8 tainting issue
+ in autohandler; bumped to 3.0.2pre2
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Apr 18 21:32:04 2003
+
+ none
+
+ rt.3.0.D043, C69, leira, Thu Apr 17 18:51:41 2003, make ids clicky
+ From: Linda L. Julien <leira@oak.starsong.org>
+ Date: Thu Apr 17 18:31:40 2003
+ Warning: the original change was in the 'awaiting_integration' state
+
+ none
+
+ rt.3.0.D042, C68, jesse, Thu Apr 17 18:39:54 2003, Bumping to 3.0.2pre1
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 17 23:37:59 2003
+
+ none
+
+ rt.3.0.D041, C67, jesse, Thu Apr 17 18:33:15 2003, updating autrijus'
+ autohandler patch. seems to break lots of stuff
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 17 23:21:02 2003
+
+ none
+
+ rt.3.0.D040, C66, jesse, Thu Apr 17 18:32:24 2003, Quicksearch bug fix from
+ Stan
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 17 23:16:58 2003
+
+ none
+
+ rt.3.0.D039, C65, jesse, Thu Apr 17 18:29:31 2003, Fixing an untainting bug in
+ 3.0.1
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 17 23:15:01 2003
+ Warning: the original change was in the 'being_developed' state
+
+ none
+
+ rt.3.0.D038, C63, jesse, Thu Apr 17 04:46:42 2003, Fixing a showmessagestanza
+ bug found in RTIR
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 17 09:45:48 2003
+
+ none
+
+ rt.3.0.D037, C62, jesse, Tue Apr 15 11:53:58 2003, Bumping to version 3.0.1
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Apr 15 11:52:00 2003
+
+ none
+
+ rt.3.0.D036, C61, jesse, Mon Apr 14 18:18:03 2003, New speedycgi support from
+ vivek khera
+ From: Jesse Vincent <jesse@eris>
+ Date: Mon Apr 14 14:49:49 2003
+
+ none
+
+ rt.3.0.D035, C60, jesse, Sun Apr 13 21:43:32 2003, SMTP and I18N fixes from
+ autrijus tang. Updated chinese translations
+ From: Jesse Vincent <jesse@eris>
+ Date: Sun Apr 13 21:32:55 2003
+
+ none
+
+ rt.3.0.D034, C59, jesse, Fri Apr 11 21:58:36 2003, Bumping to RT 3.0.1pre2
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Apr 11 21:58:06 2003
+
+ none
+
+ rt.3.0.D033, C58, jesse, Fri Apr 11 21:56:11 2003, Better binary attachment
+ handling fix from autrijus
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Apr 11 21:52:12 2003
+
+ none
+
+ rt.3.0.D032, C57, jesse, Fri Apr 11 21:35:17 2003, Testing and fixing binary
+ attachment corruption
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Apr 11 21:34:28 2003
+
+ none
+
+ rt.3.0.D031, C56, jesse, Fri Apr 11 21:34:56 2003, Testing fixes for mail
+ authentication/authorization
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 10 00:08:29 2003
+
+ none
+
+ rt.3.0.D030, C55, jesse, Wed Apr 9 13:55:18 2003, #2365 Removing outdated
+ Mason setup parameter
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Apr 9 13:54:43 2003
+
+ none
+
+ rt.3.0.D029, C53, jesse, Wed Apr 9 13:22:35 2003, Added preliminary left to
+ right hebrew translation
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Apr 9 13:18:16 2003
+
+ none
+
+ rt.3.0.D028, C52, jesse, Fri Apr 4 02:10:01 2003, More I18N testing for
+ rafael
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Apr 4 02:08:49 2003
+
+ none
+
+ rt.3.0.D027, C51, jesse, Thu Apr 3 19:33:34 2003, Changing address used in
+ example to not send mail to author
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 3 19:29:02 2003
+ Warning: the original change was in the 'being_integrated' state
+
+ none
+
+ rt.3.0.D026, C50, jesse, Thu Apr 3 19:16:37 2003, Integrating rafael's
+ failing test suite and proposed fix from autrijus
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 3 19:15:03 2003
+
+ none
+
+ rt.3.0.D025, C39, jesse, Thu Apr 3 19:16:15 2003, Many users should be able
+ to have a blank address; neew test suite to ensure this
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Apr 3 13:27:36 2003
+
+ none
+
+ rt.3.0.D024, C38, jesse, Thu Apr 3 13:30:37 2003, Updates for RT RPC
+ interface from AMS
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Apr 2 15:32:15 2003
+
+ none
+
+ rt.3.0.D023, C36, jesse, Wed Apr 2 14:14:07 2003, Updated Spanish translation
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Apr 1 14:02:36 2003
+
+ none
+
+ rt.3.0.D022, C37, jesse, Wed Apr 2 14:13:55 2003, Updated dependencies
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Apr 2 14:09:41 2003
+
+ none
+
+ rt.3.0.D021, C35, jesse, Tue Apr 1 13:00:22 2003, New czech translation
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Apr 1 12:58:57 2003
+
+ none
+
+ rt.3.0.D020, C33, jesse, Fri Mar 28 14:48:20 2003, A tiny bit of extra data
+ passing for some callbacks
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Mar 28 14:43:28 2003
+
+ none
+
+ rt.3.0.D019, C32, jesse, Fri Mar 28 14:35:10 2003, Added better error checking
+ for failed ticket creation
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Mar 28 14:32:52 2003
+
+ none
+
+ rt.3.0.D018, C31, jesse, Sun Mar 23 17:21:45 2003, Minor fixes - Bumped to
+ 3.0.0;
+ 2266 - postgres scrip creation bug
+ 2267 - Tickets not reopened after being stalled
+ 2265 - Fixed css nits from blaise
+ Unreported - Fixed quick-search bug
+ From: Jesse Vincent <jesse@eris>
+ Date: Sun Mar 23 17:17:55 2003
+
+ none
+
+ rt.3.0.D017, C30, jesse, Thu Mar 20 21:38:30 2003, Bumping to 3.0.0rc4
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Mar 20 21:28:01 2003
+
+ none
+
+ rt.3.0.D016, C29, jesse, Thu Mar 20 21:18:27 2003, RT should now be less
+ overzealous about opening and then marking a ticket 'new' again
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Mar 20 21:14:47 2003
+
+ none
+
+ rt.3.0.D015, C28, jesse, Thu Mar 20 15:21:49 2003, Further postgres tweaks and
+ fixes
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Mar 20 15:20:21 2003
+
+ none
+
+ rt.3.0.D014, C27, jesse, Thu Mar 20 01:48:42 2003, Postgres schema tweak
+ From: Jesse Vincent <jesse@eris>
+ Date: Thu Mar 20 01:47:20 2003
+
+ none
+
+ rt.3.0.D013, C26, jesse, Wed Mar 19 21:26:47 2003, Postgres fixes
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 21:26:24 2003
+
+ none
+
+ rt.3.0.D012, C25, jesse, Wed Mar 19 16:27:29 2003, Brazilian Portuguese
+ translation
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 16:25:48 2003
+
+ none
+
+ rt.3.0.D011, C23, jesse, Wed Mar 19 13:03:15 2003, New french translation
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 13:02:47 2003
+
+ none
+
+ rt.3.0.D010, C22, jesse, Wed Mar 19 01:01:24 2003, Bumping to RC3; fixing the
+ display of 'This user's n highest priority tix'
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 01:00:33 2003
+
+ none
+
+ rt.3.0.D009, C20, jesse, Wed Mar 19 00:46:17 2003, finishing the notify stuff
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 00:45:09 2003
+
+ none
+
+ rt.3.0.D008, C19, jesse, Wed Mar 19 00:40:45 2003, Caving in to the masses and
+ making 'notify sender' configurable
+ From: Jesse Vincent <jesse@eris>
+ Date: Wed Mar 19 00:38:50 2003
+
+ none
+
+ rt.3.0.D007, C18, jesse, Tue Mar 18 16:29:45 2003, More performance work;
+ backing out 'enhancements' that killed system performance
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Mar 18 16:26:07 2003
+
+ none
+
+ rt.3.0.D006, C17, jesse, Tue Mar 18 11:29:28 2003, fixing fastcgi's ability to
+ load webmux.pl on some platforms
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Mar 18 11:28:11 2003
+
+ none
+
+ rt.3.0.D005, C12, jesse, Tue Mar 18 00:58:52 2003, fixing indices for postgres
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Mar 18 00:53:06 2003
+
+ none
+
+ rt.3.0.D004, C16, jesse, Tue Mar 18 00:41:26 2003, Performance work on
+ 'WhoHaveRight'; bumping to 3.0.0rc2
+ From: Jesse Vincent <jesse@eris>
+ Date: Tue Mar 18 00:37:30 2003
+
+ none
+
+ rt.3.0.D003, C15, jesse, Fri Mar 14 17:07:28 2003, removing the old REST API
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Mar 14 16:50:38 2003
+
+ none
+
+ rt.3.0.D002, C11, jesse, Fri Mar 14 16:42:46 2003, Minor CSS update. rollback
+ fix; new database indices; copyright update
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Mar 14 16:35:01 2003
+
+ none
+
+ rt.3.0.D001, C10, jesse, Fri Mar 14 01:26:22 2003, Bumping the version to
+ rt.3.0.0rc0
+ From: Jesse Vincent <jesse@eris>
+ Date: Fri Mar 14 01:25:51 2003
+
+ none
diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED
new file mode 100644
index 0000000..3937c30
--- /dev/null
+++ b/rt/FREESIDE_MODIFIED
@@ -0,0 +1,6 @@
+sbin/rt-setup-database
+sbin/rt-setup-database.in
+config.layout
+config.layout.in
+etc/RT_SiteConfig.pm
+lib/RT/URI/freeside.pm
diff --git a/rt/HOWTO/README b/rt/HOWTO/README
new file mode 100644
index 0000000..942096b
--- /dev/null
+++ b/rt/HOWTO/README
@@ -0,0 +1,14 @@
+Here you'll find plain text documentation of how to handle various
+project procedures. Files contained herein:
+
+change.txt
+ How changes are integrated, including generating and
+ distributing aedist change sets, and updating the CVS repository.
+
+release.txt
+ Steps to go through when releasing a new version of RT.
+
+
+These procedures are based on documentation from the scons project
+as http://www.scons.org/
+
diff --git a/rt/HOWTO/change.txt b/rt/HOWTO/change.txt
new file mode 100644
index 0000000..de31645
--- /dev/null
+++ b/rt/HOWTO/change.txt
@@ -0,0 +1,67 @@
+Handling a change set:
+
+ -- Start the change:
+
+ aedist -r [if it's a remote submission]
+
+ -or-
+
+ aedb {cnum} [if it's initiated locally]
+
+ -- Normal development cycle:
+
+ aecd -c {cnum}
+ aecp . # Copy the baseline to your working dir
+ # work on your change
+ aenf {new file names}
+
+ aecpu -unch # Remove unchanged files, for faster diffs
+ aeb # Currently does nothing
+ aet # Currently does nothing
+ aed # Diff your change
+ aede # End the change
+
+ -- As the reviewer:
+
+ aerpass {cnum}
+
+ -- As the integrator:
+
+ aeib {cnum}
+ aeb
+ aet
+ aed
+ cd ~ # Get out of the current working directory
+ aeipass
+
+
+
+
+ -- Update the aedist baseline on the web site:
+
+ aedist -s -bl -p rt.2.1 > rt.2.1.ae
+ scp rt.2.1.ae jesse@fsck.com:/home/ftp/pub/rt/devel/rt.2.1.ae
+ rm rt.2.1.ae
+
+ [This will eventually be automated.]
+
+ -- Distribute the change to CVS:
+
+ WARNING. DOES NOT YET WORK
+
+ export CVS_RSH=ssh
+ ae2cvs -n -aegis -p rt.2.1 -c {cnum} -u ~/SCons/scons
+ ae2cvs -X -aegis -p rt.2.1 -c {cnum} -u ~/SCons/scons
+
+ If you need the "ae2cvs" Perl script, you can find a copy
+ checked in to the bin/subdirectory.
+
+ [This may eventually be automated.]
+
+
+
+ -- Grabbing the latest dev sources over ssh
+
+ ssh fsck.com "aedist -s -p rt.2.1 -naa -bl -entire-source" | aedist -r
+
+
diff --git a/rt/HOWTO/release.txt b/rt/HOWTO/release.txt
new file mode 100644
index 0000000..285041c
--- /dev/null
+++ b/rt/HOWTO/release.txt
@@ -0,0 +1,124 @@
+Things to do to release a new version of rt:
+
+ Build and test candidate packages
+
+ Read through the README and src/README.txt files for any updates
+
+ Prepare ChangeLog
+
+ date -R the latest release
+
+ should be current if this has been updated as each
+ change went in.
+
+ [ Should be automated ]
+
+
+ TODO: nothing below this line is accurate for RT
+
+ END THE BRANCH
+
+ ae_p rt.2
+ aede {5}
+ aerpass {5}
+ aeib {5}
+ aeb
+ aet
+ aet -reg
+ aed
+ aeipass
+
+ START THE NEW BRANCH
+
+ aenbr -p rt.2 {6}
+ aenc -p rt.2.{6}
+
+ Call it something like, "Initialize the new
+ branch." Cause = internal_enhancement. Exempt
+ it from all tests (*_exempt = true).
+
+ ae_p rt.2.{6}
+
+ aedb 100
+
+ aecd
+
+ # Change the hard-coded package version numbers
+ # in the following files.
+ aecp rttruct debian/changelog rpm/rt.spec
+
+ vi rttruct debian/changelog rpm/rt.spec
+
+ # Optionally, do the same in the following:
+ [optional] aecp HOWTO/change.txt
+ [optional] aecp HOWTO/release.txt
+ [optional] aecp debian/rt.postinst
+
+ [optional] vi HOWTO/change.txt
+ [optional] vi HOWTO/release.txt
+ [optional] vi debian/rt.postinst
+
+ aeb
+
+ aet -reg
+
+ aed
+
+ aede
+
+ etc.
+
+
+ Read through the FAQ for any updates
+
+ Test downloading from the web site download page
+
+
+ In the Bugs Tracker, add a Group for the new release (0.05)
+
+ Announce to the following mailing lists (template below):
+
+ rt-announce@lists.fsck.com
+
+
+ Notify www.cmtoday.com/contribute.html
+
+ [This guy wants an announcement no more frequently than
+ once a month, so save it for a future release if it's
+ been too soon since the previous one.]
+
+ Notify freshmeat.net
+
+ [Wait until the morning so the announcement hits the
+ main freshmeat.net page while people in the U.S. are
+ awake and working]
+
+
+
+
+=======================
+
+Template release announcement:
+
+
+
+Version 2.1.XXX of rt has been released and is available for download
+from the rt web site:
+
+ http://bestpractical.com/rt/
+
+
+
+WHAT'S NEW IN THIS RELEASE?
+
+Version 2.1.XXX of rt contains the following important changes:
+
+ - XXX
+
+For a complete list of changes in version 2.1.XXX, see the CHANGES.txt
+file in the release itself.
+
+
+WHAT IS RT?
+
+ FILL THIS IN
diff --git a/rt/HOWTO/version-control.txt b/rt/HOWTO/version-control.txt
new file mode 100644
index 0000000..06babfd
--- /dev/null
+++ b/rt/HOWTO/version-control.txt
@@ -0,0 +1,41 @@
+Using Aegis for RT development
+
+ 1. The main line of RT development will be under the control
+ of the Aegis change management system, as administered by
+ Best Practical Solutions, LLC
+
+ 2. We will use aedist to generate change sets for each change
+ checked in to the main Aegis repository. These change sets will be
+ either distributed by a mailing list or made available via the web,
+ or both.
+
+ 3. Remote developers using Aegis will send aedist output for
+ their changes to rt-patches@bestpractical.com for review and
+ integration.
+
+ 4. The aedist output should be sent to rt-patches@bestpractical.com
+ after the change has completed its local aede, but before aerpass.
+
+ 5. If the change is rejected, the developer can aedeu to reopen
+ the change and fix whatever problems caused the review to not pass.
+
+ 6. A baseline snapshot (aedist -bl) of the main Aegis repository
+ will be generated at least daily and made available via http
+ to provide a central location for synchronizing remote Aegis
+ repositories.
+
+ 7. Changes to the main Aegis repository will also be propagated
+ automatically to the tracking CVS repository.
+
+Using CVS for RT development
+
+ 1. CVS is accessed via anonymous cvs with the following CVSROOT:
+
+ :pserver:anoncvs@cvs.fsck.com:/raid/cvsroot/rt-2-1
+
+ 2. Remote developers using CVS will send patches (cvs -diff
+ output) to rt-patches@bestpractical.com for integration into the
+ main Aegis repository. This allows anonymous CVS access to be used
+ for RT development by developers who are unable to use Aegis.
+
+
diff --git a/rt/Makefile b/rt/Makefile
new file mode 100644
index 0000000..0895874
--- /dev/null
+++ b/rt/Makefile
@@ -0,0 +1,490 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+#
+# DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated.
+# Have a look at "configure" and "Makefile.in" instead
+#
+
+
+PERL = /usr/bin/perl
+
+CONFIG_FILE_PATH = /opt/rt3/etc
+CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm
+SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm
+
+
+RT_VERSION_MAJOR = 3
+RT_VERSION_MINOR = 0
+RT_VERSION_PATCH = 9
+
+RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH)
+TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH)
+
+
+# This is the group that all of the installed files will be chgrp'ed to.
+RTGROUP = rt
+
+
+# User which should own rt binaries.
+BIN_OWNER = root
+
+# User that should own all of RT's libraries, generally root.
+LIBS_OWNER = root
+
+# Group that should own all of RT's libraries, generally root.
+LIBS_GROUP = bin
+
+WEB_USER = www
+WEB_GROUP = www
+
+# {{{ Files and directories
+
+# DESTDIR allows you to specify that RT be installed somewhere other than
+# where it will eventually reside
+
+DESTDIR =
+
+
+RT_PATH = /opt/rt3
+RT_ETC_PATH = /opt/rt3/etc
+RT_BIN_PATH = /opt/rt3/bin
+RT_SBIN_PATH = /opt/rt3/sbin
+RT_LIB_PATH = /opt/rt3/lib
+RT_MAN_PATH = /opt/rt3/man
+RT_VAR_PATH = /opt/rt3/var
+RT_DOC_PATH = /opt/rt3/share/doc
+RT_LOCAL_PATH = /opt/rt3/local
+LOCAL_ETC_PATH = /opt/rt3/local/etc
+LOCAL_LEXICON_PATH = /opt/rt3/local/po
+MASON_HTML_PATH = /opt/rt3/share/html
+MASON_LOCAL_HTML_PATH = /opt/rt3/local/html
+MASON_DATA_PATH = /opt/rt3/var/mason_data
+MASON_SESSION_PATH = /opt/rt3/var/session_data
+RT_LOG_PATH = /opt/rt3/var/log
+
+# RT_READABLE_DIR_MODE is the mode of directories that are generally meant
+# to be accessable
+RT_READABLE_DIR_MODE = 0755
+
+
+
+
+# {{{ all these define the places that RT's binaries should get installed
+
+# RT_MODPERL_HANDLER is the mason handler script for mod_perl
+RT_MODPERL_HANDLER = $(RT_BIN_PATH)/webmux.pl
+# RT_FASTCGI_HANDLER is the mason handler script for FastCGI
+RT_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.fcgi
+# RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI
+RT_WIN32_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.svc
+# RT's CLI
+RT_CLI_BIN = $(RT_BIN_PATH)/rt
+# RT's mail gateway
+RT_MAILGATE_BIN = $(RT_BIN_PATH)/rt-mailgate
+# RT's cron tool
+RT_CRON_BIN = $(RT_BIN_PATH)/rt-crontool
+
+# }}}
+
+SETGID_BINARIES = $(DESTDIR)/$(RT_FASTCGI_HANDLER) \
+ $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER)
+
+BINARIES = $(DESTDIR)/$(RT_MODPERL_HANDLER) \
+ $(DESTDIR)/$(RT_MAILGATE_BIN) \
+ $(DESTDIR)/$(RT_CLI_BIN) \
+ $(DESTDIR)/$(RT_CRON_BIN) \
+ $(SETGID_BINARIES)
+SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/
+
+
+# }}}
+
+# {{{ Database setup
+
+#
+# DB_TYPE defines what sort of database RT trys to talk to
+# "mysql" is known to work.
+# "Pg" is known to work
+# "Informix" is known to work
+
+DB_TYPE = mysql
+
+# Set DBA to the name of a unix account with the proper permissions and
+# environment to run your commandline SQL sbin
+
+# Set DB_DBA to the name of a DB user with permission to create new databases
+
+# For mysql, you probably want 'root'
+# For Pg, you probably want 'postgres'
+# For Oracle, you want 'system'
+# For Informix, you want 'informix'
+
+DB_DBA = root
+
+DB_HOST = localhost
+
+# If you're not running your database server on its default port,
+# specifiy the port the database server is running on below.
+# It's generally safe to leave this blank
+
+DB_PORT =
+
+
+
+
+#
+# Set this to the canonical name of the interface RT will be talking to the
+# database on. If you said that the RT_DB_HOST above was "localhost," this
+# should be too. This value will be used to grant rt access to the database.
+# If you want to access the RT database from multiple hosts, you'll need
+# to grant those database rights by hand.
+#
+
+DB_RT_HOST = localhost
+
+# set this to the name you want to give to the RT database in
+# your database server. For Oracle, this should be the name of your sid
+
+DB_DATABASE = rt3
+DB_RT_USER = rt_user
+DB_RT_PASS = rt_pass
+
+# }}}
+
+
+####################################################################
+
+all: default
+
+default:
+ @echo "Please read RT's readme before installing. Not doing so could"
+ @echo "be dangerous."
+
+
+
+instruct:
+ @echo "Congratulations. RT has been installed. "
+ @echo ""
+ @echo ""
+ @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)."
+ @echo ""
+ @echo "(You will definitely need to set RT's database password before continuing."
+ @echo " Not doing so could be very dangerous)"
+ @echo ""
+ @echo "After that, you need to initialize RT's database by running"
+ @echo " 'make initialize-database'"
+
+# @echo " or by executing "
+# @echo " '$(RT_SBIN_PATH)/rt-setup-database --action init \ "
+# @echo " --dba $(DB_DBA) --prompt-for-dba-password'"
+
+
+
+upgrade-instruct:
+ @echo "Congratulations. RT has been upgraded. You should now check-over"
+ @echo "$(CONFIG_FILE) for any necessary site customization. Additionally,"
+ @echo "you should update RT's system database objects by running "
+ @echo " ls etc/upgrade"
+ @echo "For each file in that directory whose name is greater than"
+ @echo "your previously installed RT version, run:"
+ @echo " $(RT_SBIN_PATH)/rt-setup-database --action insert --datafile etc/upgrade/<version>"
+
+
+upgrade: config-install dirs files-install fixperms upgrade-instruct
+
+upgrade-noclobber: config-install libs-install html-install bin-install local-install doc-install fixperms
+
+
+# {{{ dependencies
+testdeps:
+ $(PERL) ./sbin/rt-test-dependencies --with-$(DB_TYPE)
+
+fixdeps:
+ $(PERL) ./sbin/rt-test-dependencies --install --with-$(DB_TYPE)
+
+#}}}
+
+# {{{ fixperms
+fixperms:
+ # Make the libraries readable
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_PATH)
+ chown -R $(LIBS_OWNER) $(DESTDIR)/$(RT_LIB_PATH)
+ chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(RT_LIB_PATH)
+ chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(RT_LIB_PATH)
+
+
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH)
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH)
+
+ chmod 0755 $(DESTDIR)/$(RT_ETC_PATH)
+ chmod 0500 $(DESTDIR)/$(RT_ETC_PATH)/*
+
+ #TODO: the config file should probably be able to have its
+ # owner set seperately from the binaries.
+ chown -R $(BIN_OWNER) $(DESTDIR)/$(RT_ETC_PATH)
+ chgrp -R $(RTGROUP) $(DESTDIR)/$(RT_ETC_PATH)
+
+ chmod 0550 $(DESTDIR)/$(CONFIG_FILE)
+ chmod 0550 $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ # Make the interfaces executable and setgid rt
+ chown $(BIN_OWNER) $(BINARIES)
+ chgrp $(RTGROUP) $(BINARIES)
+ chmod 0755 $(BINARIES)
+ chmod g+s $(SETGID_BINARIES)
+
+ # Make the web ui readable by all.
+ chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) \
+ $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+ chown -R $(LIBS_OWNER) $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+
+ # Make the web ui's data dir writable
+ chmod 0770 $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+ chown -R $(WEB_USER) $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+ chgrp -R $(WEB_GROUP) $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+# }}}
+
+fixperms-nosetgid: fixperms
+ @echo "You should never be running RT this way. it's unsafe"
+ chmod 0555 $(SETGID_BINARIES)
+ chmod 0555 $(DESTDIR)/$(CONFIG_FILE)
+ chmod 0555 $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+# {{{ dirs
+dirs:
+ mkdir -p $(DESTDIR)/$(RT_LOG_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/cache
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/etc
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/obj
+ mkdir -p $(DESTDIR)/$(MASON_SESSION_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_HTML_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ mkdir -p $(DESTDIR)/$(LOCAL_ETC_PATH)
+ mkdir -p $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+# }}}
+
+install: config-install dirs files-install fixperms instruct
+
+files-install: libs-install etc-install bin-install sbin-install html-install local-install doc-install
+
+config-install:
+ mkdir -p $(DESTDIR)/$(CONFIG_FILE_PATH)
+ cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE)
+ [ -f $(DESTDIR)/$(SITE_CONFIG_FILE) ] || cp etc/RT_SiteConfig.pm $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ chgrp $(RTGROUP) $(DESTDIR)/$(CONFIG_FILE)
+ chown $(BIN_OWNER) $(DESTDIR)/$(CONFIG_FILE)
+
+ chgrp $(RTGROUP) $(DESTDIR)/$(SITE_CONFIG_FILE)
+ chown $(BIN_OWNER) $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ @echo "Installed configuration. about to install rt in $(RT_PATH)"
+
+test:
+ $(PERL) -Ilib lib/t/00smoke.t
+
+regression-install: config-install
+ $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE)
+
+regression-nosetgid-quiet: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl
+ $(PERL) sbin/regression_harness
+
+regression-nosetgid: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl
+ $(PERL) lib/t/02regression.t
+
+regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms apachectl
+ $(PERL) lib/t/02regression.t
+
+regression-quiet:
+ $(PERL) sbin/regression_harness
+
+regression-instruct:
+ @echo "About to wipe your database for a regression test. ABORT NOW with Control-C"
+
+
+# {{{ database-installation
+
+regression-reset-db:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password ''
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --dba-password ''
+
+initialize-database:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password
+
+dropdb:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password
+
+insert-approval-data:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/insert_approval_scrips
+# }}}
+
+# {{{ libs-install
+libs-install:
+ [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir $(DESTDIR)/$(RT_LIB_PATH)
+ -cp -rp lib/* $(DESTDIR)/$(RT_LIB_PATH)
+# }}}
+
+# {{{ html-install
+html-install:
+ [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir $(DESTDIR)/$(MASON_HTML_PATH)
+ -cp -rp ./html/* $(DESTDIR)/$(MASON_HTML_PATH)
+# }}}
+
+# {{{ doc-install
+doc-install:
+ # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir
+ -[ -f $(DESTDIR)/$(RT_DOC_PATH) ] && rm $(DESTDIR)/$(RT_DOC_PATH)
+ [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir $(DESTDIR)/$(RT_DOC_PATH)
+ -cp -rp ./README $(DESTDIR)/$(RT_DOC_PATH)
+# }}}
+
+# {{{ etc-install
+
+etc-install:
+ mkdir -p $(DESTDIR)/$(RT_ETC_PATH)
+ -cp -rp \
+ etc/acl.* \
+ etc/initialdata \
+ etc/schema.* \
+ $(DESTDIR)/$(RT_ETC_PATH)
+# }}}
+
+# {{{ sbin-install
+
+sbin-install:
+ mkdir -p $(DESTDIR)/$(RT_SBIN_PATH)
+ chmod +x sbin/rt-setup-database \
+ sbin/rt-test-dependencies
+ -cp -rp \
+ sbin/rt-setup-database \
+ sbin/rt-test-dependencies \
+ $(DESTDIR)/$(RT_SBIN_PATH)
+
+# }}}
+
+# {{{ bin-install
+
+bin-install:
+ mkdir -p $(DESTDIR)/$(RT_BIN_PATH)
+ chmod +x bin/rt-mailgate \
+ bin/rt-crontool
+ -cp -rp \
+ bin/rt-mailgate \
+ bin/mason_handler.fcgi \
+ bin/mason_handler.scgi \
+ bin/mason_handler.svc \
+ bin/rt \
+ bin/webmux.pl \
+ bin/rt-crontool \
+ $(DESTDIR)/$(RT_BIN_PATH)
+# }}}
+
+# {{{ local-install
+local-install:
+ -cp -rp ./local/html/* $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ -cp -rp ./local/po/* $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+ -cp -rp ./local/etc/* $(DESTDIR)/$(LOCAL_ETC_PATH)
+# }}}
+
+# {{{ Best Practical Build targets -- no user servicable parts inside
+
+
+POD2TEST_EXE = sbin/extract_pod_tests
+
+testify-pods:
+ [ -d lib/t/autogen ] || mkdir lib/t/autogen
+ find lib -name \*pm |grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE)
+ find bin -type f |grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE)
+
+
+
+regenerate-catalogs:
+ $(PERL) sbin/extract-message-catalog
+
+license-tag:
+ $(PERL) sbin/license_tag
+
+factory: initialize-database
+ cd lib; $(PERL) ../sbin/factory $(DB_DATABASE) RT
+
+commit:
+ aegis -build ; aegis -diff ; aegis -test; aegis -develop_end
+
+integrate:
+ aegis -integrate_begin; aegis -build; aegis -diff; aegis -test ; aegis -integrate_pass
+
+predist: commit tag-and-tar
+
+tag-and-release-baseline:
+ aegis -cp -ind Makefile -output /tmp/Makefile.tagandrelease; \
+ $(MAKE) -f /tmp/Makefile.tagandrelease tag-and-release-never-by-hand
+
+
+# Running this target in a working directory is
+# WRONG WRONG WRONG.
+# it will tag the current baseline with the version of RT defined
+# in the currently-being-worked-on makefile. which is wrong.
+# you want tag-and-release-baseline
+
+tag-and-release-never-by-hand:
+ aegis --delta-name $(TAG)
+ rm -rf /tmp/$(TAG)
+ mkdir /tmp/$(TAG)
+ cd /tmp/$(TAG); \
+ aegis -cp -ind -delta $(TAG) . ;\
+ make reconfigure;\
+ chmod 600 Makefile;\
+ aegis --report --project rt.$(RT_VERSION_MAJOR) \
+ --page_width 80 \
+ --page_length 9999 \
+ --change $(RT_VERSION_MINOR) --output Changelog Change_Log
+
+ cd /tmp; tar czvf /home/ftp/pub/rt/devel/$(TAG).tar.gz $(TAG)/
+ chmod 644 /home/ftp/pub/rt/devel/$(TAG).tar.gz
+
+
+reconfigure:
+ aclocal -I m4
+ autoconf
+ chmod 755 ./configure
+ ./configure
+
+rpm:
+ (cd ..; tar czvf /usr/src/redhat/SOURCES/rt.tar.gz rt)
+ rpm -ba etc/rt.spec
+
+
+apachectl:
+ apachectl stop
+ sleep 3
+ apachectl start
+# }}}
diff --git a/rt/Makefile.in b/rt/Makefile.in
new file mode 100644
index 0000000..c3eabc6
--- /dev/null
+++ b/rt/Makefile.in
@@ -0,0 +1,490 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+#
+# DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated.
+# Have a look at "configure" and "Makefile.in" instead
+#
+
+
+PERL = @PERL@
+
+CONFIG_FILE_PATH = @CONFIG_FILE_PATH@
+CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm
+SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm
+
+
+RT_VERSION_MAJOR = @RT_VERSION_MAJOR@
+RT_VERSION_MINOR = @RT_VERSION_MINOR@
+RT_VERSION_PATCH = @RT_VERSION_PATCH@
+
+RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH)
+TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH)
+
+
+# This is the group that all of the installed files will be chgrp'ed to.
+RTGROUP = @RTGROUP@
+
+
+# User which should own rt binaries.
+BIN_OWNER = @BIN_OWNER@
+
+# User that should own all of RT's libraries, generally root.
+LIBS_OWNER = @LIBS_OWNER@
+
+# Group that should own all of RT's libraries, generally root.
+LIBS_GROUP = @LIBS_GROUP@
+
+WEB_USER = @WEB_USER@
+WEB_GROUP = @WEB_GROUP@
+
+# {{{ Files and directories
+
+# DESTDIR allows you to specify that RT be installed somewhere other than
+# where it will eventually reside
+
+DESTDIR =
+
+
+RT_PATH = @RT_PATH@
+RT_ETC_PATH = @RT_ETC_PATH@
+RT_BIN_PATH = @RT_BIN_PATH@
+RT_SBIN_PATH = @RT_SBIN_PATH@
+RT_LIB_PATH = @RT_LIB_PATH@
+RT_MAN_PATH = @RT_MAN_PATH@
+RT_VAR_PATH = @RT_VAR_PATH@
+RT_DOC_PATH = @RT_DOC_PATH@
+RT_LOCAL_PATH = @RT_LOCAL_PATH@
+LOCAL_ETC_PATH = @LOCAL_ETC_PATH@
+LOCAL_LEXICON_PATH = @LOCAL_LEXICON_PATH@
+MASON_HTML_PATH = @MASON_HTML_PATH@
+MASON_LOCAL_HTML_PATH = @MASON_LOCAL_HTML_PATH@
+MASON_DATA_PATH = @MASON_DATA_PATH@
+MASON_SESSION_PATH = @MASON_SESSION_PATH@
+RT_LOG_PATH = @RT_LOG_PATH@
+
+# RT_READABLE_DIR_MODE is the mode of directories that are generally meant
+# to be accessable
+RT_READABLE_DIR_MODE = 0755
+
+
+
+
+# {{{ all these define the places that RT's binaries should get installed
+
+# RT_MODPERL_HANDLER is the mason handler script for mod_perl
+RT_MODPERL_HANDLER = $(RT_BIN_PATH)/webmux.pl
+# RT_FASTCGI_HANDLER is the mason handler script for FastCGI
+RT_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.fcgi
+# RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI
+RT_WIN32_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.svc
+# RT's CLI
+RT_CLI_BIN = $(RT_BIN_PATH)/rt
+# RT's mail gateway
+RT_MAILGATE_BIN = $(RT_BIN_PATH)/rt-mailgate
+# RT's cron tool
+RT_CRON_BIN = $(RT_BIN_PATH)/rt-crontool
+
+# }}}
+
+SETGID_BINARIES = $(DESTDIR)/$(RT_FASTCGI_HANDLER) \
+ $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER)
+
+BINARIES = $(DESTDIR)/$(RT_MODPERL_HANDLER) \
+ $(DESTDIR)/$(RT_MAILGATE_BIN) \
+ $(DESTDIR)/$(RT_CLI_BIN) \
+ $(DESTDIR)/$(RT_CRON_BIN) \
+ $(SETGID_BINARIES)
+SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/
+
+
+# }}}
+
+# {{{ Database setup
+
+#
+# DB_TYPE defines what sort of database RT trys to talk to
+# "mysql" is known to work.
+# "Pg" is known to work
+# "Informix" is known to work
+
+DB_TYPE = @DB_TYPE@
+
+# Set DBA to the name of a unix account with the proper permissions and
+# environment to run your commandline SQL sbin
+
+# Set DB_DBA to the name of a DB user with permission to create new databases
+
+# For mysql, you probably want 'root'
+# For Pg, you probably want 'postgres'
+# For Oracle, you want 'system'
+# For Informix, you want 'informix'
+
+DB_DBA = @DB_DBA@
+
+DB_HOST = @DB_HOST@
+
+# If you're not running your database server on its default port,
+# specifiy the port the database server is running on below.
+# It's generally safe to leave this blank
+
+DB_PORT = @DB_PORT@
+
+
+
+
+#
+# Set this to the canonical name of the interface RT will be talking to the
+# database on. If you said that the RT_DB_HOST above was "localhost," this
+# should be too. This value will be used to grant rt access to the database.
+# If you want to access the RT database from multiple hosts, you'll need
+# to grant those database rights by hand.
+#
+
+DB_RT_HOST = @DB_RT_HOST@
+
+# set this to the name you want to give to the RT database in
+# your database server. For Oracle, this should be the name of your sid
+
+DB_DATABASE = @DB_DATABASE@
+DB_RT_USER = @DB_RT_USER@
+DB_RT_PASS = @DB_RT_PASS@
+
+# }}}
+
+
+####################################################################
+
+all: default
+
+default:
+ @echo "Please read RT's readme before installing. Not doing so could"
+ @echo "be dangerous."
+
+
+
+instruct:
+ @echo "Congratulations. RT has been installed. "
+ @echo ""
+ @echo ""
+ @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)."
+ @echo ""
+ @echo "(You will definitely need to set RT's database password before continuing."
+ @echo " Not doing so could be very dangerous)"
+ @echo ""
+ @echo "After that, you need to initialize RT's database by running"
+ @echo " 'make initialize-database'"
+
+# @echo " or by executing "
+# @echo " '$(RT_SBIN_PATH)/rt-setup-database --action init \ "
+# @echo " --dba $(DB_DBA) --prompt-for-dba-password'"
+
+
+
+upgrade-instruct:
+ @echo "Congratulations. RT has been upgraded. You should now check-over"
+ @echo "$(CONFIG_FILE) for any necessary site customization. Additionally,"
+ @echo "you should update RT's system database objects by running "
+ @echo " ls etc/upgrade"
+ @echo "For each file in that directory whose name is greater than"
+ @echo "your previously installed RT version, run:"
+ @echo " $(RT_SBIN_PATH)/rt-setup-database --action insert --datafile etc/upgrade/<version>"
+
+
+upgrade: config-install dirs files-install fixperms upgrade-instruct
+
+upgrade-noclobber: config-install libs-install html-install bin-install local-install doc-install fixperms
+
+
+# {{{ dependencies
+testdeps:
+ $(PERL) ./sbin/rt-test-dependencies --with-$(DB_TYPE)
+
+fixdeps:
+ $(PERL) ./sbin/rt-test-dependencies --install --with-$(DB_TYPE)
+
+#}}}
+
+# {{{ fixperms
+fixperms:
+ # Make the libraries readable
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_PATH)
+ chown -R $(LIBS_OWNER) $(DESTDIR)/$(RT_LIB_PATH)
+ chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(RT_LIB_PATH)
+ chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(RT_LIB_PATH)
+
+
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH)
+ chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH)
+
+ chmod 0755 $(DESTDIR)/$(RT_ETC_PATH)
+ chmod 0500 $(DESTDIR)/$(RT_ETC_PATH)/*
+
+ #TODO: the config file should probably be able to have its
+ # owner set seperately from the binaries.
+ chown -R $(BIN_OWNER) $(DESTDIR)/$(RT_ETC_PATH)
+ chgrp -R $(RTGROUP) $(DESTDIR)/$(RT_ETC_PATH)
+
+ chmod 0550 $(DESTDIR)/$(CONFIG_FILE)
+ chmod 0550 $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ # Make the interfaces executable and setgid rt
+ chown $(BIN_OWNER) $(BINARIES)
+ chgrp $(RTGROUP) $(BINARIES)
+ chmod 0755 $(BINARIES)
+ chmod g+s $(SETGID_BINARIES)
+
+ # Make the web ui readable by all.
+ chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) \
+ $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+ chown -R $(LIBS_OWNER) $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(MASON_HTML_PATH) \
+ $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+
+ # Make the web ui's data dir writable
+ chmod 0770 $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+ chown -R $(WEB_USER) $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+ chgrp -R $(WEB_GROUP) $(DESTDIR)/$(MASON_DATA_PATH) \
+ $(DESTDIR)/$(MASON_SESSION_PATH)
+# }}}
+
+fixperms-nosetgid: fixperms
+ @echo "You should never be running RT this way. it's unsafe"
+ chmod 0555 $(SETGID_BINARIES)
+ chmod 0555 $(DESTDIR)/$(CONFIG_FILE)
+ chmod 0555 $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+# {{{ dirs
+dirs:
+ mkdir -p $(DESTDIR)/$(RT_LOG_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/cache
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/etc
+ mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/obj
+ mkdir -p $(DESTDIR)/$(MASON_SESSION_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_HTML_PATH)
+ mkdir -p $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ mkdir -p $(DESTDIR)/$(LOCAL_ETC_PATH)
+ mkdir -p $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+# }}}
+
+install: config-install dirs files-install fixperms instruct
+
+files-install: libs-install etc-install bin-install sbin-install html-install local-install doc-install
+
+config-install:
+ mkdir -p $(DESTDIR)/$(CONFIG_FILE_PATH)
+ cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE)
+ [ -f $(DESTDIR)/$(SITE_CONFIG_FILE) ] || cp etc/RT_SiteConfig.pm $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ chgrp $(RTGROUP) $(DESTDIR)/$(CONFIG_FILE)
+ chown $(BIN_OWNER) $(DESTDIR)/$(CONFIG_FILE)
+
+ chgrp $(RTGROUP) $(DESTDIR)/$(SITE_CONFIG_FILE)
+ chown $(BIN_OWNER) $(DESTDIR)/$(SITE_CONFIG_FILE)
+
+ @echo "Installed configuration. about to install rt in $(RT_PATH)"
+
+test:
+ $(PERL) -Ilib lib/t/00smoke.t
+
+regression-install: config-install
+ $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE)
+
+regression-nosetgid-quiet: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl
+ $(PERL) sbin/regression_harness
+
+regression-nosetgid: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms-nosetgid apachectl
+ $(PERL) lib/t/02regression.t
+
+regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms apachectl
+ $(PERL) lib/t/02regression.t
+
+regression-quiet:
+ $(PERL) sbin/regression_harness
+
+regression-instruct:
+ @echo "About to wipe your database for a regression test. ABORT NOW with Control-C"
+
+
+# {{{ database-installation
+
+regression-reset-db:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password ''
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --dba-password ''
+
+initialize-database:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password
+
+dropdb:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password
+
+insert-approval-data:
+ $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/insert_approval_scrips
+# }}}
+
+# {{{ libs-install
+libs-install:
+ [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir $(DESTDIR)/$(RT_LIB_PATH)
+ -cp -rp lib/* $(DESTDIR)/$(RT_LIB_PATH)
+# }}}
+
+# {{{ html-install
+html-install:
+ [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir $(DESTDIR)/$(MASON_HTML_PATH)
+ -cp -rp ./html/* $(DESTDIR)/$(MASON_HTML_PATH)
+# }}}
+
+# {{{ doc-install
+doc-install:
+ # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir
+ -[ -f $(DESTDIR)/$(RT_DOC_PATH) ] && rm $(DESTDIR)/$(RT_DOC_PATH)
+ [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir $(DESTDIR)/$(RT_DOC_PATH)
+ -cp -rp ./README $(DESTDIR)/$(RT_DOC_PATH)
+# }}}
+
+# {{{ etc-install
+
+etc-install:
+ mkdir -p $(DESTDIR)/$(RT_ETC_PATH)
+ -cp -rp \
+ etc/acl.* \
+ etc/initialdata \
+ etc/schema.* \
+ $(DESTDIR)/$(RT_ETC_PATH)
+# }}}
+
+# {{{ sbin-install
+
+sbin-install:
+ mkdir -p $(DESTDIR)/$(RT_SBIN_PATH)
+ chmod +x sbin/rt-setup-database \
+ sbin/rt-test-dependencies
+ -cp -rp \
+ sbin/rt-setup-database \
+ sbin/rt-test-dependencies \
+ $(DESTDIR)/$(RT_SBIN_PATH)
+
+# }}}
+
+# {{{ bin-install
+
+bin-install:
+ mkdir -p $(DESTDIR)/$(RT_BIN_PATH)
+ chmod +x bin/rt-mailgate \
+ bin/rt-crontool
+ -cp -rp \
+ bin/rt-mailgate \
+ bin/mason_handler.fcgi \
+ bin/mason_handler.scgi \
+ bin/mason_handler.svc \
+ bin/rt \
+ bin/webmux.pl \
+ bin/rt-crontool \
+ $(DESTDIR)/$(RT_BIN_PATH)
+# }}}
+
+# {{{ local-install
+local-install:
+ -cp -rp ./local/html/* $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
+ -cp -rp ./local/po/* $(DESTDIR)/$(LOCAL_LEXICON_PATH)
+ -cp -rp ./local/etc/* $(DESTDIR)/$(LOCAL_ETC_PATH)
+# }}}
+
+# {{{ Best Practical Build targets -- no user servicable parts inside
+
+
+POD2TEST_EXE = sbin/extract_pod_tests
+
+testify-pods:
+ [ -d lib/t/autogen ] || mkdir lib/t/autogen
+ find lib -name \*pm |grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE)
+ find bin -type f |grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE)
+
+
+
+regenerate-catalogs:
+ $(PERL) sbin/extract-message-catalog
+
+license-tag:
+ $(PERL) sbin/license_tag
+
+factory: initialize-database
+ cd lib; $(PERL) ../sbin/factory $(DB_DATABASE) RT
+
+commit:
+ aegis -build ; aegis -diff ; aegis -test; aegis -develop_end
+
+integrate:
+ aegis -integrate_begin; aegis -build; aegis -diff; aegis -test ; aegis -integrate_pass
+
+predist: commit tag-and-tar
+
+tag-and-release-baseline:
+ aegis -cp -ind Makefile -output /tmp/Makefile.tagandrelease; \
+ $(MAKE) -f /tmp/Makefile.tagandrelease tag-and-release-never-by-hand
+
+
+# Running this target in a working directory is
+# WRONG WRONG WRONG.
+# it will tag the current baseline with the version of RT defined
+# in the currently-being-worked-on makefile. which is wrong.
+# you want tag-and-release-baseline
+
+tag-and-release-never-by-hand:
+ aegis --delta-name $(TAG)
+ rm -rf /tmp/$(TAG)
+ mkdir /tmp/$(TAG)
+ cd /tmp/$(TAG); \
+ aegis -cp -ind -delta $(TAG) . ;\
+ make reconfigure;\
+ chmod 600 Makefile;\
+ aegis --report --project rt.$(RT_VERSION_MAJOR) \
+ --page_width 80 \
+ --page_length 9999 \
+ --change $(RT_VERSION_MINOR) --output Changelog Change_Log
+
+ cd /tmp; tar czvf /home/ftp/pub/rt/devel/$(TAG).tar.gz $(TAG)/
+ chmod 644 /home/ftp/pub/rt/devel/$(TAG).tar.gz
+
+
+reconfigure:
+ aclocal -I m4
+ autoconf
+ chmod 755 ./configure
+ ./configure
+
+rpm:
+ (cd ..; tar czvf /usr/src/redhat/SOURCES/rt.tar.gz rt)
+ rpm -ba etc/rt.spec
+
+
+apachectl:
+ apachectl stop
+ sleep 3
+ apachectl start
+# }}}
diff --git a/rt/README b/rt/README
new file mode 100755
index 0000000..7188f09
--- /dev/null
+++ b/rt/README
@@ -0,0 +1,302 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+RT is an enterprise-grade issue tracking system. It allows
+organizations to keep track of their to-do lists, who is working
+on which tasks, what's already been done, and when tasks were
+completed. It is available under the terms of version 2 of the GNU
+General Public License (GPL), so it doesn't cost anything to set
+up and use.
+
+
+ Jesse Vincent
+ Best Practical Solutions, LLC
+ March 2003
+
+REQUIRED PACKAGES:
+------------------
+
+o Perl 5.8.3 or later (http://www.perl.com).
+
+ (If you intend to use the FastCGI or SpeedyCGI support, you
+ need to make sure that perl has been built with support for
+ setgid perl scripts.)`
+
+ Perl versions prior to 5.8.3 contain bugs that could result in data
+ corruption. We recommend strongly that you use 5.8.3 or newer.
+
+ Perl 5.6.1 is currently deprecated and will be officially desupported
+ in a future release
+
+o A DB backend; MySQL is recommended ( http://www.mysql.com )
+ Currently supported: Mysql 4.0.13 or later.
+ Postgres 7.2 or later.
+ Oracle 9iR2.
+
+o Apache version 1.3.x or 2.x (http://httpd.apache.org)
+ with mod_perl -- (http://perl.apache.org )
+ or a webserver with FastCGI support (www.fastcgi.com)
+
+ mod_perl 2.0 isn't quite ready for prime_time just yet;
+ Best Practical Solutions strongly recommends that sites use
+ Apache 1.3 or FastCGI.
+
+ Compiling mod_perl on Apache 1.3.x as a DSO has been known
+ to have massive stability problems and is not recommended.
+
+ mod_perl 1.x must be build with EVERYTHING=1
+
+ RT's FastCGI handler runs setgid to the 'rt' group to
+ protect RT's database password. You may need to install
+ a special "suidperl" package or reconfigure your perl
+ setup to support "setuid scripts" if you intend to use RT
+ with FastCGI.
+
+ Debian GNU/* 3.0+: the package which installs suidperl is
+ called perl-suid, and should work without any tweaking.
+
+ FreeBSD 4.2+: the package is called sperl, and should
+ install a suidperl that just works
+
+ Conectiva Linux 6.0+: suidperl is installed by default when
+ perl is installed, but the program /bin/suidperl is not setuid.
+ You must use chmod to make it setuid.
+
+
+
+o Various and sundry perl modules
+ A tool included with RT takes care of the installation of
+ most of these automatically during the install process.
+
+ The tool supplied with RT uses Perl's CPAN system
+ (http://www.cpan.org) to install modules. Some operating
+ systems package all or some of the modules required and
+ you may be better off installing the modules that way.
+
+
+GENERAL INSTALLATION
+--------------------
+
+This is a rough guide to installing RT. For more detail, you'll want
+to read 'Chapter 2: Installing' in RT's manual, available at
+http://www.bestpractical.com/rt
+
+1 Unpack this distribution SOMWHERE OTHER THAN where you want to install RT
+
+ Granted, you've already got it open. To do this cleanly:
+
+ tar xzvf rt.tar.gz -C /tmp
+
+2 Run the "configure" script.
+
+ ./configure --help to see the list of options
+ ./configure (with the flags you want)
+
+3 Satisfy RT's myriad dependencies.
+
+3.1 Check for compliance:
+
+ perl sbin/rt-test-dependencies \
+ --with-<databasename> --with-<web-environment>
+
+ databasename is one of: mysql, postgres, oracle
+ web-environment is one of: fastcgi, modperl1, modperl2
+
+3.2 If there are unsatisfied dependencies, install them by hand or run:
+
+ perl sbin/rt-test-dependencies \
+ --with-<databasename> --with-<web-environment> --install
+
+
+3.3 Check to make sure everything was installed properly:
+
+ perl sbin/rt-test-dependencies \
+ --with-<databasename> --with-<web-environment>
+
+4 Create a group called 'rt'
+
+5a FOR A NEW INSTALLATION:
+
+ As root, type:
+ make install (replace "make" with the local name for
+ Make, if you need to)
+
+
+ make initialize-database
+
+
+ If the make fails, type:
+ make dropdb
+ and start over from step 5a
+
+5b FOR UPGRADING: (Within the RT 3.0.x series)
+
+
+ Read through the UPGRADING document included in this distribution.
+ It may contain important instructions for updating your database
+
+ As root, type:
+ make upgrade (replace "make" with the local name for
+ Make, if you need to)
+
+ This will build new binaries, config files and libraries without
+ overwriting your RT database.
+
+ It may then instruct you to update your RT system database objects
+
+5c FOR UPGRADING: (From RT 2.0.x)
+
+ Download the RT2 to RT3 migration tools from:
+
+ http://bestpractical.com/pub/rt/devel/rt2-to-rt3.tar.gz
+
+ Follow the included instructions.
+
+6 Edit etc/RT_SiteConfig.pm in your RT installation directory, by specifying
+ any values you need to change from the defaults in etc/RT_Config.pm
+
+7 Configure the email and web gateways, as described below.
+
+8 Stop and start your webserver, so it picks up your configuration changes.
+
+ NOTE: root's password for the web interface is "password"
+ (without the quotes.) Not changing this is a SECURITY risk
+
+9 Configure RT per the instructions in RT's manual.
+
+ Until you do this, RT will not be able to send or receive email,
+ nor will it be more than marginally functional. This is not an
+ optional step.
+
+
+THE WEB INTERFACE
+-----------------
+
+RT's web interface is based around HTML::Mason, which works best with the mod_perl
+perl interpreter within Apache httpd. Alternatively, support for the FastCGI
+(and plain CGI) interface is also provided as 'bin/mason_handler.fcgi'.
+
+Apache
+ You'll need to add a few lines to your httpd.conf telling it about RT:
+
+<VirtualHost your.ip.address>
+ ServerName your.rt.server.hostname
+ DocumentRoot /opt/rt3/share/html
+ AddDefaultCharset UTF-8
+
+ # these four lines apply to Apache2+mod_perl2 only: {{{
+ PerlSetVar MasonArgsMethod CGI
+ PerlModule Apache2 Apache::compat
+ RewriteEngine On
+ RewriteRule ^(.*)/$ $1/index.html
+ # }}}
+
+ PerlModule Apache::DBI
+ PerlRequire /opt/rt3/bin/webmux.pl
+
+ <Location />
+ SetHandler perl-script
+ PerlHandler RT::Mason
+ </Location>
+</VirtualHost>
+
+
+
+SETTING UP THE MAIL GATEWAY
+---------------------------
+
+An alias for the initial queue will need to be made in either your
+global mail aliases file (if you are using NIS) or locally on your
+machine.
+
+Add the following lines to /etc/aliases (or your local equivalent) :
+
+rt: "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://localhost/"
+rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://localhost/"
+ | | |
+ <queue-name>----/ | |
+ | |
+ <correspond or comment depending on whether | |
+ the mail should be resent to the requestor>---/ |
+ |
+ <URL for RT's web interface>---/
+
+
+BUGS
+----
+
+To report a bug, send email to rt-3.0-bugs@fsck.com.
+
+GETTING HELP
+------------
+
+If RT is mission-critical for you or if you use it heavily, we recommend that
+you purchase a commercial support contract. Details on support contracts
+are available at http://www.bestpractical.com.
+
+If you're interested in having RT extended or customized or would like more
+information about commercial support options, please send email to
+<sales@bestpractical.com> to discuss rates and availability.
+
+
+RT-USERS MAILINGLIST
+--------------------
+
+To keep up to date on the latest RT tips, techniques and extensions,
+you probably want to join the rt-users mailing list. Send a message to:
+
+ rt-users-request@lists.fsck.com
+
+With the body of the message consisting of only the word:
+
+ subscribe
+
+If you're interested in hacking on RT, you'll want to subscribe to
+rt-devel@lists.fsck.com. Subscribe to it with instructions similar to
+those above.
+
+Address questions about the stable release to the rt-users list, and
+questions about the development version to the rt-devel list. If you feel
+your questions are best not asked publicly, send them personally to
+<jesse@bestpractical.com>.
+
+
+RT WEBSITE
+----------
+
+For current information about RT, check out the RT website at
+ http://www.bestpractical.com/
+
+You'll find screenshots, a pointer to the current version of RT, contributed
+patches, and lots of other great stuff.
+
+
+TROUBLESHOOTING
+---------------
+
+If the solution to the problem you're running into isn't obvious and you've
+checked the FAQ, feel free to send mail to rt-users@fsck.com (for released
+versions of RT) or rt-devel@fsck.com (for development versions).
+
+Thanks!
diff --git a/rt/README.Oracle b/rt/README.Oracle
new file mode 100644
index 0000000..41bec82
--- /dev/null
+++ b/rt/README.Oracle
@@ -0,0 +1,37 @@
+In order to install RT with Oracle, the database must first be
+prepared. Ports of RT to other databases will automatically create
+the RT schema. This is not done for Oracle because most sites wishing
+to deploy RT on Oracle will have choose to make specific configuration
+of the RT user, for example to select the appropriate tablespace or to
+set up a resource profile. The RT user must have appropriate
+privileges similar to the resource and connect roles and must have the
+"query rewrite" system privilege.
+ Here is an example of commands to create an RT user called "RT" with
+a password of "rt".
+
+ create user rt identified by rt default tablespace users temporary
+ tablespace temp;
+ grant resource, connect, query rewrite to rt;
+
+
+RT should not run its schema creation as the Oracle DBA; instead the
+schema creation should be run as the RT user. To accomplish this set
+the --with-rt-dba configuration parameter to the RT user, not to the
+Oracle DBA. As an example, the following might be appropriate to
+configure RT for the example.com Oracle database.
+
+ ./configure --prefix /usr/local/rt --with-db-type=Oracle \
+ --with-db-dba=rt --with-db-database=example.com \
+ --with-db-rt-user=rt \
+ --with-db-rt-pass=rt
+
+
+As with all databases it is important to analyze the Schema and get
+current statistics after any significant dataset change. Oracle's
+cost-based optimizer can provide particularly bad performance when the
+schema statistics are significantly inaccurate. To analyze the schema
+of a user called rt, execute the following from withing Sqlplus.
+
+ execute dbms_utility.analyze_schema( 'RT', 'estimate');
+
+
diff --git a/rt/UPGRADING b/rt/UPGRADING
new file mode 100644
index 0000000..4306eb6
--- /dev/null
+++ b/rt/UPGRADING
@@ -0,0 +1,64 @@
+UPGRADING
+
+
+*******
+WARNING
+*******
+
+
+Before making any changes to your database, always ensure that you have a
+complete current backup. If you don't have a current backup, you could
+accidentally damage your database and lose data or worse.
+
+
+
+Look for the
+
+
+----------------------------------------------------------------------
+
+3.0.7
+=====
+
+All Databases
+-------------
+
+If you are upgrading from versions between 3.0.0 and 3.0.7, inclusive,
+you might find improved performance by adding the following index to
+your database:
+
+CREATE INDEX Links4 ON Links(Type,LocalBase);
+
+
+
+3.0.6
+=====
+
+
+All Databases
+-------------
+
+If you are upgrading from versions between 3.0.0 and 3.0.6, inclusive,
+you might find improved performance by adding the following indices to
+your database:
+
+ CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content);
+ CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket);
+
+ CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+
+
+
+Postgres
+--------
+
+If you have a Postgres database, the following changes to your
+database can improve performance:
+
+ ALTER TABLE groups rename instance to instance1;
+ ALTER TABLE groups add instance int;
+ UPDATE groups SET instance = instance1::text::int where btrim(instance1) <> '';
+ ALTER TABLE groups drop column instance1;
+
+
+
diff --git a/rt/aclocal.m4 b/rt/aclocal.m4
new file mode 100644
index 0000000..475b389
--- /dev/null
+++ b/rt/aclocal.m4
@@ -0,0 +1,158 @@
+dnl aclocal.m4 generated automatically by aclocal 1.4-p4
+
+dnl Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+dnl PARTICULAR PURPOSE.
+
+dnl
+dnl @synopsis RT_ENABLE_LAYOUT()
+dnl
+dnl Enable a specific directory layout for the installation to use.
+dnl This configures a command-line parameter that can be specified
+dnl at ./configure invocation.
+dnl
+dnl The use of this feature in this way is a little hackish, but
+dnl better than a heap of options for every directory.
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 code.
+dnl
+
+AC_DEFUN([RT_ENABLE_LAYOUT],[
+AC_ARG_ENABLE(layout,
+ AC_HELP_STRING([--enable-layout=LAYOUT],
+ [Use a specific directory layout (Default: RT3)]),
+ LAYOUT=$enableval)
+
+if test "x$LAYOUT" = "x"; then
+ LAYOUT="RT3"
+fi
+RT_LAYOUT($srcdir/config.layout, $LAYOUT)
+AC_MSG_CHECKING(for chosen layout)
+if test "x$rt_layout_name" = "xno"; then
+ if test "x$LAYOUT" = "xno"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($LAYOUT)
+ fi
+ AC_MSG_ERROR([a valid layout must be specified (or the default used)])
+else
+ AC_SUBST(rt_layout_name)
+ AC_MSG_RESULT($rt_layout_name)
+fi
+])
+
+dnl
+dnl @synopsis RT_LAYOUT(configlayout, layoutname)
+dnl
+dnl This macro reads an Apache-style layout file (specified as the
+dnl configlayout parameter), and searches for a specific layout
+dnl (named using the layoutname parameter).
+dnl
+dnl The entries for a given layout are then inserted into the
+dnl environment such that they become available as substitution
+dnl variables. In addition, the rt_layout_name variable is set
+dnl (but not exported) if the layout is valid.
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 codebase.
+dnl
+
+AC_DEFUN([RT_LAYOUT],[
+ if test ! -f $srcdir/config.layout; then
+ AC_MSG_WARN([Layout file $srcdir/config.layout not found])
+ rt_layout_name=no
+ else
+ pldconf=./config.pld
+ $PERL -0777 -p -e "\$layout = '$2';" -e '
+ s/.*<Layout\s+$layout>//gims;
+ s/\<\/Layout\>.*//s;
+ s/^#.*$//m;
+ s/^\s+//gim;
+ s/\s+$/\n/gim;
+ s/\+$/\/rt3/gim;
+ # m4 will not let us just use $1, we need @S|@1
+ s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim;
+ s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\@S|@@S|@1" = "xNONE" || test "x\@S|@@S|@1" = "x") && @S|@1=@S|@2/gim;
+ ' < $1 > $pldconf
+
+ if test -s $pldconf; then
+ rt_layout_name=$2
+ . $pldconf
+ changequote({,})
+ for var in prefix exec_prefix bindir sbindir \
+ sysconfdir mandir libdir datadir htmldir \
+ localstatedir logfiledir masonstatedir \
+ sessionstatedir customdir custometcdir customhtmldir \
+ customlexdir customlibdir manualdir; do
+ eval "val=\"\$$var\""
+ val=`echo $val | sed -e 's:\(.\)/*$:\1:'`
+ val=`echo $val |
+ sed -e 's:[\$]\([a-z_]*\):${\1}:g'`
+ eval "$var='$val'"
+ done
+ changequote([,])
+ else
+ rt_layout_name=no
+ fi
+ #rm $pldconf
+ fi
+ RT_SUBST_EXPANDED_ARG(prefix)
+ RT_SUBST_EXPANDED_ARG(exec_prefix)
+ RT_SUBST_EXPANDED_ARG(bindir)
+ RT_SUBST_EXPANDED_ARG(sbindir)
+ RT_SUBST_EXPANDED_ARG(sysconfdir)
+ RT_SUBST_EXPANDED_ARG(mandir)
+ RT_SUBST_EXPANDED_ARG(libdir)
+ RT_SUBST_EXPANDED_ARG(datadir)
+ RT_SUBST_EXPANDED_ARG(htmldir)
+ RT_SUBST_EXPANDED_ARG(manualdir)
+ RT_SUBST_EXPANDED_ARG(localstatedir)
+ RT_SUBST_EXPANDED_ARG(logfiledir)
+ RT_SUBST_EXPANDED_ARG(masonstatedir)
+ RT_SUBST_EXPANDED_ARG(sessionstatedir)
+ RT_SUBST_EXPANDED_ARG(customdir)
+ RT_SUBST_EXPANDED_ARG(custometcdir)
+ RT_SUBST_EXPANDED_ARG(customhtmldir)
+ RT_SUBST_EXPANDED_ARG(customlexdir)
+ RT_SUBST_EXPANDED_ARG(customlibdir)
+])dnl
+
+dnl
+dnl @synopsis RT_SUBST_EXPANDED_ARG(var)
+dnl
+dnl Export (via AC_SUBST) a given variable, along with an expanded
+dnl version of the variable (same name, but with exp_ prefix).
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 source.
+dnl
+
+AC_DEFUN([RT_SUBST_EXPANDED_ARG],[
+ RT_EXPAND_VAR(exp_$1, [$]$1)
+ AC_SUBST($1)
+ AC_SUBST(exp_$1)
+])
+
+dnl
+dnl @synopsis RT_EXPAND_VAR(baz, $fraz)
+dnl
+dnl Iteratively expands the second parameter, until successive iterations
+dnl yield no change. The result is then assigned to the first parameter.
+dnl
+dnl This code is heavily borrowed from the Apache 2 codebase.
+dnl
+
+AC_DEFUN([RT_EXPAND_VAR],[
+ ap_last=''
+ ap_cur='$2'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ $1="${ap_cur}"
+])
+
diff --git a/rt/autom4te.cache/output.0 b/rt/autom4te.cache/output.0
new file mode 100644
index 0000000..3d27db9
--- /dev/null
+++ b/rt/autom4te.cache/output.0
@@ -0,0 +1,2771 @@
+@%:@! /bin/sh
+@%:@ From configure.ac Revision: 1.1 .
+@%:@ Guess values for system-dependent variables and create Makefiles.
+@%:@ Generated by GNU Autoconf 2.53 for RT 3.0.9.
+@%:@
+@%:@ Report bugs to <rt-3.0-bugs@fsck.com>.
+@%:@
+@%:@ Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002
+@%:@ Free Software Foundation, Inc.
+@%:@ This configure script is free software; the Free Software Foundation
+@%:@ gives unlimited permission to copy, distribute and modify it.
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+
+# NLS nuisances.
+# Support unset when possible.
+if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
+ { $as_unset LANG || test "${LANG+set}" != set; } ||
+ { LANG=C; export LANG; }
+(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
+ { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
+ { LC_ALL=C; export LC_ALL; }
+(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
+ { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
+ { LC_TIME=C; export LC_TIME; }
+(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
+ { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
+ { LC_CTYPE=C; export LC_CTYPE; }
+(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
+ { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
+ { LANGUAGE=C; export LANGUAGE; }
+(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
+ { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
+ { LC_COLLATE=C; export LC_COLLATE; }
+(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
+ { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
+ { LC_NUMERIC=C; export LC_NUMERIC; }
+(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
+ { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
+ { LC_MESSAGES=C; export LC_MESSAGES; }
+
+
+# Name of the executable.
+as_me=`(basename "$0") 2>/dev/null ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conftest.sh
+ echo "exit 0" >>conftest.sh
+ chmod +x conftest.sh
+ if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conftest.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+exec 6>&1
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Maximum number of lines to put in a shell here document.
+# This variable seems obsolete. It should probably be removed, and
+# only ac_max_sed_lines should be used.
+: ${ac_max_here_lines=38}
+
+# Identity of this package.
+PACKAGE_NAME='RT'
+PACKAGE_TARNAME='rt'
+PACKAGE_VERSION='3.0.9'
+PACKAGE_STRING='RT 3.0.9'
+PACKAGE_BUGREPORT='rt-3.0-bugs@fsck.com'
+
+ac_unique_file="lib/RT.pm.in"
+ac_default_prefix=/opt/rt3
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+ac_prev=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_option in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ eval "enable_$ac_feature=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_$ac_feature='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_$ac_package='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package | sed 's/-/_/g'`
+ eval "with_$ac_package=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+ { (exit 1); exit 1; }; }
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+ { (exit 1); exit 1; }; }
+ ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
+ eval "$ac_envvar='$ac_optarg'"
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ { echo "$as_me: error: missing argument to $ac_option" >&2
+ { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute paths.
+for ac_var in exec_prefix prefix
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* | NONE | '' ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# Be sure to have absolute paths.
+for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \
+ localstatedir libdir includedir oldincludedir infodir mandir
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+ If a cross compiler is detected then cross compile mode will be used." >&2
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_confdir=`(dirname "$0") 2>/dev/null ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$0" : 'X\(//\)[^/]' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$0" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2
+ { (exit 1); exit 1; }; }
+ else
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+ { (exit 1); exit 1; }; }
+ fi
+fi
+srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'`
+ac_env_build_alias_set=${build_alias+set}
+ac_env_build_alias_value=$build_alias
+ac_cv_env_build_alias_set=${build_alias+set}
+ac_cv_env_build_alias_value=$build_alias
+ac_env_host_alias_set=${host_alias+set}
+ac_env_host_alias_value=$host_alias
+ac_cv_env_host_alias_set=${host_alias+set}
+ac_cv_env_host_alias_value=$host_alias
+ac_env_target_alias_set=${target_alias+set}
+ac_env_target_alias_value=$target_alias
+ac_cv_env_target_alias_set=${target_alias+set}
+ac_cv_env_target_alias_value=$target_alias
+ac_env_PERL_set=${PERL+set}
+ac_env_PERL_value=$PERL
+ac_cv_env_PERL_set=${PERL+set}
+ac_cv_env_PERL_value=$PERL
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures RT 3.0.9 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+_ACEOF
+
+ cat <<_ACEOF
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --infodir=DIR info documentation [PREFIX/info]
+ --mandir=DIR man documentation [PREFIX/man]
+_ACEOF
+
+ cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of RT 3.0.9:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-layout=LAYOUT Use a specific directory layout (Default: RT3)
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-speedycgi=/path/to/speedy
+ path to your speedycgi binary, if it exists
+ --with-rt-group=GROUP group to own all files (default: rt)
+ --with-bin-owner=OWNER user that will own rt binaries (default root)
+ --with-libs-owner=OWNER user that will own RT libraries (default root)
+ --with-libs-group=GROUP group that will own rt binaries (default bin)
+ --with-db-type=TYPE sort of database RT will use (default: mysql)
+ (mysql, Pg, Oracle and Informix are valid)
+ --with-db-host=HOSTNAME FQDN of database server (default: localhost)
+ --with-db-port=PORT port on which the database listens on
+ --with-db-rt-host=HOSTNAME
+ FQDN of RT server which talks to the database server
+ (default: localhost)
+ --with-db-dba=DBA name of database administrator (default: root)
+ --with-db-database=DBNAME
+ name of the database to use (default: rt3)
+ --with-db-rt-user=DBUSER
+ name of database user (default: rt_user)
+ --with-db-rt-pass=PASSWORD
+ password for database user (default: rt_pass)
+ --with-web-user=USER user the web server runs as (default: www)
+ --with-web-group=GROUP group the web server runs as (default: www)
+ --with-my-user-group set all users and groups to current user/group
+
+Some influential environment variables:
+ PERL Perl interpreter command
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <rt-3.0-bugs@fsck.com>.
+_ACEOF
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ ac_popdir=`pwd`
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d $ac_dir || continue
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
+# absolute.
+ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
+ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
+ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
+ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+ cd $ac_dir
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f $ac_srcdir/configure.gnu; then
+ echo
+ $SHELL $ac_srcdir/configure.gnu --help=recursive
+ elif test -f $ac_srcdir/configure; then
+ echo
+ $SHELL $ac_srcdir/configure --help=recursive
+ elif test -f $ac_srcdir/configure.ac ||
+ test -f $ac_srcdir/configure.in; then
+ echo
+ $ac_configure --help
+ else
+ echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi
+ cd $ac_popdir
+ done
+fi
+
+test -n "$ac_init_help" && exit 0
+if $ac_init_version; then
+ cat <<\_ACEOF
+RT configure 3.0.9
+generated by GNU Autoconf 2.53
+
+Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002
+Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit 0
+fi
+exec 5>config.log
+cat >&5 <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by RT $as_me 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+{
+cat <<_ASUNAME
+@%:@@%:@ --------- @%:@@%:@
+@%:@@%:@ Platform. @%:@@%:@
+@%:@@%:@ --------- @%:@@%:@
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+hostinfo = `(hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ echo "PATH: $as_dir"
+done
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+@%:@@%:@ ----------- @%:@@%:@
+@%:@@%:@ Core tests. @%:@@%:@
+@%:@@%:@ ----------- @%:@@%:@
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell meta-characters.
+ac_configure_args=
+ac_sep=
+for ac_arg
+do
+ case $ac_arg in
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n ) continue ;;
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ continue ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'"
+ ac_sep=" " ;;
+ esac
+ # Get rid of the leading space.
+done
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Be sure not to use single quotes in there, as some shells,
+# such as our DU 5.0 friend, will then `close' the trap.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+ cat <<\_ASBOX
+@%:@@%:@ ---------------- @%:@@%:@
+@%:@@%:@ Cache variables. @%:@@%:@
+@%:@@%:@ ---------------- @%:@@%:@
+_ASBOX
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+{
+ (set) 2>&1 |
+ case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ sed -n \
+ "s/'"'"'/'"'"'\\\\'"'"''"'"'/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p"
+ ;;
+ *)
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+}
+ echo
+ if test -s confdefs.h; then
+ cat <<\_ASBOX
+@%:@@%:@ ----------- @%:@@%:@
+@%:@@%:@ confdefs.h. @%:@@%:@
+@%:@@%:@ ----------- @%:@@%:@
+_ASBOX
+ echo
+ sed "/^$/d" confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ echo "$as_me: caught signal $ac_signal"
+ echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core core.* *.core &&
+ rm -rf conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+ ' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo >confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+@%:@define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+@%:@define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+@%:@define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+@%:@define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+@%:@define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special
+ # files actually), so we avoid doing that.
+ if test -f "$cache_file"; then
+ { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . $cache_file;;
+ *) . ./$cache_file;;
+ esac
+ fi
+else
+ { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in `(set) 2>&1 |
+ sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val="\$ac_cv_env_${ac_var}_value"
+ eval ac_new_val="\$ac_env_${ac_var}_value"
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ { echo "$as_me:$LINENO: former value: $ac_old_val" >&5
+echo "$as_me: former value: $ac_old_val" >&2;}
+ { echo "$as_me:$LINENO: current value: $ac_new_val" >&5
+echo "$as_me: current value: $ac_new_val" >&2;}
+ ac_cache_corrupted=:
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+rt_version_major=3
+
+rt_version_minor=0
+
+rt_version_patch=9
+
+test "x$rt_version_major" = 'x' && rt_version_major=0
+test "x$rt_version_minor" = 'x' && rt_version_minor=0
+test "x$rt_version_patch" = 'x' && rt_version_patch=0
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f $ac_dir/shtool; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&5
+echo "$as_me: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"
+ac_configure="$SHELL $ac_aux_dir/configure" # This should be Cygnus configure.
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5
+echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6
+if test -z "$INSTALL"; then
+if test "${ac_cv_path_install+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in
+ ./ | .// | /cC/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+done
+
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL=$ac_install_sh
+ fi
+fi
+echo "$as_me:$LINENO: result: $INSTALL" >&5
+echo "${ECHO_T}$INSTALL" >&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_path_PERL+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ case $PERL in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PERL="$PERL" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+ test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="not found"
+ ;;
+esac
+fi
+PERL=$ac_cv_path_PERL
+
+if test -n "$PERL"; then
+ echo "$as_me:$LINENO: result: $PERL" >&5
+echo "${ECHO_T}$PERL" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+if test "$PERL" = 'not found'; then
+ { { echo "$as_me:$LINENO: error: cannot use $PACKAGE_NAME without perl" >&5
+echo "$as_me: error: cannot use $PACKAGE_NAME without perl" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+# Check whether --with-speedycgi or --without-speedycgi was given.
+if test "${with_speedycgi+set}" = set; then
+ withval="$with_speedycgi"
+ SPEEDY_BIN=$withval
+else
+ SPEEDY_BIN=/usr/local/bin/speedy
+fi;
+
+
+
+
+
+# Check whether --enable-layout or --disable-layout was given.
+if test "${enable_layout+set}" = set; then
+ enableval="$enable_layout"
+ LAYOUT=$enableval
+fi;
+
+if test "x$LAYOUT" = "x"; then
+ LAYOUT="RT3"
+fi
+
+ if test ! -f $srcdir/config.layout; then
+ { echo "$as_me:$LINENO: WARNING: Layout file $srcdir/config.layout not found" >&5
+echo "$as_me: WARNING: Layout file $srcdir/config.layout not found" >&2;}
+ rt_layout_name=no
+ else
+ pldconf=./config.pld
+ $PERL -0777 -p -e "\$layout = '$LAYOUT';" -e '
+ s/.*<Layout\s+$layout>//gims;
+ s/\<\/Layout\>.*//s;
+ s/^#.*$//m;
+ s/^\s+//gim;
+ s/\s+$/\n/gim;
+ s/\+$/\/rt3/gim;
+ # m4 will not let us just use $srcdir/config.layout, we need @S|@1
+ s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim;
+ s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\@S|@@S|@1" = "xNONE" || test "x\@S|@@S|@1" = "x") && @S|@1=@S|@2/gim;
+ ' < $srcdir/config.layout > $pldconf
+
+ if test -s $pldconf; then
+ rt_layout_name=$LAYOUT
+ . $pldconf
+
+ for var in prefix exec_prefix bindir sbindir \
+ sysconfdir mandir libdir datadir htmldir \
+ localstatedir logfiledir masonstatedir \
+ sessionstatedir customdir custometcdir customhtmldir \
+ customlexdir customlibdir manualdir; do
+ eval "val=\"\$$var\""
+ val=`echo $val | sed -e 's:\(.\)/*$:\1:'`
+ val=`echo $val |
+ sed -e 's:[\$]\([a-z_]*\):$\1:g'`
+ eval "$var='$val'"
+ done
+
+ else
+ rt_layout_name=no
+ fi
+ #rm $pldconf
+ fi
+
+
+ ap_last=''
+ ap_cur='$prefix'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_prefix="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$exec_prefix'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_exec_prefix="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$bindir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_bindir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sbindir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sbindir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sysconfdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sysconfdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$mandir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_mandir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$libdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_libdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$datadir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_datadir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$htmldir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_htmldir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$manualdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_manualdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$localstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_localstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$logfiledir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_logfiledir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$masonstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_masonstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sessionstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sessionstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$custometcdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_custometcdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customhtmldir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customhtmldir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customlexdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customlexdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customlibdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customlibdir="${ap_cur}"
+
+
+
+
+
+echo "$as_me:$LINENO: checking for chosen layout" >&5
+echo $ECHO_N "checking for chosen layout... $ECHO_C" >&6
+if test "x$rt_layout_name" = "xno"; then
+ if test "x$LAYOUT" = "xno"; then
+ echo "$as_me:$LINENO: result: none" >&5
+echo "${ECHO_T}none" >&6
+ else
+ echo "$as_me:$LINENO: result: $LAYOUT" >&5
+echo "${ECHO_T}$LAYOUT" >&6
+ fi
+ { { echo "$as_me:$LINENO: error: a valid layout must be specified (or the default used)" >&5
+echo "$as_me: error: a valid layout must be specified (or the default used)" >&2;}
+ { (exit 1); exit 1; }; }
+else
+
+ echo "$as_me:$LINENO: result: $rt_layout_name" >&5
+echo "${ECHO_T}$rt_layout_name" >&6
+fi
+
+
+
+# Check whether --with-rt-group or --without-rt-group was given.
+if test "${with_rt_group+set}" = set; then
+ withval="$with_rt_group"
+ RTGROUP=$withval
+else
+ RTGROUP=rt
+fi;
+
+
+
+# Check whether --with-bin-owner or --without-bin-owner was given.
+if test "${with_bin_owner+set}" = set; then
+ withval="$with_bin_owner"
+ BIN_OWNER=$withval
+else
+ BIN_OWNER=root
+fi;
+
+
+
+# Check whether --with-libs-owner or --without-libs-owner was given.
+if test "${with_libs_owner+set}" = set; then
+ withval="$with_libs_owner"
+ LIBS_OWNER=$withval
+else
+ LIBS_OWNER=root
+fi;
+
+
+
+# Check whether --with-libs-group or --without-libs-group was given.
+if test "${with_libs_group+set}" = set; then
+ withval="$with_libs_group"
+ LIBS_GROUP=$withval
+else
+ LIBS_GROUP=bin
+fi;
+
+
+
+# Check whether --with-db-type or --without-db-type was given.
+if test "${with_db_type+set}" = set; then
+ withval="$with_db_type"
+ DB_TYPE=$withval
+else
+ DB_TYPE=mysql
+fi;
+if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' -a "$DB_TYPE" != 'Informix' ; then
+ { { echo "$as_me:$LINENO: error: Only Oracle, Informix, Pg and mysql are valid db types" >&5
+echo "$as_me: error: Only Oracle, Informix, Pg and mysql are valid db types" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+
+if test "$DB_TYPE" = 'Oracle'; then
+ test "x$ORACLE_HOME" = 'x' && { { echo "$as_me:$LINENO: error: Please declare the ORACLE_HOME environment variable" >&5
+echo "$as_me: error: Please declare the ORACLE_HOME environment variable" >&2;}
+ { (exit 1); exit 1; }; }
+ ORACLE_ENV_PREF="\$ENV{'ORACLE_HOME'} = '$ORACLE_HOME';"
+fi
+
+
+
+# Check whether --with-db-host or --without-db-host was given.
+if test "${with_db_host+set}" = set; then
+ withval="$with_db_host"
+ DB_HOST=$withval
+else
+ DB_HOST=localhost
+fi;
+
+
+
+# Check whether --with-db-port or --without-db-port was given.
+if test "${with_db_port+set}" = set; then
+ withval="$with_db_port"
+ DB_PORT=$withval
+else
+ DB_PORT=
+fi;
+
+
+
+# Check whether --with-db-rt-host or --without-db-rt-host was given.
+if test "${with_db_rt_host+set}" = set; then
+ withval="$with_db_rt_host"
+ DB_RT_HOST=$withval
+else
+ DB_RT_HOST=localhost
+fi;
+
+
+
+# Check whether --with-db-dba or --without-db-dba was given.
+if test "${with_db_dba+set}" = set; then
+ withval="$with_db_dba"
+ DB_DBA=$withval
+else
+ DB_DBA=root
+fi;
+
+
+
+# Check whether --with-db-database or --without-db-database was given.
+if test "${with_db_database+set}" = set; then
+ withval="$with_db_database"
+ DB_DATABASE=$withval
+else
+ DB_DATABASE=rt3
+fi;
+
+
+
+# Check whether --with-db-rt-user or --without-db-rt-user was given.
+if test "${with_db_rt_user+set}" = set; then
+ withval="$with_db_rt_user"
+ DB_RT_USER=$withval
+else
+ DB_RT_USER=rt_user
+fi;
+
+
+
+# Check whether --with-db-rt-pass or --without-db-rt-pass was given.
+if test "${with_db_rt_pass+set}" = set; then
+ withval="$with_db_rt_pass"
+ DB_RT_PASS=$withval
+else
+ DB_RT_PASS=rt_pass
+fi;
+
+
+
+# Check whether --with-web-user or --without-web-user was given.
+if test "${with_web_user+set}" = set; then
+ withval="$with_web_user"
+ WEB_USER=$withval
+else
+ WEB_USER=www
+fi;
+
+
+
+# Check whether --with-web-group or --without-web-group was given.
+if test "${with_web_group+set}" = set; then
+ withval="$with_web_group"
+ WEB_GROUP=$withval
+else
+ WEB_GROUP=www
+fi;
+
+
+my_group=$(groups|cut -f1 -d' ')
+
+# Check whether --with-my-user-group or --without-my-user-group was given.
+if test "${with_my_user_group+set}" = set; then
+ withval="$with_my_user_group"
+ RTGROUP=$my_group
+ BIN_OWNER=$USER
+ LIBS_OWNER=$USER
+ LIBS_GROUP=$my_group
+ WEB_USER=$USER
+ WEB_GROUP=$my_group
+fi;
+
+
+RT_VERSION_MAJOR=${rt_version_major}
+
+RT_VERSION_MINOR=${rt_version_minor}
+
+RT_VERSION_PATCH=${rt_version_patch}
+
+
+RT_PATH=${exp_prefix}
+
+RT_DOC_PATH=${exp_manualdir}
+
+RT_LOCAL_PATH=${exp_customdir}
+
+RT_LIB_PATH=${exp_libdir}
+
+RT_ETC_PATH=${exp_sysconfdir}
+
+CONFIG_FILE_PATH=${exp_sysconfdir}
+
+RT_BIN_PATH=${exp_bindir}
+
+RT_SBIN_PATH=${exp_sbindir}
+
+RT_VAR_PATH=${exp_localstatedir}
+
+RT_MAN_PATH=${exp_mandir}
+
+MASON_DATA_PATH=${exp_masonstatedir}
+
+MASON_SESSION_PATH=${exp_sessionstatedir}
+
+MASON_HTML_PATH=${exp_htmldir}
+
+LOCAL_ETC_PATH=${exp_custometcdir}
+
+MASON_LOCAL_HTML_PATH=${exp_customhtmldir}
+
+LOCAL_LEXICON_PATH=${exp_customlexdir}
+
+LOCAL_LIB_PATH=${exp_customlibdir}
+
+DESTDIR=${exp_prefix}
+
+RT_LOG_PATH=${exp_logfiledir}
+
+
+
+ac_config_files="$ac_config_files sbin/rt-setup-database sbin/rt-test-dependencies Makefile etc/RT_Config.pm lib/RT.pm lib/t/00smoke.t lib/t/01harness.t lib/t/02regression.t lib/t/03web.pl lib/t/04_send_email.pl bin/mason_handler.fcgi bin/mason_handler.scgi bin/mason_handler.svc bin/rt-commit-handler bin/rt-crontool bin/rt-mailgate bin/rt bin/webmux.pl"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overriden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+{
+ (set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+} |
+ sed '
+ t clear
+ : clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ : end' >>confcache
+if cmp -s $cache_file confcache; then :; else
+ if test -w $cache_file; then
+ test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file"
+ cat confcache >$cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[ ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[ ]*$//;
+}'
+fi
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then we branch to the quote section. Otherwise,
+# look for a macro that doesn't take arguments.
+cat >confdef2opt.sed <<\_ACEOF
+t clear
+: clear
+s,^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\),-D\1=\2,g
+t quote
+s,^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\),-D\1=\2,g
+t quote
+d
+: quote
+s,[ `~#$^&*(){}\\|;'"<>?],\\&,g
+s,\[,\\&,g
+s,\],\\&,g
+s,\$,$$,g
+p
+_ACEOF
+# We use echo to avoid assuming a particular line-breaking character.
+# The extra dot is to prevent the shell from consuming trailing
+# line-breaks from the sub-command output. A line-break within
+# single-quotes doesn't work because, if this script is created in a
+# platform that uses two characters for line-breaks (e.g., DOS), tr
+# would break.
+ac_LF_and_DOT=`echo; echo .`
+DEFS=`sed -n -f confdef2opt.sed confdefs.h | tr "$ac_LF_and_DOT" ' .'`
+rm -f confdef2opt.sed
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+
+# NLS nuisances.
+# Support unset when possible.
+if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
+ { $as_unset LANG || test "${LANG+set}" != set; } ||
+ { LANG=C; export LANG; }
+(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
+ { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
+ { LC_ALL=C; export LC_ALL; }
+(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
+ { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
+ { LC_TIME=C; export LC_TIME; }
+(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
+ { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
+ { LC_CTYPE=C; export LC_CTYPE; }
+(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
+ { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
+ { LANGUAGE=C; export LANGUAGE; }
+(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
+ { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
+ { LC_COLLATE=C; export LC_COLLATE; }
+(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
+ { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
+ { LC_NUMERIC=C; export LC_NUMERIC; }
+(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
+ { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
+ { LC_MESSAGES=C; export LC_MESSAGES; }
+
+
+# Name of the executable.
+as_me=`(basename "$0") 2>/dev/null ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conftest.sh
+ echo "exit 0" >>conftest.sh
+ chmod +x conftest.sh
+ if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conftest.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling. Logging --version etc. is OK.
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../@%:@@%:@ /;s/...$/ @%:@@%:@/;p;x;p;x' <<_ASBOX
+@%:@@%:@ Running $as_me. @%:@@%:@
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by RT $as_me 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+_ACEOF
+
+# Files that config.status was made for.
+if test -n "$ac_config_files"; then
+ echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_headers"; then
+ echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_links"; then
+ echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_commands"; then
+ echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <bug-autoconf@gnu.org>."
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+RT config.status 3.0.9
+configured by $0, generated by GNU Autoconf 2.53,
+ with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001
+Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=$srcdir
+INSTALL="$INSTALL"
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value. By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=*)
+ ac_option=`expr "x$1" : 'x\([^=]*\)='`
+ ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+ shift
+ set dummy "$ac_option" "$ac_optarg" ${1+"$@"}
+ shift
+ ;;
+ -*);;
+ *) # This is not an option, so the user has probably given explicit
+ # arguments.
+ ac_need_defaults=false;;
+ esac
+
+ case $1 in
+ # Handling of the options.
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running $SHELL $0 " $ac_configure_args " --no-create --no-recursion"
+ exec $SHELL $0 $ac_configure_args --no-create --no-recursion ;;
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+ --version | --vers* | -V )
+ echo "$ac_cs_version"; exit 0 ;;
+ --he | --h)
+ # Conflict between --help and --header
+ { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; };;
+ --help | --hel | -h )
+ echo "$ac_cs_usage"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ shift
+ CONFIG_FILES="$CONFIG_FILES $1"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ shift
+ CONFIG_HEADERS="$CONFIG_HEADERS $1"
+ ac_need_defaults=false;;
+
+ # This is an error.
+ -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; } ;;
+
+ *) ac_config_targets="$ac_config_targets $1" ;;
+
+ esac
+ shift
+done
+
+_ACEOF
+
+
+
+
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_config_target in $ac_config_targets
+do
+ case "$ac_config_target" in
+ # Handling of arguments.
+ "sbin/rt-setup-database" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;;
+ "sbin/rt-test-dependencies" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;;
+ "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "etc/RT_Config.pm" ) CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;;
+ "lib/RT.pm" ) CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;;
+ "lib/t/00smoke.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/00smoke.t" ;;
+ "lib/t/01harness.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/01harness.t" ;;
+ "lib/t/02regression.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/02regression.t" ;;
+ "lib/t/03web.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/03web.pl" ;;
+ "lib/t/04_send_email.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/04_send_email.pl" ;;
+ "bin/mason_handler.fcgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;;
+ "bin/mason_handler.scgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;;
+ "bin/mason_handler.svc" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;;
+ "bin/rt-commit-handler" ) CONFIG_FILES="$CONFIG_FILES bin/rt-commit-handler" ;;
+ "bin/rt-crontool" ) CONFIG_FILES="$CONFIG_FILES bin/rt-crontool" ;;
+ "bin/rt-mailgate" ) CONFIG_FILES="$CONFIG_FILES bin/rt-mailgate" ;;
+ "bin/rt" ) CONFIG_FILES="$CONFIG_FILES bin/rt" ;;
+ "bin/webmux.pl" ) CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;;
+ *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+ trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+ trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+: ${TMPDIR=/tmp}
+{
+ tmp=`(umask 077 && mktemp -d -q "$TMPDIR/csXXXXXX") 2>/dev/null` &&
+ test -n "$tmp" && test -d "$tmp"
+} ||
+{
+ tmp=$TMPDIR/cs$$-$RANDOM
+ (umask 077 && mkdir $tmp)
+} ||
+{
+ echo "$me: cannot create a temporary directory in $TMPDIR" >&2
+ { (exit 1); exit 1; }
+}
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "\$CONFIG_FILES"; then
+ # Protect against being on the right side of a sed subst in config.status.
+ sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g;
+ s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF
+s,@SHELL@,$SHELL,;t t
+s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t
+s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t
+s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t
+s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t
+s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t
+s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t
+s,@exec_prefix@,$exec_prefix,;t t
+s,@prefix@,$prefix,;t t
+s,@program_transform_name@,$program_transform_name,;t t
+s,@bindir@,$bindir,;t t
+s,@sbindir@,$sbindir,;t t
+s,@libexecdir@,$libexecdir,;t t
+s,@datadir@,$datadir,;t t
+s,@sysconfdir@,$sysconfdir,;t t
+s,@sharedstatedir@,$sharedstatedir,;t t
+s,@localstatedir@,$localstatedir,;t t
+s,@libdir@,$libdir,;t t
+s,@includedir@,$includedir,;t t
+s,@oldincludedir@,$oldincludedir,;t t
+s,@infodir@,$infodir,;t t
+s,@mandir@,$mandir,;t t
+s,@build_alias@,$build_alias,;t t
+s,@host_alias@,$host_alias,;t t
+s,@target_alias@,$target_alias,;t t
+s,@DEFS@,$DEFS,;t t
+s,@ECHO_C@,$ECHO_C,;t t
+s,@ECHO_N@,$ECHO_N,;t t
+s,@ECHO_T@,$ECHO_T,;t t
+s,@LIBS@,$LIBS,;t t
+s,@rt_version_major@,$rt_version_major,;t t
+s,@rt_version_minor@,$rt_version_minor,;t t
+s,@rt_version_patch@,$rt_version_patch,;t t
+s,@INSTALL_PROGRAM@,$INSTALL_PROGRAM,;t t
+s,@INSTALL_SCRIPT@,$INSTALL_SCRIPT,;t t
+s,@INSTALL_DATA@,$INSTALL_DATA,;t t
+s,@PERL@,$PERL,;t t
+s,@SPEEDY_BIN@,$SPEEDY_BIN,;t t
+s,@exp_prefix@,$exp_prefix,;t t
+s,@exp_exec_prefix@,$exp_exec_prefix,;t t
+s,@exp_bindir@,$exp_bindir,;t t
+s,@exp_sbindir@,$exp_sbindir,;t t
+s,@exp_sysconfdir@,$exp_sysconfdir,;t t
+s,@exp_mandir@,$exp_mandir,;t t
+s,@exp_libdir@,$exp_libdir,;t t
+s,@exp_datadir@,$exp_datadir,;t t
+s,@htmldir@,$htmldir,;t t
+s,@exp_htmldir@,$exp_htmldir,;t t
+s,@manualdir@,$manualdir,;t t
+s,@exp_manualdir@,$exp_manualdir,;t t
+s,@exp_localstatedir@,$exp_localstatedir,;t t
+s,@logfiledir@,$logfiledir,;t t
+s,@exp_logfiledir@,$exp_logfiledir,;t t
+s,@masonstatedir@,$masonstatedir,;t t
+s,@exp_masonstatedir@,$exp_masonstatedir,;t t
+s,@sessionstatedir@,$sessionstatedir,;t t
+s,@exp_sessionstatedir@,$exp_sessionstatedir,;t t
+s,@customdir@,$customdir,;t t
+s,@exp_customdir@,$exp_customdir,;t t
+s,@custometcdir@,$custometcdir,;t t
+s,@exp_custometcdir@,$exp_custometcdir,;t t
+s,@customhtmldir@,$customhtmldir,;t t
+s,@exp_customhtmldir@,$exp_customhtmldir,;t t
+s,@customlexdir@,$customlexdir,;t t
+s,@exp_customlexdir@,$exp_customlexdir,;t t
+s,@customlibdir@,$customlibdir,;t t
+s,@exp_customlibdir@,$exp_customlibdir,;t t
+s,@rt_layout_name@,$rt_layout_name,;t t
+s,@RTGROUP@,$RTGROUP,;t t
+s,@BIN_OWNER@,$BIN_OWNER,;t t
+s,@LIBS_OWNER@,$LIBS_OWNER,;t t
+s,@LIBS_GROUP@,$LIBS_GROUP,;t t
+s,@DB_TYPE@,$DB_TYPE,;t t
+s,@ORACLE_ENV_PREF@,$ORACLE_ENV_PREF,;t t
+s,@DB_HOST@,$DB_HOST,;t t
+s,@DB_PORT@,$DB_PORT,;t t
+s,@DB_RT_HOST@,$DB_RT_HOST,;t t
+s,@DB_DBA@,$DB_DBA,;t t
+s,@DB_DATABASE@,$DB_DATABASE,;t t
+s,@DB_RT_USER@,$DB_RT_USER,;t t
+s,@DB_RT_PASS@,$DB_RT_PASS,;t t
+s,@WEB_USER@,$WEB_USER,;t t
+s,@WEB_GROUP@,$WEB_GROUP,;t t
+s,@RT_VERSION_MAJOR@,$RT_VERSION_MAJOR,;t t
+s,@RT_VERSION_MINOR@,$RT_VERSION_MINOR,;t t
+s,@RT_VERSION_PATCH@,$RT_VERSION_PATCH,;t t
+s,@RT_PATH@,$RT_PATH,;t t
+s,@RT_DOC_PATH@,$RT_DOC_PATH,;t t
+s,@RT_LOCAL_PATH@,$RT_LOCAL_PATH,;t t
+s,@RT_LIB_PATH@,$RT_LIB_PATH,;t t
+s,@RT_ETC_PATH@,$RT_ETC_PATH,;t t
+s,@CONFIG_FILE_PATH@,$CONFIG_FILE_PATH,;t t
+s,@RT_BIN_PATH@,$RT_BIN_PATH,;t t
+s,@RT_SBIN_PATH@,$RT_SBIN_PATH,;t t
+s,@RT_VAR_PATH@,$RT_VAR_PATH,;t t
+s,@RT_MAN_PATH@,$RT_MAN_PATH,;t t
+s,@MASON_DATA_PATH@,$MASON_DATA_PATH,;t t
+s,@MASON_SESSION_PATH@,$MASON_SESSION_PATH,;t t
+s,@MASON_HTML_PATH@,$MASON_HTML_PATH,;t t
+s,@LOCAL_ETC_PATH@,$LOCAL_ETC_PATH,;t t
+s,@MASON_LOCAL_HTML_PATH@,$MASON_LOCAL_HTML_PATH,;t t
+s,@LOCAL_LEXICON_PATH@,$LOCAL_LEXICON_PATH,;t t
+s,@LOCAL_LIB_PATH@,$LOCAL_LIB_PATH,;t t
+s,@DESTDIR@,$DESTDIR,;t t
+s,@RT_LOG_PATH@,$RT_LOG_PATH,;t t
+CEOF
+
+_ACEOF
+
+ cat >>$CONFIG_STATUS <<\_ACEOF
+ # Split the substitutions into bite-sized pieces for seds with
+ # small command number limits, like on Digital OSF/1 and HP-UX.
+ ac_max_sed_lines=48
+ ac_sed_frag=1 # Number of current file.
+ ac_beg=1 # First line for current file.
+ ac_end=$ac_max_sed_lines # Line after last line for current file.
+ ac_more_lines=:
+ ac_sed_cmds=
+ while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ else
+ sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ fi
+ if test ! -s $tmp/subs.frag; then
+ ac_more_lines=false
+ else
+ # The purpose of the label and of the branching condition is to
+ # speed up the sed processing (if there are no `@' at all, there
+ # is no need to browse any of the substitutions).
+ # These are the two extra sed commands mentioned above.
+ (echo ':t
+ /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+ fi
+ ac_sed_frag=`expr $ac_sed_frag + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_lines`
+ fi
+ done
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+ fi
+fi # test -n "$CONFIG_FILES"
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { case "$ac_dir" in
+ [\\/]* | ?:[\\/]* ) as_incr_dir=;;
+ *) as_incr_dir=.;;
+esac
+as_dummy="$ac_dir"
+for as_mkdir_dir in `IFS='/\\'; set X $as_dummy; shift; echo "$@"`; do
+ case $as_mkdir_dir in
+ # Skip DOS drivespec
+ ?:) as_incr_dir=$as_mkdir_dir ;;
+ *)
+ as_incr_dir=$as_incr_dir/$as_mkdir_dir
+ test -d "$as_incr_dir" ||
+ mkdir "$as_incr_dir" ||
+ { { echo "$as_me:$LINENO: error: cannot create \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }
+ ;;
+ esac
+done; }
+
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
+# absolute.
+ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
+ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
+ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
+ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_builddir$INSTALL ;;
+ esac
+
+ if test x"$ac_file" != x-; then
+ { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+ rm -f "$ac_file"
+ fi
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ configure_input=
+ else
+ configure_input="$ac_file. "
+ fi
+ configure_input=$configure_input"Generated from `echo $ac_file_in |
+ sed 's,.*/,,'` by configure."
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ echo $f;;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo $f
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo $srcdir/$f
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+s,@INSTALL@,$ac_INSTALL,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+ rm -f $tmp/stdin
+ if test x"$ac_file" != x-; then
+ mv $tmp/out $ac_file
+ else
+ cat $tmp/out
+ rm -f $tmp/out
+ fi
+
+done
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || { (exit 1); exit 1; }
+fi
+
diff --git a/rt/autom4te.cache/requests b/rt/autom4te.cache/requests
new file mode 100644
index 0000000..fad7b54
--- /dev/null
+++ b/rt/autom4te.cache/requests
@@ -0,0 +1,94 @@
+# This file was created by autom4te.
+# It contains the lists of macros which have been traced.
+# It can be safely removed.
+
+@request = (
+ bless( [
+ '0',
+ 1,
+ [
+ '/usr/share/autoconf'
+ ],
+ [
+ '--reload-state=/usr/share/autoconf/autoconf/autoconf.m4f',
+ 'aclocal.m4',
+ 'configure.ac'
+ ],
+ {
+ 'AC_HEADER_STAT' => 1,
+ 'AC_FUNC_STRFTIME' => 1,
+ 'AC_PROG_RANLIB' => 1,
+ 'AC_FUNC_WAIT3' => 1,
+ 'AC_FUNC_SETPGRP' => 1,
+ 'AC_HEADER_TIME' => 1,
+ 'AC_FUNC_SETVBUF_REVERSED' => 1,
+ 'AC_HEADER_SYS_WAIT' => 1,
+ 'AC_TYPE_UID_T' => 1,
+ 'AM_CONDITIONAL' => 1,
+ 'AC_CHECK_LIB' => 1,
+ 'AC_PROG_LN_S' => 1,
+ 'AC_FUNC_MEMCMP' => 1,
+ 'AC_FUNC_FORK' => 1,
+ 'AC_FUNC_GETGROUPS' => 1,
+ 'AC_HEADER_MAJOR' => 1,
+ 'AC_FUNC_STRTOD' => 1,
+ 'AC_HEADER_DIRENT' => 1,
+ 'AC_FUNC_UTIME_NULL' => 1,
+ 'AC_CONFIG_FILES' => 1,
+ 'AC_FUNC_ALLOCA' => 1,
+ 'AC_C_CONST' => 1,
+ 'include' => 1,
+ 'AC_FUNC_OBSTACK' => 1,
+ 'AC_FUNC_LSTAT' => 1,
+ 'AC_STRUCT_TIMEZONE' => 1,
+ 'AC_FUNC_GETPGRP' => 1,
+ 'AC_DEFINE_TRACE_LITERAL' => 1,
+ 'AC_CHECK_HEADERS' => 1,
+ 'AC_TYPE_MODE_T' => 1,
+ 'AC_CHECK_TYPES' => 1,
+ 'AC_PROG_YACC' => 1,
+ 'AC_TYPE_PID_T' => 1,
+ 'AC_FUNC_STRERROR_R' => 1,
+ 'AC_STRUCT_ST_BLOCKS' => 1,
+ 'AC_PROG_GCC_TRADITIONAL' => 1,
+ 'AC_TYPE_SIGNAL' => 1,
+ 'AC_FUNC_FNMATCH' => 1,
+ 'AC_PROG_CPP' => 1,
+ 'AM_PROG_LIBTOOL' => 1,
+ 'AC_FUNC_STAT' => 1,
+ 'AC_PROG_INSTALL' => 1,
+ 'AM_GNU_GETTEXT' => 1,
+ 'AC_FUNC_STRCOLL' => 1,
+ 'AC_LIBSOURCE' => 1,
+ 'AC_C_INLINE' => 1,
+ 'AC_FUNC_CHOWN' => 1,
+ 'AC_PROG_LEX' => 1,
+ 'AH_OUTPUT' => 1,
+ 'AC_HEADER_STDC' => 1,
+ 'AC_FUNC_GETLOADAVG' => 1,
+ 'AC_CHECK_FUNCS' => 1,
+ 'AC_TYPE_SIZE_T' => 1,
+ 'AC_DECL_SYS_SIGLIST' => 1,
+ 'AC_FUNC_MKTIME' => 1,
+ 'AC_PROG_MAKE_SET' => 1,
+ 'AC_PROG_CXX' => 1,
+ 'm4_pattern_allow' => 1,
+ 'm4_include' => 1,
+ 'm4_pattern_forbid' => 1,
+ 'AC_PROG_AWK' => 1,
+ 'AC_FUNC_VPRINTF' => 1,
+ 'AC_CONFIG_HEADERS' => 1,
+ 'AC_PATH_X' => 1,
+ 'AC_TYPE_OFF_T' => 1,
+ 'AC_FUNC_MALLOC' => 1,
+ 'AC_FUNC_ERROR_AT_LINE' => 1,
+ 'AC_FUNC_FSEEKO' => 1,
+ 'AC_FUNC_MMAP' => 1,
+ 'AC_STRUCT_TM' => 1,
+ 'AC_SUBST' => 1,
+ 'AC_PROG_CC' => 1,
+ 'AC_PROG_LIBTOOL' => 1
+ }
+ ], 'Request' )
+ );
+
diff --git a/rt/autom4te.cache/traces.0 b/rt/autom4te.cache/traces.0
new file mode 100644
index 0000000..f132762
--- /dev/null
+++ b/rt/autom4te.cache/traces.0
@@ -0,0 +1,158 @@
+m4trace:configure.ac:9: -1- m4_pattern_forbid([^_?A[CHUM]_])
+m4trace:configure.ac:9: -1- m4_pattern_forbid([_AC_])
+m4trace:configure.ac:9: -1- m4_pattern_forbid([^LIBOBJS$], [do not use LIBOBJS directly, use AC_LIBOBJ (see section `AC_LIBOBJ vs. LIBOBJS'])
+m4trace:configure.ac:9: -1- m4_pattern_allow([^AS_FLAGS$])
+m4trace:configure.ac:9: -1- m4_pattern_forbid([^_?m4_])
+m4trace:configure.ac:9: -1- m4_pattern_forbid([^dnl$])
+m4trace:configure.ac:9: -1- m4_pattern_forbid([^_?AS_])
+m4trace:configure.ac:9: -1- AC_SUBST([SHELL], [${CONFIG_SHELL-/bin/sh}])
+m4trace:configure.ac:9: -1- AC_SUBST([PATH_SEPARATOR])
+m4trace:configure.ac:9: -1- AC_SUBST([PACKAGE_NAME], [m4_ifdef([AC_PACKAGE_NAME], ['AC_PACKAGE_NAME'])])
+m4trace:configure.ac:9: -1- AC_SUBST([PACKAGE_TARNAME], [m4_ifdef([AC_PACKAGE_TARNAME], ['AC_PACKAGE_TARNAME'])])
+m4trace:configure.ac:9: -1- AC_SUBST([PACKAGE_VERSION], [m4_ifdef([AC_PACKAGE_VERSION], ['AC_PACKAGE_VERSION'])])
+m4trace:configure.ac:9: -1- AC_SUBST([PACKAGE_STRING], [m4_ifdef([AC_PACKAGE_STRING], ['AC_PACKAGE_STRING'])])
+m4trace:configure.ac:9: -1- AC_SUBST([PACKAGE_BUGREPORT], [m4_ifdef([AC_PACKAGE_BUGREPORT], ['AC_PACKAGE_BUGREPORT'])])
+m4trace:configure.ac:9: -1- AC_SUBST([exec_prefix], [NONE])
+m4trace:configure.ac:9: -1- AC_SUBST([prefix], [NONE])
+m4trace:configure.ac:9: -1- AC_SUBST([program_transform_name], [s,x,x,])
+m4trace:configure.ac:9: -1- AC_SUBST([bindir], ['${exec_prefix}/bin'])
+m4trace:configure.ac:9: -1- AC_SUBST([sbindir], ['${exec_prefix}/sbin'])
+m4trace:configure.ac:9: -1- AC_SUBST([libexecdir], ['${exec_prefix}/libexec'])
+m4trace:configure.ac:9: -1- AC_SUBST([datadir], ['${prefix}/share'])
+m4trace:configure.ac:9: -1- AC_SUBST([sysconfdir], ['${prefix}/etc'])
+m4trace:configure.ac:9: -1- AC_SUBST([sharedstatedir], ['${prefix}/com'])
+m4trace:configure.ac:9: -1- AC_SUBST([localstatedir], ['${prefix}/var'])
+m4trace:configure.ac:9: -1- AC_SUBST([libdir], ['${exec_prefix}/lib'])
+m4trace:configure.ac:9: -1- AC_SUBST([includedir], ['${prefix}/include'])
+m4trace:configure.ac:9: -1- AC_SUBST([oldincludedir], ['/usr/include'])
+m4trace:configure.ac:9: -1- AC_SUBST([infodir], ['${prefix}/info'])
+m4trace:configure.ac:9: -1- AC_SUBST([mandir], ['${prefix}/man'])
+m4trace:configure.ac:9: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE_NAME])
+m4trace:configure.ac:9: -1- AH_OUTPUT([PACKAGE_NAME], [/* Define to the full name of this package. */
+#undef PACKAGE_NAME])
+m4trace:configure.ac:9: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE_TARNAME])
+m4trace:configure.ac:9: -1- AH_OUTPUT([PACKAGE_TARNAME], [/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME])
+m4trace:configure.ac:9: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE_VERSION])
+m4trace:configure.ac:9: -1- AH_OUTPUT([PACKAGE_VERSION], [/* Define to the version of this package. */
+#undef PACKAGE_VERSION])
+m4trace:configure.ac:9: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE_STRING])
+m4trace:configure.ac:9: -1- AH_OUTPUT([PACKAGE_STRING], [/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING])
+m4trace:configure.ac:9: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE_BUGREPORT])
+m4trace:configure.ac:9: -1- AH_OUTPUT([PACKAGE_BUGREPORT], [/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT])
+m4trace:configure.ac:9: -1- AC_SUBST([build_alias])
+m4trace:configure.ac:9: -1- AC_SUBST([host_alias])
+m4trace:configure.ac:9: -1- AC_SUBST([target_alias])
+m4trace:configure.ac:9: -1- AC_SUBST([DEFS])
+m4trace:configure.ac:9: -1- AC_SUBST([ECHO_C])
+m4trace:configure.ac:9: -1- AC_SUBST([ECHO_N])
+m4trace:configure.ac:9: -1- AC_SUBST([ECHO_T])
+m4trace:configure.ac:9: -1- AC_SUBST([LIBS])
+m4trace:configure.ac:14: -1- AC_SUBST([rt_version_major], [3])
+m4trace:configure.ac:16: -1- AC_SUBST([rt_version_minor], [0])
+m4trace:configure.ac:18: -1- AC_SUBST([rt_version_patch], [9])
+m4trace:configure.ac:24: -1- AC_PROG_INSTALL
+m4trace:configure.ac:24: -1- AC_SUBST([INSTALL_PROGRAM])
+m4trace:configure.ac:24: -1- AC_SUBST([INSTALL_SCRIPT])
+m4trace:configure.ac:24: -1- AC_SUBST([INSTALL_DATA])
+m4trace:configure.ac:25: -1- AC_SUBST([PERL])
+m4trace:configure.ac:26: -1- AC_SUBST([PERL], [$ac_cv_path_PERL])
+m4trace:configure.ac:36: -1- AC_SUBST([SPEEDY_BIN])
+m4trace:configure.ac:41: -1- AC_SUBST([prefix])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_prefix])
+m4trace:configure.ac:41: -1- AC_SUBST([exec_prefix])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_exec_prefix])
+m4trace:configure.ac:41: -1- AC_SUBST([bindir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_bindir])
+m4trace:configure.ac:41: -1- AC_SUBST([sbindir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_sbindir])
+m4trace:configure.ac:41: -1- AC_SUBST([sysconfdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_sysconfdir])
+m4trace:configure.ac:41: -1- AC_SUBST([mandir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_mandir])
+m4trace:configure.ac:41: -1- AC_SUBST([libdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_libdir])
+m4trace:configure.ac:41: -1- AC_SUBST([datadir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_datadir])
+m4trace:configure.ac:41: -1- AC_SUBST([htmldir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_htmldir])
+m4trace:configure.ac:41: -1- AC_SUBST([manualdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_manualdir])
+m4trace:configure.ac:41: -1- AC_SUBST([localstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_localstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([logfiledir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_logfiledir])
+m4trace:configure.ac:41: -1- AC_SUBST([masonstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_masonstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([sessionstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_sessionstatedir])
+m4trace:configure.ac:41: -1- AC_SUBST([customdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_customdir])
+m4trace:configure.ac:41: -1- AC_SUBST([custometcdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_custometcdir])
+m4trace:configure.ac:41: -1- AC_SUBST([customhtmldir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_customhtmldir])
+m4trace:configure.ac:41: -1- AC_SUBST([customlexdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_customlexdir])
+m4trace:configure.ac:41: -1- AC_SUBST([customlibdir])
+m4trace:configure.ac:41: -1- AC_SUBST([exp_customlibdir])
+m4trace:configure.ac:41: -1- AC_SUBST([rt_layout_name])
+m4trace:configure.ac:49: -1- AC_SUBST([RTGROUP])
+m4trace:configure.ac:57: -1- AC_SUBST([BIN_OWNER])
+m4trace:configure.ac:65: -1- AC_SUBST([LIBS_OWNER])
+m4trace:configure.ac:73: -1- AC_SUBST([LIBS_GROUP])
+m4trace:configure.ac:84: -1- AC_SUBST([DB_TYPE])
+m4trace:configure.ac:91: -1- AC_SUBST([ORACLE_ENV_PREF])
+m4trace:configure.ac:99: -1- AC_SUBST([DB_HOST])
+m4trace:configure.ac:107: -1- AC_SUBST([DB_PORT])
+m4trace:configure.ac:115: -1- AC_SUBST([DB_RT_HOST])
+m4trace:configure.ac:123: -1- AC_SUBST([DB_DBA])
+m4trace:configure.ac:131: -1- AC_SUBST([DB_DATABASE])
+m4trace:configure.ac:139: -1- AC_SUBST([DB_RT_USER])
+m4trace:configure.ac:147: -1- AC_SUBST([DB_RT_PASS])
+m4trace:configure.ac:155: -1- AC_SUBST([WEB_USER])
+m4trace:configure.ac:163: -1- AC_SUBST([WEB_GROUP])
+m4trace:configure.ac:182: -1- AC_SUBST([RT_VERSION_MAJOR], [${rt_version_major}])
+m4trace:configure.ac:183: -1- AC_SUBST([RT_VERSION_MINOR], [${rt_version_minor}])
+m4trace:configure.ac:184: -1- AC_SUBST([RT_VERSION_PATCH], [${rt_version_patch}])
+m4trace:configure.ac:187: -1- AC_SUBST([RT_PATH], [${exp_prefix}])
+m4trace:configure.ac:188: -1- AC_SUBST([RT_DOC_PATH], [${exp_manualdir}])
+m4trace:configure.ac:189: -1- AC_SUBST([RT_LOCAL_PATH], [${exp_customdir}])
+m4trace:configure.ac:190: -1- AC_SUBST([RT_LIB_PATH], [${exp_libdir}])
+m4trace:configure.ac:191: -1- AC_SUBST([RT_ETC_PATH], [${exp_sysconfdir}])
+m4trace:configure.ac:192: -1- AC_SUBST([CONFIG_FILE_PATH], [${exp_sysconfdir}])
+m4trace:configure.ac:193: -1- AC_SUBST([RT_BIN_PATH], [${exp_bindir}])
+m4trace:configure.ac:194: -1- AC_SUBST([RT_SBIN_PATH], [${exp_sbindir}])
+m4trace:configure.ac:195: -1- AC_SUBST([RT_VAR_PATH], [${exp_localstatedir}])
+m4trace:configure.ac:196: -1- AC_SUBST([RT_MAN_PATH], [${exp_mandir}])
+m4trace:configure.ac:197: -1- AC_SUBST([MASON_DATA_PATH], [${exp_masonstatedir}])
+m4trace:configure.ac:198: -1- AC_SUBST([MASON_SESSION_PATH], [${exp_sessionstatedir}])
+m4trace:configure.ac:199: -1- AC_SUBST([MASON_HTML_PATH], [${exp_htmldir}])
+m4trace:configure.ac:200: -1- AC_SUBST([LOCAL_ETC_PATH], [${exp_custometcdir}])
+m4trace:configure.ac:201: -1- AC_SUBST([MASON_LOCAL_HTML_PATH], [${exp_customhtmldir}])
+m4trace:configure.ac:202: -1- AC_SUBST([LOCAL_LEXICON_PATH], [${exp_customlexdir}])
+m4trace:configure.ac:203: -1- AC_SUBST([LOCAL_LIB_PATH], [${exp_customlibdir}])
+m4trace:configure.ac:204: -1- AC_SUBST([DESTDIR], [${exp_prefix}])
+m4trace:configure.ac:205: -1- AC_SUBST([RT_LOG_PATH], [${exp_logfiledir}])
+m4trace:configure.ac:228: -1- AC_CONFIG_FILES([
+ sbin/rt-setup-database
+ sbin/rt-test-dependencies
+ Makefile
+ etc/RT_Config.pm
+ lib/RT.pm
+ lib/t/00smoke.t
+ lib/t/01harness.t
+ lib/t/02regression.t
+ lib/t/03web.pl
+ lib/t/04_send_email.pl
+ bin/mason_handler.fcgi
+ bin/mason_handler.scgi
+ bin/mason_handler.svc
+ bin/rt-commit-handler
+ bin/rt-crontool
+ bin/rt-mailgate
+ bin/rt
+ bin/webmux.pl
+ ])
diff --git a/rt/bin/mason_handler.fcgi b/rt/bin/mason_handler.fcgi
new file mode 100755
index 0000000..93d1f88
--- /dev/null
+++ b/rt/bin/mason_handler.fcgi
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use File::Basename;
+require ('/opt/rt3/bin/webmux.pl');
+
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+# Enter CGI::Fast mode, which should also work as a vanilla CGI script.
+require CGI::Fast;
+
+RT::Init();
+
+# Response loop
+while ( my $cgi = CGI::Fast->new ) {
+ # the whole point of fastcgi requires the env to get reset here..
+ # So we must squash it again
+ $ENV{'PATH'} = '/bin:/usr/bin';
+ $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
+ $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'};
+ $ENV{'ENV'} = '' if defined $ENV{'ENV'};
+ $ENV{'IFS'} = '' if defined $ENV{'IFS'};
+
+ RT::ConnectToDatabase();
+
+ if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
+ && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+ $cgi->path_info( $cgi->path_info . "/index.html" );
+ }
+
+ eval { $h->handle_cgi_object($cgi); };
+ if ($@) {
+ $RT::Logger->crit($@);
+ }
+
+
+ if ($RT::Handle->TransactionDepth) {
+ $RT::Handle->ForceRollback;
+ $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
+ }
+
+
+}
+
+1;
diff --git a/rt/bin/mason_handler.fcgi.in b/rt/bin/mason_handler.fcgi.in
new file mode 100644
index 0000000..a009663
--- /dev/null
+++ b/rt/bin/mason_handler.fcgi.in
@@ -0,0 +1,68 @@
+#!@PERL@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use File::Basename;
+require ('@RT_BIN_PATH@/webmux.pl');
+
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+# Enter CGI::Fast mode, which should also work as a vanilla CGI script.
+require CGI::Fast;
+
+RT::Init();
+
+# Response loop
+while ( my $cgi = CGI::Fast->new ) {
+ # the whole point of fastcgi requires the env to get reset here..
+ # So we must squash it again
+ $ENV{'PATH'} = '/bin:/usr/bin';
+ $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
+ $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'};
+ $ENV{'ENV'} = '' if defined $ENV{'ENV'};
+ $ENV{'IFS'} = '' if defined $ENV{'IFS'};
+
+ RT::ConnectToDatabase();
+
+ if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
+ && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+ $cgi->path_info( $cgi->path_info . "/index.html" );
+ }
+
+ eval { $h->handle_cgi_object($cgi); };
+ if ($@) {
+ $RT::Logger->crit($@);
+ }
+
+
+ if ($RT::Handle->TransactionDepth) {
+ $RT::Handle->ForceRollback;
+ $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
+ }
+
+
+}
+
+1;
diff --git a/rt/bin/mason_handler.scgi b/rt/bin/mason_handler.scgi
new file mode 100755
index 0000000..7774189
--- /dev/null
+++ b/rt/bin/mason_handler.scgi
@@ -0,0 +1,43 @@
+#!/usr/local/bin/speedy
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+require ('/opt/rt3/bin/webmux.pl');
+
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+require CGI;
+
+RT::Init();
+
+my $cgi = CGI->new;
+if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
+ && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+ $cgi->path_info( $cgi->path_info . "/index.html" );
+}
+
+$h->handle_cgi_object($cgi);
+
+1;
diff --git a/rt/bin/mason_handler.scgi.in b/rt/bin/mason_handler.scgi.in
new file mode 100644
index 0000000..614d4d4
--- /dev/null
+++ b/rt/bin/mason_handler.scgi.in
@@ -0,0 +1,43 @@
+#!@SPEEDY_BIN@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+require ('@RT_BIN_PATH@/webmux.pl');
+
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+require CGI;
+
+RT::Init();
+
+my $cgi = CGI->new;
+if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
+ && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+ $cgi->path_info( $cgi->path_info . "/index.html" );
+}
+
+$h->handle_cgi_object($cgi);
+
+1;
diff --git a/rt/bin/mason_handler.svc b/rt/bin/mason_handler.svc
new file mode 100644
index 0000000..c05d21e
--- /dev/null
+++ b/rt/bin/mason_handler.svc
@@ -0,0 +1,234 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+=head1 NAME
+
+mason_handler.svc - Win32 IIS Service handler for RT
+
+=head1 SYNOPSIS
+
+ perl mason_handler.svc --install # install as service
+ perl mason_handler.svc --deinstall # deinstall this service
+ perl mason_handler.svc --help # show this help
+ perl mason_handler.svc # launch handler from command line
+
+=head1 DESCRIPTION
+
+This script manages a stand-alone FastCGI server, and populates the necessary
+registry settings to run it with Microsoft IIS Server 4.0 or above.
+
+Before running it, you need to install the B<FCGI> module from CPAN, as well as
+B<Win32::Daemon> from L<http://www.roth.net/perl/Daemon/> if you want to install
+itself as a service.
+
+This script will automatically create a virtual directory under the IIS root;
+its name is taken from C<$WebPath> in the F<RT_Config.pm> file. Additionally,
+please install the ISAPI binary from L<http://www.caraveo.com/fastcgi/> and set
+up an ISAPI Script Map that maps F<.html> files to F<isapi_fcgi.dll>.
+
+Once the service is launched (either via C<net start RTFastCGI> or by running
+C<perl mason_handler.svc>), a FCGI server will start and bind to port C<8284>
+(mnemonics: the ASCII value of C<R> and C<T>); the ISAPI handler's C<BindPath>
+registry setting will also be automatically populated.
+
+=cut
+
+use strict;
+use File::Basename;
+require (dirname(__FILE__) . '/webmux.pl');
+
+use Cwd;
+use File::Spec;
+
+use Win32;
+use Win32::Process;
+use Win32::Service;
+use Win32::TieRegistry;
+
+my $ProcessObj;
+
+BEGIN {
+ my $runsvc = sub {
+ Win32::Process::Create(
+ $ProcessObj, $^X, "$^X $0 --run", 0, NORMAL_PRIORITY_CLASS, "."
+ ) or do {
+ die Win32::FormatMessage( Win32::GetLastError() );
+ };
+
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+ $path =~ s|bin$|share\\html|;
+
+ $Win32::TieRegistry::Registry->{
+ 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\'.
+ 'W3SVC\Parameters\Virtual Roots\\'
+ }->{$RT::WebPath || '/'} = "$path,,205";
+
+ $Win32::TieRegistry::Registry->{
+ 'HKEY_LOCAL_MACHINE\Software\FASTCGI\.html\\'
+ }->{'BindPath'} = $ENV{'FCGI_SOCKET_PATH'};
+
+ Win32::Service::StartService(Win32::NodeName, 'W3SVC');
+ };
+
+ if ($ARGV[0] eq '--deinstall') {
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+
+ require Win32::Daemon;
+ Win32::Daemon::DeleteService('RTFastCGI');
+ warn "Service 'RTFastCGI' successfully deleted.\n";
+ exit;
+ }
+ elsif ($ARGV[0] eq '--install') {
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+
+ require Win32::Daemon;
+ Win32::Daemon::DeleteService('RTFastCGI');
+
+ my $rv = Win32::Daemon::CreateService( {
+ machine => '',
+ name => 'RTFastCGI',
+ display => 'RT FastCGI Handler',
+ path => $^X,
+ user => '',
+ pwd => $path,
+ description => 'Enables port 8284 as the RT FastCGI handler.',
+ parameters => File::Spec->catfile(
+ $path, File::Basename::basename($0)
+ ) . ' --service',
+ } );
+
+ if ($rv) {
+ warn "Service 'RTFastCGI' successfully created.\n";
+ }
+ else {
+ warn "Failed to add service: " . Win32::FormatMessage(
+ Win32::Daemon::GetLastError()
+ ) . "\n";
+ }
+ exit;
+ }
+ elsif ($ARGV[0] eq '--service') {
+ require Win32::Daemon;
+
+ my $PrevState = Win32::Daemon::SERVICE_START_PENDING();
+ Win32::Daemon::StartService() or die $^E;
+
+ while ( 1 ) {
+ my $State = Win32::Daemon::State();
+ last if $State == Win32::Daemon::SERVICE_STOPPED();
+
+ if ( $State == Win32::Daemon::SERVICE_START_PENDING() ) {
+ $runsvc->();
+ Win32::Daemon::State( Win32::Daemon::SERVICE_RUNNING() );
+ $PrevState = Win32::Daemon::SERVICE_RUNNING();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_CONTINUE_PENDING() ) {
+ $ProcessObj->Resume;
+ Win32::Daemon::State( Win32::Daemon::SERVICE_RUNNING() );
+ $PrevState = Win32::Daemon::SERVICE_RUNNING();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_STOP_PENDING() ) {
+ $ProcessObj->Kill(0);
+ Win32::Daemon::State( Win32::Daemon::SERVICE_STOPPED() );
+ $PrevState = Win32::Daemon::SERVICE_STOPPED();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_RUNNING() ) {
+ my $Message = Win32::Daemon::QueryLastMessage(1);
+ if ( $Message == Win32::Daemon::SERVICE_CONTROL_INTERROGATE() ) {
+ Win32::Daemon::State( $PrevState );
+ }
+ elsif ( $Message == Win32::Daemon::SERVICE_CONTROL_SHUTDOWN() ) {
+ Win32::Daemon::State( Win32::Daemon::SERVICE_STOP_PENDING(), 15000 );
+ }
+ elsif ( $Message != Win32::Daemon::SERVICE_CONTROL_NONE() ) {
+ Win32::Daemon::State( $PrevState );
+ }
+ }
+
+ Win32::Sleep( 1000 );
+ }
+
+ Win32::Daemon::StopService();
+ exit;
+ }
+ elsif ($ARGV[0] eq '--help') {
+ system("perldoc $0");
+ exit;
+ }
+ elsif ($ARGV[0] ne '--run') {
+ $SIG{__DIE__} = sub { $ProcessObj->Kill(0) if $ProcessObj };
+ $runsvc->();
+ warn "RT FastCGI Handler launched. Press [Enter] to terminate...\n";
+ <STDIN>;
+ exit;
+ }
+}
+
+###############################################################################
+
+warn "Begin listening on $ENV{'FCGI_SOCKET_PATH'}\n";
+
+require CGI::Fast;
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+RT::Init();
+
+# Response loop
+while( my $cgi = CGI::Fast->new ) {
+ my $comp = $ENV{'PATH_INFO'};
+
+ $comp = $1 if ($comp =~ /^(.*)$/);
+ $comp =~ s|^$RT::WebPath\b||i;
+ $comp .= "index.html" if ($comp =~ /\/$/);
+ $comp =~ s/.pl$/.html/g;
+
+ warn "Serving $comp\n";
+
+ $h->handle_cgi($comp);
+ # _should_ always be tied
+}
+
+1;
+
+=head1 AUTHORS
+
+Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002 by Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
diff --git a/rt/bin/mason_handler.svc.in b/rt/bin/mason_handler.svc.in
new file mode 100644
index 0000000..0ba1f51
--- /dev/null
+++ b/rt/bin/mason_handler.svc.in
@@ -0,0 +1,234 @@
+#!@PERL@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+=head1 NAME
+
+mason_handler.svc - Win32 IIS Service handler for RT
+
+=head1 SYNOPSIS
+
+ perl mason_handler.svc --install # install as service
+ perl mason_handler.svc --deinstall # deinstall this service
+ perl mason_handler.svc --help # show this help
+ perl mason_handler.svc # launch handler from command line
+
+=head1 DESCRIPTION
+
+This script manages a stand-alone FastCGI server, and populates the necessary
+registry settings to run it with Microsoft IIS Server 4.0 or above.
+
+Before running it, you need to install the B<FCGI> module from CPAN, as well as
+B<Win32::Daemon> from L<http://www.roth.net/perl/Daemon/> if you want to install
+itself as a service.
+
+This script will automatically create a virtual directory under the IIS root;
+its name is taken from C<$WebPath> in the F<RT_Config.pm> file. Additionally,
+please install the ISAPI binary from L<http://www.caraveo.com/fastcgi/> and set
+up an ISAPI Script Map that maps F<.html> files to F<isapi_fcgi.dll>.
+
+Once the service is launched (either via C<net start RTFastCGI> or by running
+C<perl mason_handler.svc>), a FCGI server will start and bind to port C<8284>
+(mnemonics: the ASCII value of C<R> and C<T>); the ISAPI handler's C<BindPath>
+registry setting will also be automatically populated.
+
+=cut
+
+use strict;
+use File::Basename;
+require (dirname(__FILE__) . '/webmux.pl');
+
+use Cwd;
+use File::Spec;
+
+use Win32;
+use Win32::Process;
+use Win32::Service;
+use Win32::TieRegistry;
+
+my $ProcessObj;
+
+BEGIN {
+ my $runsvc = sub {
+ Win32::Process::Create(
+ $ProcessObj, $^X, "$^X $0 --run", 0, NORMAL_PRIORITY_CLASS, "."
+ ) or do {
+ die Win32::FormatMessage( Win32::GetLastError() );
+ };
+
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+ $path =~ s|bin$|share\\html|;
+
+ $Win32::TieRegistry::Registry->{
+ 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\'.
+ 'W3SVC\Parameters\Virtual Roots\\'
+ }->{$RT::WebPath || '/'} = "$path,,205";
+
+ $Win32::TieRegistry::Registry->{
+ 'HKEY_LOCAL_MACHINE\Software\FASTCGI\.html\\'
+ }->{'BindPath'} = $ENV{'FCGI_SOCKET_PATH'};
+
+ Win32::Service::StartService(Win32::NodeName, 'W3SVC');
+ };
+
+ if ($ARGV[0] eq '--deinstall') {
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+
+ require Win32::Daemon;
+ Win32::Daemon::DeleteService('RTFastCGI');
+ warn "Service 'RTFastCGI' successfully deleted.\n";
+ exit;
+ }
+ elsif ($ARGV[0] eq '--install') {
+ chdir File::Basename::dirname($0);
+ my $path = Cwd::cwd();
+ $path =~ s|/|\\|g;
+
+ require Win32::Daemon;
+ Win32::Daemon::DeleteService('RTFastCGI');
+
+ my $rv = Win32::Daemon::CreateService( {
+ machine => '',
+ name => 'RTFastCGI',
+ display => 'RT FastCGI Handler',
+ path => $^X,
+ user => '',
+ pwd => $path,
+ description => 'Enables port 8284 as the RT FastCGI handler.',
+ parameters => File::Spec->catfile(
+ $path, File::Basename::basename($0)
+ ) . ' --service',
+ } );
+
+ if ($rv) {
+ warn "Service 'RTFastCGI' successfully created.\n";
+ }
+ else {
+ warn "Failed to add service: " . Win32::FormatMessage(
+ Win32::Daemon::GetLastError()
+ ) . "\n";
+ }
+ exit;
+ }
+ elsif ($ARGV[0] eq '--service') {
+ require Win32::Daemon;
+
+ my $PrevState = Win32::Daemon::SERVICE_START_PENDING();
+ Win32::Daemon::StartService() or die $^E;
+
+ while ( 1 ) {
+ my $State = Win32::Daemon::State();
+ last if $State == Win32::Daemon::SERVICE_STOPPED();
+
+ if ( $State == Win32::Daemon::SERVICE_START_PENDING() ) {
+ $runsvc->();
+ Win32::Daemon::State( Win32::Daemon::SERVICE_RUNNING() );
+ $PrevState = Win32::Daemon::SERVICE_RUNNING();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_CONTINUE_PENDING() ) {
+ $ProcessObj->Resume;
+ Win32::Daemon::State( Win32::Daemon::SERVICE_RUNNING() );
+ $PrevState = Win32::Daemon::SERVICE_RUNNING();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_STOP_PENDING() ) {
+ $ProcessObj->Kill(0);
+ Win32::Daemon::State( Win32::Daemon::SERVICE_STOPPED() );
+ $PrevState = Win32::Daemon::SERVICE_STOPPED();
+ }
+ elsif ( $State == Win32::Daemon::SERVICE_RUNNING() ) {
+ my $Message = Win32::Daemon::QueryLastMessage(1);
+ if ( $Message == Win32::Daemon::SERVICE_CONTROL_INTERROGATE() ) {
+ Win32::Daemon::State( $PrevState );
+ }
+ elsif ( $Message == Win32::Daemon::SERVICE_CONTROL_SHUTDOWN() ) {
+ Win32::Daemon::State( Win32::Daemon::SERVICE_STOP_PENDING(), 15000 );
+ }
+ elsif ( $Message != Win32::Daemon::SERVICE_CONTROL_NONE() ) {
+ Win32::Daemon::State( $PrevState );
+ }
+ }
+
+ Win32::Sleep( 1000 );
+ }
+
+ Win32::Daemon::StopService();
+ exit;
+ }
+ elsif ($ARGV[0] eq '--help') {
+ system("perldoc $0");
+ exit;
+ }
+ elsif ($ARGV[0] ne '--run') {
+ $SIG{__DIE__} = sub { $ProcessObj->Kill(0) if $ProcessObj };
+ $runsvc->();
+ warn "RT FastCGI Handler launched. Press [Enter] to terminate...\n";
+ <STDIN>;
+ exit;
+ }
+}
+
+###############################################################################
+
+warn "Begin listening on $ENV{'FCGI_SOCKET_PATH'}\n";
+
+require CGI::Fast;
+my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
+
+RT::Init();
+
+# Response loop
+while( my $cgi = CGI::Fast->new ) {
+ my $comp = $ENV{'PATH_INFO'};
+
+ $comp = $1 if ($comp =~ /^(.*)$/);
+ $comp =~ s|^$RT::WebPath\b||i;
+ $comp .= "index.html" if ($comp =~ /\/$/);
+ $comp =~ s/.pl$/.html/g;
+
+ warn "Serving $comp\n";
+
+ $h->handle_cgi($comp);
+ # _should_ always be tied
+}
+
+1;
+
+=head1 AUTHORS
+
+Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002 by Autrijus Tang E<lt>autrijus@autrijus.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
diff --git a/rt/bin/rt-commit-handler b/rt/bin/rt-commit-handler
new file mode 100644
index 0000000..29e443e
--- /dev/null
+++ b/rt/bin/rt-commit-handler
@@ -0,0 +1,846 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+# {{{ Docs
+# -*-Perl-*-
+#
+#ident "@(#)ccvs/contrib:$Name: $:$Id: rt-commit-handler,v 1.1 2003-07-15 13:16:15 ivan Exp $"
+#
+# Perl filter to handle the log messages from the checkin of files in multiple
+# directories. This script will group the lists of files by log message, and
+# send one piece of mail per unique message, no matter how many files are
+# committed.
+
+=head1 NAME rt-commit-handler
+
+=head1 USAGE
+
+
+
+=head2 Regular use
+
+Stick the following in in CVSROOT/commitinfo
+
+ ALL /opt/rt3/bin/rt-commit-handler --record-last-dir
+
+Stick the following in CVSROOT/loginfo
+
+ ALL /opt/rt3/bin/rt-commit-handler --cvs-root /pathtocvs/root --rt %{Vvts}
+
+=head2 Invocation (advanced use)
+
+rt-commit-handler --cvs-root /path/to/cvs/root [-d] [-D] [-r] [-M module] \
+ [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
+
+
+ -d - turn on debugging
+ -m mailto - send mail to "mailto" (multiple)
+ -R replyto - set the "Reply-To:" to "replyto" (multiple)
+ -M modulename - set module name to "modulename"
+ -f logfile - write commit messages to logfile too
+ -D - generate diff commands
+ --rt - invoke RT commit handler
+ --cvs-root - specify your CVS root
+
+ --record-last-dir - Record the last directory with changes in
+ pre-commit (commitinfo) mode
+
+
+=cut
+
+# }}}
+
+use strict;
+use Carp;
+use Getopt::Long;
+use Text::Wrap;
+use Digest::MD5;
+use MIME::Entity;
+
+use lib ("/opt/rt3/lib", "/opt/rt3/local/lib");
+
+use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+
+use vars
+ qw(@MAILER $TMPDIR $FILE_PREFIX $LASTDIR_FILE $HASH_FILE $VERSION_FILE $MESSAGE_FILE $MAIL_FILE $DEBUG $MAILTO $REPLYTO $id $MODULE_NAME
+ $LOGIN $COMMITLOG $CVS_ROOT $RT_HANDLER);
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+#Load etc/config.pm and drop privs
+RT::LoadConfig();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+# {{{ Variable setup
+$TMPDIR = '/tmp';
+$FILE_PREFIX = $TMPDIR . '/#cvs.';
+
+# The root of your CVS install. we should get this from some smarter place.
+# It needs a trailing /
+
+$LASTDIR_FILE = $FILE_PREFIX . "lastdir";
+$HASH_FILE = $FILE_PREFIX . "hash";
+$VERSION_FILE = $FILE_PREFIX . "version";
+$MESSAGE_FILE = $FILE_PREFIX . "message";
+$MAIL_FILE = $FILE_PREFIX . "mail";
+
+$DEBUG = 0;
+$RT_HANDLER = 1;
+
+$MAILTO = '';
+
+my @files = ();
+my (@log_lines);
+my $do_diff = 0;
+my $id = getpgrp(); # note, you *must* use a shell which does setpgrp()
+$LOGIN = getpwuid($<);
+
+# }}}
+
+die "User could not be found" unless ($LOGIN);
+
+# {{{ parse command line arguments (file list is seen as one arg)
+#
+while ( my $arg = shift @ARGV ) {
+
+ if ( $arg eq '-d' ) {
+ $DEBUG = 1;
+ warn "Debug turned on...\n";
+ }
+ elsif ( $arg =~ /^--record-last-dir$/i ) {
+ record_last_dir( $id, $ARGV[0] );
+ exit(0);
+ }
+ elsif ( $arg eq '-m' ) {
+ $MAILTO .= ", " if $MAILTO;
+ $MAILTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '--rt' ) {
+ $RT_HANDLER = 1;
+ }
+ elsif ( $arg eq '-R' ) {
+ $REPLYTO .= ", " if $REPLYTO;
+ $REPLYTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '-M' ) {
+ die ("too many '-M' args\n") if $MODULE_NAME;
+ $MODULE_NAME = shift @ARGV;
+ }
+ elsif ( $arg eq '--cvs-root' ) {
+ $CVS_ROOT = shift @ARGV;
+ $CVS_ROOT .= "/" unless ( $CVS_ROOT =~ /\/$/ );
+ }
+ elsif ( $arg eq '-f' ) {
+ die ("too many '-f' args\n") if $COMMITLOG;
+ $COMMITLOG = shift @ARGV;
+
+ # This is a disgusting hack to untaint $COMMITLOG if we're running from
+ # setgid cvs.
+ $COMMITLOG = untaint($COMMITLOG);
+ }
+ elsif ( $arg eq '-D' ) {
+ $do_diff = 1;
+ }
+ else {
+ @files = split ( ' ', $arg );
+ last;
+ }
+}
+
+# }}}
+
+$REPLYTO = $LOGIN unless ($REPLYTO);
+
+# for now, the first "file" is the repository directory being committed,
+# relative to the $CVSROOT location
+#
+my $dir = shift @files;
+
+# XXX there are some ugly assumptions in here about module names and
+# XXX directories relative to the $CVSROOT location -- really should
+# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
+# XXX we have to parse it backwards.
+#
+# XXX For now we set the `module' name to the top-level directory name.
+#
+unless ($MODULE_NAME) {
+ ($MODULE_NAME) = split ( '/', $dir, 2 );
+}
+
+if ($DEBUG) {
+ warn "module - ", $MODULE_NAME, "\n";
+ warn "dir - ", $dir, "\n";
+ warn "files - ", join ( " ", @files ), "\n";
+ warn "id - ", $id, "\n";
+}
+
+# {{{ Check for a new directory or an import command.
+
+#
+# files[0] - "-"
+# files[1] - "New"
+# files[2] - "directory"
+#
+# files[0] - "-"
+# files[1] - "Imported"
+# files[2] - "sources"
+#
+if ( $files[0] eq "-" ) {
+
+ #we just don't care about New Directory notes
+ unless ( $files[1] eq "New" && $files[2] eq "directory" ) {
+
+ my @text = ();
+
+ push @text, build_header();
+ push @text, "";
+
+ while ( my $line = <STDIN> ) {
+ chop $line; # Drop the newline
+ push @text, $line;
+ }
+
+ append_logfile( $COMMITLOG, @text ) if ($COMMITLOG);
+
+ mail_notification( $id, @text );
+ }
+
+ exit 0;
+}
+
+# }}}
+
+# {{{ Collect just the log message from stdin.
+#
+
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ last if ( $line =~ /^Log Message:$/ );
+}
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ $line =~ s/\s+$//; # strip trailing white space
+ push @log_lines, $line;
+}
+
+my $md5 = Digest::MD5->new();
+foreach my $line (@log_lines) {
+ $md5->add( $line . "\n" );
+}
+my $hash = $md5->hexdigest();
+
+warn "hash = $hash\n" if ($DEBUG);
+
+if ( !-e "$MESSAGE_FILE.$id.$hash" ) {
+ append_logfile( "$HASH_FILE.$id", $hash );
+ write_file( "$MESSAGE_FILE.$id.$hash", @log_lines );
+}
+
+# }}}
+
+# Spit out the information gathered in this pass.
+
+append_logfile( "$VERSION_FILE.$id.$hash", $dir . '/', @files );
+
+# {{{ Check whether this is the last directory. If not, quit.
+
+warn "Checking current dir against last dir $LASTDIR_FILE.$id\n" if ($DEBUG);
+
+my @last_dir = read_file("$LASTDIR_FILE.$id");
+
+unless ($CVS_ROOT) {
+ die "No cvs root specified with --cvs-root. Can't continue.";
+}
+
+if ( $last_dir[0] ne $CVS_ROOT . $dir ) {
+ warn "Current directory $CVS_ROOT$dir is not last directory $last_dir[0].\n"
+ if ($DEBUG);
+ exit 0;
+}
+
+# }}}
+
+# {{{ End Of Commits!
+#
+
+# This is it. The commits are all finished. Lump everything together
+# into a single message, fire a copy off to the mailing list, and drop
+# it on the end of the Changes file.
+#
+
+#
+# Produce the final compilation of the log messages
+#
+
+my @hashes = read_file("$HASH_FILE.$id");
+my (@text);
+
+push @text, build_header();
+push @text, "";
+
+my ( @added_files, @modified_files, @removed_files );
+
+foreach my $hash (@hashes) {
+
+ # In case we're running setgid, make sure the hash file hasn't been hacked.
+ $hash =~ m/([a-z0-9]*)/ || die "*** Hacking attempt detected\n";
+ $hash = $1;
+
+ my @files = read_file("$VERSION_FILE.$id.$hash");
+ my @log_lines = read_file("$MESSAGE_FILE.$id.$hash");
+
+ my $working_on_dir; # gets set as we iterate through the files.
+ foreach my $file (@files) {
+
+ #If we've entered a new directory, make a note of that and remove the trailing /
+
+ if ( $file =~ s'\/$'' ) {
+ $working_on_dir = $file;
+ next;
+ }
+
+ my @file_entry = ( split ( ',', $file, 4 ), $working_on_dir );
+
+ # file_entry looks like ths:
+
+ # 0 1 2 3 4
+ # Old rev : new rev : tag: file :directory
+ my $entry = {};
+ $entry->{'old'} = $file_entry[0];
+ $entry->{'new'} = $file_entry[1];
+ $entry->{'tag'} = $file_entry[2];
+ $entry->{'file'} = $file_entry[3];
+ $entry->{'dir'} = $file_entry[4];
+
+ if ( $file_entry[0] eq 'NONE' ) {
+ $entry->{'old'} = '0';
+ push @added_files, $entry;
+ }
+ elsif ( $file_entry[1] eq 'NONE' ) {
+ $entry->{'new'} = '0';
+ push @removed_files, $entry;
+ }
+ else {
+ push @modified_files, $entry;
+ }
+ }
+}
+
+# }}}
+
+# {{{ start building up the body
+
+# Strip leading and trailing blank lines from the log message. Also
+# compress multiple blank lines in the body of the message down to a
+# single blank line.
+#
+
+my $blank = 1;
+@log_lines = map {
+ my $wasblank = $blank;
+ $blank = $_ eq '';
+ $blank && $wasblank ? () : $_;
+} @log_lines;
+
+pop @log_lines if $blank;
+
+@modified_files = order_and_summarize_diffs(@modified_files);
+@added_files = order_and_summarize_diffs(@added_files);
+@removed_files = order_and_summarize_diffs(@removed_files);
+
+push @text, "Modified Files:", format_lists(@modified_files)
+ if (@modified_files);
+
+push @text, "Added Files:", format_lists(@added_files) if (@added_files);
+
+push @text, "Removed Files:", format_lists(@removed_files) if (@removed_files);
+
+push @text, "", "Log Message", @log_lines if (@log_lines);
+
+push @text, "";
+
+if ($RT_HANDLER) {
+ rt_handler(
+ @log_lines, "\n",
+ loc("To generate a diff of this commit:\n"), "\n",
+ format_diffs( @modified_files, @added_files, @removed_files )
+ );
+}
+
+if ($COMMITLOG) {
+ append_logfile( $COMMITLOG, @text );
+}
+
+if ($do_diff) {
+ push @text, "";
+ push @text, loc("To generate a diff of this commit:");
+ push @text, format_diffs( @modified_files, @added_files, @removed_files );
+ push @text, "";
+}
+
+# }}}
+
+# {{{ Mail out the notification.
+
+mail_notification( $id, @text );
+
+# }}}
+
+# {{{ clean up
+
+unless ($DEBUG) {
+ $hash = untaint($hash);
+ $id = untaint($id);
+ unlink "$VERSION_FILE.$id.$hash";
+ unlink "$MESSAGE_FILE.$id.$hash";
+ unlink "$MAIL_FILE.$id";
+ unlink "$LASTDIR_FILE.$id";
+ unlink "$HASH_FILE.$id";
+}
+
+# }}}
+
+exit 0;
+
+# {{{ Subroutines
+#
+
+# {{{ append_logfile
+sub append_logfile {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">>$filename" )
+ || die ("Cannot open file $filename for append.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ write_file
+sub write_file {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">$filename" )
+ || die ("Cannot open file $filename for write.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ read_file
+sub read_file {
+ my $filename = shift;
+ my (@lines);
+
+ open( FILE, "<$filename" )
+ || die ("Cannot open file $filename for read.\n");
+ while ( my $line = <FILE> ) {
+ chop $line;
+ push @lines, $line;
+ }
+ close(FILE);
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub format_lists
+
+sub format_lists {
+ my @items = (@_);
+
+ my $files = "";
+ map {
+ $_->{'files'} && ( $files .= ' ' . join ( ' ', @{ $_->{'files'} } ) );
+ } @items;
+
+ my @lines = wrap( "\t", "\t\t", $files );
+ return (@lines);
+
+}
+
+# }}}
+
+# {{{ sub format_diffs
+
+sub format_diffs {
+ my @items = (@_);
+
+ my @lines;
+ foreach my $item (@items) {
+ next unless ( $item->{'files'} );
+ push ( @lines,
+ "cvs diff -r"
+ . $item->{'old'} . " -r"
+ . $item->{'new'} . " "
+ . join ( " ", @{ $item->{'files'} } ) . "\n" );
+
+ }
+
+ @lines = fill( "\t", "\t\t", @lines );
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub order_and_summarize_diffs {
+
+# takes an array of file items
+# returns a sorted array of fileset items, which are like file items, except they can have an array of files, rather than
+# a singleton file.
+
+sub order_and_summarize_diffs {
+
+ my @files = (@_);
+
+ # Sort by tag, dir, file.
+ @files = sort {
+ $a->{'tag'} cmp $b->{'tag'}
+ || $a->{'dir'} cmp $b->{'dir'}
+ || $a->{'file'} cmp $b->{'file'};
+ } @files;
+
+ # Combine adjacent rows that are the same modulo the file name.
+
+ my @items = (undef);
+
+ foreach my $file (@files) {
+ if ( $#items == -1 #if it's empty
+ || ( !defined $items[-1]->{'old'}
+ || $items[-1]->{'old'} ne $file->{'old'} )
+ || ( !defined $items[-1]->{'new'}
+ || $items[-1]->{'new'} ne $file->{'new'} )
+ || ( !defined $items[-1]->{'tag'}
+ || $items[-1]->{'tag'} ne $file->{'tag'} ) )
+ {
+
+ push ( @items, $file );
+ }
+ push ( @{ $items[-1]->{'files'} },
+ $file->{'dir'} . "/" . $file->{'file'} );
+ }
+
+ return (@items);
+}
+
+# }}}
+
+# {{{ build_header
+
+sub build_header {
+ my $now = gmtime;
+ my $header =
+ sprintf( "Module Name:\t%s\nCommitted By:\t%s\nDate:\t\t%s %s %s",
+ $MODULE_NAME, $LOGIN, substr( $now, 0, 19 ), "UTC",
+ substr( $now, 20, 4 ) );
+ return ($header);
+}
+
+# }}}
+
+# {{{ mail_notification
+sub mail_notification {
+ my $id = shift;
+ my (@text) = @_;
+ write_file( "$MAIL_FILE.$id", "From: " . $LOGIN,
+ "Subject: CVS commit: " . $MODULE_NAME, "To: " . $MAILTO,
+ "Reply-To: " . $REPLYTO, "", "", @text );
+
+ my $entity = MIME::Entity->build(
+ From => $LOGIN,
+ To => $MAILTO,
+ Subject => "CVS commit: " . $MODULE_NAME,
+ 'Reply-To' => $REPLYTO,
+ Data => join ( "\n", @text )
+ );
+ if ( $RT::MailCommand eq 'sendmailpipe' ) {
+ open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" )
+ || die "Couldn't send mail: " . $@ . "\n";
+ print MAIL $entity->as_string;
+ close(MAIL);
+ }
+ else {
+ $entity->send( $RT::MailCommand, $RT::MailParams );
+ }
+
+}
+
+# }}}
+
+# {{{ sub record_last_dir
+
+sub record_last_dir {
+ my $id = shift;
+ my $dir = shift;
+
+ # make a note of this directory. later, we'll use this to
+ # figure out if we've gone through the whole commit,
+ # for something that is a bad mockery of attomic commits.
+
+ warn "about to write $dir to $LASTDIR_FILE.$id" if ($DEBUG);
+
+ write_file( "$LASTDIR_FILE.$id", $dir );
+}
+
+# }}}
+
+# {{{ Get the RT stuff set up
+
+# {{{ sub rt_handler
+
+sub rt_handler {
+ my (@LogMessage) = (@_);
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ require RT::Ticket;
+
+ #Get the current user all loaded
+ my $CurrentUser = GetCurrentUser();
+
+ if ( !$CurrentUser->Id ) {
+ print
+loc("No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\n");
+ return;
+ }
+
+ my (@commands) = find_commands( \@LogMessage );
+
+ my ( @tickets, @errors );
+
+ # Get the list of tickets we're working with out of commands
+ grep { $_ =~ /^RT-Ticket:\s*(.*?)$/i && push ( @tickets, $1 ) } @commands;
+
+ my $message = new MIME::Entity;
+ $message->build(
+ From => $CurrentUser->EmailAddress,
+ Subject => 'CVS Commit',
+ Data => \@LogMessage
+ );
+
+ # {{{ comment or correspond, as needed
+
+ foreach my $ticket (@tickets) {
+ my $TicketObj = RT::Ticket->new($CurrentUser);
+ $TicketObj->Load($ticket);
+ my ( $id, $msg );
+ unless ( $TicketObj->Id ) {
+ push ( @errors,
+"Couldn't load ticket #$ticket. Not adding commit log to ticket history.\n"
+ );
+ }
+
+ if ( $LogMessage[0] =~ /^(comment|private)$/ ) {
+ ( $id, $msg ) = $TicketObj->Comment( MIMEObj => $message );
+
+ }
+ else {
+ ( $id, $msg ) = $TicketObj->Correspond( MIMEObj => $message );
+ }
+
+ push ( @errors, ">> Log message",
+ "Ticket #" . $TicketObj->Id . ": " . $msg );
+
+ }
+
+ # }}}
+
+ my ($reply) = ActOnPseudoHeaders( $CurrentUser, @commands );
+ print "$reply\n" if ($reply);
+ print join ( "\n", @errors );
+ print "\n";
+
+}
+
+# }}}
+
+# {{{ sub find_commands
+
+sub find_commands {
+ my $lines = shift;
+ my (@pseudoheaders);
+
+ while ( my $line = shift @{$lines} ) {
+ next if $line =~ /^\s*?$/;
+ if ( $line =~ /^RT-/i ) {
+
+ push ( @pseudoheaders, $line );
+ }
+
+ #If we find a line that's not a command, get out.
+ else {
+ unshift ( @{$lines}, $line );
+ last;
+ }
+ }
+
+ return (@pseudoheaders);
+
+}
+
+# }}}
+
+# {{{ sub ActOnPseudoHeaders
+
+=item ActOnPseudoHeaders $PseudoHeaders
+
+Takes a string of pseudo-headers, iterates through them and does what they tell it to.
+
+=cut
+
+sub ActOnPseudoHeaders {
+ my $CurrentUser = shift;
+ my (@actions) = (@_);
+
+ my $ResultsMessage = '';
+ my $Ticket = RT::Ticket->new($CurrentUser);
+
+ foreach my $action (@actions) {
+ my ($val);
+ my $msg = '';
+
+ $ResultsMessage .= ">>> $action\n";
+
+ if ( $action =~ /^RT-(.*?):\s*(.*)$/i ) {
+ my $command = $1;
+ my $args = $2;
+
+ if ( $command =~ /^ticket$/i ) {
+
+ $val = $Ticket->Load($args);
+ unless ($val) {
+ $ResultsMessage .=
+ loc("ERROR: Couldn't load ticket '[_1]': [_2].\n", $1, $msg);
+ . loc("Aborting to avoid unintended ticket modifications.\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+ $ResultsMessage .= loc("Ticket [_1] loaded\n", $Ticket->Id);
+ }
+ else {
+ unless ( $Ticket->Id ) {
+ $ResultsMessage .= loc("No Ticket specified. Aborting ticket ")
+ . loc("modifications\n\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+
+ # Deal with the basics
+ if ( $command =~ /^(Subject|Owner|Status|Queue)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ ( $val, $msg ) = $Ticket->$method($args);
+ }
+
+ # Deal with the dates
+ elsif ( $command =~ /^(due|starts|started|resolved)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ my $date = new RT::Date($CurrentUser);
+ $date->Set( Format => 'unknown', Value => $args );
+ ( $val, $msg ) = $Ticket->$method( $date->ISO );
+ }
+
+ # Deal with the watchers
+ elsif ( $command =~ /^(requestor|requestors|cc|admincc)$/i ) {
+ my $operator = "+";
+ my ($type);
+ if ( $args =~ /^(\+|\-)(.*)$/ ) {
+ $operator = $1;
+ $args = $2;
+ }
+ $type = 'Requestor' if ( $command =~ /^requestor/i );
+ $type = 'Cc' if ( $command =~ /^cc/i );
+ $type = 'AdminCc' if ( $command =~ /^admincc/i );
+
+ my $user = RT::User->new($CurrentUser);
+ $user->Load($args);
+
+ if ($operator eq '+') {
+ ($val, $msg) = $Ticket->AddWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ } elsif ($operator eq '-') {
+ ($val, $msg) = $Ticket->DeleteWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ }
+
+ }
+ $ResultsMessage .= $msg . "\n";
+ }
+
+ }
+ return ($ResultsMessage);
+
+}
+
+# }}}
+
+# {{{ sub untaint
+sub untaint {
+ my $val = shift;
+
+ if ( $val =~ /^([-\#\/\w.]+)$/ ) {
+ $val = $1; # $data now untainted
+ }
+ else {
+ die loc("Bad data in [_1]", $val); # log this somewhere
+ }
+ return ($val);
+}
+
+# }}}
+
+=head1 AUTHOR
+
+
+
+ rt-commit-handler is a rewritten version of the NetBSD commit handler,
+ which was placed in the public domain by Charles Hannum. It bore the following
+ authors statement:
+
+ Contributed by David Hampton <hampton@cisco.com>
+ Hacked greatly by Greg A. Woods <woods@planix.com>
+ Rewritten by Charles M. Hannum <mycroft@netbsd.org>
+
+=cut
+
diff --git a/rt/bin/rt-commit-handler.in b/rt/bin/rt-commit-handler.in
new file mode 100644
index 0000000..02b01ab
--- /dev/null
+++ b/rt/bin/rt-commit-handler.in
@@ -0,0 +1,846 @@
+#!@PERL@ -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+# {{{ Docs
+# -*-Perl-*-
+#
+#ident "@(#)ccvs/contrib:$Name: $:$Id: rt-commit-handler.in,v 1.1 2003-07-15 13:16:15 ivan Exp $"
+#
+# Perl filter to handle the log messages from the checkin of files in multiple
+# directories. This script will group the lists of files by log message, and
+# send one piece of mail per unique message, no matter how many files are
+# committed.
+
+=head1 NAME rt-commit-handler
+
+=head1 USAGE
+
+
+
+=head2 Regular use
+
+Stick the following in in CVSROOT/commitinfo
+
+ ALL @RT_BIN_PATH@/rt-commit-handler --record-last-dir
+
+Stick the following in CVSROOT/loginfo
+
+ ALL @RT_BIN_PATH@/rt-commit-handler --cvs-root /pathtocvs/root --rt %{Vvts}
+
+=head2 Invocation (advanced use)
+
+rt-commit-handler --cvs-root /path/to/cvs/root [-d] [-D] [-r] [-M module] \
+ [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
+
+
+ -d - turn on debugging
+ -m mailto - send mail to "mailto" (multiple)
+ -R replyto - set the "Reply-To:" to "replyto" (multiple)
+ -M modulename - set module name to "modulename"
+ -f logfile - write commit messages to logfile too
+ -D - generate diff commands
+ --rt - invoke RT commit handler
+ --cvs-root - specify your CVS root
+
+ --record-last-dir - Record the last directory with changes in
+ pre-commit (commitinfo) mode
+
+
+=cut
+
+# }}}
+
+use strict;
+use Carp;
+use Getopt::Long;
+use Text::Wrap;
+use Digest::MD5;
+use MIME::Entity;
+
+use lib ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
+
+use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+
+use vars
+ qw(@MAILER $TMPDIR $FILE_PREFIX $LASTDIR_FILE $HASH_FILE $VERSION_FILE $MESSAGE_FILE $MAIL_FILE $DEBUG $MAILTO $REPLYTO $id $MODULE_NAME
+ $LOGIN $COMMITLOG $CVS_ROOT $RT_HANDLER);
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+#Load etc/config.pm and drop privs
+RT::LoadConfig();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+# {{{ Variable setup
+$TMPDIR = '/tmp';
+$FILE_PREFIX = $TMPDIR . '/#cvs.';
+
+# The root of your CVS install. we should get this from some smarter place.
+# It needs a trailing /
+
+$LASTDIR_FILE = $FILE_PREFIX . "lastdir";
+$HASH_FILE = $FILE_PREFIX . "hash";
+$VERSION_FILE = $FILE_PREFIX . "version";
+$MESSAGE_FILE = $FILE_PREFIX . "message";
+$MAIL_FILE = $FILE_PREFIX . "mail";
+
+$DEBUG = 0;
+$RT_HANDLER = 1;
+
+$MAILTO = '';
+
+my @files = ();
+my (@log_lines);
+my $do_diff = 0;
+my $id = getpgrp(); # note, you *must* use a shell which does setpgrp()
+$LOGIN = getpwuid($<);
+
+# }}}
+
+die "User could not be found" unless ($LOGIN);
+
+# {{{ parse command line arguments (file list is seen as one arg)
+#
+while ( my $arg = shift @ARGV ) {
+
+ if ( $arg eq '-d' ) {
+ $DEBUG = 1;
+ warn "Debug turned on...\n";
+ }
+ elsif ( $arg =~ /^--record-last-dir$/i ) {
+ record_last_dir( $id, $ARGV[0] );
+ exit(0);
+ }
+ elsif ( $arg eq '-m' ) {
+ $MAILTO .= ", " if $MAILTO;
+ $MAILTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '--rt' ) {
+ $RT_HANDLER = 1;
+ }
+ elsif ( $arg eq '-R' ) {
+ $REPLYTO .= ", " if $REPLYTO;
+ $REPLYTO .= shift @ARGV;
+ }
+ elsif ( $arg eq '-M' ) {
+ die ("too many '-M' args\n") if $MODULE_NAME;
+ $MODULE_NAME = shift @ARGV;
+ }
+ elsif ( $arg eq '--cvs-root' ) {
+ $CVS_ROOT = shift @ARGV;
+ $CVS_ROOT .= "/" unless ( $CVS_ROOT =~ /\/$/ );
+ }
+ elsif ( $arg eq '-f' ) {
+ die ("too many '-f' args\n") if $COMMITLOG;
+ $COMMITLOG = shift @ARGV;
+
+ # This is a disgusting hack to untaint $COMMITLOG if we're running from
+ # setgid cvs.
+ $COMMITLOG = untaint($COMMITLOG);
+ }
+ elsif ( $arg eq '-D' ) {
+ $do_diff = 1;
+ }
+ else {
+ @files = split ( ' ', $arg );
+ last;
+ }
+}
+
+# }}}
+
+$REPLYTO = $LOGIN unless ($REPLYTO);
+
+# for now, the first "file" is the repository directory being committed,
+# relative to the $CVSROOT location
+#
+my $dir = shift @files;
+
+# XXX there are some ugly assumptions in here about module names and
+# XXX directories relative to the $CVSROOT location -- really should
+# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
+# XXX we have to parse it backwards.
+#
+# XXX For now we set the `module' name to the top-level directory name.
+#
+unless ($MODULE_NAME) {
+ ($MODULE_NAME) = split ( '/', $dir, 2 );
+}
+
+if ($DEBUG) {
+ warn "module - ", $MODULE_NAME, "\n";
+ warn "dir - ", $dir, "\n";
+ warn "files - ", join ( " ", @files ), "\n";
+ warn "id - ", $id, "\n";
+}
+
+# {{{ Check for a new directory or an import command.
+
+#
+# files[0] - "-"
+# files[1] - "New"
+# files[2] - "directory"
+#
+# files[0] - "-"
+# files[1] - "Imported"
+# files[2] - "sources"
+#
+if ( $files[0] eq "-" ) {
+
+ #we just don't care about New Directory notes
+ unless ( $files[1] eq "New" && $files[2] eq "directory" ) {
+
+ my @text = ();
+
+ push @text, build_header();
+ push @text, "";
+
+ while ( my $line = <STDIN> ) {
+ chop $line; # Drop the newline
+ push @text, $line;
+ }
+
+ append_logfile( $COMMITLOG, @text ) if ($COMMITLOG);
+
+ mail_notification( $id, @text );
+ }
+
+ exit 0;
+}
+
+# }}}
+
+# {{{ Collect just the log message from stdin.
+#
+
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ last if ( $line =~ /^Log Message:$/ );
+}
+while ( my $line = <STDIN> ) {
+ chop $line; # strip the newline
+ $line =~ s/\s+$//; # strip trailing white space
+ push @log_lines, $line;
+}
+
+my $md5 = Digest::MD5->new();
+foreach my $line (@log_lines) {
+ $md5->add( $line . "\n" );
+}
+my $hash = $md5->hexdigest();
+
+warn "hash = $hash\n" if ($DEBUG);
+
+if ( !-e "$MESSAGE_FILE.$id.$hash" ) {
+ append_logfile( "$HASH_FILE.$id", $hash );
+ write_file( "$MESSAGE_FILE.$id.$hash", @log_lines );
+}
+
+# }}}
+
+# Spit out the information gathered in this pass.
+
+append_logfile( "$VERSION_FILE.$id.$hash", $dir . '/', @files );
+
+# {{{ Check whether this is the last directory. If not, quit.
+
+warn "Checking current dir against last dir $LASTDIR_FILE.$id\n" if ($DEBUG);
+
+my @last_dir = read_file("$LASTDIR_FILE.$id");
+
+unless ($CVS_ROOT) {
+ die "No cvs root specified with --cvs-root. Can't continue.";
+}
+
+if ( $last_dir[0] ne $CVS_ROOT . $dir ) {
+ warn "Current directory $CVS_ROOT$dir is not last directory $last_dir[0].\n"
+ if ($DEBUG);
+ exit 0;
+}
+
+# }}}
+
+# {{{ End Of Commits!
+#
+
+# This is it. The commits are all finished. Lump everything together
+# into a single message, fire a copy off to the mailing list, and drop
+# it on the end of the Changes file.
+#
+
+#
+# Produce the final compilation of the log messages
+#
+
+my @hashes = read_file("$HASH_FILE.$id");
+my (@text);
+
+push @text, build_header();
+push @text, "";
+
+my ( @added_files, @modified_files, @removed_files );
+
+foreach my $hash (@hashes) {
+
+ # In case we're running setgid, make sure the hash file hasn't been hacked.
+ $hash =~ m/([a-z0-9]*)/ || die "*** Hacking attempt detected\n";
+ $hash = $1;
+
+ my @files = read_file("$VERSION_FILE.$id.$hash");
+ my @log_lines = read_file("$MESSAGE_FILE.$id.$hash");
+
+ my $working_on_dir; # gets set as we iterate through the files.
+ foreach my $file (@files) {
+
+ #If we've entered a new directory, make a note of that and remove the trailing /
+
+ if ( $file =~ s'\/$'' ) {
+ $working_on_dir = $file;
+ next;
+ }
+
+ my @file_entry = ( split ( ',', $file, 4 ), $working_on_dir );
+
+ # file_entry looks like ths:
+
+ # 0 1 2 3 4
+ # Old rev : new rev : tag: file :directory
+ my $entry = {};
+ $entry->{'old'} = $file_entry[0];
+ $entry->{'new'} = $file_entry[1];
+ $entry->{'tag'} = $file_entry[2];
+ $entry->{'file'} = $file_entry[3];
+ $entry->{'dir'} = $file_entry[4];
+
+ if ( $file_entry[0] eq 'NONE' ) {
+ $entry->{'old'} = '0';
+ push @added_files, $entry;
+ }
+ elsif ( $file_entry[1] eq 'NONE' ) {
+ $entry->{'new'} = '0';
+ push @removed_files, $entry;
+ }
+ else {
+ push @modified_files, $entry;
+ }
+ }
+}
+
+# }}}
+
+# {{{ start building up the body
+
+# Strip leading and trailing blank lines from the log message. Also
+# compress multiple blank lines in the body of the message down to a
+# single blank line.
+#
+
+my $blank = 1;
+@log_lines = map {
+ my $wasblank = $blank;
+ $blank = $_ eq '';
+ $blank && $wasblank ? () : $_;
+} @log_lines;
+
+pop @log_lines if $blank;
+
+@modified_files = order_and_summarize_diffs(@modified_files);
+@added_files = order_and_summarize_diffs(@added_files);
+@removed_files = order_and_summarize_diffs(@removed_files);
+
+push @text, "Modified Files:", format_lists(@modified_files)
+ if (@modified_files);
+
+push @text, "Added Files:", format_lists(@added_files) if (@added_files);
+
+push @text, "Removed Files:", format_lists(@removed_files) if (@removed_files);
+
+push @text, "", "Log Message", @log_lines if (@log_lines);
+
+push @text, "";
+
+if ($RT_HANDLER) {
+ rt_handler(
+ @log_lines, "\n",
+ loc("To generate a diff of this commit:\n"), "\n",
+ format_diffs( @modified_files, @added_files, @removed_files )
+ );
+}
+
+if ($COMMITLOG) {
+ append_logfile( $COMMITLOG, @text );
+}
+
+if ($do_diff) {
+ push @text, "";
+ push @text, loc("To generate a diff of this commit:");
+ push @text, format_diffs( @modified_files, @added_files, @removed_files );
+ push @text, "";
+}
+
+# }}}
+
+# {{{ Mail out the notification.
+
+mail_notification( $id, @text );
+
+# }}}
+
+# {{{ clean up
+
+unless ($DEBUG) {
+ $hash = untaint($hash);
+ $id = untaint($id);
+ unlink "$VERSION_FILE.$id.$hash";
+ unlink "$MESSAGE_FILE.$id.$hash";
+ unlink "$MAIL_FILE.$id";
+ unlink "$LASTDIR_FILE.$id";
+ unlink "$HASH_FILE.$id";
+}
+
+# }}}
+
+exit 0;
+
+# {{{ Subroutines
+#
+
+# {{{ append_logfile
+sub append_logfile {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">>$filename" )
+ || die ("Cannot open file $filename for append.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ write_file
+sub write_file {
+ my $filename = shift;
+ my (@lines) = @_;
+
+ $filename = untaint($filename);
+
+ open( FILE, ">$filename" )
+ || die ("Cannot open file $filename for write.\n");
+ foreach my $line (@lines) {
+ print FILE $line . "\n";
+ }
+ close(FILE);
+}
+
+# }}}
+
+# {{{ read_file
+sub read_file {
+ my $filename = shift;
+ my (@lines);
+
+ open( FILE, "<$filename" )
+ || die ("Cannot open file $filename for read.\n");
+ while ( my $line = <FILE> ) {
+ chop $line;
+ push @lines, $line;
+ }
+ close(FILE);
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub format_lists
+
+sub format_lists {
+ my @items = (@_);
+
+ my $files = "";
+ map {
+ $_->{'files'} && ( $files .= ' ' . join ( ' ', @{ $_->{'files'} } ) );
+ } @items;
+
+ my @lines = wrap( "\t", "\t\t", $files );
+ return (@lines);
+
+}
+
+# }}}
+
+# {{{ sub format_diffs
+
+sub format_diffs {
+ my @items = (@_);
+
+ my @lines;
+ foreach my $item (@items) {
+ next unless ( $item->{'files'} );
+ push ( @lines,
+ "cvs diff -r"
+ . $item->{'old'} . " -r"
+ . $item->{'new'} . " "
+ . join ( " ", @{ $item->{'files'} } ) . "\n" );
+
+ }
+
+ @lines = fill( "\t", "\t\t", @lines );
+
+ return (@lines);
+}
+
+# }}}
+
+# {{{ sub order_and_summarize_diffs {
+
+# takes an array of file items
+# returns a sorted array of fileset items, which are like file items, except they can have an array of files, rather than
+# a singleton file.
+
+sub order_and_summarize_diffs {
+
+ my @files = (@_);
+
+ # Sort by tag, dir, file.
+ @files = sort {
+ $a->{'tag'} cmp $b->{'tag'}
+ || $a->{'dir'} cmp $b->{'dir'}
+ || $a->{'file'} cmp $b->{'file'};
+ } @files;
+
+ # Combine adjacent rows that are the same modulo the file name.
+
+ my @items = (undef);
+
+ foreach my $file (@files) {
+ if ( $#items == -1 #if it's empty
+ || ( !defined $items[-1]->{'old'}
+ || $items[-1]->{'old'} ne $file->{'old'} )
+ || ( !defined $items[-1]->{'new'}
+ || $items[-1]->{'new'} ne $file->{'new'} )
+ || ( !defined $items[-1]->{'tag'}
+ || $items[-1]->{'tag'} ne $file->{'tag'} ) )
+ {
+
+ push ( @items, $file );
+ }
+ push ( @{ $items[-1]->{'files'} },
+ $file->{'dir'} . "/" . $file->{'file'} );
+ }
+
+ return (@items);
+}
+
+# }}}
+
+# {{{ build_header
+
+sub build_header {
+ my $now = gmtime;
+ my $header =
+ sprintf( "Module Name:\t%s\nCommitted By:\t%s\nDate:\t\t%s %s %s",
+ $MODULE_NAME, $LOGIN, substr( $now, 0, 19 ), "UTC",
+ substr( $now, 20, 4 ) );
+ return ($header);
+}
+
+# }}}
+
+# {{{ mail_notification
+sub mail_notification {
+ my $id = shift;
+ my (@text) = @_;
+ write_file( "$MAIL_FILE.$id", "From: " . $LOGIN,
+ "Subject: CVS commit: " . $MODULE_NAME, "To: " . $MAILTO,
+ "Reply-To: " . $REPLYTO, "", "", @text );
+
+ my $entity = MIME::Entity->build(
+ From => $LOGIN,
+ To => $MAILTO,
+ Subject => "CVS commit: " . $MODULE_NAME,
+ 'Reply-To' => $REPLYTO,
+ Data => join ( "\n", @text )
+ );
+ if ( $RT::MailCommand eq 'sendmailpipe' ) {
+ open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" )
+ || die "Couldn't send mail: " . $@ . "\n";
+ print MAIL $entity->as_string;
+ close(MAIL);
+ }
+ else {
+ $entity->send( $RT::MailCommand, $RT::MailParams );
+ }
+
+}
+
+# }}}
+
+# {{{ sub record_last_dir
+
+sub record_last_dir {
+ my $id = shift;
+ my $dir = shift;
+
+ # make a note of this directory. later, we'll use this to
+ # figure out if we've gone through the whole commit,
+ # for something that is a bad mockery of attomic commits.
+
+ warn "about to write $dir to $LASTDIR_FILE.$id" if ($DEBUG);
+
+ write_file( "$LASTDIR_FILE.$id", $dir );
+}
+
+# }}}
+
+# {{{ Get the RT stuff set up
+
+# {{{ sub rt_handler
+
+sub rt_handler {
+ my (@LogMessage) = (@_);
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ require RT::Ticket;
+
+ #Get the current user all loaded
+ my $CurrentUser = GetCurrentUser();
+
+ if ( !$CurrentUser->Id ) {
+ print
+loc("No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\n");
+ return;
+ }
+
+ my (@commands) = find_commands( \@LogMessage );
+
+ my ( @tickets, @errors );
+
+ # Get the list of tickets we're working with out of commands
+ grep { $_ =~ /^RT-Ticket:\s*(.*?)$/i && push ( @tickets, $1 ) } @commands;
+
+ my $message = new MIME::Entity;
+ $message->build(
+ From => $CurrentUser->EmailAddress,
+ Subject => 'CVS Commit',
+ Data => \@LogMessage
+ );
+
+ # {{{ comment or correspond, as needed
+
+ foreach my $ticket (@tickets) {
+ my $TicketObj = RT::Ticket->new($CurrentUser);
+ $TicketObj->Load($ticket);
+ my ( $id, $msg );
+ unless ( $TicketObj->Id ) {
+ push ( @errors,
+"Couldn't load ticket #$ticket. Not adding commit log to ticket history.\n"
+ );
+ }
+
+ if ( $LogMessage[0] =~ /^(comment|private)$/ ) {
+ ( $id, $msg ) = $TicketObj->Comment( MIMEObj => $message );
+
+ }
+ else {
+ ( $id, $msg ) = $TicketObj->Correspond( MIMEObj => $message );
+ }
+
+ push ( @errors, ">> Log message",
+ "Ticket #" . $TicketObj->Id . ": " . $msg );
+
+ }
+
+ # }}}
+
+ my ($reply) = ActOnPseudoHeaders( $CurrentUser, @commands );
+ print "$reply\n" if ($reply);
+ print join ( "\n", @errors );
+ print "\n";
+
+}
+
+# }}}
+
+# {{{ sub find_commands
+
+sub find_commands {
+ my $lines = shift;
+ my (@pseudoheaders);
+
+ while ( my $line = shift @{$lines} ) {
+ next if $line =~ /^\s*?$/;
+ if ( $line =~ /^RT-/i ) {
+
+ push ( @pseudoheaders, $line );
+ }
+
+ #If we find a line that's not a command, get out.
+ else {
+ unshift ( @{$lines}, $line );
+ last;
+ }
+ }
+
+ return (@pseudoheaders);
+
+}
+
+# }}}
+
+# {{{ sub ActOnPseudoHeaders
+
+=item ActOnPseudoHeaders $PseudoHeaders
+
+Takes a string of pseudo-headers, iterates through them and does what they tell it to.
+
+=cut
+
+sub ActOnPseudoHeaders {
+ my $CurrentUser = shift;
+ my (@actions) = (@_);
+
+ my $ResultsMessage = '';
+ my $Ticket = RT::Ticket->new($CurrentUser);
+
+ foreach my $action (@actions) {
+ my ($val);
+ my $msg = '';
+
+ $ResultsMessage .= ">>> $action\n";
+
+ if ( $action =~ /^RT-(.*?):\s*(.*)$/i ) {
+ my $command = $1;
+ my $args = $2;
+
+ if ( $command =~ /^ticket$/i ) {
+
+ $val = $Ticket->Load($args);
+ unless ($val) {
+ $ResultsMessage .=
+ loc("ERROR: Couldn't load ticket '[_1]': [_2].\n", $1, $msg);
+ . loc("Aborting to avoid unintended ticket modifications.\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+ $ResultsMessage .= loc("Ticket [_1] loaded\n", $Ticket->Id);
+ }
+ else {
+ unless ( $Ticket->Id ) {
+ $ResultsMessage .= loc("No Ticket specified. Aborting ticket ")
+ . loc("modifications\n\n")
+ . loc("The following commands were not proccessed:\n\n")
+ . join ( "\n", @actions );
+ return ($ResultsMessage);
+ }
+
+ # Deal with the basics
+ if ( $command =~ /^(Subject|Owner|Status|Queue)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ ( $val, $msg ) = $Ticket->$method($args);
+ }
+
+ # Deal with the dates
+ elsif ( $command =~ /^(due|starts|started|resolved)$/i ) {
+ my $method = 'Set' . ucfirst( lc($1) );
+ my $date = new RT::Date($CurrentUser);
+ $date->Set( Format => 'unknown', Value => $args );
+ ( $val, $msg ) = $Ticket->$method( $date->ISO );
+ }
+
+ # Deal with the watchers
+ elsif ( $command =~ /^(requestor|requestors|cc|admincc)$/i ) {
+ my $operator = "+";
+ my ($type);
+ if ( $args =~ /^(\+|\-)(.*)$/ ) {
+ $operator = $1;
+ $args = $2;
+ }
+ $type = 'Requestor' if ( $command =~ /^requestor/i );
+ $type = 'Cc' if ( $command =~ /^cc/i );
+ $type = 'AdminCc' if ( $command =~ /^admincc/i );
+
+ my $user = RT::User->new($CurrentUser);
+ $user->Load($args);
+
+ if ($operator eq '+') {
+ ($val, $msg) = $Ticket->AddWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ } elsif ($operator eq '-') {
+ ($val, $msg) = $Ticket->DeleteWatcher( Type => $type,
+ PrincipalId => $user->PrincipalId);
+ }
+
+ }
+ $ResultsMessage .= $msg . "\n";
+ }
+
+ }
+ return ($ResultsMessage);
+
+}
+
+# }}}
+
+# {{{ sub untaint
+sub untaint {
+ my $val = shift;
+
+ if ( $val =~ /^([-\#\/\w.]+)$/ ) {
+ $val = $1; # $data now untainted
+ }
+ else {
+ die loc("Bad data in [_1]", $val); # log this somewhere
+ }
+ return ($val);
+}
+
+# }}}
+
+=head1 AUTHOR
+
+
+
+ rt-commit-handler is a rewritten version of the NetBSD commit handler,
+ which was placed in the public domain by Charles Hannum. It bore the following
+ authors statement:
+
+ Contributed by David Hampton <hampton@cisco.com>
+ Hacked greatly by Greg A. Woods <woods@planix.com>
+ Rewritten by Charles M. Hannum <mycroft@netbsd.org>
+
+=cut
+
diff --git a/rt/bin/rt-crontool b/rt/bin/rt-crontool
new file mode 100644
index 0000000..cdbc3cb
--- /dev/null
+++ b/rt/bin/rt-crontool
@@ -0,0 +1,220 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use Carp;
+
+use lib ("/opt/rt3/lib", "/opt/rt3/local/lib");
+
+package RT;
+
+use Getopt::Long;
+
+use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+use RT::Tickets;
+use RT::Template;
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+# Load the config file
+RT::LoadConfig();
+
+#Connect to the database and get RT::SystemUser and RT::Nobody loaded
+RT::Init();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+#Get the current user all loaded
+my $CurrentUser = GetCurrentUser();
+
+unless ( $CurrentUser->Id ) {
+ print loc("No RT user found. Please consult your RT administrator.\n");
+ exit(1);
+}
+
+my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
+ $template_id, $help, $verbose );
+GetOptions( "search=s" => \$search,
+ "search-arg=s" => \$search_arg,
+ "condition=s" => \$condition,
+ "condition-arg=s" => \$condition_arg,
+ "action-arg=s" => \$action_arg,
+ "action=s" => \$action,
+ "template-id=s" => \$template_id,
+ "help" => \$help,
+ "verbose|v" => \$verbose );
+
+help() if $help;
+
+# We _must_ have a search object
+load_module($search);
+load_module($action) if ($action);
+load_module($condition) if ($condition);
+
+# load template if specified
+my $template_obj;
+if ($template_id) {
+ $template_obj = RT::Template->new($RT::Nobody);
+ $template_obj->LoadById($template_id);
+}
+
+#At the appointed time:
+
+#find a bunch of tickets
+my $tickets = RT::Tickets->new($CurrentUser);
+my $search = $search->new( TicketsObj => $tickets, Argument => $search_arg );
+
+$search->Prepare();
+
+# TicketsFound is an RT::Tickets object
+my $tickets = $search->TicketsObj;
+
+#for each ticket we've found
+while ( my $ticket = $tickets->Next() ) {
+ print "\n" . $ticket->Id() . ": " if ($verbose);
+
+ # perform some more advanced check
+ if ($condition) {
+ my $condition_obj = $condition->new( TicketObj => $ticket,
+ Argument => $condition_arg );
+
+ # if the condition doesn't apply, get out of here
+
+ next unless ( $condition_obj->IsApplicable );
+ print loc("Condition matches...") if ($verbose);
+ }
+
+ #prepare our action
+ my $action_obj = $action->new( TicketObj => $ticket,
+ TemplateObj => $template_obj,
+ Argument => $action_arg );
+
+ #if our preparation, move onto the next ticket
+ next unless ( $action_obj->Prepare );
+ print loc("Action prepared...") if ($verbose);
+
+ #commit our action.
+ next unless ( $action_obj->Commit );
+ print loc("Action committed.") if ($verbose);
+}
+
+# {{{ load_module
+
+=head2 load_module
+
+Loads a perl module, dying nicely if it can't find it.
+
+=cut
+
+sub load_module {
+ my $modname = shift;
+ eval "require $modname";
+ if ($@) {
+ die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
+ }
+
+}
+
+# }}}
+
+# {{{ loc
+
+=head2 loc LIST
+
+Localize this string, with the current user's currentuser object
+
+=cut
+
+sub loc {
+ $CurrentUser->loc(@_);
+}
+
+# }}}
+
+sub help {
+
+ print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
+ . "\n";
+ print loc("It takes several arguments:") . "\n\n";
+
+ print " "
+ . loc( "[_1] - Specify the search module you want to use", "--search" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--search-argument", "--search" )
+ . "\n";
+
+ print " "
+ . loc( "[_1] - Specify the condition module you want to use", "--condition" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--condition-argument", "--condition" )
+ . "\n";
+ print " "
+ . loc( "[_1] - Specify the action module you want to use", "--action" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--action-argument", "--action" )
+ . "\n";
+ print " "
+ . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
+ print "\n";
+ print "\n";
+ print loc("Security:")."\n";
+ print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
+ loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
+ loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
+ loc("It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool.")."\n";
+ print "\n";
+ print loc("Example:");
+ print "\n";
+ print " "
+ . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+ )
+ . "\n\n";
+
+ print " bin/rt-cron-tool \\\n";
+ print
+ " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
+ print
+ " --condition RT::Condition::UntouchedInHours --condition-arg 4 \\\n";
+ print " --action RT::Action::SetPriority --action-arg 99 \\\n";
+ print " --verbose\n";
+
+ print "\n";
+ print loc("Escalate tickets");
+ print "rt-crontool \\\n";
+ print " --search RT::Search::ActiveTicketsInQueue --search-arg thequeuename \\\n";
+ print " --action RT::Action::EscalatePriority \\\n";
+
+
+
+
+
+
+ exit(0);
+}
diff --git a/rt/bin/rt-crontool.in b/rt/bin/rt-crontool.in
new file mode 100644
index 0000000..8ecc718
--- /dev/null
+++ b/rt/bin/rt-crontool.in
@@ -0,0 +1,220 @@
+#!@PERL@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use Carp;
+
+use lib ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
+
+package RT;
+
+use Getopt::Long;
+
+use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
+use RT::Tickets;
+use RT::Template;
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+# Load the config file
+RT::LoadConfig();
+
+#Connect to the database and get RT::SystemUser and RT::Nobody loaded
+RT::Init();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+#Get the current user all loaded
+my $CurrentUser = GetCurrentUser();
+
+unless ( $CurrentUser->Id ) {
+ print loc("No RT user found. Please consult your RT administrator.\n");
+ exit(1);
+}
+
+my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
+ $template_id, $help, $verbose );
+GetOptions( "search=s" => \$search,
+ "search-arg=s" => \$search_arg,
+ "condition=s" => \$condition,
+ "condition-arg=s" => \$condition_arg,
+ "action-arg=s" => \$action_arg,
+ "action=s" => \$action,
+ "template-id=s" => \$template_id,
+ "help" => \$help,
+ "verbose|v" => \$verbose );
+
+help() if $help;
+
+# We _must_ have a search object
+load_module($search);
+load_module($action) if ($action);
+load_module($condition) if ($condition);
+
+# load template if specified
+my $template_obj;
+if ($template_id) {
+ $template_obj = RT::Template->new($RT::Nobody);
+ $template_obj->LoadById($template_id);
+}
+
+#At the appointed time:
+
+#find a bunch of tickets
+my $tickets = RT::Tickets->new($CurrentUser);
+my $search = $search->new( TicketsObj => $tickets, Argument => $search_arg );
+
+$search->Prepare();
+
+# TicketsFound is an RT::Tickets object
+my $tickets = $search->TicketsObj;
+
+#for each ticket we've found
+while ( my $ticket = $tickets->Next() ) {
+ print "\n" . $ticket->Id() . ": " if ($verbose);
+
+ # perform some more advanced check
+ if ($condition) {
+ my $condition_obj = $condition->new( TicketObj => $ticket,
+ Argument => $condition_arg );
+
+ # if the condition doesn't apply, get out of here
+
+ next unless ( $condition_obj->IsApplicable );
+ print loc("Condition matches...") if ($verbose);
+ }
+
+ #prepare our action
+ my $action_obj = $action->new( TicketObj => $ticket,
+ TemplateObj => $template_obj,
+ Argument => $action_arg );
+
+ #if our preparation, move onto the next ticket
+ next unless ( $action_obj->Prepare );
+ print loc("Action prepared...") if ($verbose);
+
+ #commit our action.
+ next unless ( $action_obj->Commit );
+ print loc("Action committed.") if ($verbose);
+}
+
+# {{{ load_module
+
+=head2 load_module
+
+Loads a perl module, dying nicely if it can't find it.
+
+=cut
+
+sub load_module {
+ my $modname = shift;
+ eval "require $modname";
+ if ($@) {
+ die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
+ }
+
+}
+
+# }}}
+
+# {{{ loc
+
+=head2 loc LIST
+
+Localize this string, with the current user's currentuser object
+
+=cut
+
+sub loc {
+ $CurrentUser->loc(@_);
+}
+
+# }}}
+
+sub help {
+
+ print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
+ . "\n";
+ print loc("It takes several arguments:") . "\n\n";
+
+ print " "
+ . loc( "[_1] - Specify the search module you want to use", "--search" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--search-argument", "--search" )
+ . "\n";
+
+ print " "
+ . loc( "[_1] - Specify the condition module you want to use", "--condition" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--condition-argument", "--condition" )
+ . "\n";
+ print " "
+ . loc( "[_1] - Specify the action module you want to use", "--action" )
+ . "\n";
+ print " "
+ . loc( "[_1] - An argument to pass to [_2]", "--action-argument", "--action" )
+ . "\n";
+ print " "
+ . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
+ print "\n";
+ print "\n";
+ print loc("Security:")."\n";
+ print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
+ loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
+ loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
+ loc("It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool.")."\n";
+ print "\n";
+ print loc("Example:");
+ print "\n";
+ print " "
+ . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+ )
+ . "\n\n";
+
+ print " bin/rt-cron-tool \\\n";
+ print
+ " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
+ print
+ " --condition RT::Condition::UntouchedInHours --condition-arg 4 \\\n";
+ print " --action RT::Action::SetPriority --action-arg 99 \\\n";
+ print " --verbose\n";
+
+ print "\n";
+ print loc("Escalate tickets");
+ print "rt-crontool \\\n";
+ print " --search RT::Search::ActiveTicketsInQueue --search-arg thequeuename \\\n";
+ print " --action RT::Action::EscalatePriority \\\n";
+
+
+
+
+
+
+ exit(0);
+}
diff --git a/rt/bin/rt-mailgate b/rt/bin/rt-mailgate
new file mode 100755
index 0000000..8af8002
--- /dev/null
+++ b/rt/bin/rt-mailgate
@@ -0,0 +1,648 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+=head1 NAME
+
+rt-mailgate - Mail interface to RT3.
+
+=begin testing
+
+use RT::I18N;
+
+# Make sure that when we call the mailgate wrong, it tempfails
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://bad.address"), "Opened the mailgate - The error below is expected - $@");
+print MAIL <<EOF;
+From: root\@localhost
+To: rt\@example.com
+Subject: This is a test of new ticket creation
+
+Foob!
+EOF
+close (MAIL);
+
+# Check the return value
+is ( $? >> 8, 75, "The error message above is expected The mail gateway exited with a failure. yay");
+
+
+# {{{ Test new ticket creation by root who is privileged and superuser
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: root\@localhost
+To: rt\@example.com
+Subject: This is a test of new ticket creation
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+use RT::Tickets;
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok (UNIVERSAL::isa($tick,'RT::Ticket'));
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'This is a test of new ticket creation', "Created the ticket");
+
+# }}}
+
+
+# {{{This is a test of new ticket creation as an unknown user
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist\@example.com
+To: rt\@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+$tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject ne 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
+my $u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by failed ticket submission");
+
+
+# }}}
+
+# {{{ now everybody can create tickets. can a random unkown user create tickets?
+
+my $g = RT::Group->new($RT::SystemUser);
+$g->LoadSystemInternalGroup('Everyone');
+ok( $g->Id, "Found 'everybody'");
+
+my ($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+ok ($val, "Granted everybody the right to create tickets - $msg");
+
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist\@example.com
+To: rt\@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+
+$tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+$tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
+my $u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist@example.com');
+ok( $u->Id != 0, " user does not exist and was created by ticket submission");
+
+# }}}
+
+
+# {{{ can another random reply to a ticket without being granted privs? answer should be no.
+
+
+#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+#ok ($val, "Granted everybody the right to create tickets - $msg");
+#sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-2\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-2@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by ticket correspondence submission");
+# }}}
+# {{{ can another random reply to a ticket after being granted privs? answer should be yes
+
+
+($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'ReplyToTicket');
+ok ($val, "Granted everybody the right to reply to tickets - $msg");
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-2\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-2@example.com');
+ok( $u->Id != 0, " user exists and was created by ticket correspondence submission");
+
+# }}}
+
+# {{{ can another random comment on a ticket without being granted privs? answer should be no.
+
+
+#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+#ok ($val, "Granted everybody the right to create tickets - $msg");
+#sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-3\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-3@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by ticket comment submission");
+
+# }}}
+# {{{ can another random reply to a ticket after being granted privs? answer should be yes
+
+
+($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CommentOnTicket');
+ok ($val, "Granted everybody the right to reply to tickets - $msg");
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-3\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-3@example.com');
+ok( $u->Id != 0, " user exists and was created by ticket comment submission");
+
+# }}}
+
+# {{{ Testing preservation of binary attachments
+
+# Get a binary blob (Best Practical logo)
+
+# Create a mime entity with an attachment
+
+use MIME::Entity;
+my $entity = MIME::Entity->build( From => 'root@localhost',
+ To => 'rt@localhost',
+ Subject => 'binary attachment test',
+ Data => ['This is a test of a binary attachment']);
+
+# currently in lib/t/autogen
+$entity->attach(Path => '/opt/rt3/share/html/NoAuth/images/spacer.gif',
+ Type => 'image/gif',
+ Encoding => 'base64');
+
+# Create a ticket with a binary attachment
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+$entity->print(\*MAIL);
+
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+ $tick = $tickets->First();
+ok (UNIVERSAL::isa($tick,'RT::Ticket'));
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'binary attachment test', "Created the ticket - ".$tick->Id);
+
+my $file = `cat ../../../html/NoAuth/images/spacer.gif`;
+ok ($file, "Read in the logo image");
+
+
+ use Digest::MD5;
+warn "for the raw file the content is ".Digest::MD5::md5_base64($file);
+
+
+
+# Verify that the binary attachment is valid in the database
+my $attachments = RT::Attachments->new($RT::SystemUser);
+$attachments->Limit(FIELD => 'ContentType', VALUE => 'image/gif');
+ok ($attachments->Count == 1, 'Found only one gif in the database');
+my $attachment = $attachments->First;
+my $acontent = $attachment->Content;
+
+ warn "coming from the database, the content is ".Digest::MD5::md5_base64($acontent);
+
+is( $acontent, $file, 'The attachment isn\'t screwed up in the database.');
+# Log in as root
+use Getopt::Long;
+use LWP::UserAgent;
+
+
+# Grab the binary attachment via the web ui
+my $ua = LWP::UserAgent->new();
+
+my $full_url = "http://localhost".$RT::WebPath."/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/spacer.gif?&user=root&pass=password";
+my $r = $ua->get( $full_url);
+
+
+# Verify that the downloaded attachment is the same as what we uploaded.
+is($file, $r->content, 'The attachment isn\'t screwed up in download');
+
+
+
+# }}}
+
+# {{{ Simple I18N testing
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+print MAIL <<EOF;
+From: root\@localhost
+To: rtemail\@example.com
+Subject: This is a test of I18N ticket creation
+Content-Type: text/plain; charset="utf-8"
+
+2 accented lines
+\303\242\303\252\303\256\303\264\303\273
+\303\241\303\251\303\255\303\263\303\272
+bye
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $unitickets = RT::Tickets->new($RT::SystemUser);
+$unitickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$unitickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $unitick = $unitickets->First();
+ok (UNIVERSAL::isa($unitick,'RT::Ticket'));
+ok ($unitick->Id, "found ticket ".$unitick->Id);
+ok ($unitick->Subject eq 'This is a test of I18N ticket creation', "Created the ticket - ". $unitick->Subject);
+
+
+
+my $unistring = "\303\241\303\251\303\255\303\263\303\272";
+Encode::_utf8_on($unistring);
+is ($unitick->Transactions->First->Content, $unitick->Transactions->First->Attachments->First->Content, "Content is ". $unitick->Transactions->First->Attachments->First->Content);
+ok($unitick->Transactions->First->Attachments->First->Content =~ /$unistring/i, $unitick->Id." appears to be unicode ". $unitick->Transactions->First->Attachments->First->Id);
+# supposedly I18N fails on the second message sent in.
+
+ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+print MAIL <<EOF;
+From: root\@localhost
+To: rtemail\@example.com
+Subject: This is a test of I18N ticket creation
+Content-Type: text/plain; charset="utf-8"
+
+2 accented lines
+\303\242\303\252\303\256\303\264\303\273
+\303\241\303\251\303\255\303\263\303\272
+bye
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $tickets2 = RT::Tickets->new($RT::SystemUser);
+$tickets2->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets2->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $tick2 = $tickets2->First();
+ok (UNIVERSAL::isa($tick2,'RT::Ticket'));
+ok ($tick2->Id, "found ticket ".$tick2->Id);
+ok ($tick2->Subject eq 'This is a test of I18N ticket creation', "Created the ticket");
+
+
+
+my $unistring = "\303\241\303\251\303\255\303\263\303\272";
+Encode::_utf8_on($unistring);
+
+ok ($tick2->Transactions->First->Content =~ $unistring, "It appears to be unicode - ".$tick2->Transactions->First->Content);
+
+# }}}
+
+
+($val,$msg) = $g->PrincipalObj->RevokeRight(Right => 'CreateTicket');
+ok ($val, $msg);
+
+
+
+=end testing
+
+=cut
+
+
+use strict;
+use Getopt::Long;
+use LWP::UserAgent;
+
+use constant EX_TEMPFAIL => 75;
+
+my %opts;
+GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" );
+
+if ( $opts{help} ) {
+ require Pod::Usage;
+ import Pod::Usage;
+ pod2usage("RT Mail Gateway\n");
+ exit 1; # Don't want to succeed if this is really an email!
+}
+
+for (qw(url)) {
+ die "$0 invoked improperly\n\nNo $_ provided to mail gateway!\n" unless $opts{$_};
+}
+
+undef $/;
+my $ua = LWP::UserAgent->new();
+$ua->cookie_jar( { file => $opts{jar} } );
+
+my %args = (
+ queue => $opts{queue},
+ action => $opts{action},
+ SessionType => 'REST', # Surpress login box
+);
+
+# Read the message in from STDIN
+$args{'message'} = <>;
+
+
+if ($opts{'extension'}) {
+ $args{$opts{'extension'}} = $ENV{'EXTENSION'};
+}
+
+# Set up cookie here.
+
+my $full_url = $opts{'url'}. "/REST/1.0/NoAuth/mail-gateway";
+warn "Connecting to $full_url" if $opts{'debug'};
+
+
+
+$ua->timeout(exists($opts{'timeout'}) ? $opts{'timeout'} : 180);
+my $r = $ua->post( $full_url, {%args} );
+check_failure($r);
+
+my $content = $r->content;
+warn $content if ($opts{debug});
+
+if ( $content !~ /^(ok|not ok)/ ) {
+
+ # It's not the server's fault if the mail is bogus. We just want to know that
+ # *something* came out of the server.
+ warn <<EOF;
+RT server error.
+
+The RT server which handled your email did not behave as expected. It
+said:
+
+$content
+EOF
+
+exit EX_TEMPFAIL;
+
+}
+
+exit;
+
+
+sub check_failure {
+ my $r = shift;
+ return if $r->is_success();
+
+ # This ordinarily oughtn't to be able to happen, suggests a bug in RT.
+ # So only load these heavy modules when they're needed.
+ require HTML::TreeBuilder;
+ require HTML::FormatText;
+
+ my $error = $r->error_as_HTML;
+ my $tree = HTML::TreeBuilder->new->parse($error);
+ $tree->eof;
+
+ # It'll be a cold day in hell before RT sends out bounces in HTML
+ my $formatter = HTML::FormatText->new( leftmargin => 0,
+ rightmargin => 50 );
+ warn $formatter->format($tree);
+ warn "This is $0 exiting because of an undefined server error" if ($opts{debug});
+ exit EX_TEMPFAIL;
+}
+
+
+=head1 SYNOPSIS
+
+ rt-mailgate --help : this text
+
+Usual invocation (from MTA):
+
+ rt-mailgate --action (correspond|comment) --queue queuename
+ --url http://your.rt.server/
+ [ --debug ]
+ [ --extension (queue|action|ticket) ]
+ [ --timeout seconds ]
+
+
+
+See C<man rt-mailgate> for more.
+
+=head1 OPTIONS
+
+=over 3
+
+=item C<--action>
+
+Specifies whether this is a correspondence or comment address.
+
+=item C<--queue>
+
+Reflects which queue this address handles.
+
+=item C<--url>
+
+The location of the web server for your RT instance.
+
+
+=item C<--extension> OPTIONAL
+
+Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host
+and present "foo" in the environment variable $EXTENSION. By specifying
+the value "queue" for this parameter, the queue this message should be
+submitted to will be set to the value of $EXTENSION. By specifying
+"ticket", $EXTENSION will be interpreted as the id of the ticket this message
+is related to. "action" will allow the user to specify either "comment" or
+"correspond" in the address extension.
+
+=item C<--debug> OPTIONAL
+
+Print debugging output to standard error
+
+
+=item C<--timeout> OPTIONAL
+
+Configure the timeout for posting the message to the web server. The
+default timeout is 3 minutes (180 seconds).
+
+
+=head1 DESCRIPTION
+
+The RT mail gateway is the primary mechanism for communicating with RT
+via email. This program simply directs the email to the RT web server,
+which handles filing correspondence and sending out any required mail.
+It is designed to be run as part of the mail delivery process, either
+called directly by the MTA or C<procmail>, or in a F<.forward> or
+equivalent.
+
+=head1 SETUP
+
+Much of the set up of the mail gateway depends on your MTA and mail
+routing configuration. However, you will need first of all to create an
+RT user for the mail gateway and assign it a password; this helps to
+ensure that mail coming into the web server did originate from the
+gateway.
+
+Next, you need to route mail to C<rt-mailgate> for the queues you're
+monitoring. For instance, if you're using F</etc/aliases> and you have a
+"bugs" queue, you will want something like this:
+
+ bugs: "|/opt/rt3/bin/rt-mailgate --queue bugs --action correspond
+ --url http://rt.mycorp.com/"
+
+ bugs-comment: "|/opt/rt3/bin/rt-mailgate --queue bugs --action comment
+ --url http://rt.mycorp.com/"
+
+Note that you don't have to run your RT server on your mail server, as
+the mail gateway will happily relay to a different machine.
+
+=head1 CUSTOMIZATION
+
+By default, the mail gateway will accept mail from anyone. However,
+there are situations in which you will want to authenticate users
+before allowing them to communicate with the system. You can do this
+via a plug-in mechanism in the RT configuration.
+
+You can set the array C<@RT::MailPlugins> to be a list of plugins. The
+default plugin, if this is not given, is C<Auth::MailFrom> - that is,
+authentication of the person is done based on the C<From> header of the
+email. If you have additional filters or authentication mechanisms, you
+can list them here and they will be called in order:
+
+ @RT::MailPlugins = (
+ "Filter::SpamAssassin",
+ "Auth::LDAP",
+ # ...
+ );
+
+See the documentation for any additional plugins you have.
+
+You may also put Perl subroutines into the C<@RT::MailPlugins> array, if
+they behave as described below.
+
+=head1 WRITING PLUGINS
+
+What's actually going on in the above is that C<@RT::MailPlugins> is a
+list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
+to form a package name, and then C<use>'s this module. The module is
+expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
+several parameters:
+
+=over 4
+
+=item Message
+
+A C<MIME::Entity> object representing the email
+=item CurrentUser
+
+An C<RT::CurrentUser> object
+
+=item AuthStat
+
+The authentication level returned from the previous plugin.
+
+=item Ticket [OPTIONAL]
+
+The ticket under discussion
+
+=item Queue [OPTIONAL]
+
+If we don't already have a ticket id, we need to know which queue we're talking about
+
+=item Action
+
+The action being performed. At the moment, it's one of "comment" or "correspond"
+
+=back 4
+
+It returns two values, the new C<RT::CurrentUser> object, and the new
+authentication level. The authentication level can be zero, not allowed
+to communicate with RT at all, (a "permission denied" error is mailed to
+the correspondent) or one, which is the normal mode of operation.
+Additionally, if C<-1> is returned, then the processing of the plug-ins
+stops immediately and the message is ignored.
+
+=cut
+
diff --git a/rt/bin/rt-mailgate.in b/rt/bin/rt-mailgate.in
new file mode 100644
index 0000000..2ddb604
--- /dev/null
+++ b/rt/bin/rt-mailgate.in
@@ -0,0 +1,648 @@
+#!@PERL@ -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+=head1 NAME
+
+rt-mailgate - Mail interface to RT3.
+
+=begin testing
+
+use RT::I18N;
+
+# Make sure that when we call the mailgate wrong, it tempfails
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://bad.address"), "Opened the mailgate - The error below is expected - $@");
+print MAIL <<EOF;
+From: root\@localhost
+To: rt\@example.com
+Subject: This is a test of new ticket creation
+
+Foob!
+EOF
+close (MAIL);
+
+# Check the return value
+is ( $? >> 8, 75, "The error message above is expected The mail gateway exited with a failure. yay");
+
+
+# {{{ Test new ticket creation by root who is privileged and superuser
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: root\@localhost
+To: rt\@example.com
+Subject: This is a test of new ticket creation
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+use RT::Tickets;
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok (UNIVERSAL::isa($tick,'RT::Ticket'));
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'This is a test of new ticket creation', "Created the ticket");
+
+# }}}
+
+
+# {{{This is a test of new ticket creation as an unknown user
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist\@example.com
+To: rt\@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+$tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject ne 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
+my $u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by failed ticket submission");
+
+
+# }}}
+
+# {{{ now everybody can create tickets. can a random unkown user create tickets?
+
+my $g = RT::Group->new($RT::SystemUser);
+$g->LoadSystemInternalGroup('Everyone');
+ok( $g->Id, "Found 'everybody'");
+
+my ($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+ok ($val, "Granted everybody the right to create tickets - $msg");
+
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist\@example.com
+To: rt\@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+
+$tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+$tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
+my $u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist@example.com');
+ok( $u->Id != 0, " user does not exist and was created by ticket submission");
+
+# }}}
+
+
+# {{{ can another random reply to a ticket without being granted privs? answer should be no.
+
+
+#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+#ok ($val, "Granted everybody the right to create tickets - $msg");
+#sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-2\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-2@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by ticket correspondence submission");
+# }}}
+# {{{ can another random reply to a ticket after being granted privs? answer should be yes
+
+
+($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'ReplyToTicket');
+ok ($val, "Granted everybody the right to reply to tickets - $msg");
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-2\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-2@example.com');
+ok( $u->Id != 0, " user exists and was created by ticket correspondence submission");
+
+# }}}
+
+# {{{ can another random comment on a ticket without being granted privs? answer should be no.
+
+
+#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
+#ok ($val, "Granted everybody the right to create tickets - $msg");
+#sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-3\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-3@example.com');
+ok( $u->Id == 0, " user does not exist and was not created by ticket comment submission");
+
+# }}}
+# {{{ can another random reply to a ticket after being granted privs? answer should be yes
+
+
+($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CommentOnTicket');
+ok ($val, "Granted everybody the right to reply to tickets - $msg");
+sleep(60); # gotta sleep so the remote process' ACL cache times out
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
+print MAIL <<EOF;
+From: doesnotexist-3\@example.com
+To: rt\@example.com
+Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
+
+Blah!
+Foob!
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+$u = RT::User->new($RT::SystemUser);
+$u->Load('doesnotexist-3@example.com');
+ok( $u->Id != 0, " user exists and was created by ticket comment submission");
+
+# }}}
+
+# {{{ Testing preservation of binary attachments
+
+# Get a binary blob (Best Practical logo)
+
+# Create a mime entity with an attachment
+
+use MIME::Entity;
+my $entity = MIME::Entity->build( From => 'root@localhost',
+ To => 'rt@localhost',
+ Subject => 'binary attachment test',
+ Data => ['This is a test of a binary attachment']);
+
+# currently in lib/t/autogen
+$entity->attach(Path => '@MASON_HTML_PATH@/NoAuth/images/spacer.gif',
+ Type => 'image/gif',
+ Encoding => 'base64');
+
+# Create a ticket with a binary attachment
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+$entity->print(\*MAIL);
+
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+ $tick = $tickets->First();
+ok (UNIVERSAL::isa($tick,'RT::Ticket'));
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'binary attachment test', "Created the ticket - ".$tick->Id);
+
+my $file = `cat ../../../html/NoAuth/images/spacer.gif`;
+ok ($file, "Read in the logo image");
+
+
+ use Digest::MD5;
+warn "for the raw file the content is ".Digest::MD5::md5_base64($file);
+
+
+
+# Verify that the binary attachment is valid in the database
+my $attachments = RT::Attachments->new($RT::SystemUser);
+$attachments->Limit(FIELD => 'ContentType', VALUE => 'image/gif');
+ok ($attachments->Count == 1, 'Found only one gif in the database');
+my $attachment = $attachments->First;
+my $acontent = $attachment->Content;
+
+ warn "coming from the database, the content is ".Digest::MD5::md5_base64($acontent);
+
+is( $acontent, $file, 'The attachment isn\'t screwed up in the database.');
+# Log in as root
+use Getopt::Long;
+use LWP::UserAgent;
+
+
+# Grab the binary attachment via the web ui
+my $ua = LWP::UserAgent->new();
+
+my $full_url = "http://localhost".$RT::WebPath."/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/spacer.gif?&user=root&pass=password";
+my $r = $ua->get( $full_url);
+
+
+# Verify that the downloaded attachment is the same as what we uploaded.
+is($file, $r->content, 'The attachment isn\'t screwed up in download');
+
+
+
+# }}}
+
+# {{{ Simple I18N testing
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+print MAIL <<EOF;
+From: root\@localhost
+To: rtemail\@example.com
+Subject: This is a test of I18N ticket creation
+Content-Type: text/plain; charset="utf-8"
+
+2 accented lines
+\303\242\303\252\303\256\303\264\303\273
+\303\241\303\251\303\255\303\263\303\272
+bye
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $unitickets = RT::Tickets->new($RT::SystemUser);
+$unitickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$unitickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $unitick = $unitickets->First();
+ok (UNIVERSAL::isa($unitick,'RT::Ticket'));
+ok ($unitick->Id, "found ticket ".$unitick->Id);
+ok ($unitick->Subject eq 'This is a test of I18N ticket creation', "Created the ticket - ". $unitick->Subject);
+
+
+
+my $unistring = "\303\241\303\251\303\255\303\263\303\272";
+Encode::_utf8_on($unistring);
+is ($unitick->Transactions->First->Content, $unitick->Transactions->First->Attachments->First->Content, "Content is ". $unitick->Transactions->First->Attachments->First->Content);
+ok($unitick->Transactions->First->Attachments->First->Content =~ /$unistring/i, $unitick->Id." appears to be unicode ". $unitick->Transactions->First->Attachments->First->Id);
+# supposedly I18N fails on the second message sent in.
+
+ok(open(MAIL, "|@RT_BIN_PATH@/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
+
+print MAIL <<EOF;
+From: root\@localhost
+To: rtemail\@example.com
+Subject: This is a test of I18N ticket creation
+Content-Type: text/plain; charset="utf-8"
+
+2 accented lines
+\303\242\303\252\303\256\303\264\303\273
+\303\241\303\251\303\255\303\263\303\272
+bye
+EOF
+close (MAIL);
+
+#Check the return value
+is ($? >> 8, 0, "The mail gateway exited normally. yay");
+
+my $tickets2 = RT::Tickets->new($RT::SystemUser);
+$tickets2->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets2->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
+my $tick2 = $tickets2->First();
+ok (UNIVERSAL::isa($tick2,'RT::Ticket'));
+ok ($tick2->Id, "found ticket ".$tick2->Id);
+ok ($tick2->Subject eq 'This is a test of I18N ticket creation', "Created the ticket");
+
+
+
+my $unistring = "\303\241\303\251\303\255\303\263\303\272";
+Encode::_utf8_on($unistring);
+
+ok ($tick2->Transactions->First->Content =~ $unistring, "It appears to be unicode - ".$tick2->Transactions->First->Content);
+
+# }}}
+
+
+($val,$msg) = $g->PrincipalObj->RevokeRight(Right => 'CreateTicket');
+ok ($val, $msg);
+
+
+
+=end testing
+
+=cut
+
+
+use strict;
+use Getopt::Long;
+use LWP::UserAgent;
+
+use constant EX_TEMPFAIL => 75;
+
+my %opts;
+GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" );
+
+if ( $opts{help} ) {
+ require Pod::Usage;
+ import Pod::Usage;
+ pod2usage("RT Mail Gateway\n");
+ exit 1; # Don't want to succeed if this is really an email!
+}
+
+for (qw(url)) {
+ die "$0 invoked improperly\n\nNo $_ provided to mail gateway!\n" unless $opts{$_};
+}
+
+undef $/;
+my $ua = LWP::UserAgent->new();
+$ua->cookie_jar( { file => $opts{jar} } );
+
+my %args = (
+ queue => $opts{queue},
+ action => $opts{action},
+ SessionType => 'REST', # Surpress login box
+);
+
+# Read the message in from STDIN
+$args{'message'} = <>;
+
+
+if ($opts{'extension'}) {
+ $args{$opts{'extension'}} = $ENV{'EXTENSION'};
+}
+
+# Set up cookie here.
+
+my $full_url = $opts{'url'}. "/REST/1.0/NoAuth/mail-gateway";
+warn "Connecting to $full_url" if $opts{'debug'};
+
+
+
+$ua->timeout(exists($opts{'timeout'}) ? $opts{'timeout'} : 180);
+my $r = $ua->post( $full_url, {%args} );
+check_failure($r);
+
+my $content = $r->content;
+warn $content if ($opts{debug});
+
+if ( $content !~ /^(ok|not ok)/ ) {
+
+ # It's not the server's fault if the mail is bogus. We just want to know that
+ # *something* came out of the server.
+ warn <<EOF;
+RT server error.
+
+The RT server which handled your email did not behave as expected. It
+said:
+
+$content
+EOF
+
+exit EX_TEMPFAIL;
+
+}
+
+exit;
+
+
+sub check_failure {
+ my $r = shift;
+ return if $r->is_success();
+
+ # This ordinarily oughtn't to be able to happen, suggests a bug in RT.
+ # So only load these heavy modules when they're needed.
+ require HTML::TreeBuilder;
+ require HTML::FormatText;
+
+ my $error = $r->error_as_HTML;
+ my $tree = HTML::TreeBuilder->new->parse($error);
+ $tree->eof;
+
+ # It'll be a cold day in hell before RT sends out bounces in HTML
+ my $formatter = HTML::FormatText->new( leftmargin => 0,
+ rightmargin => 50 );
+ warn $formatter->format($tree);
+ warn "This is $0 exiting because of an undefined server error" if ($opts{debug});
+ exit EX_TEMPFAIL;
+}
+
+
+=head1 SYNOPSIS
+
+ rt-mailgate --help : this text
+
+Usual invocation (from MTA):
+
+ rt-mailgate --action (correspond|comment) --queue queuename
+ --url http://your.rt.server/
+ [ --debug ]
+ [ --extension (queue|action|ticket) ]
+ [ --timeout seconds ]
+
+
+
+See C<man rt-mailgate> for more.
+
+=head1 OPTIONS
+
+=over 3
+
+=item C<--action>
+
+Specifies whether this is a correspondence or comment address.
+
+=item C<--queue>
+
+Reflects which queue this address handles.
+
+=item C<--url>
+
+The location of the web server for your RT instance.
+
+
+=item C<--extension> OPTIONAL
+
+Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host
+and present "foo" in the environment variable $EXTENSION. By specifying
+the value "queue" for this parameter, the queue this message should be
+submitted to will be set to the value of $EXTENSION. By specifying
+"ticket", $EXTENSION will be interpreted as the id of the ticket this message
+is related to. "action" will allow the user to specify either "comment" or
+"correspond" in the address extension.
+
+=item C<--debug> OPTIONAL
+
+Print debugging output to standard error
+
+
+=item C<--timeout> OPTIONAL
+
+Configure the timeout for posting the message to the web server. The
+default timeout is 3 minutes (180 seconds).
+
+
+=head1 DESCRIPTION
+
+The RT mail gateway is the primary mechanism for communicating with RT
+via email. This program simply directs the email to the RT web server,
+which handles filing correspondence and sending out any required mail.
+It is designed to be run as part of the mail delivery process, either
+called directly by the MTA or C<procmail>, or in a F<.forward> or
+equivalent.
+
+=head1 SETUP
+
+Much of the set up of the mail gateway depends on your MTA and mail
+routing configuration. However, you will need first of all to create an
+RT user for the mail gateway and assign it a password; this helps to
+ensure that mail coming into the web server did originate from the
+gateway.
+
+Next, you need to route mail to C<rt-mailgate> for the queues you're
+monitoring. For instance, if you're using F</etc/aliases> and you have a
+"bugs" queue, you will want something like this:
+
+ bugs: "|/opt/rt3/bin/rt-mailgate --queue bugs --action correspond
+ --url http://rt.mycorp.com/"
+
+ bugs-comment: "|/opt/rt3/bin/rt-mailgate --queue bugs --action comment
+ --url http://rt.mycorp.com/"
+
+Note that you don't have to run your RT server on your mail server, as
+the mail gateway will happily relay to a different machine.
+
+=head1 CUSTOMIZATION
+
+By default, the mail gateway will accept mail from anyone. However,
+there are situations in which you will want to authenticate users
+before allowing them to communicate with the system. You can do this
+via a plug-in mechanism in the RT configuration.
+
+You can set the array C<@RT::MailPlugins> to be a list of plugins. The
+default plugin, if this is not given, is C<Auth::MailFrom> - that is,
+authentication of the person is done based on the C<From> header of the
+email. If you have additional filters or authentication mechanisms, you
+can list them here and they will be called in order:
+
+ @RT::MailPlugins = (
+ "Filter::SpamAssassin",
+ "Auth::LDAP",
+ # ...
+ );
+
+See the documentation for any additional plugins you have.
+
+You may also put Perl subroutines into the C<@RT::MailPlugins> array, if
+they behave as described below.
+
+=head1 WRITING PLUGINS
+
+What's actually going on in the above is that C<@RT::MailPlugins> is a
+list of Perl modules; RT prepends C<RT::Interface::Email::> to the name,
+to form a package name, and then C<use>'s this module. The module is
+expected to provide a C<GetCurrentUser> subroutine, which takes a hash of
+several parameters:
+
+=over 4
+
+=item Message
+
+A C<MIME::Entity> object representing the email
+=item CurrentUser
+
+An C<RT::CurrentUser> object
+
+=item AuthStat
+
+The authentication level returned from the previous plugin.
+
+=item Ticket [OPTIONAL]
+
+The ticket under discussion
+
+=item Queue [OPTIONAL]
+
+If we don't already have a ticket id, we need to know which queue we're talking about
+
+=item Action
+
+The action being performed. At the moment, it's one of "comment" or "correspond"
+
+=back 4
+
+It returns two values, the new C<RT::CurrentUser> object, and the new
+authentication level. The authentication level can be zero, not allowed
+to communicate with RT at all, (a "permission denied" error is mailed to
+the correspondent) or one, which is the normal mode of operation.
+Additionally, if C<-1> is returned, then the processing of the plug-ins
+stops immediately and the message is ignored.
+
+=cut
+
diff --git a/rt/bin/rt.in b/rt/bin/rt.in
new file mode 100644
index 0000000..90369b5
--- /dev/null
+++ b/rt/bin/rt.in
@@ -0,0 +1,1816 @@
+#!@PERL@ -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+
+# This program is intentionally written to have as few non-core module
+# dependencies as possible. It should stay that way.
+
+use Cwd;
+use LWP;
+use HTTP::Request::Common;
+
+# We derive configuration information from hardwired defaults, dotfiles,
+# and the RT* environment variables (in increasing order of precedence).
+# Session information is stored in ~/.rt_sessions.
+
+my $VERSION = 0.02;
+my $HOME = eval{(getpwuid($<))[7]}
+ || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH}
+ || ".";
+my %config = (
+ (
+ debug => 0,
+ user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
+ passwd => undef,
+ server => 'http://localhost/rt/',
+ ),
+ config_from_file($ENV{RTCONFIG} || ".rtrc"),
+ config_from_env()
+);
+my $session = new Session("$HOME/.rt_sessions");
+my $REST = "$config{server}/REST/1.0";
+
+sub whine;
+sub DEBUG { warn @_ if $config{debug} >= shift }
+
+# These regexes are used by command handlers to parse arguments.
+# (XXX: Ask Autrijus how i18n changes these definitions.)
+
+my $name = '[\w.-]+';
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+my $label = '[a-zA-Z0-9@_.+-]+';
+my $labels = "(?:$label,)*$label";
+my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
+
+# Our command line looks like this:
+#
+# rt <action> [options] [arguments]
+#
+# We'll parse just enough of it to decide upon an action to perform, and
+# leave the rest to per-action handlers to interpret appropriately.
+
+my %handlers = (
+# handler => [ ...aliases... ],
+ version => ["version", "ver"],
+ logout => ["logout"],
+ help => ["help", "man"],
+ show => ["show", "cat"],
+ edit => ["create", "edit", "new", "ed"],
+ list => ["search", "list", "ls"],
+ comment => ["comment", "correspond"],
+ link => ["link", "ln"],
+ merge => ["merge"],
+ grant => ["grant", "revoke"],
+);
+
+# Once we find and call an appropriate handler, we're done.
+
+my (%actions, $action);
+foreach my $fn (keys %handlers) {
+ foreach my $alias (@{ $handlers{$fn} }) {
+ $actions{$alias} = \&{"$fn"};
+ }
+}
+if (@ARGV && exists $actions{$ARGV[0]}) {
+ $action = shift @ARGV;
+}
+$actions{$action || "help"}->($action || ());
+exit;
+
+# Handler functions.
+# ------------------
+#
+# The following subs are handlers for each entry in %actions.
+
+sub version {
+ print "rt $VERSION\n";
+}
+
+sub logout {
+ submit("$REST/logout") if defined $session->cookie;
+}
+
+sub help {
+ my ($action, $type) = @_;
+ my (%help, $key);
+
+ # What help topics do we know about?
+ local $/ = undef;
+ foreach my $item (@{ Form::parse(<DATA>) }) {
+ my $title = $item->[2]{Title};
+ my @titles = ref $title eq 'ARRAY' ? @$title : $title;
+
+ foreach $title (grep $_, @titles) {
+ $help{$title} = $item->[2]{Text};
+ }
+ }
+
+ # What does the user want help with?
+ undef $action if ($action && $actions{$action} eq \&help);
+ unless ($action || $type) {
+ # If we don't know, we'll look for clues in @ARGV.
+ foreach (@ARGV) {
+ if (exists $help{$_}) { $key = $_; last; }
+ }
+ unless ($key) {
+ # Tolerate possibly plural words.
+ foreach (@ARGV) {
+ if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; }
+ }
+ }
+ }
+
+ if ($type && $action) {
+ $key = "$type.$action";
+ }
+ $key ||= $type || $action || "introduction";
+
+ # Find a suitable topic to display.
+ while (!exists $help{$key}) {
+ if ($type && $action) {
+ if ($key eq "$type.$action") { $key = $action; }
+ elsif ($key eq $action) { $key = $type; }
+ else { $key = "introduction"; }
+ }
+ else {
+ $key = "introduction";
+ }
+ }
+
+ print STDERR $help{$key}, "\n\n";
+}
+
+# Displays a list of objects that match some specified condition.
+
+sub list {
+ my ($q, $type, %data, $orderby);
+ my $bad = 0;
+
+ while (@ARGV) {
+ $_ = shift @ARGV;
+
+ if (/^-t$/) {
+ $bad = 1, last unless defined($type = get_type_argument());
+ }
+ elsif (/^-S$/) {
+ $bad = 1, last unless get_var_argument(\%data);
+ }
+ elsif (/^-o$/) {
+ $orderby = shift @ARGV;
+ }
+ elsif (/^-([isl])$/) {
+ $data{format} = $1;
+ }
+ elsif (/^-f$/) {
+ if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
+ whine "No valid field list in '-f $ARGV[0]'.";
+ $bad = 1; last;
+ }
+ $data{fields} = shift @ARGV;
+ }
+ elsif (!defined $q && !/^-/) {
+ $q = $_;
+ }
+ else {
+ my $datum = /^-/ ? "option" : "argument";
+ whine "Unrecognised $datum '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ $type ||= "ticket";
+ unless ($type && defined $q) {
+ my $item = $type ? "query string" : "object type";
+ whine "No $item specified.";
+ $bad = 1;
+ }
+ return help("list", $type) if $bad;
+
+ my $r = submit("$REST/search/$type", { query => $q, %data, orderby => $orderby || "" });
+ print $r->content;
+}
+
+# Displays selected information about a single object.
+
+sub show {
+ my ($type, @objects, %data);
+ my $slurped = 0;
+ my $bad = 0;
+
+ while (@ARGV) {
+ $_ = shift @ARGV;
+
+ if (/^-t$/) {
+ $bad = 1, last unless defined($type = get_type_argument());
+ }
+ elsif (/^-S$/) {
+ $bad = 1, last unless get_var_argument(\%data);
+ }
+ elsif (/^-([isl])$/) {
+ $data{format} = $1;
+ }
+ elsif (/^-$/ && !$slurped) {
+ chomp(my @lines = <STDIN>);
+ foreach (@lines) {
+ unless (is_object_spec($_, $type)) {
+ whine "Invalid object on STDIN: '$_'.";
+ $bad = 1; last;
+ }
+ push @objects, $_;
+ }
+ $slurped = 1;
+ }
+ elsif (/^-f$/) {
+ if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
+ whine "No valid field list in '-f $ARGV[0]'.";
+ $bad = 1; last;
+ }
+ $data{fields} = shift @ARGV;
+ }
+ elsif (my $spec = is_object_spec($_, $type)) {
+ push @objects, $spec;
+ }
+ else {
+ my $datum = /^-/ ? "option" : "argument";
+ whine "Unrecognised $datum '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ unless (@objects) {
+ whine "No objects specified.";
+ $bad = 1;
+ }
+ return help("show", $type) if $bad;
+
+ my $r = submit("$REST/show", { id => \@objects, %data });
+ print $r->content;
+}
+
+# To create a new object, we ask the server for a form with the defaults
+# filled in, allow the user to edit it, and send the form back.
+#
+# To edit an object, we must ask the server for a form representing that
+# object, make changes requested by the user (either on the command line
+# or interactively via $EDITOR), and send the form back.
+
+sub edit {
+ my ($action) = @_;
+ my (%data, $type, @objects);
+ my ($cl, $text, $edit, $input, $output);
+
+ use vars qw(%set %add %del);
+ %set = %add = %del = ();
+ my $slurped = 0;
+ my $bad = 0;
+
+ while (@ARGV) {
+ $_ = shift @ARGV;
+
+ if (/^-e$/) { $edit = 1 }
+ elsif (/^-i$/) { $input = 1 }
+ elsif (/^-o$/) { $output = 1 }
+ elsif (/^-t$/) {
+ $bad = 1, last unless defined($type = get_type_argument());
+ }
+ elsif (/^-S$/) {
+ $bad = 1, last unless get_var_argument(\%data);
+ }
+ elsif (/^-$/ && !($slurped || $input)) {
+ chomp(my @lines = <STDIN>);
+ foreach (@lines) {
+ unless (is_object_spec($_, $type)) {
+ whine "Invalid object on STDIN: '$_'.";
+ $bad = 1; last;
+ }
+ push @objects, $_;
+ }
+ $slurped = 1;
+ }
+ elsif (/^set$/i) {
+ my $vars = 0;
+
+ while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) {
+ my ($key, $op, $val) = ($1, $2, $3);
+ my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del;
+
+ vpush($hash, lc $key, $val);
+ shift @ARGV;
+ $vars++;
+ }
+ unless ($vars) {
+ whine "No variables to set.";
+ $bad = 1; last;
+ }
+ $cl = $vars;
+ }
+ elsif (/^(?:add|del)$/i) {
+ my $vars = 0;
+ my $hash = ($_ eq "add") ? \%add : \%del;
+
+ while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) {
+ my ($key, $val) = ($1, $2);
+
+ vpush($hash, lc $key, $val);
+ shift @ARGV;
+ $vars++;
+ }
+ unless ($vars) {
+ whine "No variables to set.";
+ $bad = 1; last;
+ }
+ $cl = $vars;
+ }
+ elsif (my $spec = is_object_spec($_, $type)) {
+ push @objects, $spec;
+ }
+ else {
+ my $datum = /^-/ ? "option" : "argument";
+ whine "Unrecognised $datum '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ if ($action =~ /^ed(?:it)?$/) {
+ unless (@objects) {
+ whine "No objects specified.";
+ $bad = 1;
+ }
+ }
+ else {
+ if (@objects) {
+ whine "You shouldn't specify objects as arguments to $action.";
+ $bad = 1;
+ }
+ unless ($type) {
+ whine "What type of object do you want to create?";
+ $bad = 1;
+ }
+ @objects = ("$type/new");
+ }
+ return help($action, $type) if $bad;
+
+ # We need a form to make changes to. We usually ask the server for
+ # one, but we can avoid that if we are fed one on STDIN, or if the
+ # user doesn't want to edit the form by hand, and the command line
+ # specifies only simple variable assignments.
+
+ if ($input) {
+ local $/ = undef;
+ $text = <STDIN>;
+ }
+ elsif ($edit || %add || %del || !$cl) {
+ my $r = submit("$REST/show", { id => \@objects, format => 'l' });
+ $text = $r->content;
+ }
+
+ # If any changes were specified on the command line, apply them.
+ if ($cl) {
+ if ($text) {
+ # We're updating forms from the server.
+ my $forms = Form::parse($text);
+
+ foreach my $form (@$forms) {
+ my ($c, $o, $k, $e) = @$form;
+ my ($key, $val);
+
+ next if ($e || !@$o);
+
+ local %add = %add;
+ local %del = %del;
+ local %set = %set;
+
+ # Make changes to existing fields.
+ foreach $key (@$o) {
+ if (exists $add{lc $key}) {
+ $val = delete $add{lc $key};
+ vpush($k, $key, $val);
+ $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/;
+ }
+ if (exists $del{lc $key}) {
+ $val = delete $del{lc $key};
+ my %val = map {$_=>1} @{ vsplit($val) };
+ $k->{$key} = vsplit($k->{$key});
+ @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}};
+ }
+ if (exists $set{lc $key}) {
+ $k->{$key} = delete $set{lc $key};
+ }
+ }
+
+ # Then update the others.
+ foreach $key (keys %set) { vpush($k, $key, $set{$key}) }
+ foreach $key (keys %add) {
+ vpush($k, $key, $add{$key});
+ $k->{$key} = vsplit($k->{$key});
+ }
+ push @$o, (keys %add, keys %set);
+ }
+
+ $text = Form::compose($forms);
+ }
+ else {
+ # We're rolling our own set of forms.
+ my @forms;
+ foreach (@objects) {
+ my ($type, $ids, $args) =
+ m{^($name)/($idlist|$labels)(?:(/.*))?$}o;
+
+ $args ||= "";
+ foreach my $obj (expand_list($ids)) {
+ my %set = (%set, id => "$type/$obj$args");
+ push @forms, ["", [keys %set], \%set];
+ }
+ }
+ $text = Form::compose(\@forms);
+ }
+ }
+
+ if ($output) {
+ print $text;
+ exit;
+ }
+
+ my $synerr = 0;
+
+EDIT:
+ # We'll let the user edit the form before sending it to the server,
+ # unless we have enough information to submit it non-interactively.
+ if ($edit || (!$input && !$cl)) {
+ my $newtext = vi($text);
+ # We won't resubmit a bad form unless it was changed.
+ $text = ($synerr && $newtext eq $text) ? undef : $newtext;
+ }
+
+ if ($text) {
+ my $r = submit("$REST/edit", {content => $text, %data});
+ if ($r->code == 409) {
+ # 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;
+ $synerr = 1;
+ goto EDIT;
+ }
+ else {
+ print $r->content;
+ exit -1;
+ }
+ }
+ print $r->content;
+ }
+}
+
+# We roll "comment" and "correspond" into the same handler.
+
+sub comment {
+ my ($action) = @_;
+ my (%data, $id, @files, @bcc, @cc, $msg, $wtime, $edit);
+ my $bad = 0;
+
+ while (@ARGV) {
+ $_ = shift @ARGV;
+
+ if (/^-e$/) {
+ $edit = 1;
+ }
+ elsif (/^-[abcmw]$/) {
+ unless (@ARGV) {
+ whine "No argument specified with $_.";
+ $bad = 1; last;
+ }
+
+ if (/-a/) {
+ unless (-f $ARGV[0] && -r $ARGV[0]) {
+ whine "Cannot read attachment: '$ARGV[0]'.";
+ exit -1;
+ }
+ push @files, shift @ARGV;
+ }
+ elsif (/-([bc])/) {
+ my $a = $_ eq "-b" ? \@bcc : \@cc;
+ @$a = split /\s*,\s*/, shift @ARGV;
+ }
+ elsif (/-m/) { $msg = shift @ARGV }
+ elsif (/-w/) { $wtime = shift @ARGV }
+ }
+ elsif (!$id && m|^(?:ticket/)?($idlist)$|) {
+ $id = $1;
+ }
+ else {
+ my $datum = /^-/ ? "option" : "argument";
+ whine "Unrecognised $datum '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ unless ($id) {
+ whine "No object specified.";
+ $bad = 1;
+ }
+ return help($action, "ticket") if $bad;
+
+ my $form = [
+ "",
+ [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Text" ],
+ {
+ Ticket => $id,
+ Action => $action,
+ Cc => [ @cc ],
+ Bcc => [ @bcc ],
+ Attachment => [ @files ],
+ TimeWorked => $wtime || '',
+ Text => $msg || '',
+ }
+ ];
+
+ my $text = Form::compose([ $form ]);
+
+ if ($edit || !$msg) {
+ my $error = 0;
+ my ($c, $o, $k, $e);
+
+ do {
+ my $ntext = vi($text);
+ exit if ($error && $ntext eq $text);
+ $text = $ntext;
+ $form = Form::parse($text);
+ $error = 0;
+
+ ($c, $o, $k, $e) = @{ $form->[0] };
+ if ($e) {
+ $error = 1;
+ $c = "# Syntax error.";
+ goto NEXT;
+ }
+ elsif (!@$o) {
+ exit;
+ }
+ @files = @{ vsplit($k->{Attachment}) };
+
+ NEXT:
+ $text = Form::compose([[$c, $o, $k, $e]]);
+ } while ($error);
+ }
+
+ my $i = 1;
+ foreach my $file (@files) {
+ $data{"attachment_$i"} = bless([ $file ], "Attachment");
+ $i++;
+ }
+ $data{content} = $text;
+
+ my $r = submit("$REST/ticket/comment/$id", \%data);
+ print $r->content;
+}
+
+# Merge one ticket into another.
+
+sub merge {
+ my @id;
+ my $bad = 0;
+
+ while (@ARGV) {
+ $_ = shift @ARGV;
+
+ if (/^\d+$/) {
+ push @id, $_;
+ }
+ else {
+ whine "Unrecognised argument: '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ unless (@id == 2) {
+ my $evil = @id > 2 ? "many" : "few";
+ whine "Too $evil arguments specified.";
+ $bad = 1;
+ }
+ return help("merge", "ticket") if $bad;
+
+ my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]});
+ print $r->content;
+}
+
+# Link one ticket to another.
+
+sub link {
+ my ($bad, $del, %data) = (0, 0, ());
+ my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
+ ReferredToBy HasMember MemberOf);
+
+ while (@ARGV && $ARGV[0] =~ /^-/) {
+ $_ = shift @ARGV;
+
+ if (/^-d$/) {
+ $del = 1;
+ }
+ else {
+ whine "Unrecognised option: '$_'.";
+ $bad = 1; last;
+ }
+ }
+
+ if (@ARGV == 3) {
+ my ($from, $rel, $to) = @ARGV;
+ if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
+ my $bad = $from =~ /^\d+$/ ? $to : $from;
+ whine "Invalid ticket ID '$bad' specified.";
+ $bad = 1;
+ }
+ unless (exists $ltypes{lc $rel}) {
+ whine "Invalid relationship '$rel' specified.";
+ $bad = 1;
+ }
+ %data = (id => $from, rel => $rel, to => $to, del => $del);
+ }
+ else {
+ my $bad = @ARGV < 3 ? "few" : "many";
+ whine "Too $bad arguments specified.";
+ $bad = 1;
+ }
+ return help("link", "ticket") if $bad;
+
+ my $r = submit("$REST/ticket/link", \%data);
+ print $r->content;
+}
+
+# Grant/revoke a user's rights.
+
+sub grant {
+ my ($cmd) = @_;
+
+ my $revoke = 0;
+ while (@ARGV) {
+ }
+
+ $revoke = 1 if $cmd->{action} eq 'revoke';
+}
+
+# Client <-> Server communication.
+# --------------------------------
+#
+# This function composes and sends an HTTP request to the RT server, and
+# interprets the response. It takes a request URI, and optional request
+# data (a string, or a reference to a set of key-value pairs).
+
+sub submit {
+ my ($uri, $content) = @_;
+ my ($req, $data);
+ my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1);
+
+ # Did the caller specify any data to send with the request?
+ $data = [];
+ if (defined $content) {
+ unless (ref $content) {
+ # If it's just a string, make sure LWP handles it properly.
+ # (By pretending that it's a file!)
+ $content = [ content => [undef, "", Content => $content] ];
+ }
+ elsif (ref $content eq 'HASH') {
+ my @data;
+ foreach my $k (keys %$content) {
+ if (ref $content->{$k} eq 'ARRAY') {
+ foreach my $v (@{ $content->{$k} }) {
+ push @data, $k, $v;
+ }
+ }
+ else { push @data, $k, $content->{$k} }
+ }
+ $content = \@data;
+ }
+ $data = $content;
+ }
+
+ # Should we send authentication information to start a new session?
+ if (!defined $session->cookie) {
+ push @$data, ( user => $config{user} );
+ push @$data, ( pass => $config{passwd} || read_passwd() );
+ }
+
+ # Now, we construct the request.
+ if (@$data) {
+ $req = POST($uri, $data, Content_Type => 'form-data');
+ }
+ else {
+ $req = GET($uri);
+ }
+ $session->add_cookie_header($req);
+
+ # Then we send the request and parse the response.
+ DEBUG(3, $req->as_string);
+ my $res = $ua->request($req);
+ DEBUG(3, $res->as_string);
+
+ if ($res->is_success) {
+ # The content of the response we get from the RT server consists
+ # of an HTTP-like status line followed by optional header lines,
+ # a blank line, and arbitrary text.
+
+ my ($head, $text) = split /\n\n/, $res->content, 2;
+ my ($status, @headers) = split /\n/, $head;
+ $text =~ s/\n*$/\n/;
+
+ # "RT/3.0.1 401 Credentials required"
+ if ($status !~ m#^RT/\d+(?:\.\d+)+(?:-?\w+)? (\d+) ([\w\s]+)$#) {
+ warn "rt: Malformed RT response from $config{server}.\n";
+ warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
+ exit -1;
+ }
+
+ # Our caller can pretend that the server returned a custom HTTP
+ # response code and message. (Doing that directly is apparently
+ # not sufficiently portable and uncomplicated.)
+ $res->code($1);
+ $res->message($2);
+ $res->content($text);
+ $session->update($res) if ($res->is_success || $res->code != 401);
+
+ if (!$res->is_success) {
+ # We can deal with authentication failures ourselves. Either
+ # we sent invalid credentials, or our session has expired.
+ if ($res->code == 401) {
+ my %d = @$data;
+ if (exists $d{user}) {
+ warn "rt: Incorrect username or password.\n";
+ exit -1;
+ }
+ elsif ($req->header("Cookie")) {
+ # We'll retry the request with credentials, unless
+ # we only wanted to logout in the first place.
+ $session->delete;
+ return submit(@_) unless $uri eq "$REST/logout";
+ }
+ }
+ # Conflicts should be dealt with by the handler and user.
+ # For anything else, we just die.
+ elsif ($res->code != 409) {
+ warn "rt: ", $res->content;
+ exit;
+ }
+ }
+ }
+ else {
+ warn "rt: Server error: ", $res->message, " (", $res->code, ")\n";
+ exit -1;
+ }
+
+ return $res;
+}
+
+# Session management.
+# -------------------
+#
+# Maintains a list of active sessions in the ~/.rt_sessions file.
+{
+ package Session;
+ my ($s, $u);
+
+ # Initialises the session cache.
+ sub new {
+ my ($class, $file) = @_;
+ my $self = {
+ file => $file || "$HOME/.rt_sessions",
+ sids => { }
+ };
+
+ # The current session is identified by the currently configured
+ # server and user.
+ ($s, $u) = @config{"server", "user"};
+
+ bless $self, $class;
+ $self->load();
+
+ return $self;
+ }
+
+ # Returns the current session cookie.
+ sub cookie {
+ my ($self) = @_;
+ my $cookie = $self->{sids}{$s}{$u};
+ return defined $cookie ? "RT_SID=$cookie" : undef;
+ }
+
+ # Deletes the current session cookie.
+ sub delete {
+ my ($self) = @_;
+ delete $self->{sids}{$s}{$u};
+ }
+
+ # Adds a Cookie header to an outgoing HTTP request.
+ sub add_cookie_header {
+ my ($self, $request) = @_;
+ my $cookie = $self->cookie();
+
+ $request->header(Cookie => $cookie) if defined $cookie;
+ }
+
+ # Extracts the Set-Cookie header from an HTTP response, and updates
+ # session information accordingly.
+ sub update {
+ my ($self, $response) = @_;
+ my $cookie = $response->header("Set-Cookie");
+
+ if (defined $cookie && $cookie =~ /^RT_SID=([0-9A-Fa-f]+);/) {
+ $self->{sids}{$s}{$u} = $1;
+ }
+ }
+
+ # Loads the session cache from the specified file.
+ sub load {
+ my ($self, $file) = @_;
+ $file ||= $self->{file};
+ local *F;
+
+ open(F, $file) && do {
+ $self->{file} = $file;
+ my $sids = $self->{sids} = {};
+ while (<F>) {
+ chomp;
+ next if /^$/ || /^#/;
+ next unless m#^https?://[^ ]+ \w+ [0-9A-Fa-f]+$#;
+ my ($server, $user, $cookie) = split / /, $_;
+ $sids->{$server}{$user} = $cookie;
+ }
+ return 1;
+ };
+ return 0;
+ }
+
+ # Writes the current session cache to the specified file.
+ sub save {
+ my ($self, $file) = shift;
+ $file ||= $self->{file};
+ local *F;
+
+ open(F, ">$file") && do {
+ my $sids = $self->{sids};
+ foreach my $server (keys %$sids) {
+ foreach my $user (keys %{ $sids->{$server} }) {
+ my $sid = $sids->{$server}{$user};
+ if (defined $sid) {
+ print F "$server $user $sid\n";
+ }
+ }
+ }
+ close(F);
+ chmod 0600, $file;
+ return 1;
+ };
+ return 0;
+ }
+
+ sub DESTROY {
+ my $self = shift;
+ $self->save;
+ }
+}
+
+# Form handling.
+# --------------
+#
+# Forms are RFC822-style sets of (field, value) specifications with some
+# initial comments and interspersed blank lines allowed for convenience.
+# Sets of forms are separated by --\n (in a cheap parody of MIME).
+#
+# Each form is parsed into an array with four elements: commented text
+# at the start of the form, an array with the order of keys, a hash with
+# key/value pairs, and optional error text if the form syntax was wrong.
+
+# Returns a reference to an array of parsed forms.
+sub Form::parse {
+ my $state = 0;
+ my @forms = ();
+ my @lines = split /\n/, $_[0];
+ my ($c, $o, $k, $e) = ("", [], {}, "");
+
+ LINE:
+ while (@lines) {
+ my $line = shift @lines;
+
+ next LINE if $line eq '';
+
+ if ($line eq '--') {
+ # We reached the end of one form. We'll ignore it if it was
+ # empty, and store it otherwise, errors and all.
+ if ($e || $c || @$o) {
+ push @forms, [ $c, $o, $k, $e ];
+ $c = ""; $o = []; $k = {}; $e = "";
+ }
+ $state = 0;
+ }
+ elsif ($state != -1) {
+ if ($state == 0 && $line =~ /^#/) {
+ # Read an optional block of comments (only) at the start
+ # of the form.
+ $state = 1;
+ $c = $line;
+ while (@lines && $lines[0] =~ /^#/) {
+ $c .= "\n".shift @lines;
+ }
+ $c .= "\n";
+ }
+ elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) {
+ # Read a field: value specification.
+ my $f = $1;
+ my @v = ($2 || ());
+
+ # Read continuation lines, if any.
+ while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) {
+ push @v, shift @lines;
+ }
+ pop @v while (@v && $v[-1] eq '');
+
+ # Strip longest common leading indent from text.
+ my $ws = "";
+ foreach my $ls (map {/^(\s+)/} @v[1..$#v]) {
+ $ws = $ls if (!$ws || length($ls) < length($ws));
+ }
+ s/^$ws// foreach @v;
+
+ push(@$o, $f) unless exists $k->{$f};
+ vpush($k, $f, join("\n", @v));
+
+ $state = 1;
+ }
+ elsif ($line !~ /^#/) {
+ # We've found a syntax error, so we'll reconstruct the
+ # form parsed thus far, and add an error marker. (>>)
+ $state = -1;
+ $e = Form::compose([[ "", $o, $k, "" ]]);
+ $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n";
+ }
+ }
+ else {
+ # We saw a syntax error earlier, so we'll accumulate the
+ # contents of this form until the end.
+ $e .= "$line\n";
+ }
+ }
+ push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o);
+
+ foreach my $l (keys %$k) {
+ $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY');
+ }
+
+ return \@forms;
+}
+
+# Returns text representing a set of forms.
+sub Form::compose {
+ my ($forms) = @_;
+ my @text;
+
+ foreach my $form (@$forms) {
+ my ($c, $o, $k, $e) = @$form;
+ my $text = "";
+
+ if ($c) {
+ $c =~ s/\n*$/\n/;
+ $text = "$c\n";
+ }
+ if ($e) {
+ $text .= $e;
+ }
+ elsif ($o) {
+ my @lines;
+
+ foreach my $key (@$o) {
+ my ($line, $sp);
+ my $v = $k->{$key};
+ my @values = ref $v eq 'ARRAY' ? @$v : $v;
+
+ $sp = " "x(length("$key: "));
+ $sp = " "x4 if length($sp) > 16;
+
+ foreach $v (@values) {
+ if ($v =~ /\n/) {
+ $v =~ s/^/$sp/gm;
+ $v =~ s/^$sp//;
+
+ if ($line) {
+ push @lines, "$line\n\n";
+ $line = "";
+ }
+ elsif (@lines && $lines[-1] !~ /\n\n$/) {
+ $lines[-1] .= "\n";
+ }
+ push @lines, "$key: $v\n\n";
+ }
+ elsif ($line &&
+ length($line)+length($v)-rindex($line, "\n") >= 70)
+ {
+ $line .= ",\n$sp$v";
+ }
+ else {
+ $line = $line ? "$line, $v" : "$key: $v";
+ }
+ }
+
+ $line = "$key:" unless @values;
+ if ($line) {
+ if ($line =~ /\n/) {
+ if (@lines && $lines[-1] !~ /\n\n$/) {
+ $lines[-1] .= "\n";
+ }
+ $line .= "\n";
+ }
+ push @lines, "$line\n";
+ }
+ }
+
+ $text .= join "", @lines;
+ }
+ else {
+ chomp $text;
+ }
+ push @text, $text;
+ }
+
+ return join "\n--\n\n", @text;
+}
+
+# Configuration.
+# --------------
+
+# Returns configuration information from the environment.
+sub config_from_env {
+ my %env;
+
+ foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER") {
+ if (exists $ENV{"RT$k"}) {
+ $env{lc $k} = $ENV{"RT$k"};
+ }
+ }
+
+ return %env;
+}
+
+# Finds a suitable configuration file and returns information from it.
+sub config_from_file {
+ my ($rc) = @_;
+
+ if ($rc =~ m#^/#) {
+ # We'll use an absolute path if we were given one.
+ return parse_config_file($rc);
+ }
+ else {
+ # Otherwise we'll use the first file we can find in the current
+ # directory, or in one of its (increasingly distant) ancestors.
+
+ my @dirs = split /\//, cwd;
+ while (@dirs) {
+ my $file = join('/', @dirs, $rc);
+ if (-r $file) {
+ return parse_config_file($file);
+ }
+
+ # Remove the last directory component each time.
+ pop @dirs;
+ }
+
+ # Still nothing? We'll fall back to some likely defaults.
+ for ("$HOME/$rc", "/etc/rt.conf") {
+ return parse_config_file($_) if (-r $_);
+ }
+ }
+
+ return ();
+}
+
+# Makes a hash of the specified configuration file.
+sub parse_config_file {
+ my %cfg;
+ my ($file) = @_;
+
+ open(CFG, $file) && do {
+ while (<CFG>) {
+ chomp;
+ next if (/^#/ || /^\s*$/);
+
+ if (/^(user|passwd|server)\s+([^ ]+)$/) {
+ $cfg{$1} = $2;
+ }
+ else {
+ die "rt: $file:$.: unknown configuration directive.\n";
+ }
+ }
+ };
+
+ return %cfg;
+}
+
+# Helper functions.
+# -----------------
+
+sub whine {
+ my $sub = (caller(1))[3];
+ $sub =~ s/^main:://;
+ warn "rt: $sub: @_\n";
+ return;
+}
+
+sub read_passwd {
+ eval 'require Term::ReadKey';
+ if ($@) {
+ die "No password specified (and Term::ReadKey not installed).\n";
+ }
+
+ print "Password: ";
+ Term::ReadKey::ReadMode('noecho');
+ chomp(my $passwd = Term::ReadKey::ReadLine(0));
+ Term::ReadKey::ReadMode('restore');
+ print "\n";
+
+ return $passwd;
+}
+
+sub vi {
+ my ($text) = @_;
+ my $file = "/tmp/rt.form.$$";
+ my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi";
+
+ local *F;
+ local $/ = undef;
+
+ open(F, ">$file") || die "$file: $!\n"; print F $text; close(F);
+ system($editor, $file) && die "Couldn't run $editor.\n";
+ open(F, $file) || die "$file: $!\n"; $text = <F>; close(F);
+ unlink($file);
+
+ return $text;
+}
+
+# Add a value to a (possibly multi-valued) hash key.
+sub vpush {
+ my ($hash, $key, $val) = @_;
+ my @val = ref $val eq 'ARRAY' ? @$val : $val;
+
+ if (exists $hash->{$key}) {
+ unless (ref $hash->{$key} eq 'ARRAY') {
+ my @v = $hash->{$key} ne '' ? $hash->{$key} : ();
+ $hash->{$key} = \@v;
+ }
+ push @{ $hash->{$key} }, @val;
+ }
+ else {
+ $hash->{$key} = $val;
+ }
+}
+
+# "Normalise" a hash key that's known to be multi-valued.
+sub vsplit {
+ my ($val) = @_;
+ my ($word, @words);
+ my @values = ref $val eq 'ARRAY' ? @$val : $val;
+
+ foreach my $line (map {split /\n/} @values) {
+ # XXX: This should become a real parser, à la Text::ParseWords.
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
+ push @words, split /\s*,\s*/, $line;
+ }
+
+ return \@words;
+}
+
+sub expand_list {
+ my ($list) = @_;
+ my ($elt, @elts, %elts);
+
+ foreach $elt (split /,/, $list) {
+ if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) }
+ else { push @elts, $elt }
+ }
+
+ @elts{@elts}=();
+ return sort {$a<=>$b} keys %elts;
+}
+
+sub get_type_argument {
+ my $type;
+
+ if (@ARGV) {
+ $type = shift @ARGV;
+ unless ($type =~ /^[A-Za-z0-9_.-]+$/) {
+ # We want whine to mention our caller, not us.
+ @_ = ("Invalid type '$type' specified.");
+ goto &whine;
+ }
+ }
+ else {
+ @_ = ("No type argument specified with -t.");
+ goto &whine;
+ }
+
+ $type =~ s/s$//; # "Plural". Ugh.
+ return $type;
+}
+
+sub get_var_argument {
+ my ($data) = @_;
+
+ if (@ARGV) {
+ my $kv = shift @ARGV;
+ if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) {
+ push @{ $data->{$k} }, $v;
+ }
+ else {
+ @_ = ("Invalid variable specification: '$kv'.");
+ goto &whine;
+ }
+ }
+ else {
+ @_ = ("No variable argument specified with -S.");
+ goto &whine;
+ }
+}
+
+sub is_object_spec {
+ my ($spec, $type) = @_;
+
+ $spec =~ s|^(?:$type/)?|$type/| if defined $type;
+ return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o);
+ return;
+}
+
+__DATA__
+
+Title: intro
+Title: introduction
+Text:
+
+ ** THIS IS AN UNSUPPORTED PREVIEW RELEASE **
+ ** PLEASE REPORT BUGS TO rt-bugs@fsck.com **
+
+ This is a command-line interface to RT 3.
+
+ It allows you to interact with an RT server over HTTP, and offers an
+ interface to RT's functionality that is better-suited to automation
+ and integration with other tools.
+
+ In general, each invocation of this program should specify an action
+ to perform on one or more objects, and any other arguments required
+ to complete the desired action.
+
+ For more information:
+
+ - rt help actions (a list of possible actions)
+ - rt help objects (how to specify objects)
+ - rt help usage (syntax information)
+
+ - rt help config (configuration details)
+ - rt help examples (a few useful examples)
+ - rt help topics (a list of help topics)
+
+--
+
+Title: usage
+Title: syntax
+Text:
+
+ Syntax:
+
+ rt <action> [options] [arguments]
+
+ Each invocation of this program must specify an action (e.g. "edit",
+ "create"), options to modify behaviour, and other arguments required
+ by the specified action. (For example, most actions expect a list of
+ numeric object IDs to act upon.)
+
+ The details of the syntax and arguments for each action are given by
+ "rt help <action>". Some actions may be referred to by more than one
+ name ("create" is the same as "new", for example).
+
+ Objects are identified by a type and an ID (which can be a name or a
+ number, depending on the type). For some actions, the object type is
+ implied (you can only comment on tickets); for others, the user must
+ specify it explicitly. See "rt help objects" for details.
+
+ In syntax descriptions, mandatory arguments that must be replaced by
+ appropriate value are enclosed in <>, and optional arguments are
+ indicated by [] (for example, <action> and [options] above).
+
+ For more information:
+
+ - rt help objects (how to specify objects)
+ - rt help actions (a list of actions)
+ - rt help types (a list of object types)
+
+--
+
+Title: conf
+Title: config
+Title: configuration
+Text:
+
+ This program has two major sources of configuration information: its
+ configuration files, and the environment.
+
+ The program looks for configuration directives in a file named .rtrc
+ (or $RTCONFIG; see below) in the current directory, and then in more
+ distant ancestors, until it reaches /. If no suitable configuration
+ files are found, it will also check for ~/.rtrc and /etc/rt.conf.
+
+ Configuration directives:
+
+ The following directives may occur, one per line:
+
+ - server <URL> URL to RT server.
+ - user <username> RT username.
+ - passwd <passwd> RT user's password.
+
+ Blank and #-commented lines are ignored.
+
+ Environment variables:
+
+ The following environment variables override any corresponding
+ values defined in configuration files:
+
+ - RTUSER
+ - RTPASSWD
+ - RTSERVER
+ - RTDEBUG Numeric debug level. (Set to 3 for full logs.)
+ - RTCONFIG Specifies a name other than ".rtrc" for the
+ configuration file.
+
+--
+
+Title: objects
+Text:
+
+ Syntax:
+
+ <type>/<id>[/<attributes>]
+
+ Every object in RT has a type (e.g. "ticket", "queue") and a numeric
+ ID. Some types of objects can also be identified by name (like users
+ and queues). Furthermore, objects may have named attributes (such as
+ "ticket/1/history").
+
+ An object specification is like a path in a virtual filesystem, with
+ object types as top-level directories, object IDs as subdirectories,
+ and named attributes as further subdirectories.
+
+ A comma-separated list of names, numeric IDs, or numeric ranges can
+ be used to specify more than one object of the same type. Note that
+ the list must be a single argument (i.e., no spaces). For example,
+ "user/root,1-3,5,7-10,ams" is a list of ten users; the same list
+ can also be written as "user/ams,root,1,2,3,5,7,8-20".
+
+ Examples:
+
+ ticket/1
+ ticket/1/attachments
+ ticket/1/attachments/3
+ ticket/1/attachments/3/content
+ ticket/1-3/links
+ ticket/1-3,5-7/history
+
+ user/ams
+ user/ams/rights
+ user/ams,rai,1/rights
+
+ For more information:
+
+ - rt help <action> (action-specific details)
+ - rt help <type> (type-specific details)
+
+--
+
+Title: actions
+Title: commands
+Text:
+
+ You can currently perform the following actions on all objects:
+
+ - list (list objects matching some condition)
+ - show (display object details)
+ - edit (edit object details)
+ - create (create a new object)
+
+ Each type may define actions specific to itself; these are listed in
+ the help item about that type.
+
+ For more information:
+
+ - rt help <action> (action-specific details)
+ - rt help types (a list of possible types)
+
+--
+
+Title: types
+Text:
+
+ You can currently operate on the following types of objects:
+
+ - tickets
+ - users
+ - groups
+ - queues
+
+ For more information:
+
+ - rt help <type> (type-specific details)
+ - rt help objects (how to specify objects)
+ - rt help actions (a list of possible actions)
+
+--
+
+Title: ticket
+Text:
+
+ Tickets are identified by a numeric ID.
+
+ The following generic operations may be performed upon tickets:
+
+ - list
+ - show
+ - edit
+ - create
+
+ In addition, the following ticket-specific actions exist:
+
+ - link
+ - merge
+ - comment
+ - correspond
+
+ Attributes:
+
+ The following attributes can be used with "rt show" or "rt edit"
+ to retrieve or edit other information associated with tickets:
+
+ links A ticket's relationships with others.
+ history All of a ticket's transactions.
+ history/type/<type> Only a particular type of transaction.
+ history/id/<id> Only the transaction of the specified id.
+ attachments A list of attachments.
+ attachments/<id> The metadata for an individual attachment.
+ attachments/<id>/content The content of an individual attachment.
+
+--
+
+Title: user
+Title: group
+Text:
+
+ Users and groups are identified by name or numeric ID.
+
+ The following generic operations may be performed upon them:
+
+ - list
+ - show
+ - edit
+ - create
+
+ In addition, the following type-specific actions exist:
+
+ - grant
+ - revoke
+
+ Attributes:
+
+ The following attributes can be used with "rt show" or "rt edit"
+ to retrieve or edit other information associated with users and
+ groups:
+
+ rights Global rights granted to this user.
+ rights/<queue> Queue rights for this user.
+
+--
+
+Title: queue
+Text:
+
+ Queues are identified by name or numeric ID.
+
+ Currently, they can be subjected to the following actions:
+
+ - show
+ - edit
+ - create
+
+--
+
+Title: logout
+Text:
+
+ Syntax:
+
+ rt logout
+
+ Terminates the currently established login session. You will need to
+ provide authentication credentials before you can continue using the
+ server. (See "rt help config" for details about authentication.)
+
+--
+
+Title: ls
+Title: list
+Title: search
+Text:
+
+ Syntax:
+
+ rt <ls|list|search> [options] "query string"
+
+ Displays a list of objects matching the specified conditions.
+ ("ls", "list", and "search" are synonyms.)
+
+ Conditions are expressed in the SQL-like syntax used internally by
+ RT3. (For more information, see "rt help query".) The query string
+ must be supplied as one argument.
+
+ (Right now, the server doesn't support listing anything but tickets.
+ Other types will be supported in future; this client will be able to
+ take advantage of that support without any changes.)
+
+ Options:
+
+ The following options control how much information is displayed
+ about each matching object:
+
+ -i Numeric IDs only. (Useful for |rt edit -; see examples.)
+ -s Short description.
+ -l Longer description.
+
+ In addition,
+
+ -o +/-<field> Orders the returned list by the specified field.
+ -S var=val Submits the specified variable with the request.
+ -t type Specifies the type of object to look for. (The
+ default is "ticket".)
+
+ Examples:
+
+ rt ls "Priority > 5 and Status='new'"
+ rt ls -o +Subject "Priority > 5 and Status='new'"
+ rt ls -o -Created "Priority > 5 and Status='new'"
+ rt ls -i "Priority > 5"|rt edit - set status=resolved
+ rt ls -t ticket "Subject like '[PATCH]%'"
+
+--
+
+Title: show
+Text:
+
+ Syntax:
+
+ rt show [options] <object-ids>
+
+ Displays details of the specified objects.
+
+ For some types, object information is further classified into named
+ attributes (for example, "1-3/links" is a valid ticket specification
+ that refers to the links for tickets 1-3). Consult "rt help <type>"
+ and "rt help objects" for further details.
+
+ This command writes a set of forms representing the requested object
+ data to STDOUT.
+
+ Options:
+
+ - Read IDs from STDIN instead of the command-line.
+ -t type Specifies object type.
+ -f a,b,c Restrict the display to the specified fields.
+ -S var=val Submits the specified variable with the request.
+
+ Examples:
+
+ rt show -t ticket -f id,subject,status 1-3
+ rt show ticket/3/attachments/29
+ rt show ticket/3/attachments/29/content
+ rt show ticket/1-3/links
+ rt show -t user 2
+
+--
+
+Title: new
+Title: edit
+Title: create
+Text:
+
+ Syntax:
+
+ rt edit [options] <object-ids> set field=value [field=value] ...
+ add field=value [field=value] ...
+ del field=value [field=value] ...
+
+ Edits information corresponding to the specified objects.
+
+ If, instead of "edit", an action of "new" or "create" is specified,
+ then a new object is created. In this case, no numeric object IDs
+ may be specified, but the syntax and behaviour remain otherwise
+ unchanged.
+
+ This command typically starts an editor to allow you to edit object
+ data in a form for submission. If you specified enough information
+ on the command-line, however, it will make the submission directly.
+
+ The command line may specify field-values in three different ways.
+ "set" sets the named field to the given value, "add" adds a value
+ to a multi-valued field, and "del" deletes the corresponding value.
+ Each "field=value" specification must be given as a single argument.
+
+ For some types, object information is further classified into named
+ attributes (for example, "1-3/links" is a valid ticket specification
+ that refers to the links for tickets 1-3). These attributes may also
+ be edited. Consult "rt help <type>" and "rt help object" for further
+ details.
+
+ Options:
+
+ - Read numeric IDs from STDIN instead of the command-line.
+ (Useful with rt ls ... | rt edit -; see examples below.)
+ -i Read a completed form from STDIN before submitting.
+ -o Dump the completed form to STDOUT instead of submitting.
+ -e Allows you to edit the form even if the command-line has
+ enough information to make a submission directly.
+ -S var=val
+ Submits the specified variable with the request.
+ -t type Specifies object type.
+
+ Examples:
+
+ # Interactive (starts $EDITOR with a form).
+ rt edit ticket/3
+ rt create -t ticket
+
+ # Non-interactive.
+ rt edit ticket/1-3 add cc=foo@example.com set priority=3
+ rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved
+ rt edit ticket/4 set priority=3 owner=bar@example.com \
+ add cc=foo@example.com bcc=quux@example.net
+ rt create -t ticket subject='new ticket' priority=10 \
+ add cc=foo@example.com
+
+--
+
+Title: comment
+Title: correspond
+Text:
+
+ Syntax:
+
+ rt <comment|correspond> [options] <ticket-id>
+
+ Adds a comment (or correspondence) to the specified ticket (the only
+ difference being that comments aren't sent to the requestors.)
+
+ This command will typically start an editor and allow you to type a
+ comment into a form. If, however, you specified all the necessary
+ information on the command line, it submits the comment directly.
+
+ (See "rt help forms" for more information about forms.)
+
+ Options:
+
+ -m <text> Specify comment text.
+ -a <file> Attach a file to the comment. (May be used more
+ than once to attach multiple files.)
+ -c <addrs> A comma-separated list of Cc addresses.
+ -b <addrs> A comma-separated list of Bcc addresses.
+ -w <time> Specify the time spent working on this ticket.
+ -e Starts an editor before the submission, even if
+ arguments from the command line were sufficient.
+
+ Examples:
+
+ rt comment -t 'Not worth fixing.' -a stddisclaimer.h 23
+
+--
+
+Title: merge
+Text:
+
+ Syntax:
+
+ rt merge <from-id> <to-id>
+
+ Merges the two specified tickets.
+
+--
+
+Title: link
+Text:
+
+ Syntax:
+
+ rt link [-d] <id-A> <relationship> <id-B>
+
+ Creates (or, with -d, deletes) a link between the specified tickets.
+ The relationship can (irrespective of case) be any of:
+
+ DependsOn/DependedOnBy: A depends upon B (or vice versa).
+ RefersTo/ReferredToBy: A refers to B (or vice versa).
+ MemberOf/HasMember: A is a member of B (or vice versa).
+
+ To view a ticket's relationships, use "rt show ticket/3/links". (See
+ "rt help ticket" and "rt help show".)
+
+ Options:
+
+ -d Deletes the specified link.
+
+ Examples:
+
+ rt link 2 dependson 3
+ rt link -d 4 referredtoby 6 # 6 no longer refers to 4
+
+--
+
+Title: grant
+Title: revoke
+Text:
+
+--
+
+Title: query
+Text:
+
+ RT3 uses an SQL-like syntax to specify object selection constraints.
+ See the <RT:...> documentation for details.
+
+ (XXX: I'm going to have to write it, aren't I?)
+
+--
+
+Title: form
+Title: forms
+Text:
+
+ This program uses RFC822 header-style forms to represent object data
+ in a form that's suitable for processing both by humans and scripts.
+
+ A form is a set of (field, value) specifications, with some initial
+ commented text and interspersed blank lines allowed for convenience.
+ Field names may appear more than once in a form; a comma-separated
+ list of multiple field values may also be specified directly.
+
+ Field values can be wrapped as in RFC822, with leading whitespace.
+ The longest sequence of leading whitespace common to all the lines
+ is removed (preserving further indentation). There is no limit on
+ the length of a value.
+
+ Multiple forms are separated by a line containing only "--\n".
+
+ (XXX: A more detailed specification will be provided soon. For now,
+ the server-side syntax checking will suffice.)
+
+--
+
+Title: topics
+Text:
+
+ Use "rt help <topic>" for help on any of the following subjects:
+
+ - tickets, users, groups, queues.
+ - show, edit, ls/list/search, new/create.
+
+ - query (search query syntax)
+ - forms (form specification)
+
+ - objects (how to specify objects)
+ - types (a list of object types)
+ - actions/commands (a list of actions)
+ - usage/syntax (syntax details)
+ - conf/config/configuration (configuration details)
+ - examples (a few useful examples)
+
+--
+
+Title: example
+Title: examples
+Text:
+
+ This section will be filled in with useful examples, once it becomes
+ more clear what examples may be useful.
+
+ For the moment, please consult examples provided with each action.
+
+--
diff --git a/rt/bin/webmux.pl b/rt/bin/webmux.pl
new file mode 100755
index 0000000..96e7ebf
--- /dev/null
+++ b/rt/bin/webmux.pl
@@ -0,0 +1,148 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+
+BEGIN {
+ $ENV{'PATH'} = '/bin:/usr/bin'; # or whatever you need
+ $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
+ $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'};
+ $ENV{'ENV'} = '' if defined $ENV{'ENV'};
+ $ENV{'IFS'} = '' if defined $ENV{'IFS'};
+
+}
+
+use lib ("/opt/rt3/local/lib", "/opt/rt3/lib");
+use RT;
+
+package RT::Mason;
+
+use CGI qw(-private_tempfiles); #bring this in before mason, to make sure we
+ #set private_tempfiles
+
+BEGIN {
+ if ($mod_perl::VERSION >= 1.9908) {
+ require Apache::RequestUtil;
+ no warnings 'redefine';
+ my $sub = *Apache::request{CODE};
+ *Apache::request = sub {
+ my $r;
+ eval { $r = $sub->('Apache'); };
+ # warn $@ if $@;
+ return $r;
+ };
+ }
+ if ($CGI::MOD_PERL) {
+ require HTML::Mason::ApacheHandler;
+ }
+ else {
+ require HTML::Mason::CGIHandler;
+ }
+}
+
+use HTML::Mason; # brings in subpackages: Parser, Interp, etc.
+
+use vars qw($Nobody $SystemUser $r);
+
+#This drags in RT's config.pm
+RT::LoadConfig();
+
+use Carp;
+
+{
+ package HTML::Mason::Commands;
+ use vars qw(%session);
+
+ use RT::Tickets;
+ use RT::Transactions;
+ use RT::Users;
+ use RT::CurrentUser;
+ use RT::Templates;
+ use RT::Queues;
+ use RT::ScripActions;
+ use RT::ScripConditions;
+ use RT::Scrips;
+ use RT::Groups;
+ use RT::GroupMembers;
+ use RT::CustomFields;
+ use RT::CustomFieldValues;
+ use RT::TicketCustomFieldValues;
+
+ use RT::Interface::Web;
+ use MIME::Entity;
+ use Text::Wrapper;
+ use CGI::Cookie;
+ use Time::ParseDate;
+ use HTML::Entities;
+}
+
+
+# Activate the following if running httpd as root (the normal case).
+# Resets ownership of all files created by Mason at startup.
+# Note that mysql uses DB for sessions, so there's no need to do this.
+unless ($RT::DatabaseType =~ /(mysql|Pg)/) {
+ # Clean up our umask to protect session files
+ umask(0077);
+
+if ( $CGI::MOD_PERL) {
+ chown( Apache->server->uid, Apache->server->gid, [$RT::MasonSessionDir] )
+ if Apache->server->can('uid');
+ }
+ # Die if WebSessionDir doesn't exist or we can't write to it
+ stat($RT::MasonSessionDir);
+ die "Can't read and write $RT::MasonSessionDir"
+ unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) );
+}
+
+my $ah = &RT::Interface::Web::NewApacheHandler(@RT::MasonParameters) if $CGI::MOD_PERL;
+
+sub handler {
+ ($r) = @_;
+
+ local $SIG{__WARN__};
+ local $SIG{__DIE__};
+
+ RT::Init();
+
+ # We don't need to handle non-text items
+ return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io;
+
+ my %session;
+ my $status;
+ eval { $status = $ah->handle_request($r) };
+ if ($@) {
+ $RT::Logger->crit($@);
+ }
+
+ undef (%session);
+
+ if ($RT::Handle->TransactionDepth) {
+ $RT::Handle->ForceRollback;
+ $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
+ }
+ return $status;
+}
+
+1;
diff --git a/rt/bin/webmux.pl.in b/rt/bin/webmux.pl.in
new file mode 100644
index 0000000..dca5705
--- /dev/null
+++ b/rt/bin/webmux.pl.in
@@ -0,0 +1,148 @@
+#!@PERL@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+
+BEGIN {
+ $ENV{'PATH'} = '/bin:/usr/bin'; # or whatever you need
+ $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
+ $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'};
+ $ENV{'ENV'} = '' if defined $ENV{'ENV'};
+ $ENV{'IFS'} = '' if defined $ENV{'IFS'};
+ @ORACLE_ENV_PREF@
+}
+
+use lib ("@LOCAL_LIB_PATH@", "@RT_LIB_PATH@");
+use RT;
+
+package RT::Mason;
+
+use CGI qw(-private_tempfiles); #bring this in before mason, to make sure we
+ #set private_tempfiles
+
+BEGIN {
+ if ($mod_perl::VERSION >= 1.9908) {
+ require Apache::RequestUtil;
+ no warnings 'redefine';
+ my $sub = *Apache::request{CODE};
+ *Apache::request = sub {
+ my $r;
+ eval { $r = $sub->('Apache'); };
+ # warn $@ if $@;
+ return $r;
+ };
+ }
+ if ($CGI::MOD_PERL) {
+ require HTML::Mason::ApacheHandler;
+ }
+ else {
+ require HTML::Mason::CGIHandler;
+ }
+}
+
+use HTML::Mason; # brings in subpackages: Parser, Interp, etc.
+
+use vars qw($Nobody $SystemUser $r);
+
+#This drags in RT's config.pm
+RT::LoadConfig();
+
+use Carp;
+
+{
+ package HTML::Mason::Commands;
+ use vars qw(%session);
+
+ use RT::Tickets;
+ use RT::Transactions;
+ use RT::Users;
+ use RT::CurrentUser;
+ use RT::Templates;
+ use RT::Queues;
+ use RT::ScripActions;
+ use RT::ScripConditions;
+ use RT::Scrips;
+ use RT::Groups;
+ use RT::GroupMembers;
+ use RT::CustomFields;
+ use RT::CustomFieldValues;
+ use RT::TicketCustomFieldValues;
+
+ use RT::Interface::Web;
+ use MIME::Entity;
+ use Text::Wrapper;
+ use CGI::Cookie;
+ use Time::ParseDate;
+ use HTML::Entities;
+}
+
+
+# Activate the following if running httpd as root (the normal case).
+# Resets ownership of all files created by Mason at startup.
+# Note that mysql uses DB for sessions, so there's no need to do this.
+unless ($RT::DatabaseType =~ /(mysql|Pg)/) {
+ # Clean up our umask to protect session files
+ umask(0077);
+
+if ( $CGI::MOD_PERL) {
+ chown( Apache->server->uid, Apache->server->gid, [$RT::MasonSessionDir] )
+ if Apache->server->can('uid');
+ }
+ # Die if WebSessionDir doesn't exist or we can't write to it
+ stat($RT::MasonSessionDir);
+ die "Can't read and write $RT::MasonSessionDir"
+ unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) );
+}
+
+my $ah = &RT::Interface::Web::NewApacheHandler(@RT::MasonParameters) if $CGI::MOD_PERL;
+
+sub handler {
+ ($r) = @_;
+
+ local $SIG{__WARN__};
+ local $SIG{__DIE__};
+
+ RT::Init();
+
+ # We don't need to handle non-text items
+ return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io;
+
+ my %session;
+ my $status;
+ eval { $status = $ah->handle_request($r) };
+ if ($@) {
+ $RT::Logger->crit($@);
+ }
+
+ undef (%session);
+
+ if ($RT::Handle->TransactionDepth) {
+ $RT::Handle->ForceRollback;
+ $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
+ }
+ return $status;
+}
+
+1;
diff --git a/rt/config b/rt/config
new file mode 100644
index 0000000..b9418a6
--- /dev/null
+++ b/rt/config
@@ -0,0 +1,256 @@
+/*
+ * This is the project ``config'' file. It controls many aspects of
+ * how Aegis interacts with your project.
+ *
+ * There are several sections of this file, each dealing with a different
+ * aspect of the interaction between Aegis and the tools used to manage
+ * yout project.
+ */
+
+/*
+ * -------------------------------------------------------------------------
+ *
+ * The build tool is delegated.
+ */
+
+/*
+ * The build_command field of the config file is used to invoke the relevant
+ * build command. The following command tells cook where to find the recipes.
+ * The ${s Howto.cook} expands to a path into the baseline during development
+ * if the file is not in the change. Look in aesub(5) for more information
+ * about command substitutions.
+ */
+build_command =
+ "";
+
+/* cook -book ${s Howto.cook} search_path=$search_path \
+project=$p change=$c version=$v -star -no-log -action -notouch";
+
+/*
+ * The recipes in the User Guide will all remove their targets before
+ * constructing them, which qualifies them to use the following entry in the
+ * config file. The targets MUST be removed first if this field is true,
+ * otherwise the baseline would cease to be self-consistent.
+ *
+ * Fortunately, Cook has a nifty ``set unlink;'' statement which is
+ * placed at the top of the cookbook.
+ */
+link_integration_directory = true;
+
+
+/*
+ * -------------------------------------------------------------------------
+ *
+ * The history tool is delegated.
+ *
+ * The fhist program was written by David I. Bell and is admirably
+ * suited to providing a history mechanism with out the "cruft" that
+ * SCCS and RCS impose. The fhist program also comes with two other
+ * utilities, fcomp and fmerge, which use the same minimal difference
+ * algorithm.
+ *
+ * Please note that the [# edit #] feature needs to be avoided, or the
+ * -Fored_Update (-fu) flag needs to be used in addition to the
+ * -Conditional_Update (-cu) flag, otherwise updates will complain that
+ * ``Input file "XXX" contains edit A instead of B for module "YYY"''
+ *
+ * The history_create_command and the history_put_command are
+ * intentionally identical. This minimizes problems when using
+ * branches.
+ *
+ * The ${quote ...} construct is used to quote filesnames whicg contain
+ * shell special characters. A minimum of quoting is performed, so if
+ * the filenames do not contail shell special characters, no quotes will
+ * be used.
+ */
+
+/*
+ * This command is used to create a new project history. The command is
+ * always executed as the project owner. Note he the source is left in
+ * the baseline. The following substitutions are available:
+ *
+ * ${Input}
+ * absolute path of the source file
+ * ${History}
+ * absolute path of the history file
+ *
+ * The history_create_command and the history_put_command are
+ * intentionally identical. This minimizes problems when using
+ * branches.
+ */
+history_create_command =
+ "fhist ${quote ${basename $input}} -cr -cu -i ${quote $input} \
+-p ${quote ${dirname $history}} -r";
+
+/*
+ * This command is used to get a specific edit back from history. The
+ * command may be executed by developers. The following substitutions
+ * are available:
+ *
+ * ${History}
+ * absolute path of the history file
+ * ${Edit}
+ * edit number, as given by history_query_command
+ * ${Output}
+ * absolute path of the destination file
+ *
+ * Note that the destination filename will never look anything like the
+ * history source filename, so the -p is essential.
+ */
+history_get_command =
+ "fhist ${quote ${basename $history}} -e ${quote $e} \
+-o ${quote $output} -p ${quote ${dirname $history}}";
+
+/*
+ * This command is used to add a new "top-most" entry to the history
+ * file. This command is always executed as the project owner. Note
+ * that the source file is left in the baseline. The following
+ * substitutions are available:
+ *
+ * ${Input}
+ * absolute path of source file
+ * ${History}
+ * absolute path of history file
+ *
+ * The history_create_command and the history_put_command are
+ * intentionally identical. This minimizes problems when using
+ * branches.
+ */
+history_put_command =
+ "fhist ${quote ${basename $input}} -cr -cu -i ${quote $input} \
+-p ${quote ${dirname $history}} -r";
+
+/*
+ * This command is used to query what the history mechanism calls the
+ * "top-most" edit of a history file. The result may be any arbitrary
+ * string, it need not be anything like a number, just so long as it
+ * uniquely identifies the edit for use by the history_get_command at a
+ * later date. The edit number is to be printed on the standard output.
+ * This command may be executed by developers. The following
+ * substitutions are available:
+ *
+ * ${History}
+ * absolute path of the history file
+ */
+history_query_command =
+ "fhist ${quote ${basename $history}} -l 0 \
+-p ${quote ${dirname $history}} -q";
+
+/*
+ * -------------------------------------------------------------------------
+ *
+ * The difference and merge tools are delegated.
+ */
+
+/*
+ * Compare two files using fcomp. The -w option produces an output of
+ * the entire file, with insertions an deletions marked by "change bars"
+ * in the left margin. This is superior to context difference, as it
+ * shows the entire file as context. The -s option could be added to
+ * compare runs of white space as equal.
+ *
+ * This command is used by aed(1) to produce a difference listing when
+ * file in the development directory was originally copied from the
+ * current version in the baseline.
+ *
+ * All of the command substitutions described in aesub(5) are available.
+ * In addition, the following substitutions are also available:
+ *
+ * ${ORiginal}
+ * The absolute path name of a file containing the version
+ * originally copied. Usually in the baseline.
+ * ${Input}
+ * The absolute path name of the edited version of the file.
+ * Usually in the development directory.
+ * ${Output}
+ * The absolute path name of the file in which to write the
+ * difference listing. Usually in the development directory.
+ *
+ * An exit status of 0 means successful, even of the files differ (and
+ * they usually do). An exit status which is non-zero means something
+ * is wrong.
+ *
+ * The non-zero exit status may be used to overload this command with
+ * extra tests, such as line length limits. The difference files must
+ * be produced in addition to these extra tests.
+ */
+diff_command =
+ "fcomp -w ${quote $original} ${quote $input} -o ${quote $output}";
+
+/*
+ * Compare three files using fmerge. Conflicts are marked in the
+ * output.
+ *
+ * This command is used by aed(1) to produce a difference listing when a
+ * file in the development directory is out of date compared to the
+ * current version in the baseline.
+ *
+ * All of the command substitutions described in aesub(5) are available.
+ * In addition, the following substitutions are also available:
+ *
+ * ${ORiginal}
+ * The absolute path name of a file containing the common ancestor
+ * version of ${MostRecent} and {$Input}. Usually the version
+ * originally copied into the change. Usually in a temporary file.
+ * ${Most_Recent}
+ * The absolute path name of a file containing the most recent
+ * version. Usually in the baseline.
+ * ${Input}
+ * The absolute path name of the edited version of the file.
+ * Usually in the development directory.
+ * ${Output}
+ * The absolute path name of the file in which to write the
+ * difference listing. Usually in the development directory.
+ *
+ * An exit status of 0 means successful, even of the files differ (and
+ * they usually do). An exit status which is non-zero means something
+ * is wrong.
+ */
+merge_command =
+ "fmerge ${quote $original} ${quote $MostRecent} ${quote $input} \
+-o ${quote $output} -c /dev/null";
+
+/*
+ * -------------------------------------------------------------------------
+ *
+ * The new file templates are very handy. They allow all sorts of things
+ * to be se automatically. You need to edit them to add your own name,
+ * and copyright conditions.
+ */
+
+file_template =
+[
+ {
+ pattern = [ "*" ];
+ body = "${read_file ${source etc/template/generic abs}}";
+
+ }
+];
+
+/* -------------------------------------------------------------------------
+ *
+ * The integrate_begin_exceptions are files which are not hard linked
+ * from the baseline to the integration directory. In this case, this
+ * is done to ensure the version stmp is updated appropriately.
+ */
+
+integrate_begin_exceptions = [ ];
+
+
+
+
+/* -------------------------------------------------------------------------
+ *
+ * The trojan_horse_suspect field is a list of filename patterns which
+ * indicate files which *could* host a Trojan horse attack. It makes
+ * aedist --receive more cautions. It is NOT a silver bullet: just
+ * about ANY file can host a Trojan, one way or the other.
+ */
+
+trojan_horse_suspect = [ ];
+
+build_covers_all_architectures = true;
+
+test_command = "make test";
+
+build_time_adjust=dont_adjust;
diff --git a/rt/config.layout.in b/rt/config.layout.in
new file mode 100644
index 0000000..a08f489
--- /dev/null
+++ b/rt/config.layout.in
@@ -0,0 +1,127 @@
+##
+## config.layout -- Pre-defined Installation Path Layouts
+##
+## Hints:
+## - layouts can be loaded with configure's --enable-layout=ID option
+## - when no --enable-layout option is given, the default layout is `RT'
+## - a trailing plus character (`+') on paths is replaced with a
+## `/<target>' suffix where <target> is currently hardcoded to 'rt3'.
+## (This may become a configurable parameter at some point.)
+##
+## The following variables must _all_ be set:
+## prefix exec_prefix bindir sbindir sysconfdir mandir libdir
+## datadir htmldir localstatedir logfiledir masonstatedir
+## sessionstatedir customdir customhtmldir customlexdir
+## (This can be seen in m4/rt_layout.m4.)
+##
+
+# Default RT3 path layout.
+<Layout RT3>
+ prefix: /opt/rt3
+ exec_prefix: ${prefix}
+ bindir: ${exec_prefix}/bin
+ sbindir: ${exec_prefix}/sbin
+ sysconfdir: ${prefix}/etc
+ mandir: ${prefix}/man
+ libdir: ${prefix}/lib
+ datadir: ${prefix}/share
+ htmldir: ${datadir}/html
+ manualdir: ${datadir}/doc
+ localstatedir: ${prefix}/var
+ logfiledir: ${localstatedir}/log
+ masonstatedir: ${localstatedir}/mason_data
+ sessionstatedir: ${localstatedir}/session_data
+ customdir: ${prefix}/local
+ custometcdir: ${customdir}/etc
+ customhtmldir: ${customdir}/html
+ customlexdir: ${customdir}/po
+ customlibdir: ${customdir}/lib
+</Layout>
+<Layout inplace>
+ prefix: `pwd`
+ exec_prefix: ${prefix}
+ bindir: ${exec_prefix}/bin
+ sbindir: ${exec_prefix}/sbin
+ sysconfdir: ${prefix}/etc
+ mandir: ${prefix}/man
+ libdir: ${prefix}/lib
+ datadir: ${prefix}/share
+ htmldir: ${prefix}/html
+ manualdir: ${datadir}/doc
+ localstatedir: ${prefix}/var
+ logfiledir: ${localstatedir}/log
+ masonstatedir: ${localstatedir}/mason_data
+ sessionstatedir: ${localstatedir}/session_data
+ customdir: ${prefix}/local
+ custometcdir: ${customdir}/etc
+ customhtmldir: ${customdir}/html
+ customlexdir: ${customdir}/po
+ customlibdir: ${customdir}/lib
+</Layout>
+
+<Layout FreeBSD>
+ prefix: /usr/local
+ exec_prefix: ${prefix}
+ bindir: ${exec_prefix}/bin
+ sbindir: ${exec_prefix}/sbin
+ sysconfdir: ${prefix}/etc+
+ mandir: ${prefix}/man
+ libdir: ${prefix}/lib+
+ datadir: ${prefix}/share+
+ htmldir: ${datadir}/html
+ manualdir: ${prefix}/share/doc+
+ logfiledir: /var/log
+ localstatedir: /var/run+
+ masonstatedir: ${localstatedir}/mason_data
+ sessionstatedir: ${localstatedir}/session_data
+ customdir: ${prefix}/share+
+ custometcdir: ${customdir}/local/etc
+ customhtmldir: ${customdir}/local/html
+ customlexdir: ${customdir}/local/po
+ customlibdir: ${customdir}/local/lib
+</Layout>
+
+<Layout Win32>
+ prefix: C:/Program Files/Request Tracker
+ exec_prefix: ${prefix}
+ bindir: ${exec_prefix}/bin
+ sbindir: ${exec_prefix}/sbin
+ sysconfdir: ${prefix}/etc
+ mandir: ${prefix}/man
+ libdir: ${prefix}/lib
+ datadir: ${prefix}
+ htmldir: ${datadir}/html
+ manualdir: ${datadir}/doc
+ localstatedir: ${prefix}/var
+ logfiledir: ${localstatedir}/log
+ masonstatedir: ${localstatedir}/mason_data
+ sessionstatedir: ${localstatedir}/session_data
+ customdir: ${prefix}/local
+ custometcdir: ${customdir}/etc
+ customhtmldir: ${customdir}/html
+ customlexdir: ${customdir}/po
+ customlibdir: ${customdir}/lib
+</Layout>
+
+<Layout Freeside>
+ prefix: /opt/rt3
+ exec_prefix: ${prefix}
+ bindir: ${exec_prefix}/bin
+ sbindir: ${exec_prefix}/sbin
+ sysconfdir: ${prefix}/etc
+ mandir: ${prefix}/man
+ libdir: ${prefix}/lib
+ datadir: ${prefix}/share
+ htmldir: %%%FREESIDE_DOCUMENT_ROOT%%%/rt
+ manualdir: ${datadir}/doc
+ localstatedir: ${prefix}/var
+ logfiledir: ${localstatedir}/log
+ masonstatedir: %%%MASONDATA%%%
+ sessionstatedir: ${localstatedir}/session_data
+ customdir: ${prefix}/local
+ custometcdir: ${customdir}/etc
+ customhtmldir: ${customdir}/html
+ customlexdir: ${customdir}/po
+ customlibdir: ${customdir}/lib
+</Layout>
+
diff --git a/rt/config.log b/rt/config.log
new file mode 100644
index 0000000..24e15e3
--- /dev/null
+++ b/rt/config.log
@@ -0,0 +1,118 @@
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by RT configure 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ $ ./configure
+
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = pallas
+uname -m = i686
+uname -r = 2.4.18-686
+uname -s = Linux
+uname -v = #1 Sun Apr 14 11:32:47 EST 2002
+
+/usr/bin/uname -p = unknown
+/bin/uname -X = unknown
+
+/bin/arch = i686
+/usr/bin/arch -k = unknown
+/usr/convex/getsysinfo = unknown
+hostinfo = unknown
+/bin/machine = unknown
+/usr/bin/oslevel = unknown
+/bin/universe = unknown
+
+PATH: /usr/X11R6/bin/
+PATH: /opt/rt/bin
+PATH: /usr/athena/bin
+PATH: /usr/local/bin
+PATH: /bin
+PATH: /usr/bin
+PATH: /usr/sbin
+PATH: /usr/bin
+PATH: /usr/games
+PATH: $HOME/bin
+PATH: /opt/kerberos/bin
+PATH: /opt/StarOffice-4.0/bin
+PATH: /opt/mysql/bin/
+PATH: .
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+configure:1218: checking for a BSD-compatible install
+configure:1272: result: /usr/bin/install -c
+configure:1286: checking for perl
+configure:1304: found /usr/bin/perl
+configure:1317: result: /usr/bin/perl
+configure:1639: checking for chosen layout
+configure:1654: result: RT3
+configure:1986: creating ./config.status
+
+## ---------------------- ##
+## Running config.status. ##
+## ---------------------- ##
+
+This file was extended by RT config.status 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ CONFIG_FILES =
+ CONFIG_HEADERS =
+ CONFIG_LINKS =
+ CONFIG_COMMANDS =
+ $ ./config.status
+
+on pallas
+
+config.status:639: creating sbin/rt-setup-database
+config.status:639: creating sbin/rt-test-dependencies
+config.status:639: creating Makefile
+config.status:639: creating etc/RT_Config.pm
+config.status:639: creating lib/RT.pm
+config.status:639: creating lib/t/00smoke.t
+config.status:639: creating lib/t/01harness.t
+config.status:639: creating lib/t/02regression.t
+config.status:639: creating lib/t/03web.pl
+config.status:639: creating lib/t/04_send_email.pl
+config.status:639: creating bin/mason_handler.fcgi
+config.status:639: creating bin/mason_handler.scgi
+config.status:639: creating bin/mason_handler.svc
+config.status:639: creating bin/rt-commit-handler
+config.status:639: creating bin/rt-crontool
+config.status:639: creating bin/rt-mailgate
+config.status:639: creating bin/rt
+config.status:639: creating bin/webmux.pl
+
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+
+ac_cv_env_PERL_set=
+ac_cv_env_PERL_value=
+ac_cv_env_build_alias_set=
+ac_cv_env_build_alias_value=
+ac_cv_env_host_alias_set=
+ac_cv_env_host_alias_value=
+ac_cv_env_target_alias_set=
+ac_cv_env_target_alias_value=
+ac_cv_path_PERL=/usr/bin/perl
+ac_cv_path_install='/usr/bin/install -c'
+
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+
+#define PACKAGE_NAME "RT"
+#define PACKAGE_TARNAME "rt"
+#define PACKAGE_VERSION "3.0.9"
+#define PACKAGE_STRING "RT 3.0.9"
+#define PACKAGE_BUGREPORT "rt-3.0-bugs@fsck.com"
+
+configure: exit 0
diff --git a/rt/config.pld b/rt/config.pld
new file mode 100644
index 0000000..c71c7bb
--- /dev/null
+++ b/rt/config.pld
@@ -0,0 +1,19 @@
+(test "x$prefix" = "xNONE" || test "x$prefix" = "x") && prefix=/opt/rt3
+(test "x$exec_prefix" = "xNONE" || test "x$exec_prefix" = "x") && exec_prefix=${prefix}
+bindir=${exec_prefix}/bin
+sbindir=${exec_prefix}/sbin
+sysconfdir=${prefix}/etc
+mandir=${prefix}/man
+libdir=${prefix}/lib
+datadir=${prefix}/share
+(test "x$htmldir" = "xNONE" || test "x$htmldir" = "x") && htmldir=${datadir}/html
+(test "x$manualdir" = "xNONE" || test "x$manualdir" = "x") && manualdir=${datadir}/doc
+localstatedir=${prefix}/var
+(test "x$logfiledir" = "xNONE" || test "x$logfiledir" = "x") && logfiledir=${localstatedir}/log
+(test "x$masonstatedir" = "xNONE" || test "x$masonstatedir" = "x") && masonstatedir=${localstatedir}/mason_data
+(test "x$sessionstatedir" = "xNONE" || test "x$sessionstatedir" = "x") && sessionstatedir=${localstatedir}/session_data
+(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=${prefix}/local
+(test "x$custometcdir" = "xNONE" || test "x$custometcdir" = "x") && custometcdir=${customdir}/etc
+(test "x$customhtmldir" = "xNONE" || test "x$customhtmldir" = "x") && customhtmldir=${customdir}/html
+(test "x$customlexdir" = "xNONE" || test "x$customlexdir" = "x") && customlexdir=${customdir}/po
+(test "x$customlibdir" = "xNONE" || test "x$customlibdir" = "x") && customlibdir=${customdir}/lib
diff --git a/rt/config.status b/rt/config.status
new file mode 100755
index 0000000..e7d81b3
--- /dev/null
+++ b/rt/config.status
@@ -0,0 +1,713 @@
+#! /bin/sh
+# Generated by configure.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+
+# NLS nuisances.
+# Support unset when possible.
+if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
+ { $as_unset LANG || test "${LANG+set}" != set; } ||
+ { LANG=C; export LANG; }
+(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
+ { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
+ { LC_ALL=C; export LC_ALL; }
+(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
+ { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
+ { LC_TIME=C; export LC_TIME; }
+(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
+ { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
+ { LC_CTYPE=C; export LC_CTYPE; }
+(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
+ { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
+ { LANGUAGE=C; export LANGUAGE; }
+(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
+ { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
+ { LC_COLLATE=C; export LC_COLLATE; }
+(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
+ { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
+ { LC_NUMERIC=C; export LC_NUMERIC; }
+(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
+ { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
+ { LC_MESSAGES=C; export LC_MESSAGES; }
+
+
+# Name of the executable.
+as_me=`(basename "$0") 2>/dev/null ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conftest.sh
+ echo "exit 0" >>conftest.sh
+ chmod +x conftest.sh
+ if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conftest.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling. Logging --version etc. is OK.
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by RT $as_me 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+config_files=" sbin/rt-setup-database sbin/rt-test-dependencies Makefile etc/RT_Config.pm lib/RT.pm lib/t/00smoke.t lib/t/01harness.t lib/t/02regression.t lib/t/03web.pl lib/t/04_send_email.pl bin/mason_handler.fcgi bin/mason_handler.scgi bin/mason_handler.svc bin/rt-commit-handler bin/rt-crontool bin/rt-mailgate bin/rt bin/webmux.pl"
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <bug-autoconf@gnu.org>."
+ac_cs_version="\
+RT config.status 3.0.9
+configured by ./configure, generated by GNU Autoconf 2.53,
+ with options \"\"
+
+Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001
+Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=.
+INSTALL="/usr/bin/install -c"
+# If no file are specified by the user, then we need to provide default
+# value. By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=*)
+ ac_option=`expr "x$1" : 'x\([^=]*\)='`
+ ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+ shift
+ set dummy "$ac_option" "$ac_optarg" ${1+"$@"}
+ shift
+ ;;
+ -*);;
+ *) # This is not an option, so the user has probably given explicit
+ # arguments.
+ ac_need_defaults=false;;
+ esac
+
+ case $1 in
+ # Handling of the options.
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running /bin/sh ./configure " " --no-create --no-recursion"
+ exec /bin/sh ./configure --no-create --no-recursion ;;
+ --version | --vers* | -V )
+ echo "$ac_cs_version"; exit 0 ;;
+ --he | --h)
+ # Conflict between --help and --header
+ { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; };;
+ --help | --hel | -h )
+ echo "$ac_cs_usage"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ shift
+ CONFIG_FILES="$CONFIG_FILES $1"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ shift
+ CONFIG_HEADERS="$CONFIG_HEADERS $1"
+ ac_need_defaults=false;;
+
+ # This is an error.
+ -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; } ;;
+
+ *) ac_config_targets="$ac_config_targets $1" ;;
+
+ esac
+ shift
+done
+
+for ac_config_target in $ac_config_targets
+do
+ case "$ac_config_target" in
+ # Handling of arguments.
+ "sbin/rt-setup-database" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;;
+ "sbin/rt-test-dependencies" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;;
+ "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "etc/RT_Config.pm" ) CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;;
+ "lib/RT.pm" ) CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;;
+ "lib/t/00smoke.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/00smoke.t" ;;
+ "lib/t/01harness.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/01harness.t" ;;
+ "lib/t/02regression.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/02regression.t" ;;
+ "lib/t/03web.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/03web.pl" ;;
+ "lib/t/04_send_email.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/04_send_email.pl" ;;
+ "bin/mason_handler.fcgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;;
+ "bin/mason_handler.scgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;;
+ "bin/mason_handler.svc" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;;
+ "bin/rt-commit-handler" ) CONFIG_FILES="$CONFIG_FILES bin/rt-commit-handler" ;;
+ "bin/rt-crontool" ) CONFIG_FILES="$CONFIG_FILES bin/rt-crontool" ;;
+ "bin/rt-mailgate" ) CONFIG_FILES="$CONFIG_FILES bin/rt-mailgate" ;;
+ "bin/rt" ) CONFIG_FILES="$CONFIG_FILES bin/rt" ;;
+ "bin/webmux.pl" ) CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;;
+ *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+ trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+ trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+: ${TMPDIR=/tmp}
+{
+ tmp=`(umask 077 && mktemp -d -q "$TMPDIR/csXXXXXX") 2>/dev/null` &&
+ test -n "$tmp" && test -d "$tmp"
+} ||
+{
+ tmp=$TMPDIR/cs$$-$RANDOM
+ (umask 077 && mkdir $tmp)
+} ||
+{
+ echo "$me: cannot create a temporary directory in $TMPDIR" >&2
+ { (exit 1); exit 1; }
+}
+
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "$CONFIG_FILES"; then
+ # Protect against being on the right side of a sed subst in config.status.
+ sed 's/,@/@@/; s/@,/@@/; s/,;t t$/@;t t/; /@;t t$/s/[\\&,]/\\&/g;
+ s/@@/,@/; s/@@/@,/; s/@;t t$/,;t t/' >$tmp/subs.sed <<\CEOF
+s,@SHELL@,/bin/sh,;t t
+s,@PATH_SEPARATOR@,:,;t t
+s,@PACKAGE_NAME@,RT,;t t
+s,@PACKAGE_TARNAME@,rt,;t t
+s,@PACKAGE_VERSION@,3.0.9,;t t
+s,@PACKAGE_STRING@,RT 3.0.9,;t t
+s,@PACKAGE_BUGREPORT@,rt-3.0-bugs@fsck.com,;t t
+s,@exec_prefix@,/opt/rt3,;t t
+s,@prefix@,/opt/rt3,;t t
+s,@program_transform_name@,s,x,x,,;t t
+s,@bindir@,/opt/rt3/bin,;t t
+s,@sbindir@,/opt/rt3/sbin,;t t
+s,@libexecdir@,${exec_prefix}/libexec,;t t
+s,@datadir@,/opt/rt3/share,;t t
+s,@sysconfdir@,/opt/rt3/etc,;t t
+s,@sharedstatedir@,${prefix}/com,;t t
+s,@localstatedir@,/opt/rt3/var,;t t
+s,@libdir@,/opt/rt3/lib,;t t
+s,@includedir@,${prefix}/include,;t t
+s,@oldincludedir@,/usr/include,;t t
+s,@infodir@,${prefix}/info,;t t
+s,@mandir@,/opt/rt3/man,;t t
+s,@build_alias@,,;t t
+s,@host_alias@,,;t t
+s,@target_alias@,,;t t
+s,@DEFS@,-DPACKAGE_NAME=\"RT\" -DPACKAGE_TARNAME=\"rt\" -DPACKAGE_VERSION=\"3.0.9\" -DPACKAGE_STRING=\"RT\ 3.0.9\" -DPACKAGE_BUGREPORT=\"rt-3.0-bugs@fsck.com\" ,;t t
+s,@ECHO_C@,,;t t
+s,@ECHO_N@,-n,;t t
+s,@ECHO_T@,,;t t
+s,@LIBS@,,;t t
+s,@rt_version_major@,3,;t t
+s,@rt_version_minor@,0,;t t
+s,@rt_version_patch@,9,;t t
+s,@INSTALL_PROGRAM@,${INSTALL},;t t
+s,@INSTALL_SCRIPT@,${INSTALL},;t t
+s,@INSTALL_DATA@,${INSTALL} -m 644,;t t
+s,@PERL@,/usr/bin/perl,;t t
+s,@SPEEDY_BIN@,/usr/local/bin/speedy,;t t
+s,@exp_prefix@,/opt/rt3,;t t
+s,@exp_exec_prefix@,/opt/rt3,;t t
+s,@exp_bindir@,/opt/rt3/bin,;t t
+s,@exp_sbindir@,/opt/rt3/sbin,;t t
+s,@exp_sysconfdir@,/opt/rt3/etc,;t t
+s,@exp_mandir@,/opt/rt3/man,;t t
+s,@exp_libdir@,/opt/rt3/lib,;t t
+s,@exp_datadir@,/opt/rt3/share,;t t
+s,@htmldir@,/opt/rt3/share/html,;t t
+s,@exp_htmldir@,/opt/rt3/share/html,;t t
+s,@manualdir@,/opt/rt3/share/doc,;t t
+s,@exp_manualdir@,/opt/rt3/share/doc,;t t
+s,@exp_localstatedir@,/opt/rt3/var,;t t
+s,@logfiledir@,/opt/rt3/var/log,;t t
+s,@exp_logfiledir@,/opt/rt3/var/log,;t t
+s,@masonstatedir@,/opt/rt3/var/mason_data,;t t
+s,@exp_masonstatedir@,/opt/rt3/var/mason_data,;t t
+s,@sessionstatedir@,/opt/rt3/var/session_data,;t t
+s,@exp_sessionstatedir@,/opt/rt3/var/session_data,;t t
+s,@customdir@,/opt/rt3/local,;t t
+s,@exp_customdir@,/opt/rt3/local,;t t
+s,@custometcdir@,/opt/rt3/local/etc,;t t
+s,@exp_custometcdir@,/opt/rt3/local/etc,;t t
+s,@customhtmldir@,/opt/rt3/local/html,;t t
+s,@exp_customhtmldir@,/opt/rt3/local/html,;t t
+s,@customlexdir@,/opt/rt3/local/po,;t t
+s,@exp_customlexdir@,/opt/rt3/local/po,;t t
+s,@customlibdir@,/opt/rt3/local/lib,;t t
+s,@exp_customlibdir@,/opt/rt3/local/lib,;t t
+s,@rt_layout_name@,RT3,;t t
+s,@RTGROUP@,rt,;t t
+s,@BIN_OWNER@,root,;t t
+s,@LIBS_OWNER@,root,;t t
+s,@LIBS_GROUP@,bin,;t t
+s,@DB_TYPE@,mysql,;t t
+s,@ORACLE_ENV_PREF@,,;t t
+s,@DB_HOST@,localhost,;t t
+s,@DB_PORT@,,;t t
+s,@DB_RT_HOST@,localhost,;t t
+s,@DB_DBA@,root,;t t
+s,@DB_DATABASE@,rt3,;t t
+s,@DB_RT_USER@,rt_user,;t t
+s,@DB_RT_PASS@,rt_pass,;t t
+s,@WEB_USER@,www,;t t
+s,@WEB_GROUP@,www,;t t
+s,@RT_VERSION_MAJOR@,3,;t t
+s,@RT_VERSION_MINOR@,0,;t t
+s,@RT_VERSION_PATCH@,9,;t t
+s,@RT_PATH@,/opt/rt3,;t t
+s,@RT_DOC_PATH@,/opt/rt3/share/doc,;t t
+s,@RT_LOCAL_PATH@,/opt/rt3/local,;t t
+s,@RT_LIB_PATH@,/opt/rt3/lib,;t t
+s,@RT_ETC_PATH@,/opt/rt3/etc,;t t
+s,@CONFIG_FILE_PATH@,/opt/rt3/etc,;t t
+s,@RT_BIN_PATH@,/opt/rt3/bin,;t t
+s,@RT_SBIN_PATH@,/opt/rt3/sbin,;t t
+s,@RT_VAR_PATH@,/opt/rt3/var,;t t
+s,@RT_MAN_PATH@,/opt/rt3/man,;t t
+s,@MASON_DATA_PATH@,/opt/rt3/var/mason_data,;t t
+s,@MASON_SESSION_PATH@,/opt/rt3/var/session_data,;t t
+s,@MASON_HTML_PATH@,/opt/rt3/share/html,;t t
+s,@LOCAL_ETC_PATH@,/opt/rt3/local/etc,;t t
+s,@MASON_LOCAL_HTML_PATH@,/opt/rt3/local/html,;t t
+s,@LOCAL_LEXICON_PATH@,/opt/rt3/local/po,;t t
+s,@LOCAL_LIB_PATH@,/opt/rt3/local/lib,;t t
+s,@DESTDIR@,/opt/rt3,;t t
+s,@RT_LOG_PATH@,/opt/rt3/var/log,;t t
+CEOF
+
+ # Split the substitutions into bite-sized pieces for seds with
+ # small command number limits, like on Digital OSF/1 and HP-UX.
+ ac_max_sed_lines=48
+ ac_sed_frag=1 # Number of current file.
+ ac_beg=1 # First line for current file.
+ ac_end=$ac_max_sed_lines # Line after last line for current file.
+ ac_more_lines=:
+ ac_sed_cmds=
+ while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ else
+ sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ fi
+ if test ! -s $tmp/subs.frag; then
+ ac_more_lines=false
+ else
+ # The purpose of the label and of the branching condition is to
+ # speed up the sed processing (if there are no `@' at all, there
+ # is no need to browse any of the substitutions).
+ # These are the two extra sed commands mentioned above.
+ (echo ':t
+ /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+ fi
+ ac_sed_frag=`expr $ac_sed_frag + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_lines`
+ fi
+ done
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+ fi
+fi # test -n "$CONFIG_FILES"
+
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { case "$ac_dir" in
+ [\\/]* | ?:[\\/]* ) as_incr_dir=;;
+ *) as_incr_dir=.;;
+esac
+as_dummy="$ac_dir"
+for as_mkdir_dir in `IFS='/\\'; set X $as_dummy; shift; echo "$@"`; do
+ case $as_mkdir_dir in
+ # Skip DOS drivespec
+ ?:) as_incr_dir=$as_mkdir_dir ;;
+ *)
+ as_incr_dir=$as_incr_dir/$as_mkdir_dir
+ test -d "$as_incr_dir" ||
+ mkdir "$as_incr_dir" ||
+ { { echo "$as_me:$LINENO: error: cannot create \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }
+ ;;
+ esac
+done; }
+
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
+# absolute.
+ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
+ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
+ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
+ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_builddir$INSTALL ;;
+ esac
+
+ if test x"$ac_file" != x-; then
+ { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+ rm -f "$ac_file"
+ fi
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ configure_input=
+ else
+ configure_input="$ac_file. "
+ fi
+ configure_input=$configure_input"Generated from `echo $ac_file_in |
+ sed 's,.*/,,'` by configure."
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ echo $f;;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo $f
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo $srcdir/$f
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+ sed "/^[ ]*VPATH[ ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[ ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[ ]*$//;
+}
+
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+s,@INSTALL@,$ac_INSTALL,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+ rm -f $tmp/stdin
+ if test x"$ac_file" != x-; then
+ mv $tmp/out $ac_file
+ else
+ cat $tmp/out
+ rm -f $tmp/out
+ fi
+
+done
+
+{ (exit 0); exit 0; }
diff --git a/rt/configure b/rt/configure
new file mode 100755
index 0000000..a906758
--- /dev/null
+++ b/rt/configure
@@ -0,0 +1,2771 @@
+#! /bin/sh
+# From configure.ac Revision: 1.1 .
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.53 for RT 3.0.9.
+#
+# Report bugs to <rt-3.0-bugs@fsck.com>.
+#
+# Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002
+# Free Software Foundation, Inc.
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+
+# NLS nuisances.
+# Support unset when possible.
+if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
+ { $as_unset LANG || test "${LANG+set}" != set; } ||
+ { LANG=C; export LANG; }
+(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
+ { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
+ { LC_ALL=C; export LC_ALL; }
+(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
+ { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
+ { LC_TIME=C; export LC_TIME; }
+(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
+ { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
+ { LC_CTYPE=C; export LC_CTYPE; }
+(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
+ { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
+ { LANGUAGE=C; export LANGUAGE; }
+(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
+ { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
+ { LC_COLLATE=C; export LC_COLLATE; }
+(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
+ { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
+ { LC_NUMERIC=C; export LC_NUMERIC; }
+(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
+ { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
+ { LC_MESSAGES=C; export LC_MESSAGES; }
+
+
+# Name of the executable.
+as_me=`(basename "$0") 2>/dev/null ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conftest.sh
+ echo "exit 0" >>conftest.sh
+ chmod +x conftest.sh
+ if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conftest.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+exec 6>&1
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Maximum number of lines to put in a shell here document.
+# This variable seems obsolete. It should probably be removed, and
+# only ac_max_sed_lines should be used.
+: ${ac_max_here_lines=38}
+
+# Identity of this package.
+PACKAGE_NAME='RT'
+PACKAGE_TARNAME='rt'
+PACKAGE_VERSION='3.0.9'
+PACKAGE_STRING='RT 3.0.9'
+PACKAGE_BUGREPORT='rt-3.0-bugs@fsck.com'
+
+ac_unique_file="lib/RT.pm.in"
+ac_default_prefix=/opt/rt3
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+ac_prev=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_option in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ eval "enable_$ac_feature=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_$ac_feature='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_$ac_package='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package | sed 's/-/_/g'`
+ eval "with_$ac_package=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+ { (exit 1); exit 1; }; }
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+ { (exit 1); exit 1; }; }
+ ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
+ eval "$ac_envvar='$ac_optarg'"
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ { echo "$as_me: error: missing argument to $ac_option" >&2
+ { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute paths.
+for ac_var in exec_prefix prefix
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* | NONE | '' ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# Be sure to have absolute paths.
+for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \
+ localstatedir libdir includedir oldincludedir infodir mandir
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+ If a cross compiler is detected then cross compile mode will be used." >&2
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_confdir=`(dirname "$0") 2>/dev/null ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$0" : 'X\(//\)[^/]' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$0" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2
+ { (exit 1); exit 1; }; }
+ else
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+ { (exit 1); exit 1; }; }
+ fi
+fi
+srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'`
+ac_env_build_alias_set=${build_alias+set}
+ac_env_build_alias_value=$build_alias
+ac_cv_env_build_alias_set=${build_alias+set}
+ac_cv_env_build_alias_value=$build_alias
+ac_env_host_alias_set=${host_alias+set}
+ac_env_host_alias_value=$host_alias
+ac_cv_env_host_alias_set=${host_alias+set}
+ac_cv_env_host_alias_value=$host_alias
+ac_env_target_alias_set=${target_alias+set}
+ac_env_target_alias_value=$target_alias
+ac_cv_env_target_alias_set=${target_alias+set}
+ac_cv_env_target_alias_value=$target_alias
+ac_env_PERL_set=${PERL+set}
+ac_env_PERL_value=$PERL
+ac_cv_env_PERL_set=${PERL+set}
+ac_cv_env_PERL_value=$PERL
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures RT 3.0.9 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+_ACEOF
+
+ cat <<_ACEOF
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --infodir=DIR info documentation [PREFIX/info]
+ --mandir=DIR man documentation [PREFIX/man]
+_ACEOF
+
+ cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of RT 3.0.9:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-layout=LAYOUT Use a specific directory layout (Default: RT3)
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-speedycgi=/path/to/speedy
+ path to your speedycgi binary, if it exists
+ --with-rt-group=GROUP group to own all files (default: rt)
+ --with-bin-owner=OWNER user that will own rt binaries (default root)
+ --with-libs-owner=OWNER user that will own RT libraries (default root)
+ --with-libs-group=GROUP group that will own rt binaries (default bin)
+ --with-db-type=TYPE sort of database RT will use (default: mysql)
+ (mysql, Pg, Oracle and Informix are valid)
+ --with-db-host=HOSTNAME FQDN of database server (default: localhost)
+ --with-db-port=PORT port on which the database listens on
+ --with-db-rt-host=HOSTNAME
+ FQDN of RT server which talks to the database server
+ (default: localhost)
+ --with-db-dba=DBA name of database administrator (default: root)
+ --with-db-database=DBNAME
+ name of the database to use (default: rt3)
+ --with-db-rt-user=DBUSER
+ name of database user (default: rt_user)
+ --with-db-rt-pass=PASSWORD
+ password for database user (default: rt_pass)
+ --with-web-user=USER user the web server runs as (default: www)
+ --with-web-group=GROUP group the web server runs as (default: www)
+ --with-my-user-group set all users and groups to current user/group
+
+Some influential environment variables:
+ PERL Perl interpreter command
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <rt-3.0-bugs@fsck.com>.
+_ACEOF
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ ac_popdir=`pwd`
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d $ac_dir || continue
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
+# absolute.
+ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
+ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
+ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
+ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+ cd $ac_dir
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f $ac_srcdir/configure.gnu; then
+ echo
+ $SHELL $ac_srcdir/configure.gnu --help=recursive
+ elif test -f $ac_srcdir/configure; then
+ echo
+ $SHELL $ac_srcdir/configure --help=recursive
+ elif test -f $ac_srcdir/configure.ac ||
+ test -f $ac_srcdir/configure.in; then
+ echo
+ $ac_configure --help
+ else
+ echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi
+ cd $ac_popdir
+ done
+fi
+
+test -n "$ac_init_help" && exit 0
+if $ac_init_version; then
+ cat <<\_ACEOF
+RT configure 3.0.9
+generated by GNU Autoconf 2.53
+
+Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002
+Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit 0
+fi
+exec 5>config.log
+cat >&5 <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by RT $as_me 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+hostinfo = `(hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ echo "PATH: $as_dir"
+done
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell meta-characters.
+ac_configure_args=
+ac_sep=
+for ac_arg
+do
+ case $ac_arg in
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n ) continue ;;
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ continue ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'"
+ ac_sep=" " ;;
+ esac
+ # Get rid of the leading space.
+done
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Be sure not to use single quotes in there, as some shells,
+# such as our DU 5.0 friend, will then `close' the trap.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+ cat <<\_ASBOX
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+_ASBOX
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+{
+ (set) 2>&1 |
+ case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ sed -n \
+ "s/'"'"'/'"'"'\\\\'"'"''"'"'/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p"
+ ;;
+ *)
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+}
+ echo
+ if test -s confdefs.h; then
+ cat <<\_ASBOX
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+_ASBOX
+ echo
+ sed "/^$/d" confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ echo "$as_me: caught signal $ac_signal"
+ echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core core.* *.core &&
+ rm -rf conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+ ' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo >confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special
+ # files actually), so we avoid doing that.
+ if test -f "$cache_file"; then
+ { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . $cache_file;;
+ *) . ./$cache_file;;
+ esac
+ fi
+else
+ { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in `(set) 2>&1 |
+ sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val="\$ac_cv_env_${ac_var}_value"
+ eval ac_new_val="\$ac_env_${ac_var}_value"
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ { echo "$as_me:$LINENO: former value: $ac_old_val" >&5
+echo "$as_me: former value: $ac_old_val" >&2;}
+ { echo "$as_me:$LINENO: current value: $ac_new_val" >&5
+echo "$as_me: current value: $ac_new_val" >&2;}
+ ac_cache_corrupted=:
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+rt_version_major=3
+
+rt_version_minor=0
+
+rt_version_patch=9
+
+test "x$rt_version_major" = 'x' && rt_version_major=0
+test "x$rt_version_minor" = 'x' && rt_version_minor=0
+test "x$rt_version_patch" = 'x' && rt_version_patch=0
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f $ac_dir/shtool; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&5
+echo "$as_me: error: cannot find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"
+ac_configure="$SHELL $ac_aux_dir/configure" # This should be Cygnus configure.
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5
+echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6
+if test -z "$INSTALL"; then
+if test "${ac_cv_path_install+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in
+ ./ | .// | /cC/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+done
+
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL=$ac_install_sh
+ fi
+fi
+echo "$as_me:$LINENO: result: $INSTALL" >&5
+echo "${ECHO_T}$INSTALL" >&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_path_PERL+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ case $PERL in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PERL="$PERL" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+ test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="not found"
+ ;;
+esac
+fi
+PERL=$ac_cv_path_PERL
+
+if test -n "$PERL"; then
+ echo "$as_me:$LINENO: result: $PERL" >&5
+echo "${ECHO_T}$PERL" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+if test "$PERL" = 'not found'; then
+ { { echo "$as_me:$LINENO: error: cannot use $PACKAGE_NAME without perl" >&5
+echo "$as_me: error: cannot use $PACKAGE_NAME without perl" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+# Check whether --with-speedycgi or --without-speedycgi was given.
+if test "${with_speedycgi+set}" = set; then
+ withval="$with_speedycgi"
+ SPEEDY_BIN=$withval
+else
+ SPEEDY_BIN=/usr/local/bin/speedy
+fi;
+
+
+
+
+
+# Check whether --enable-layout or --disable-layout was given.
+if test "${enable_layout+set}" = set; then
+ enableval="$enable_layout"
+ LAYOUT=$enableval
+fi;
+
+if test "x$LAYOUT" = "x"; then
+ LAYOUT="RT3"
+fi
+
+ if test ! -f $srcdir/config.layout; then
+ { echo "$as_me:$LINENO: WARNING: Layout file $srcdir/config.layout not found" >&5
+echo "$as_me: WARNING: Layout file $srcdir/config.layout not found" >&2;}
+ rt_layout_name=no
+ else
+ pldconf=./config.pld
+ $PERL -0777 -p -e "\$layout = '$LAYOUT';" -e '
+ s/.*<Layout\s+$layout>//gims;
+ s/\<\/Layout\>.*//s;
+ s/^#.*$//m;
+ s/^\s+//gim;
+ s/\s+$/\n/gim;
+ s/\+$/\/rt3/gim;
+ # m4 will not let us just use $srcdir/config.layout, we need $1
+ s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man)dir)\s*:\s*(.*)$/$1=$2/gim;
+ s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\$$1" = "xNONE" || test "x\$$1" = "x") && $1=$2/gim;
+ ' < $srcdir/config.layout > $pldconf
+
+ if test -s $pldconf; then
+ rt_layout_name=$LAYOUT
+ . $pldconf
+
+ for var in prefix exec_prefix bindir sbindir \
+ sysconfdir mandir libdir datadir htmldir \
+ localstatedir logfiledir masonstatedir \
+ sessionstatedir customdir custometcdir customhtmldir \
+ customlexdir customlibdir manualdir; do
+ eval "val=\"\$$var\""
+ val=`echo $val | sed -e 's:\(.\)/*$:\1:'`
+ val=`echo $val |
+ sed -e 's:[\$]\([a-z_]*\):$\1:g'`
+ eval "$var='$val'"
+ done
+
+ else
+ rt_layout_name=no
+ fi
+ #rm $pldconf
+ fi
+
+
+ ap_last=''
+ ap_cur='$prefix'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_prefix="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$exec_prefix'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_exec_prefix="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$bindir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_bindir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sbindir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sbindir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sysconfdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sysconfdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$mandir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_mandir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$libdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_libdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$datadir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_datadir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$htmldir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_htmldir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$manualdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_manualdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$localstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_localstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$logfiledir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_logfiledir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$masonstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_masonstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$sessionstatedir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_sessionstatedir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$custometcdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_custometcdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customhtmldir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customhtmldir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customlexdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customlexdir="${ap_cur}"
+
+
+
+
+
+
+ ap_last=''
+ ap_cur='$customlibdir'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ exp_customlibdir="${ap_cur}"
+
+
+
+
+
+echo "$as_me:$LINENO: checking for chosen layout" >&5
+echo $ECHO_N "checking for chosen layout... $ECHO_C" >&6
+if test "x$rt_layout_name" = "xno"; then
+ if test "x$LAYOUT" = "xno"; then
+ echo "$as_me:$LINENO: result: none" >&5
+echo "${ECHO_T}none" >&6
+ else
+ echo "$as_me:$LINENO: result: $LAYOUT" >&5
+echo "${ECHO_T}$LAYOUT" >&6
+ fi
+ { { echo "$as_me:$LINENO: error: a valid layout must be specified (or the default used)" >&5
+echo "$as_me: error: a valid layout must be specified (or the default used)" >&2;}
+ { (exit 1); exit 1; }; }
+else
+
+ echo "$as_me:$LINENO: result: $rt_layout_name" >&5
+echo "${ECHO_T}$rt_layout_name" >&6
+fi
+
+
+
+# Check whether --with-rt-group or --without-rt-group was given.
+if test "${with_rt_group+set}" = set; then
+ withval="$with_rt_group"
+ RTGROUP=$withval
+else
+ RTGROUP=rt
+fi;
+
+
+
+# Check whether --with-bin-owner or --without-bin-owner was given.
+if test "${with_bin_owner+set}" = set; then
+ withval="$with_bin_owner"
+ BIN_OWNER=$withval
+else
+ BIN_OWNER=root
+fi;
+
+
+
+# Check whether --with-libs-owner or --without-libs-owner was given.
+if test "${with_libs_owner+set}" = set; then
+ withval="$with_libs_owner"
+ LIBS_OWNER=$withval
+else
+ LIBS_OWNER=root
+fi;
+
+
+
+# Check whether --with-libs-group or --without-libs-group was given.
+if test "${with_libs_group+set}" = set; then
+ withval="$with_libs_group"
+ LIBS_GROUP=$withval
+else
+ LIBS_GROUP=bin
+fi;
+
+
+
+# Check whether --with-db-type or --without-db-type was given.
+if test "${with_db_type+set}" = set; then
+ withval="$with_db_type"
+ DB_TYPE=$withval
+else
+ DB_TYPE=mysql
+fi;
+if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' -a "$DB_TYPE" != 'Informix' ; then
+ { { echo "$as_me:$LINENO: error: Only Oracle, Informix, Pg and mysql are valid db types" >&5
+echo "$as_me: error: Only Oracle, Informix, Pg and mysql are valid db types" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+
+if test "$DB_TYPE" = 'Oracle'; then
+ test "x$ORACLE_HOME" = 'x' && { { echo "$as_me:$LINENO: error: Please declare the ORACLE_HOME environment variable" >&5
+echo "$as_me: error: Please declare the ORACLE_HOME environment variable" >&2;}
+ { (exit 1); exit 1; }; }
+ ORACLE_ENV_PREF="\$ENV{'ORACLE_HOME'} = '$ORACLE_HOME';"
+fi
+
+
+
+# Check whether --with-db-host or --without-db-host was given.
+if test "${with_db_host+set}" = set; then
+ withval="$with_db_host"
+ DB_HOST=$withval
+else
+ DB_HOST=localhost
+fi;
+
+
+
+# Check whether --with-db-port or --without-db-port was given.
+if test "${with_db_port+set}" = set; then
+ withval="$with_db_port"
+ DB_PORT=$withval
+else
+ DB_PORT=
+fi;
+
+
+
+# Check whether --with-db-rt-host or --without-db-rt-host was given.
+if test "${with_db_rt_host+set}" = set; then
+ withval="$with_db_rt_host"
+ DB_RT_HOST=$withval
+else
+ DB_RT_HOST=localhost
+fi;
+
+
+
+# Check whether --with-db-dba or --without-db-dba was given.
+if test "${with_db_dba+set}" = set; then
+ withval="$with_db_dba"
+ DB_DBA=$withval
+else
+ DB_DBA=root
+fi;
+
+
+
+# Check whether --with-db-database or --without-db-database was given.
+if test "${with_db_database+set}" = set; then
+ withval="$with_db_database"
+ DB_DATABASE=$withval
+else
+ DB_DATABASE=rt3
+fi;
+
+
+
+# Check whether --with-db-rt-user or --without-db-rt-user was given.
+if test "${with_db_rt_user+set}" = set; then
+ withval="$with_db_rt_user"
+ DB_RT_USER=$withval
+else
+ DB_RT_USER=rt_user
+fi;
+
+
+
+# Check whether --with-db-rt-pass or --without-db-rt-pass was given.
+if test "${with_db_rt_pass+set}" = set; then
+ withval="$with_db_rt_pass"
+ DB_RT_PASS=$withval
+else
+ DB_RT_PASS=rt_pass
+fi;
+
+
+
+# Check whether --with-web-user or --without-web-user was given.
+if test "${with_web_user+set}" = set; then
+ withval="$with_web_user"
+ WEB_USER=$withval
+else
+ WEB_USER=www
+fi;
+
+
+
+# Check whether --with-web-group or --without-web-group was given.
+if test "${with_web_group+set}" = set; then
+ withval="$with_web_group"
+ WEB_GROUP=$withval
+else
+ WEB_GROUP=www
+fi;
+
+
+my_group=$(groups|cut -f1 -d' ')
+
+# Check whether --with-my-user-group or --without-my-user-group was given.
+if test "${with_my_user_group+set}" = set; then
+ withval="$with_my_user_group"
+ RTGROUP=$my_group
+ BIN_OWNER=$USER
+ LIBS_OWNER=$USER
+ LIBS_GROUP=$my_group
+ WEB_USER=$USER
+ WEB_GROUP=$my_group
+fi;
+
+
+RT_VERSION_MAJOR=${rt_version_major}
+
+RT_VERSION_MINOR=${rt_version_minor}
+
+RT_VERSION_PATCH=${rt_version_patch}
+
+
+RT_PATH=${exp_prefix}
+
+RT_DOC_PATH=${exp_manualdir}
+
+RT_LOCAL_PATH=${exp_customdir}
+
+RT_LIB_PATH=${exp_libdir}
+
+RT_ETC_PATH=${exp_sysconfdir}
+
+CONFIG_FILE_PATH=${exp_sysconfdir}
+
+RT_BIN_PATH=${exp_bindir}
+
+RT_SBIN_PATH=${exp_sbindir}
+
+RT_VAR_PATH=${exp_localstatedir}
+
+RT_MAN_PATH=${exp_mandir}
+
+MASON_DATA_PATH=${exp_masonstatedir}
+
+MASON_SESSION_PATH=${exp_sessionstatedir}
+
+MASON_HTML_PATH=${exp_htmldir}
+
+LOCAL_ETC_PATH=${exp_custometcdir}
+
+MASON_LOCAL_HTML_PATH=${exp_customhtmldir}
+
+LOCAL_LEXICON_PATH=${exp_customlexdir}
+
+LOCAL_LIB_PATH=${exp_customlibdir}
+
+DESTDIR=${exp_prefix}
+
+RT_LOG_PATH=${exp_logfiledir}
+
+
+
+ac_config_files="$ac_config_files sbin/rt-setup-database sbin/rt-test-dependencies Makefile etc/RT_Config.pm lib/RT.pm lib/t/00smoke.t lib/t/01harness.t lib/t/02regression.t lib/t/03web.pl lib/t/04_send_email.pl bin/mason_handler.fcgi bin/mason_handler.scgi bin/mason_handler.svc bin/rt-commit-handler bin/rt-crontool bin/rt-mailgate bin/rt bin/webmux.pl"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overriden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+{
+ (set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+} |
+ sed '
+ t clear
+ : clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ : end' >>confcache
+if cmp -s $cache_file confcache; then :; else
+ if test -w $cache_file; then
+ test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file"
+ cat confcache >$cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[ ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[ ]*$//;
+}'
+fi
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then we branch to the quote section. Otherwise,
+# look for a macro that doesn't take arguments.
+cat >confdef2opt.sed <<\_ACEOF
+t clear
+: clear
+s,^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\),-D\1=\2,g
+t quote
+s,^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\),-D\1=\2,g
+t quote
+d
+: quote
+s,[ `~#$^&*(){}\\|;'"<>?],\\&,g
+s,\[,\\&,g
+s,\],\\&,g
+s,\$,$$,g
+p
+_ACEOF
+# We use echo to avoid assuming a particular line-breaking character.
+# The extra dot is to prevent the shell from consuming trailing
+# line-breaks from the sub-command output. A line-break within
+# single-quotes doesn't work because, if this script is created in a
+# platform that uses two characters for line-breaks (e.g., DOS), tr
+# would break.
+ac_LF_and_DOT=`echo; echo .`
+DEFS=`sed -n -f confdef2opt.sed confdefs.h | tr "$ac_LF_and_DOT" ' .'`
+rm -f confdef2opt.sed
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+
+# NLS nuisances.
+# Support unset when possible.
+if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
+ { $as_unset LANG || test "${LANG+set}" != set; } ||
+ { LANG=C; export LANG; }
+(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
+ { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
+ { LC_ALL=C; export LC_ALL; }
+(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
+ { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
+ { LC_TIME=C; export LC_TIME; }
+(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
+ { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
+ { LC_CTYPE=C; export LC_CTYPE; }
+(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
+ { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
+ { LANGUAGE=C; export LANGUAGE; }
+(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
+ { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
+ { LC_COLLATE=C; export LC_COLLATE; }
+(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
+ { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
+ { LC_NUMERIC=C; export LC_NUMERIC; }
+(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
+ { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
+ { LC_MESSAGES=C; export LC_MESSAGES; }
+
+
+# Name of the executable.
+as_me=`(basename "$0") 2>/dev/null ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conftest.sh
+ echo "exit 0" >>conftest.sh
+ chmod +x conftest.sh
+ if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conftest.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling. Logging --version etc. is OK.
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by RT $as_me 3.0.9, which was
+generated by GNU Autoconf 2.53. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+_ACEOF
+
+# Files that config.status was made for.
+if test -n "$ac_config_files"; then
+ echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_headers"; then
+ echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_links"; then
+ echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_commands"; then
+ echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <bug-autoconf@gnu.org>."
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+RT config.status 3.0.9
+configured by $0, generated by GNU Autoconf 2.53,
+ with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001
+Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=$srcdir
+INSTALL="$INSTALL"
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value. By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=*)
+ ac_option=`expr "x$1" : 'x\([^=]*\)='`
+ ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+ shift
+ set dummy "$ac_option" "$ac_optarg" ${1+"$@"}
+ shift
+ ;;
+ -*);;
+ *) # This is not an option, so the user has probably given explicit
+ # arguments.
+ ac_need_defaults=false;;
+ esac
+
+ case $1 in
+ # Handling of the options.
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running $SHELL $0 " $ac_configure_args " --no-create --no-recursion"
+ exec $SHELL $0 $ac_configure_args --no-create --no-recursion ;;
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+ --version | --vers* | -V )
+ echo "$ac_cs_version"; exit 0 ;;
+ --he | --h)
+ # Conflict between --help and --header
+ { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; };;
+ --help | --hel | -h )
+ echo "$ac_cs_usage"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ shift
+ CONFIG_FILES="$CONFIG_FILES $1"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ shift
+ CONFIG_HEADERS="$CONFIG_HEADERS $1"
+ ac_need_defaults=false;;
+
+ # This is an error.
+ -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; } ;;
+
+ *) ac_config_targets="$ac_config_targets $1" ;;
+
+ esac
+ shift
+done
+
+_ACEOF
+
+
+
+
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_config_target in $ac_config_targets
+do
+ case "$ac_config_target" in
+ # Handling of arguments.
+ "sbin/rt-setup-database" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;;
+ "sbin/rt-test-dependencies" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;;
+ "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "etc/RT_Config.pm" ) CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;;
+ "lib/RT.pm" ) CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;;
+ "lib/t/00smoke.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/00smoke.t" ;;
+ "lib/t/01harness.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/01harness.t" ;;
+ "lib/t/02regression.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/02regression.t" ;;
+ "lib/t/03web.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/03web.pl" ;;
+ "lib/t/04_send_email.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/04_send_email.pl" ;;
+ "bin/mason_handler.fcgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;;
+ "bin/mason_handler.scgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;;
+ "bin/mason_handler.svc" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;;
+ "bin/rt-commit-handler" ) CONFIG_FILES="$CONFIG_FILES bin/rt-commit-handler" ;;
+ "bin/rt-crontool" ) CONFIG_FILES="$CONFIG_FILES bin/rt-crontool" ;;
+ "bin/rt-mailgate" ) CONFIG_FILES="$CONFIG_FILES bin/rt-mailgate" ;;
+ "bin/rt" ) CONFIG_FILES="$CONFIG_FILES bin/rt" ;;
+ "bin/webmux.pl" ) CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;;
+ *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+ trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+ trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+: ${TMPDIR=/tmp}
+{
+ tmp=`(umask 077 && mktemp -d -q "$TMPDIR/csXXXXXX") 2>/dev/null` &&
+ test -n "$tmp" && test -d "$tmp"
+} ||
+{
+ tmp=$TMPDIR/cs$$-$RANDOM
+ (umask 077 && mkdir $tmp)
+} ||
+{
+ echo "$me: cannot create a temporary directory in $TMPDIR" >&2
+ { (exit 1); exit 1; }
+}
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "\$CONFIG_FILES"; then
+ # Protect against being on the right side of a sed subst in config.status.
+ sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g;
+ s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF
+s,@SHELL@,$SHELL,;t t
+s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t
+s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t
+s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t
+s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t
+s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t
+s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t
+s,@exec_prefix@,$exec_prefix,;t t
+s,@prefix@,$prefix,;t t
+s,@program_transform_name@,$program_transform_name,;t t
+s,@bindir@,$bindir,;t t
+s,@sbindir@,$sbindir,;t t
+s,@libexecdir@,$libexecdir,;t t
+s,@datadir@,$datadir,;t t
+s,@sysconfdir@,$sysconfdir,;t t
+s,@sharedstatedir@,$sharedstatedir,;t t
+s,@localstatedir@,$localstatedir,;t t
+s,@libdir@,$libdir,;t t
+s,@includedir@,$includedir,;t t
+s,@oldincludedir@,$oldincludedir,;t t
+s,@infodir@,$infodir,;t t
+s,@mandir@,$mandir,;t t
+s,@build_alias@,$build_alias,;t t
+s,@host_alias@,$host_alias,;t t
+s,@target_alias@,$target_alias,;t t
+s,@DEFS@,$DEFS,;t t
+s,@ECHO_C@,$ECHO_C,;t t
+s,@ECHO_N@,$ECHO_N,;t t
+s,@ECHO_T@,$ECHO_T,;t t
+s,@LIBS@,$LIBS,;t t
+s,@rt_version_major@,$rt_version_major,;t t
+s,@rt_version_minor@,$rt_version_minor,;t t
+s,@rt_version_patch@,$rt_version_patch,;t t
+s,@INSTALL_PROGRAM@,$INSTALL_PROGRAM,;t t
+s,@INSTALL_SCRIPT@,$INSTALL_SCRIPT,;t t
+s,@INSTALL_DATA@,$INSTALL_DATA,;t t
+s,@PERL@,$PERL,;t t
+s,@SPEEDY_BIN@,$SPEEDY_BIN,;t t
+s,@exp_prefix@,$exp_prefix,;t t
+s,@exp_exec_prefix@,$exp_exec_prefix,;t t
+s,@exp_bindir@,$exp_bindir,;t t
+s,@exp_sbindir@,$exp_sbindir,;t t
+s,@exp_sysconfdir@,$exp_sysconfdir,;t t
+s,@exp_mandir@,$exp_mandir,;t t
+s,@exp_libdir@,$exp_libdir,;t t
+s,@exp_datadir@,$exp_datadir,;t t
+s,@htmldir@,$htmldir,;t t
+s,@exp_htmldir@,$exp_htmldir,;t t
+s,@manualdir@,$manualdir,;t t
+s,@exp_manualdir@,$exp_manualdir,;t t
+s,@exp_localstatedir@,$exp_localstatedir,;t t
+s,@logfiledir@,$logfiledir,;t t
+s,@exp_logfiledir@,$exp_logfiledir,;t t
+s,@masonstatedir@,$masonstatedir,;t t
+s,@exp_masonstatedir@,$exp_masonstatedir,;t t
+s,@sessionstatedir@,$sessionstatedir,;t t
+s,@exp_sessionstatedir@,$exp_sessionstatedir,;t t
+s,@customdir@,$customdir,;t t
+s,@exp_customdir@,$exp_customdir,;t t
+s,@custometcdir@,$custometcdir,;t t
+s,@exp_custometcdir@,$exp_custometcdir,;t t
+s,@customhtmldir@,$customhtmldir,;t t
+s,@exp_customhtmldir@,$exp_customhtmldir,;t t
+s,@customlexdir@,$customlexdir,;t t
+s,@exp_customlexdir@,$exp_customlexdir,;t t
+s,@customlibdir@,$customlibdir,;t t
+s,@exp_customlibdir@,$exp_customlibdir,;t t
+s,@rt_layout_name@,$rt_layout_name,;t t
+s,@RTGROUP@,$RTGROUP,;t t
+s,@BIN_OWNER@,$BIN_OWNER,;t t
+s,@LIBS_OWNER@,$LIBS_OWNER,;t t
+s,@LIBS_GROUP@,$LIBS_GROUP,;t t
+s,@DB_TYPE@,$DB_TYPE,;t t
+s,@ORACLE_ENV_PREF@,$ORACLE_ENV_PREF,;t t
+s,@DB_HOST@,$DB_HOST,;t t
+s,@DB_PORT@,$DB_PORT,;t t
+s,@DB_RT_HOST@,$DB_RT_HOST,;t t
+s,@DB_DBA@,$DB_DBA,;t t
+s,@DB_DATABASE@,$DB_DATABASE,;t t
+s,@DB_RT_USER@,$DB_RT_USER,;t t
+s,@DB_RT_PASS@,$DB_RT_PASS,;t t
+s,@WEB_USER@,$WEB_USER,;t t
+s,@WEB_GROUP@,$WEB_GROUP,;t t
+s,@RT_VERSION_MAJOR@,$RT_VERSION_MAJOR,;t t
+s,@RT_VERSION_MINOR@,$RT_VERSION_MINOR,;t t
+s,@RT_VERSION_PATCH@,$RT_VERSION_PATCH,;t t
+s,@RT_PATH@,$RT_PATH,;t t
+s,@RT_DOC_PATH@,$RT_DOC_PATH,;t t
+s,@RT_LOCAL_PATH@,$RT_LOCAL_PATH,;t t
+s,@RT_LIB_PATH@,$RT_LIB_PATH,;t t
+s,@RT_ETC_PATH@,$RT_ETC_PATH,;t t
+s,@CONFIG_FILE_PATH@,$CONFIG_FILE_PATH,;t t
+s,@RT_BIN_PATH@,$RT_BIN_PATH,;t t
+s,@RT_SBIN_PATH@,$RT_SBIN_PATH,;t t
+s,@RT_VAR_PATH@,$RT_VAR_PATH,;t t
+s,@RT_MAN_PATH@,$RT_MAN_PATH,;t t
+s,@MASON_DATA_PATH@,$MASON_DATA_PATH,;t t
+s,@MASON_SESSION_PATH@,$MASON_SESSION_PATH,;t t
+s,@MASON_HTML_PATH@,$MASON_HTML_PATH,;t t
+s,@LOCAL_ETC_PATH@,$LOCAL_ETC_PATH,;t t
+s,@MASON_LOCAL_HTML_PATH@,$MASON_LOCAL_HTML_PATH,;t t
+s,@LOCAL_LEXICON_PATH@,$LOCAL_LEXICON_PATH,;t t
+s,@LOCAL_LIB_PATH@,$LOCAL_LIB_PATH,;t t
+s,@DESTDIR@,$DESTDIR,;t t
+s,@RT_LOG_PATH@,$RT_LOG_PATH,;t t
+CEOF
+
+_ACEOF
+
+ cat >>$CONFIG_STATUS <<\_ACEOF
+ # Split the substitutions into bite-sized pieces for seds with
+ # small command number limits, like on Digital OSF/1 and HP-UX.
+ ac_max_sed_lines=48
+ ac_sed_frag=1 # Number of current file.
+ ac_beg=1 # First line for current file.
+ ac_end=$ac_max_sed_lines # Line after last line for current file.
+ ac_more_lines=:
+ ac_sed_cmds=
+ while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ else
+ sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ fi
+ if test ! -s $tmp/subs.frag; then
+ ac_more_lines=false
+ else
+ # The purpose of the label and of the branching condition is to
+ # speed up the sed processing (if there are no `@' at all, there
+ # is no need to browse any of the substitutions).
+ # These are the two extra sed commands mentioned above.
+ (echo ':t
+ /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+ fi
+ ac_sed_frag=`expr $ac_sed_frag + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_lines`
+ fi
+ done
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+ fi
+fi # test -n "$CONFIG_FILES"
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { case "$ac_dir" in
+ [\\/]* | ?:[\\/]* ) as_incr_dir=;;
+ *) as_incr_dir=.;;
+esac
+as_dummy="$ac_dir"
+for as_mkdir_dir in `IFS='/\\'; set X $as_dummy; shift; echo "$@"`; do
+ case $as_mkdir_dir in
+ # Skip DOS drivespec
+ ?:) as_incr_dir=$as_mkdir_dir ;;
+ *)
+ as_incr_dir=$as_incr_dir/$as_mkdir_dir
+ test -d "$as_incr_dir" ||
+ mkdir "$as_incr_dir" ||
+ { { echo "$as_me:$LINENO: error: cannot create \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }
+ ;;
+ esac
+done; }
+
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
+# absolute.
+ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
+ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
+ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
+ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_builddir$INSTALL ;;
+ esac
+
+ if test x"$ac_file" != x-; then
+ { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+ rm -f "$ac_file"
+ fi
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ configure_input=
+ else
+ configure_input="$ac_file. "
+ fi
+ configure_input=$configure_input"Generated from `echo $ac_file_in |
+ sed 's,.*/,,'` by configure."
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ echo $f;;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo $f
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo $srcdir/$f
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+s,@INSTALL@,$ac_INSTALL,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+ rm -f $tmp/stdin
+ if test x"$ac_file" != x-; then
+ mv $tmp/out $ac_file
+ else
+ cat $tmp/out
+ rm -f $tmp/out
+ fi
+
+done
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || { (exit 1); exit 1; }
+fi
+
diff --git a/rt/configure.ac b/rt/configure.ac
new file mode 100644
index 0000000..ea5de5e
--- /dev/null
+++ b/rt/configure.ac
@@ -0,0 +1,229 @@
+dnl
+dnl Process this file with autoconf to produce a configure script
+dnl
+dnl Embed in generated ./configure script the following CVS info:
+AC_REVISION($Revision: 1.1.1.2 $)dnl
+
+dnl Setup autoconf
+AC_PREREQ(2.53)
+AC_INIT(RT, [3.0.9], [rt-3.0-bugs@fsck.com])
+AC_CONFIG_SRCDIR([lib/RT.pm.in])
+
+dnl Extract RT version number components
+AC_SUBST([rt_version_major],
+ m4_bregexp(AC_PACKAGE_VERSION,[^\(\w+\)\.\(\w+\)\(\.\(\w+\)\)?],[\1]))
+AC_SUBST([rt_version_minor],
+ m4_bregexp(AC_PACKAGE_VERSION,[^\(\w+\)\.\(\w+\)\(\.\(\w+\)\)?],[\2]))
+AC_SUBST([rt_version_patch],
+ m4_bregexp(AC_PACKAGE_VERSION,[^\(\w+\)\.\(\w+\)\(\.\(\w+\)\)?],[\4]))
+test "x$rt_version_major" = 'x' && rt_version_major=0
+test "x$rt_version_minor" = 'x' && rt_version_minor=0
+test "x$rt_version_patch" = 'x' && rt_version_patch=0
+
+dnl Check for programs
+AC_PROG_INSTALL
+AC_ARG_VAR([PERL],[Perl interpreter command])
+AC_PATH_PROG([PERL], [perl], [not found])
+if test "$PERL" = 'not found'; then
+ AC_MSG_ERROR([cannot use $PACKAGE_NAME without perl])
+fi
+dnl SPEED_BIN
+AC_ARG_WITH(speedycgi,
+ AC_HELP_STRING([--with-speedycgi=/path/to/speedy],
+ [path to your speedycgi binary, if it exists]),
+ SPEEDY_BIN=$withval,
+ SPEEDY_BIN=/usr/local/bin/speedy)
+AC_SUBST(SPEEDY_BIN)
+
+
+dnl Defaults paths for installation
+AC_PREFIX_DEFAULT([/opt/rt3])
+RT_ENABLE_LAYOUT
+
+dnl RTGROUP
+AC_ARG_WITH(rt-group,
+ AC_HELP_STRING([--with-rt-group=GROUP],
+ [group to own all files (default: rt)]),
+ RTGROUP=$withval,
+ RTGROUP=rt)
+AC_SUBST(RTGROUP)
+
+dnl BIN_OWNER
+AC_ARG_WITH(bin-owner,
+ AC_HELP_STRING([--with-bin-owner=OWNER],
+ [user that will own rt binaries (default root)]),
+ BIN_OWNER=$withval,
+ BIN_OWNER=root)
+AC_SUBST(BIN_OWNER)
+
+dnl LIBS_OWNER
+AC_ARG_WITH(libs-owner,
+ AC_HELP_STRING([--with-libs-owner=OWNER],
+ [user that will own RT libraries (default root)]),
+ LIBS_OWNER=$withval,
+ LIBS_OWNER=root)
+AC_SUBST(LIBS_OWNER)
+
+dnl LIBS_GROUP
+AC_ARG_WITH(libs-group,
+ AC_HELP_STRING([--with-libs-group=GROUP],
+ [group that will own rt binaries (default bin)]),
+ LIBS_GROUP=$withval,
+ LIBS_GROUP=bin)
+AC_SUBST(LIBS_GROUP)
+
+dnl DB_TYPE
+AC_ARG_WITH(db-type,
+ AC_HELP_STRING([--with-db-type=TYPE],
+ [sort of database RT will use (default: mysql) (mysql, Pg, Oracle and Informix are valid)]),
+ DB_TYPE=$withval,
+ DB_TYPE=mysql)
+if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' -a "$DB_TYPE" != 'Informix' ; then
+ AC_MSG_ERROR([Only Oracle, Informix, Pg and mysql are valid db types])
+fi
+AC_SUBST(DB_TYPE)
+
+dnl ORACLE_ENV_PREF
+if test "$DB_TYPE" = 'Oracle'; then
+ test "x$ORACLE_HOME" = 'x' && AC_MSG_ERROR([Please declare the ORACLE_HOME environment variable])
+ ORACLE_ENV_PREF="\$ENV{'ORACLE_HOME'} = '$ORACLE_HOME';"
+fi
+AC_SUBST(ORACLE_ENV_PREF)
+
+dnl DB_HOST
+AC_ARG_WITH(db-host,
+ AC_HELP_STRING([--with-db-host=HOSTNAME],
+ [FQDN of database server (default: localhost)]),
+ DB_HOST=$withval,
+ DB_HOST=localhost)
+AC_SUBST(DB_HOST)
+
+dnl DB_PORT
+AC_ARG_WITH(db-port,
+ AC_HELP_STRING([--with-db-port=PORT],
+ [port on which the database listens on]),
+ DB_PORT=$withval,
+ DB_PORT=)
+AC_SUBST(DB_PORT)
+
+dnl DB_RT_HOST
+AC_ARG_WITH(db-rt-host,
+ AC_HELP_STRING([--with-db-rt-host=HOSTNAME],
+ [FQDN of RT server which talks to the database server (default: localhost)]),
+ DB_RT_HOST=$withval,
+ DB_RT_HOST=localhost)
+AC_SUBST(DB_RT_HOST)
+
+dnl DB_DATABASE_ADMIN
+AC_ARG_WITH(db-dba,
+ AC_HELP_STRING([--with-db-dba=DBA],
+ [name of database administrator (default: root)]),
+ DB_DBA=$withval,
+ DB_DBA=root)
+AC_SUBST(DB_DBA)
+
+dnl DB_DATABASE
+AC_ARG_WITH(db-database,
+ AC_HELP_STRING([--with-db-database=DBNAME],
+ [name of the database to use (default: rt3)]),
+ DB_DATABASE=$withval,
+ DB_DATABASE=rt3)
+AC_SUBST(DB_DATABASE)
+
+dnl DB_RT_USER
+AC_ARG_WITH(db-rt-user,
+ AC_HELP_STRING([--with-db-rt-user=DBUSER],
+ [name of database user (default: rt_user)]),
+ DB_RT_USER=$withval,
+ DB_RT_USER=rt_user)
+AC_SUBST(DB_RT_USER)
+
+dnl DB_RT_PASS
+AC_ARG_WITH(db-rt-pass,
+ AC_HELP_STRING([--with-db-rt-pass=PASSWORD],
+ [password for database user (default: rt_pass)]),
+ DB_RT_PASS=$withval,
+ DB_RT_PASS=rt_pass)
+AC_SUBST(DB_RT_PASS)
+
+dnl WEB_USER
+AC_ARG_WITH(web-user,
+ AC_HELP_STRING([--with-web-user=USER],
+ [user the web server runs as (default: www)]),
+ WEB_USER=$withval,
+ WEB_USER=www)
+AC_SUBST(WEB_USER)
+
+dnl WEB_GROUP
+AC_ARG_WITH(web-group,
+ AC_HELP_STRING([--with-web-group=GROUP],
+ [group the web server runs as (default: www)]),
+ WEB_GROUP=$withval,
+ WEB_GROUP=www)
+AC_SUBST(WEB_GROUP)
+
+dnl INSTALL AS ME
+my_group=$(groups|cut -f1 -d' ')
+AC_ARG_WITH(my-user-group,
+ AC_HELP_STRING([--with-my-user-group],
+ [set all users and groups to current user/group]),
+ RTGROUP=$my_group
+ BIN_OWNER=$USER
+ LIBS_OWNER=$USER
+ LIBS_GROUP=$my_group
+ WEB_USER=$USER
+ WEB_GROUP=$my_group)
+
+dnl This section maps the variable names this script 'natively' generates
+dnl to their existing names. They should be removed from here as the .in
+dnl files are changed to use the new names.
+
+dnl version numbers
+AC_SUBST(RT_VERSION_MAJOR, ${rt_version_major})
+AC_SUBST(RT_VERSION_MINOR, ${rt_version_minor})
+AC_SUBST(RT_VERSION_PATCH, ${rt_version_patch})
+
+dnl layout paths
+AC_SUBST([RT_PATH], ${exp_prefix})
+AC_SUBST([RT_DOC_PATH], ${exp_manualdir})
+AC_SUBST([RT_LOCAL_PATH], ${exp_customdir})
+AC_SUBST([RT_LIB_PATH], ${exp_libdir})
+AC_SUBST([RT_ETC_PATH], ${exp_sysconfdir})
+AC_SUBST([CONFIG_FILE_PATH], ${exp_sysconfdir})
+AC_SUBST([RT_BIN_PATH], ${exp_bindir})
+AC_SUBST([RT_SBIN_PATH], ${exp_sbindir})
+AC_SUBST([RT_VAR_PATH], ${exp_localstatedir})
+AC_SUBST([RT_MAN_PATH], ${exp_mandir})
+AC_SUBST([MASON_DATA_PATH], ${exp_masonstatedir})
+AC_SUBST([MASON_SESSION_PATH], ${exp_sessionstatedir})
+AC_SUBST([MASON_HTML_PATH], ${exp_htmldir})
+AC_SUBST([LOCAL_ETC_PATH], ${exp_custometcdir})
+AC_SUBST([MASON_LOCAL_HTML_PATH], ${exp_customhtmldir})
+AC_SUBST([LOCAL_LEXICON_PATH], ${exp_customlexdir})
+AC_SUBST([LOCAL_LIB_PATH], ${exp_customlibdir})
+AC_SUBST([DESTDIR], ${exp_prefix})
+AC_SUBST([RT_LOG_PATH], ${exp_logfiledir})
+
+dnl Configure the output files, and generate them.
+
+AC_CONFIG_FILES([
+ sbin/rt-setup-database
+ sbin/rt-test-dependencies
+ Makefile
+ etc/RT_Config.pm
+ lib/RT.pm
+ lib/t/00smoke.t
+ lib/t/01harness.t
+ lib/t/02regression.t
+ lib/t/03web.pl
+ lib/t/04_send_email.pl
+ bin/mason_handler.fcgi
+ bin/mason_handler.scgi
+ bin/mason_handler.svc
+ bin/rt-commit-handler
+ bin/rt-crontool
+ bin/rt-mailgate
+ bin/rt
+ bin/webmux.pl]
+ )
+AC_OUTPUT
diff --git a/rt/docs/README.docs b/rt/docs/README.docs
new file mode 100755
index 0000000..38866b3
--- /dev/null
+++ b/rt/docs/README.docs
@@ -0,0 +1,2 @@
+Questions about docs should be sent to the RT Documentation Team (rt-docs@fsck.com)
+which is led by Meri.
diff --git a/rt/docs/Security b/rt/docs/Security
new file mode 100644
index 0000000..c9787ac
--- /dev/null
+++ b/rt/docs/Security
@@ -0,0 +1,25 @@
+RT2 runs setgid to some group (it defaults to 'rt').
+
+rt's configuration file, 'config.pm', is not world readable because it
+contains rt's database password. If a user gets access to this file, he
+can arbitrarily manipulate the RT database. This is bad. You don't want
+this to happen. config.pm is mode 550. No users should be members of
+the 'rt' group unless you want them to be able to obtain your rt password.
+
+If you're running the web interface, you'll need to make sure your webserver
+has access to config.pm. You could do this by letting your webserver's user
+be a member of the 'rt' group. This has the disadvantage of letting
+any mod_perl code on your web server have access to your RT password.
+
+Alternatively, you can run RT2 on its own apache instance bound to a high
+port on 127.0.0.1
+which runs as a non-priviledged user which is a member of the group 'rt'.
+
+Configure your webserver to proxy requests to RT's
+virtual directory to the apache instance you just set up.
+
+TODO: doc the apache configs needed to do this.
+
+The same technique can be used to run multiple RT2 instances on the same host.
+
+
diff --git a/rt/docs/design_docs/CARS b/rt/docs/design_docs/CARS
new file mode 100755
index 0000000..a4d2a78
--- /dev/null
+++ b/rt/docs/design_docs/CARS
@@ -0,0 +1,66 @@
+Conditional Automated Request Shuffler
+Initial Design. <jesse@fsck.com> 9 Nov 99
+
+#Try to find out what queue the incoming ticket is in
+#Try to find out the default action for this invocation
+#Read the ticket from STDIN
+#Obtain the actor
+#Obtain the serial # if we have one
+#If the ticket has a ticket-id
+ #if this is a 'comment'
+ #add the current mime objects as a 'comment'
+
+ #if this is 'correspondence'
+ #add the current mime object as 'correspondence'
+
+
+#if this ticket does not yet have a ticket id
+
+ #For now:
+ #Create a new ticket
+
+ #In the distant future
+
+ #load the regexp table matching this queue
+ #check the message agains the regexp table, ordered by precedence
+ #when we get a match
+ #get the ruleset for that regexp from the actions table
+ #evaluate the ruleset in order of precedence.
+ #if we get an 'exit' stop proccesing ALL rulesets
+wpw #if we get a 'forward,' forward it to 'value'.
+
+ #if we get a 'create,' create a request in 'value'
+ #elseif we get a 'map', add this as additional correspondence on ticket 'value'
+
+
+ #if we get an 'associate', associate the ticket number returned from the
+ 'create' or 'map' with the master ticket from 'value'
+
+ #if we get a 'reply',
+ #load the reply template with id 'value'
+ #replace strings in the template
+ #send the template
+
+
+
+
+CREATE TABLE Rules {
+ID int AUTO_INCREMENT,
+Desc varchar(120),
+Regexp varchar(80),
+Precedence int,
+MatchField varchar(20), #Can be a headername or 'any' all header names
+ #end in :
+
+
+CREATE TABLE Actions {
+Rule int,
+Action varchar(20), # Create, Forward, Squelch, Owner, Area, Associate
+Value varchar(20), #queue or email address
+Desc varchar(120)
+}
+
+CREATE TABLE Autoreplies {
+ID int AUTO_INCREMENT,
+Content text
+); \ No newline at end of file
diff --git a/rt/docs/design_docs/TransactionTypes.txt b/rt/docs/design_docs/TransactionTypes.txt
new file mode 100755
index 0000000..942b723
--- /dev/null
+++ b/rt/docs/design_docs/TransactionTypes.txt
@@ -0,0 +1,48 @@
+This is some loose scrabbling made by TobiX, they might eventually be relevant
+for 2.1.
+
+INTERFACES, in general
+
+should:
+
+- provide the user (client?) with a list of possible actions (methods).
+- let the user execute those actions (methods).
+- Return information to the user/client.
+
+There are two kind of actions/methods:
+
+- Information retrieval
+- Transactions
+
+For the first, I think the best thing is to just provide a lot of
+methods for it in the libraries, and let it be an Interface Design
+Issue what to show and how to show it.
+
+For the second, I think we can win in the long run on having a
+generalized methods for
+
+- listing transaction types.
+- creating & committing transactions.
+
+..with the possibility of just deploying new custom-developed modules
+when new transaction types are needed.
+
+
+$RT::TransactionTypes ...and...
+%RT::TransactionTypes
+ - global object which contains all TransactionTypes
+ - used by all UIs to create menues of possible (user) actions (one TransactionType is a user action)
+
+The UIs should call sth like
+$Ticket->AddTransaction($TransactionName), which should be equivalent
+with i.e. $Ticket->Correspond when $TransactionName is 'Correspond'
+(AUTOLOAD should call the do-sub if exists
+$RT::TransactionTypes{$TransactionName})
+
+The RT::Ticket::AddTransaction will create a new transaction of the
+right TransactionClass (maybe via a sub
+RT::TransactionTypes::NewTransaction). Then $Transaction->do is
+called.
+
+TransactionType->do initializes a new object of the right TransactionClass, and
+
diff --git a/rt/docs/design_docs/acls b/rt/docs/design_docs/acls
new file mode 100644
index 0000000..bb093ad
--- /dev/null
+++ b/rt/docs/design_docs/acls
@@ -0,0 +1,50 @@
+
+
+Does principal baz have right foo for object bar
+
+What rights does user baz have for object bar
+
+# {{{ Which principals have right foo for object bar
+
+
+if ($args{'ObjectType'} eq 'Ticket') {
+ $or_check_ticket_roles = " OR ( Groups.Domain = 'TicketRole' AND Groups.Instance = '".$args{'ObjectId'}."') ";
+ # If we're looking at ticket rights, we also want to look at the associated queue rights.
+ # this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object
+ # and ask all the rest of our questions about the queue.
+ my $tick = RT::Ticket->new($RT::SystemUser);
+ $tick->Load($args{'ObjectId'});
+ $args{'ObjectType'} = 'Queue';
+ $args{'ObjectId'} = $tick->QueueObj->Id();
+
+}
+if ($args{'ObjectType'} eq 'Queue') {
+ $or_check_roles = " OR ( ( (Groups.Domain = 'QueueRole' AND Groups.Instance = '".$args{'ObjectId'}."') $or_check_ticket_roles )
+ AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.ObjectId AND Principals.PrincipalType = 'Group') ";
+}
+
+if (defined $args{'ObjectType'} ) {
+ $or_look_at_object_rights = " OR (ACL.ObjectType = '".$args{'ObjectType'}."' AND ACL.ObjectId = '".$args{'ObjectId'}."') ";
+
+}
+
+my $query = "SELECT Users.* from ACL, Groups, Users, Principals, Principals UserPrinc, CachedGroupMembers WHERE
+ Users.id = UserPrinc.ObjectId AND UserPrinc.PrincipalType = 'User' AND
+ Principals.Id = CachedGroupMembers.GroupId AND
+ CachedGroupMembers.MemberId = UserPrinc.ObjectId AND
+ UserPrinc.PrincipalType = 'User' AND
+ (ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') AND
+ (ACL.ObjectType = 'System' $or_look_at_object_rights) AND
+ (
+ (ACL.PrincipalId = Principals.Id AND
+ Principals.ObjectId = Groups.Id AND
+ ACL.PrincipalType = 'Group' AND
+ (Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence')
+ )
+ $or_check_roles
+ )";
+
+# }}}
+
+What objects does principal baz have right foo for
+;
diff --git a/rt/docs/design_docs/approval_notices b/rt/docs/design_docs/approval_notices
new file mode 100644
index 0000000..5e76119
--- /dev/null
+++ b/rt/docs/design_docs/approval_notices
@@ -0,0 +1,8 @@
+Notification on "your request approved by"
+Notification on "your request approved by all approvers"
+Notification on "your request denied by"
+Reject ticket on rejection of any approval
+
+"Ticket N is pending your approval"
+
+
diff --git a/rt/docs/design_docs/approval_template b/rt/docs/design_docs/approval_template
new file mode 100644
index 0000000..16a988c
--- /dev/null
+++ b/rt/docs/design_docs/approval_template
@@ -0,0 +1,25 @@
+===Create-Ticket: approval
+ { my $name = "HR";
+ my $groups = RT::Groups->new($RT::SystemUser);
+ $groups->LimitToUserDefinedGroups();
+ $groups->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => "$name");
+ $groups->WithMember($TransactionObj->CreatorObj->Id);
+
+ my $groupid = $groups->First->Id;
+
+ my $adminccs = RT::Users->new($RT::SystemUser);
+ $adminccs->WhoHaveRight(Right => 'AdminGroup', IncludeSystemRights => undef, IncludeSuperusers => 0, IncludeSubgroupMembers => 0, Object => $groups->First);
+
+ my @admins;
+ while (my $admin = $adminccs->Next) {
+ push (@admins, $admin->Name);
+ }
+ }
+ Queue: Approvals
+ Type: Approval
+ AdminCcs: {join (", ",@admins) }
+ Depended-On-By: {$tickets{'TOP'}->Id}
+ Refers-To: {$tickets{'TOP'}->Id}
+ Due: {time + 86400}
+ Content-Type: text/plain
+ Content: Your approval is requested for the ticket {%$tickets{'TOP'}->Id}: {$tickets{'TOP'}->Subject}
diff --git a/rt/docs/design_docs/cf_search b/rt/docs/design_docs/cf_search
new file mode 100644
index 0000000..456a9fe
--- /dev/null
+++ b/rt/docs/design_docs/cf_search
@@ -0,0 +1,72 @@
+find all tickets where:
+
+
+ CF Foo
+ Has values (talk or read) AND
+ Has values (bar and baz) AND
+ doesn't have values (bing or bong)
+
+
+LimitCustomFieldValues {
+ my %args = ( CustomField => undef,
+ ClauseId => 'CustomFields',
+ OPERATOR => undef,
+ ENTRYAGGREGATOR => undef,
+ VALUES => undef,
+ @_) ;
+
+ unless ( $self->{'TicketAliases'}{$args{'ClauseId'}}{'CustomField'} ) {
+ $self->{'TicketAliases'}{$args{'ClauseId'}}{'CustomField'} = $self->NewAlias('CustomFields');
+ $self->Join(TABLE1 =>$self->{'TicketAliases'}{$args{'ClauseId'}}{'CustomField' },
+ FIELD1 => 'QueueId',
+ TABLE2 => 'main', FIELD2 => 'QueueId');
+
+ if ($args{'OPERATOR'} =~ /!=|IS/i) {
+ }
+ else {
+ }
+
+}
+ # {{{ if it's a keyword
+ elsif ( $TYPES{ $restriction->{'FIELD'} } eq 'CUSTOMFIELD' ) {
+
+ my $null_columns_ok;
+ my $TicketCFs = $self->Join( TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'TicketCustomFieldValues',
+ FIELD2 => 'Ticket' );
+
+ foreach my $value ( @{ $restriction->{'VALUES'} } ) {
+ $self->SUPER::Limit( ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => $restriction->{'OPERATOR'},
+ VALUE => $value,
+ QUOTEVALUE => $restriction->{'QUOTEVALUE'},
+ ENTRYAGGREGATOR => 'AND', );
+ }
+ if ( ( $restriction->{'OPERATOR'} =~ /^IS$/i ) or ( $restriction->{'OPERATOR'} eq '!=' ) ) {
+ $null_columns_ok = 1;
+ }
+
+ #If we're trying to find tickets where the keyword isn't somethng, also check ones where it _IS_ null
+ if ( $restriction->{'OPERATOR'} eq '!=' ) {
+ $self->SUPER::Limit( ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'OR', );
+ }
+
+ $self->SUPER::Limit( LEFTJOIN => $TicketCFs,
+ FIELD => 'CustomField',
+ VALUE => $restriction->{'CUSTOMFIELD'},
+ ENTRYAGGREGATOR => 'OR' );
+
+ }
+
+ # }}}
+
+ }
+
diff --git a/rt/docs/design_docs/cli_spec b/rt/docs/design_docs/cli_spec
new file mode 100644
index 0000000..ae5f29f
--- /dev/null
+++ b/rt/docs/design_docs/cli_spec
@@ -0,0 +1,31 @@
+
+Things the cli must do
+ create ticket
+ comment
+ reply
+ update ticket metadata
+ search for tickets
+ update a bunch of tickets.
+ list tickets
+ login/logout
+
+
+should support multiple rt servers
+
+create/edit/update should use EDITOR or take from a file or stdin
+
+should be able to update ticket sttributes from a commandline without invoking an editor or needing to use stdin.
+
+login/logout should store RT session cookies rather than constantly transmitting the username/password combo.
+
+rtserver and rt username should come from env variables. but should be able to be overridden by commandline options.
+
+rt password should be able to be specified on the commandline (say from a script) or, failing that be prompted for within the application (as rt's sbin/initdb script does) ...or maybe able to be read from a stash file on disk.
+
+must be able to dowaload attachments from cli.
+
+ it might also be cool to be able to generate session-length urls for attavhments so you can use a browser. but that's not necessary.
+
+
+I'm envisioning this as similar to the subversion cli, actually.
+
diff --git a/rt/docs/design_docs/cvs_integration b/rt/docs/design_docs/cvs_integration
new file mode 100644
index 0000000..35c8737
--- /dev/null
+++ b/rt/docs/design_docs/cvs_integration
@@ -0,0 +1,164 @@
+jesse@FSCK.COM: ok. anyone here
+ interested in having RT as a bug tracker integrated with CVS? ()
+
+marc: in principle, sure. ()
+
+jesse@FSCK.COM: want to write up your
+ ideal of how such a beast would work? ()
+
+alex_c: what sort of integration are you thinking of, Jesse? ()
+
+jesse@FSCK.COM: well, that's what I want
+ to know, alex. lots of people want their bug trackers tied to their
+ version control. I want to know what people want it to _do_ ;) ()
+
+alex_c: weird. :) ()
+
+marc: similarly to what the debian bts does.
+ you put a magic string ("rt-closes#123") and it causes the bug in rt to
+ be closed (or appended with a different magic string) with the commit
+ message. also nice would be if rt would then generate links to a
+ webcvs server. ()
+
+jhawk: Hrmm. cvs front-end that strips 'em out?
+ Perhaps with RT: lines instead of CVS: lines in the commit
+ interaction? ()
+
+marc: the magic string goes in the commit
+ message, that is. no, use one of the magic post-commit scripts. ()
+
+
+jesse@FSCK.COM: well, there's also the
+ pre-commit script to lock out commits wihtout a ticket id ()
+
+jhawk: Personally, I don't want to force special
+ magic strings to the bug-tracking system, some of which may be
+ confidential, to appear in the cvs logs. ()
+
+marc: I could see wanting that on a release
+ branch. ()
+
+jhawk: I also think it would be cool to supply
+ template stuff for you to edit. ()
+
+jesse@FSCK.COM: I'm not sure cvs can be
+ made to do that. can it? (generate templates) ()
+
+jhawk: It would be reasonable, in my model, to
+ turn some kinds of RT: lines into things that fell in the commit
+ message, but not all kinds. ()
+
+marc: I don't quite see jhawk's objection. ()
+
+ghudson: In my observation, locking out commits
+ without a ticket ID is usually an impediment to development, and leads
+ to developers having the one bug which all commits cite. ()
+
+jhawk: If you had a CVS frontend, it could geneate
+ the template and feed it to 'cvs commit -m' ()
+
+ghudson: CVS can generate templates and verify
+ that they have been filled in. ()
+
+jhawk: What Greg says sounds cool; greg, what do
+ you mean? marc: one sec. ()
+
+marc: I think assuming a frontend is a terrible
+ idea. ()
+
+jesse@FSCK.COM: greg: agreed. but people
+ seem to want it. the idea would be only for a locked down release
+ branch. ()
+
+jhawk: marc: So, I might want to close an open
+ ticket as part of a commit message without that showing up in the
+ coommit message. Or to insert a splufty long comment into a ticket
+ while I do the commit but not close or really change the state, and
+ that comment might want to ramble a lot but not include that ramble in
+ the commit message. ()
+
+jesse@FSCK.COM: well, then arguably, you
+ might want to not use the commit message for that update, but instead
+ just go straight to the bts ()
+
+marc: I think the idea is to force you to
+ mention the ticket closing in the commit message. ()
+
+jesse@FSCK.COM: but yeah, state changing
+ and 'update messages' are seperate concepts that should both be
+ supported. ()
+
+jesse@FSCK.COM: part of the idea is to
+ drag the commit message into the BTS ()
+
+jhawk: Err, I think it quite frequent that I want
+ to put seperate info in both the commit message and the ticket system,
+ and entering them at the same time seems cool. ()
+
+jesse@FSCK.COM: ok. noted. I'll see if
+ that's doable, when i get around to this. ()
+
+marc: so I think you want a custom front-end,
+ but I don't think what you want is what jesse is talking about. ()
+
+jesse@FSCK.COM: the thing that would be
+ really cool that scare the pants off me is tracking which branches bugs
+ exist in / are fixed in ()
+
+jesse@FSCK.COM: what jhawk wants should
+ be doable, now that I understand his reqts. ()
+
+marc: that would require the bts to understand
+ branches in some fundamental way. ()
+
+jesse@FSCK.COM: yes. see above, about
+ the pants. ()
+
+sly: uh oh, not more people losing their pants... ..
+
+
+ghudson: RT needs to know the names of branches
+ and their structure (so that you can tell it "fixed in foo" and it
+ knows that the bug is still fixed in anything that branches off of foo,
+ but not necessarily in other new branches), but nothing more than that.
+
+jhawk: So, note that what I'm describing is how
+ I'd like the UI to be, from a generic architectural level, and not
+ really thinking terribly specific. Greg, can you explain the CVS
+ template thing? ()
+
+jesse@FSCK.COM: and it needs to know
+ exactly "when" a branch happens. because "fixed in foo" won't fix
+ something that branched off foo yesterday ()
+
+marc: jesse was talking about integrating rt
+ with cvs. building a new developent+repository+bts from scratch would
+ be a problem with larger scope :-) ()
+
+jesse@FSCK.COM: marc: was that in
+ response to jhawk? ()
+
+ghudson: CVS and templates: "rcsinfo" lets you
+ specify a template for log messages, and "commitinfo" lets you check
+ them. ()
+
+ghudson: Er, sorry, my bad.
+ s/commitinfo/verifymsg/ ()
+
+marc: with cvs, if you have the revision number
+ of the fix (which you should). you can use the branch version number to
+ get a date and see when the branch happened relative to the fix. ()
+
+marc: jesse: yes. ()
+
+jesse@FSCK.COM: Ok. would people
+ consider "integration with CVS" to be subpar or incomplete if it didn't
+ deal with tracking branches? ()
+
+marc: incomplete relative to an ideal, but not
+ subpar, as it would still be useful. ()
+
+allbery@CS.CMU.EDU: CVS's branch
+ support sucks so much that failure to work with it is hardly a bug ()
+
+
diff --git a/rt/docs/design_docs/delegation b/rt/docs/design_docs/delegation
new file mode 100644
index 0000000..0e57059
--- /dev/null
+++ b/rt/docs/design_docs/delegation
@@ -0,0 +1,115 @@
+Group ACLs
+
+ the rights:
+
+
+ CreatePersonalGroup
+ CreateGroup
+
+ AdminGroup
+ * Update group metadata and access control list
+ AdminGroupMembers
+ * Add ad delete members of this group
+ ModifyOwnMembership
+ * Join and quit this group
+
+
+ the primitives:
+
+In user.pm
+
+=item HasRight { Right => 'somerightname', ObjectType => 'Group', ObjectId => 'GroupId'
+
+ Returns true if this user has the right 'somerightname' for
+the group with id 'Id'
+
+=cut
+
+
+=item RightsForObject { ObjectType => 'Group', ObjectId =>'GroupId' }
+
+in users.pm
+
+=item WhoHaveRight { Right =>'somerightname', ObjectType => 'Group', ObjectId => 'GroupId' }
+
+
+ Finds all users who have the right 'somerightname' for the group
+in question.
+
+ If a user has "AdminGroupMembers" globally and we ask about
+ group 23, that user should be found.
+
+=cut
+
+Users must be able to delegate individual rights
+
+ * Is it that users can delegate any and all rights but it's
+ only rights they _have_ which actually grant rights.
+
+rights must not be redelegated
+
+users must be able to create groups to which rights can be delegated.
+
+Only users who have the "delegate rights" right can delegate rights.
+
+
+When a user's right to do something is revoked, the delegation must
+be revoked
+
+ * For any delegated ACL check, the delegator's right must be
+ checked immediately after the delegatee's right.
+ If a user has had a right delegated by multiple parties,
+ this may mean that we need to actually loop through and check
+ a bunch of possible delegations. Or can we craft a "has delegated
+ right" ACL check.
+
+
+
+
+
+
+
+ACL 1 Group Q has the right to Frob ObjectI.
+ACL 2 User A has the right "DelegateRights"
+
+Group Q has the member Group S
+Group S has the member Group R
+Group S has the member Group T
+Group R has the member user A
+Group T has the member user A
+
+User A delegates to Group P the right to Frob ObjectI
+
+ New ACL rule:
+
+ ACL 3: Group P has the right to Frob ObjectI
+ as delegated from ACL1 by User A
+
+
+In the case where ACL1 is revoked:
+
+ find all acls which are delegated from ACL1.
+ Delete them
+
+In the case where User A is removed from group R
+
+ Get the list of all groups that A was in by way of group R before the removal
+ Get the list of all groups that A is in _after_ the removal.
+
+ Find all the ACEs granted to each group that A is no longer in.
+ For each ACE in that list, find all the rights that A has delegated.
+ Whack them.
+
+In the case where Group S is removed from group Q
+
+
+ Get a list of all groups that S was in by way of Q before the removal
+ Call this list O.
+
+ For each user X who's a member of S (directly or indirectly):
+ Get a list of all groups that X is in after removal.
+ For each group in O that X is no longer a member of:
+ Find all ACEs granted to O
+ For each ACE, look up all the delegations that X has made.
+ For each delegation
+ WHACK IT
diff --git a/rt/docs/design_docs/evil_plans b/rt/docs/design_docs/evil_plans
new file mode 100644
index 0000000..5b5cc58
--- /dev/null
+++ b/rt/docs/design_docs/evil_plans
@@ -0,0 +1,162 @@
+Current planned 2.2 feature list. subject to change.
+
+
+Core
+
+
+
+Web UI
+
+Should New "Tools" top level menu
+Should "This week in RT" at a glance.
+Nice "RT Stats" overview.
+Nice recent and favorite items
+
+
+per-user configuration
+
+Must Saveable user preferences.
+
+ The ideal implementation would be "saveable user metadata",
+ including things like "Alternate Email Addresses". To
+ do this right, not all user metadata would be directly
+ editable by the user who has "ModifySelf" it may be that
+ this is a "system" datastore that gets accessed by various
+ functions, some of which the user has access to modify and
+ some of which only the system does.
+
+ API: Set field "FOO" to value "BAR" for user BAZ
+ What values does field "FOO" have for user BAZ?
+ Clear all values of "FOO" for user BAZ
+ What users have value "BAR" for field "FOO"
+
+ Example usages:
+
+ What users have the alternative email address matching
+ "boo@fsck.com"
+ What custom searches does user BAZ have defined?
+ What is baz's default queue?
+
+ Actually, I feel a little sketchy about Alternative Email
+ Addresses in there. I'm not quite sure why yet.
+
+ The same would really be useful for queues. Damn it. I think
+ I want a registry.
+
+
+
+Searching
+
+Must Ability to define search result format.
+should Saveable user searches.
+nice Sharable searches.
+
+
+Scrips
+
+must Include more Conditions; at least those contributed so far
+ that make sense in my grand scheme of things
+
+should The name should change to something that people don't think is
+ spelled wrong. ("I will not invent words\n" x 1000)
+
+nice Scrips could apply to a list of queues, rather than just one queue or
+ all of them.
+
+
+Custom fields
+
+Nice Date custom fields
+Nice Some way to order and group custom fields.
+Nice Default values
+Nice Required values
+Nice Make custom fields apply to an enumerated list of queues,
+ rather than just one.
+
+
+Web infrastructure
+
+
+Installation
+
+Should Better FSSTD conformance:
+ bins in /bin
+ admin tools in /sbin (does this include rtadmin?)
+ ephemeral data in /var
+ rename config file
+ force local RT search path?
+
+Mail gateway
+
+must Integrate gpg-authenticated command-by-mail mode
+
+
+
+Core
+
+should Use apache logging, if available
+should Use syslog, if available.
+should Mail user new password, as an Action, so it can be invoked either
+ as a scripaction or from the web ui.
+
+
+
+Web Services Framework
+
+Should Expose an API to create a ticket by HTTP posting an XML document.
+Should Provide an RSS feed to display tickets matching certain criteria
+Nice Allow ticket updates via the web ui
+Nice Export full ticket metadata and history as XML
+
+Note: I currently favor the REST philosophy that GET and POST to specific,
+ defined URLs provides everything one needs to build comprehensive
+ web services without the massive added complexity of a SOAP or XML-RPC
+ framework. Sadly, the world doesn't agree with me
+
+
+ACLs:
+
+Wish New ACL primitives for:
+
+ List all users who have right "FOO" on object "BAR"
+ List all rights user "BAZ" has for object "BAR"
+ List all objects for which user "BAR" has right "FOO"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+For the near future:
+
+ Use case:
+ Jesse wants to get notified of all tickets in queue 'RT Bugs'
+ with a severity of 'critical' and also have a requestor whcih matches 'fsck.com'.
+ I'm not sure this is the best idea.
+
+
+ Site admins define a number of subscriptions and can sign up individual
+ users, groups or metagroups to get mail on that subscription.
+
+ Basically, an admin would define "On Condition, notify as comment with
+ template _template_"
+
+ There would be a new table called "subscriptions"(?) that would have
+ the structure:
+
+ id
+ ScripId
+ PrincipalType ENUM: User, Group, Owner, Requestors, AdminCcs, Ccs
+ PrincipalId -- UserId or GroupId. For Owner, Requestors, AdminCcs, Ccs, it doesn't really make a lick of difference.
diff --git a/rt/docs/design_docs/groups_notes b/rt/docs/design_docs/groups_notes
new file mode 100644
index 0000000..234fd37
--- /dev/null
+++ b/rt/docs/design_docs/groups_notes
@@ -0,0 +1,88 @@
+CREATE TABLE Prinicpals (
+ id int auto_increment
+ PrincipalType VARCHAR(16) not_null,
+ PrincipalId int # foreign key to Users or Groups, depending
+)
+
+CREATE TABLE Groups (
+ id int auto_increment,
+ Domain varchar(255),
+ Instance varchar(16),
+ Name varchar(255),
+ Description varchar(255),
+);
+CREATE TABLE ACL (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Principal integer NULL , #Foreign key to principals
+ RightName varchar(25) NULL ,
+ RightDomain varchar(25) NULL ,
+ RightInstance integer NULL ,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE GroupMembers (
+ id int auto_increment,
+ Group int, # foreign key to Principals
+ Member int # foreign key to Principals
+)
+
+create table GroupMembersCache (
+ id int auto_increment,
+ Group int, # foreign key to Principals
+ Member int, # foreign key to Principals
+ Via int, #foreign key to g_m_u
+)
+
+insert into principals values ('bubbles);
+insert into principals values ('fubar');
+insert into principals values ('sheeri');
+insert into principals values ('sgw');
+
+insert into principals values ('staff');
+insert into principals values ('sysadmin');
+insert into principals values ('senior admin');
+
+
+insert into group_members values(1, 'staff', 'bubbles');
+insert into group_members values(2, 'sysadmin', 'sheeri');
+insert into group_members values(3,'senior admin', 'sgw');
+insert into group_members values(4,'senior admin', 'fubar');
+insert into group_members values(5, 'sysadmin', 'senior admin')
+
+Groups
+
+
+
+Domain Queues
+Instance <queueid#>
+Name AdminCc, Cc
+
+/Queues/1/AdminCc
+/Queues/3/Cc
+
+Domain Tickets
+Instance <#n>
+Name Owner, Requestor, Cc, AdminCc
+
+/Tickets/1/Owner
+/Tickets/1/Requestor
+/Tickets/1/Cc
+ Has members: /Queues/whatever queue the ticket has/Cc
+/Tickets/1/AdminCc
+ Has members: /Queues/whatever queue the ticket has/AdminCc
+
+
+Domain Users
+Instance <userid>
+
+/Users/1/MyDelegates
+/Users/1/MyOtherDelegates
+
+
+Domain System
+Name Admins, AdminManagers
+
+/System/Administrators
+/System/Blah
+
+
diff --git a/rt/docs/design_docs/link-definitions.txt b/rt/docs/design_docs/link-definitions.txt
new file mode 100644
index 0000000..30b9035
--- /dev/null
+++ b/rt/docs/design_docs/link-definitions.txt
@@ -0,0 +1,143 @@
+For 2.0, those Linking actions should be supported:
+
+1. DependentOn; TobiX-style.
+
+ BASE is dependent on TARGET.
+
+ ...meaning that TARGET has to be resolved before BASE (really) is
+ resolved.
+
+ According to TobiX, those "weird action" makes sense:
+ ...when the link and/or TARGET is created, the BASE might be stalled.
+ Alternatively, this should be very trivial to request through the UIs.
+ ...when the TARGET is resolved, BASE will be reopened if it's stalled.
+
+ An alternative to those "weird actions" is to have some run-time logic that
+ takes care of this; i.e. letting the search interface handle "please hide
+ all requests with unresolved dependencies"
+
+ TobiX will need to make dependency links into Bugzilla.
+
+ Dependency links should be made when more work to BASE should be done
+ after the TARGET is resolved and/or BASE can't be resolved before TARGET is
+ resolved.
+
+ Dependency links are often 1:1, but n:n links makes sense; one ticket can
+ depend on several others, several tickets can depend on one ticket, etc.
+
+ Loops don't make sense at all, but the system above won't break if it
+ encounter loops.
+
+ Dependency links is more for workflow than anything else. When a new
+ TARGET is created, some of the work might be passed over to another
+ department/person ... but _not_ the responsibility for the communication
+ with the external requestor.
+
+2. MemberOf link (grouping)
+
+ BASE is a member of TARGET.
+
+ TobiX-style "weird actions":
+ ...when TARGET is beeing replied to, all BASE requestors should get the
+ reply.
+
+ ...when TARGET is resolved, all BASE tickets should be resolved (unless
+ they have other unresolved Dependencies/MemberOf links).
+
+ ...when all BASEs to one TARGET are resolved, TARGET should be resolved.
+
+ The alternative is to let the user choose "reply to all" and "resolve all"
+ through the user interfaces.
+
+ MemberOf should be used when BASE ticket states more or less the same as
+ the TARGET ticket, and we do want to give a reply to all requestors, but we
+ don't want to merge them (Individual tickets from individual external
+ requestors should be respected as separate entities). If BASE tickets from
+ more than one external requestor is linked to a TARGET ticket, we denote
+ the TARGET ticket as a "Group ticket". This is only a documentation
+ definition, you won't find any references to "Group tickets" in the source.
+
+ I think the proper etiquette should be to clearly state in a reply to a
+ group ticket that the mail is going to several persons, and that the
+ requestor should reply back if they feel their Ticket hasn't got the
+ attention it deserves. The user documentation should reflect this.
+
+ MemberOf links can also be used to hand away the work flow. The person in
+ charge of the TARGET ticket will also be in charge of the BASE tickets and
+ the communication with the end user.
+
+ If a work task needs to be splitted into two subtasks, MemberOf might also be
+ used.
+
+ 1:n links makes more sense, but n:n can also work in some cases.
+ The reply stuff might break seriously upon loops. Recursement might be
+ handy for splitting a work task into subtasks (making a hierarchical tree
+ of the worktasks).
+
+
+3. Merge (connecting)
+
+ BASE is the same as TARGET.
+
+ ...the system should somehow merge together transactions for both tickets.
+ ...BASE should be more or less deleted, only the TARGET should apply.
+ ...actions done toward BASE should be redirected to TARGET.
+
+ I think MergeLinks should be used when two tickets accidentally has
+ appeared twice in the system, and/or there is no reason to keep the two
+ tickets separately. It might be that it's the same requestor (i.e.
+ clicking the "send" button twice in a web environment) or that we don't
+ care much about giving the requestor individual follow-up (typically
+ "internal" requestors, etc.)
+
+ Based on user feedback, merged tickets will be displayed as the same ticket
+within RT's user interfaces. but the original tickets' transactions will be
+kept seperated in the database. this may require some magic.
+
+4. RefersTo / No Action link (linking)
+
+ BASE is somewhat related to TARGET
+
+ No special actions will be taken.
+
+ Loops might maybe make sense
+
+BASE and TARGET are usually Tickets within one RT instance, but it
+might also point to external RT instances, other DB systems, etc.
+
+
+
+
+In future revisions, it should be very easy to set up site-specific link action types.
+We should also consider to include more linking actions in the box.
+
+An example stolen from John Rouillard. Eventually the [comments] should be
+removed, and the text modified to fit the planned 2.0 link actions:
+
+ ticket problem
+ 1 can't connect to hosts with netscape
+ 2 ping is broken
+ 3 Can't send email: error no space on spool/mqueue
+
+ You have the above in the queue. You realize that DNS is down. Spawn
+ a ticket
+
+ 4 DNS is down
+
+ mergelink 1 and 2 to it [I would rather say "make a MemberOf link _or_ a
+ dependency link from 1 and 2 to 4" --TobiX] (if you choose to stall 1 and
+ 2 automatically feel free, its just a shell script change) [well, you
+ might choose dependency instead of MemberOf --TobiX]. The person working
+ on 3 has come to the conclusion that outgoing mail is backing up because
+ of the DNS failure. She has cleared space by copying the mail queue to
+ another disk, but can't really get email working till DNS is up. So she
+ creates a Dependency linkon ticket 4 stalling ticket 3.
+
+ We finally get DNS working and resolve ticket 3. What happens? Tickets 1
+ and 2 are resolved and email is sent to requestors notifying them of the
+ resolution [This is the default behaviour for 2.0 MemberOf-linked tickets.
+ Remember that if we send Replies to "Group Tickets" (that is, the target
+ of several "MemberOf" links) --TobiX]. Ticket 4 [should be 3? --TobiX] is
+ reopened and the person working on it starts flushing the mail queue and
+ the moved mailq by hand.
+
diff --git a/rt/docs/design_docs/recursive_group_membership_algorithm b/rt/docs/design_docs/recursive_group_membership_algorithm
new file mode 100644
index 0000000..250b9ad
--- /dev/null
+++ b/rt/docs/design_docs/recursive_group_membership_algorithm
@@ -0,0 +1,109 @@
+Group A has members 1, 2, 3
+
+ Cached members 1 is a member of A via ""
+ 2 is a member of A via ""
+ 3 is a member of A via ""
+
+
+Group B has members A, 4, 5
+
+ Cached members: 4 is a member of B via "" $1
+ 5 is a member of B via "" $2
+ A is a member of B via "" $3
+ 1 is a member of B via "$3" $4
+ 2 is a member of B via "$3" $5
+ 3 is a member of B via "$3" $6
+
+Group C has members A, B, 6
+ 6 is a member of C via "" $7
+ A is a member of C via "" $8
+ 1 is a member of C via $8 $9
+ 2 is a member of C via $8 $10
+ 3 is a member of C via $8 $11
+ B is a member of C via "" $12
+ 4 is a member of C via $12 $13
+ 5 is a member of C via $12 $14
+ A is a member of C via $12 $15
+ 1 is a member of C via $15 $16
+ 2 is a member of C via $15 $17
+ 3 is a member of C via $15 $18
+
+
+
+Group D has members A, C
+
+ A is a member of D via "" $19
+ 1 is a member of D via $19 $20
+ 2 is a member of D via $19 $21
+ 3 is a member of D via $19 $22
+ C is a member of D via "" $23
+ 6 is a member of D via $23 $24
+ A is a member of D via $23 $25
+ 1 is a member of D via $25 $26
+ 2 is a member of D via $25 $27
+ 3 is a member of D via $25 $28
+ B is a member of D via $23 $29
+ 4 is a member of D via $29 $30
+ 5 is a member of D via $29 $31
+ A is a member of D via $29 $32
+ 1 is a member of D via $32 $33
+ 2 is a member of D via $32 $34
+ 3 is a member of D via $32 $35
+
+
+
+Adding a new user, 7, to group A.
+
+
+ Add the user to group A in the groups table.
+
+ Find all entries for group A in the cache table.
+
+ For each entry in that list:
+ Add "7 is a member of $entry->top via $entry->id"
+
+Deleting a user, 7, from group A:
+
+ Remove the user from group A in the groups table.
+ find all entries in the cache table where the principal id is user 7 and
+ the parent id is A. (requires a self join)
+ nuke them
+
+ Alternatively:
+ find all entries for A in the cache table.
+ For each one, find the child whose id is 7.
+ Nuke it
+
+
+Adding a group, B to group D.
+
+ Add group B as a member of D in the groups table.
+ In the cache table:
+ $id = Add group B as a member of D via ""
+
+ For each member of group B (4, 5, A):
+
+ $sid= 4 is a member of D via $id
+ $sid= 5 is a member of D via $id
+ $sid= A is a member of D via $id
+
+ if the member is a group itself, recurse down:
+
+ 1 is a member of D via $sid
+ 2 is a member of D via $sid
+ 3 is a member of D via $sid
+
+ Find all places where D is a member of $foo.
+ Repeat the above procedure, substituting $foo for D
+ and making $id D's id.
+
+Removing B as a member of D:
+
+ Remove B as a member of D in the groups table.
+ Find all references to D in the pseudogroups table.
+ Find all children of D which are B:
+ Recurse down with the following algorithm:
+ If it's a user, delete it.
+ If it's a group, recurse through each member,
+ deleting its children and then deleting the
+ group itself.
diff --git a/rt/docs/design_docs/rql_parser_machine.graphviz b/rt/docs/design_docs/rql_parser_machine.graphviz
new file mode 100644
index 0000000..36463ec
--- /dev/null
+++ b/rt/docs/design_docs/rql_parser_machine.graphviz
@@ -0,0 +1,32 @@
+
+/* GraphViz graph representing the state diagram of the RQL parser.
+*/
+
+digraph G {
+
+ PAREN -> PAREN;
+ PAREN -> KEYWORD;
+ PAREN -> AGGREG;
+
+ AGGREG -> KEYWORD;
+ AGGREG -> PAREN;
+
+ KEYWORD -> OP;
+
+ OP -> VALUE;
+
+ VALUE -> PAREN;
+ VALUE -> AGGREG;
+
+/*
+ Blue lines represent added complexity of q[IN (x,y,z)] support.
+ The only place that the "blue tree" can be entered is at IN, and
+ exited at PAREN.
+*/
+ KEYWORD -> IN [color=blue];
+ IN -> PAREN [color=blue];
+ PAREN -> VALUE [color=blue];
+ VALUE -> COMMA [color=blue];
+ COMMA -> VALUE [color=blue];
+ VALUE -> PAREN [color=blue];
+}
diff --git a/rt/docs/design_docs/string-extraction-guide.txt b/rt/docs/design_docs/string-extraction-guide.txt
new file mode 100644
index 0000000..bd60a43
--- /dev/null
+++ b/rt/docs/design_docs/string-extraction-guide.txt
@@ -0,0 +1,100 @@
+# $File: //depot/RT/rt-devel/docs/design_docs/string-extraction-guide.txt $ $Author: ivan $
+# $Revision: 1.1 $ $Change: 1431 $ $DateTime: 2002/10/15 17:24:45 $
+
+Run 'p4 edit lib/RT/I18N/zh_tw.pm' and 'perl l10n.pl' to add new
+extractions to the zh_tw.pm.
+
+Edit lib/RT/I18N/zh_tw.pm for chinese counterparts.
+
+Attached is a copy of the freshly rewritten string extraction style guide.
+Please point out anything that's unclear or underspecified. I
+localized a number of the core modules in RT 2.1.3 (Starting with
+Queue_Overlay.pm). I only touched a couple of the web templates in the
+Elements/ directory of the web ui.
+
+RT String extraction styleguide:
+
+Web templates:
+
+Templates should use the /l filtering component to call the localisation
+framework
+
+The string Foo!
+
+Should become <&|/l&>Foo!</&>
+
+All newlines should be removed from localized strings, to make it easy to
+grep the codebase for strings to be localized
+
+The string Foo
+ Bar
+ Baz
+
+Should become <&|/l&>Foo Bar Baz</&>
+
+
+Variable subsititutions should be moved to Locale::MakeText format
+
+The string Hello, <%$name %>
+
+should become <&|/l, $name &>Hello, [_1]</&>
+
+
+Multiple variables work just like single variables
+
+The string You found <%$num%> tickets in queue <%$queue%>
+
+should become <&|/l, $num, $queue &>You found [_1] tickets in queue [_2]</&>
+
+When subcomponents are called in the middle of a phrase, they need to be escaped
+too:
+
+The string <input type="submit" value="New ticket in">&nbsp<& /Elements/SelectNewTicketQueue&>
+
+should become <&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" value="New ticket in">&nbsp;[_1]</&>
+
+
+
+There are places inside the web ui where strings are defined, which need to be
+localised. it is important to note here that each localized string is split out
+onto its own line, but never split across two lines and two localized strings
+are never included on the same line. It is also important to note
+that this will genereate code which will not work in RT 2.1.3. I need
+to add a bit of framework to make it work in 2.1.4
+
+
+The string <& /Elements/TitleBoxStart, width=> "40%", titleright => "RT $RT::VERSION for $RT::rtname", title => 'Login' &>
+
+should become <& /Elements/TitleBoxStart,
+ width=> "40%",
+ titleright => loc("RT [_1] for [_2]",$RT::VERSION, $RT::rtname),
+ title => loc('Login'),
+ &>
+
+
+
+
+
+
+Within RT's core code, every module has a localization handle available through the 'loc' method:
+
+The code return ( $id, "Queue created" );
+
+should become return ( $id, $self->loc("Queue created") );
+
+When returning or localizing a single string, the "extra" set of parenthesis () should be omitted.
+
+The code return ("Subject changed to ". $self->Data );
+
+should become return $self->loc( "Subject changed to [_1]", $self->Data );
+
+
+It is important not to localize the names of rights or statuses within RT's core, as there is logic that depends on them as string identifiers. The proper place to localize these values is when they're presented for display in the web or commandline interfaces.
+
+
+
+
+
+--
+http://www.bestpractical.com/products/rt -- Trouble Ticketing. Free.
+
diff --git a/rt/docs/design_docs/subscription-definitions.txt b/rt/docs/design_docs/subscription-definitions.txt
new file mode 100755
index 0000000..deda35c
--- /dev/null
+++ b/rt/docs/design_docs/subscription-definitions.txt
@@ -0,0 +1,113 @@
+NEW SCRIP NOTES
+
+
+RT Actions:
+
+
+ EmailOwnerAsComment
+ Send mail to the ticket owner from the queue's comment address
+
+ EmailOwnerOrAdminWatchersAsComment
+ Send mail to the ticket owner, or if there is no owner, the ticket's admin watchers
+ from the queue's comment addresses
+
+ EmailAdminWatchersAsComment
+ Send mail to the ticket's adminstrative watchers from the queue's comment address
+
+
+
+ EmailOwner
+ Send mail to the ticket owner from the queue's correspond address
+
+ EmailOwnerOrAdminWatchers
+ Send mail to the ticket owner, or if there is no owner, the ticket's admin watchers
+ from the queue's correspond addresses
+
+ EmailAdminWatchers
+ Send mail to the ticket's adminstrative watchers from the queue's correspond address
+
+ EmailWatchers
+ Send mail to the ticket watchers from the queue's correspond address
+
+ AutoReply
+ Sendmail to the requestor from the queue's correspond address.
+
+
+
+RT Conditions:
+ OnCreate
+ OnEachTransaction
+ OnComment
+ OnCorrespond
+
+
+
+
+
+What is an Action?
+
+...some piece of code that can do something whenever a transaction is done.
+The actions shipped with RT sends email and can handle some logic that makes
+sense for some instances. site-specific modules can be dropped in to
+perform special actions.
+
+
+What can an Action do?
+
+- decide whether it's applicable or not
+- prepare
+- commit
+- describe itself
+
+...and if it's a subclass of SendEmail, you can also override a lot.
+
+Currently the schema.mysql contains a list of the basic subscription-related
+actions that will be bundled with RT.
+
+
+What is a Scrip?
+
+...it's an entry in the database that tells that an action is to be
+performed with a certain template and argument. Template and argument
+doesn't make sense in all contexts. A scrip can be limited to transaction
+types; the current implementation allows a comma-separated list (though for
+a "cleaner" schema design, it should be a separate table for this?). It has
+a name and a description.
+
+
+What is a ScripScope?
+
+...an indication of what queues the different Scrips applies to. It should
+be easy to remove/insert ScripScope objects by the admin tools.
+
+
+What is a Watcher?
+
+...it's a request for beeing kept updated on a ticket and/or a queue
+and/or whatever. It is to be used by the Actions. Watcher items can
+easily be enabled/disabled through the `Quiet' attribute. `Type' might
+indicate what emails the watcher wants to get and how to get them.
+
+The Bcc/Cc watchers should be handled by the NotifyWatchers action which is
+run regardless of the Scrips.
+
+
+What is a Template?
+
+...A template is a text template that is to be used for outgoing email -
+or for different use for different actions. One template can be used by
+several Scrips.
+
+
+How does the system determinate whom to send mail to?
+
+The ScripScope table in the DB should indicate whether a Scrip is relevant
+for a queue or not /* TobiX thinks that this might eventually be extended to
+keywords, tickets, etc, and not only Queues */ ... the Scope table should
+indicate whether the Scrip is relevant for a given transaction type ... then
+the given Action should determinate whether it applies or not, and finally
+the Action has to find out (via the Watchers table) whom it applies to, and
+how to contact them ... and the Template tells how the mails that are sent
+out should look like.
+
+
diff --git a/rt/docs/design_docs/ticket_templates b/rt/docs/design_docs/ticket_templates
new file mode 100644
index 0000000..7850edf
--- /dev/null
+++ b/rt/docs/design_docs/ticket_templates
@@ -0,0 +1,16 @@
+===Create-Ticket: foo
+ Subject: APPROVE <%TOP-Subject%>
+ Status: status
+ Queue: <%TOP-Queue%>
+ Owner: <%TOP-Owner%>
+ Depends-on: <%TOP-Id%>
+ Child-of: <%TOP-Id%>
+ Refers-to: <%TOP-Id%>
+ Content-Type: text/plain
+ Content: This is content
+blah
+blah
+blah
+===Create-Ticket: bar
+Subject: <%foo-Subject%>
+
diff --git a/rt/docs/design_docs/users b/rt/docs/design_docs/users
new file mode 100644
index 0000000..71c4476
--- /dev/null
+++ b/rt/docs/design_docs/users
@@ -0,0 +1,14 @@
+RT2 makes everybody a user. some sites won't like this. there
+should be away to make an "anonymous" user who the mailgate makes
+the requestor for all mailed in tickets. it would then set the
+ticket 'requestor' watcher's alternate email address to the real
+requestor's email.
+
+additionally, eventually, users will need to be deleted. RT doesn't
+want any user deleted. Instead, there will be a flag in the user's
+entry in the users table called 'Disabled.' Disabled users will
+not be able to be granted rights.
+
+ The process of disabling a user should remove their acls and
+should force the giving away of their tickets or reject the disabling.
+
diff --git a/rt/docs/rt3-schema-relationships.dot b/rt/docs/rt3-schema-relationships.dot
new file mode 100644
index 0000000..77ed35f
--- /dev/null
+++ b/rt/docs/rt3-schema-relationships.dot
@@ -0,0 +1,81 @@
+digraph g {
+graph [
+rankdir = "LR",
+concentrate = true,
+ratio = auto
+];
+node [
+fontsize = "18",
+shape = record, fontsize = 18
+];
+edge [
+];
+
+"ACL" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"ACL" -> "Principals" [label="PrincipalId -> Id"];
+"ACL" -> "Principals" [label="DelegatedBy -> Id"];
+"ACL" -> "ACL" [label="DelegatedFrom -> Id"];
+
+"Attachments" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Attachments" -> "Transactions" [label="TransactionId -> id"];
+"Attachments" -> "Attachments" [label="Parent -> id"];
+
+"CachedGroupMemers" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"CachedGroupMemers" -> "Groups" [label="GroupId -> Groups.id"];
+"CachedGroupMemers" -> "Principals" [label="MemberId -> Id"];
+"CachedGroupMemers" -> "CachedGroupMemers" [label="Via -> id"];
+"CachedGroupMemers" -> "Groups" [label="ImmediateParentId -> Groups.id"];
+
+"CustomFields" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"CustomFields" -> "Queues" [label="Queue -> id"];
+
+"CustomFieldValues" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"CustomFieldValues" -> "CustomFields" [label="CustomField -> id"];
+
+"GroupMembers" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"GroupMembers" -> "Groups" [label="GroupId => Groups.Id"];
+"GroupMembers" -> "Principals" [label="MemberId => Id"];
+
+"Groups" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Groups" -> "Principals" [label="Groups.id -> id"];
+
+"Links" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Links" -> "Tickets" [label="LocalBase => id (usually)"];
+"Links" -> "Tickets" [label="LocalTarget => id (usually)"];
+
+"Principals" [shape = record, fontsize = 18, label = "<col0> \N " ];
+
+
+"Queues" [shape = record, fontsize = 18, label = "<col0> \N " ];
+
+"ScripActions" [shape = record, fontsize = 18, label = "<col0> \N " ];
+
+"ScripConditions" [shape = record, fontsize = 18, label = "<col0> \N " ];
+
+"Scrips" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Scrips" -> "ScripConditions" [label="ScripCondition -> id"];
+"Scrips" -> "ScripActions" [label="ScripAction -> id"];
+"Scrips" -> "Templates" [label="Template -> id"];
+"Scrips" -> "Queues" [label="Queue -> id"];
+
+"Templates" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Templates" -> "Queues" [label ="Queue -> id" ];
+
+"TicketCustomFieldValues" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"TicketCustomFieldValues" -> "Tickets" [label="Ticket -> id"];
+"TicketCustomFieldValues" -> "CustomFields" [label="CustomField -> id"];
+
+"Tickets" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Tickets" -> "Tickets" [label="EffectiveId -> id"];
+"Tickets" -> "Queues" [label="Queue -> id"];
+"Tickets" -> "Principals" [label="Owner -> id"];
+
+"Transactions" [shape = record, fontsize = 18, label = "<col0> \N " ];
+"Transactions" -> "Tickets" [label="Ticket -> Id"];
+
+"Users" [shape = record, fontsize = 18, label = "<col0> \N " ];
+
+"Users" -> "Principals" [label="id -> id"];
+
+
+}
diff --git a/rt/etc/RT_Config.pm b/rt/etc/RT_Config.pm
new file mode 100644
index 0000000..5386a8e
--- /dev/null
+++ b/rt/etc/RT_Config.pm
@@ -0,0 +1,374 @@
+#
+# WARNING: NEVER EDIT RT_Config.pm. Instead, copy any sections you want to change to RT_SiteConfig.pm
+# and edit them there.
+#
+
+package RT;
+
+=head1 NAME
+
+RT::Config
+
+=for testing
+
+use RT::Config;
+
+=cut
+
+# {{{ Base Configuration
+
+# $rtname the string that RT will look for in mail messages to
+# figure out what ticket a new piece of mail belongs to
+
+# Your domain name is recommended, so as not to pollute the namespace.
+# once you start using a given tag, you should probably never change it.
+# (otherwise, mail for existing tickets won't get put in the right place
+
+Set($rtname , "example.com");
+
+# You should set this to your organization's DNS domain. For example,
+# fsck.com or asylum.arkham.ma.us. It's used by the linking interface to
+# guarantee that ticket URIs are unique and easy to construct.
+
+Set($Organization , "example.com");
+
+# $user_passwd_min defines the minimum length for user passwords. Setting
+# it to 0 disables this check
+Set($MinimumPasswordLength , "5");
+
+# $Timezone is used to convert times entered by users into GMT and back again
+# It should be set to a timezone recognized by your local unix box.
+Set($Timezone , 'US/Eastern');
+
+# }}}
+
+# }}}
+
+# {{{ Database Configuration
+
+# Database driver beeing used. Case matters
+# Valid types are "mysql", "Oracle" and "Pg"
+
+Set($DatabaseType , 'mysql');
+
+# The domain name of your database server
+# If you're running mysql and it's on localhost,
+# leave it blank for enhanced performance
+Set($DatabaseHost , 'localhost');
+Set($DatabaseRTHost , 'localhost');
+
+# The port that your database server is running on. Ignored unless it's
+# a positive integer. It's usually safe to leave this blank
+Set($DatabasePort , '');
+
+#The name of the database user (inside the database)
+Set($DatabaseUser , 'rt_user');
+
+# Password the DatabaseUser should use to access the database
+Set($DatabasePassword , 'rt_pass');
+
+# The name of the RT's database on your database server
+Set($DatabaseName , 'rt3');
+
+# If you're using Postgres and have compiled in SSL support,
+# set DatabaseRequireSSL to 1 to turn on SSL communication
+Set($DatabaseRequireSSL , undef);
+
+# }}}
+
+# {{{ Incoming mail gateway configuration
+
+# OwnerEmail is the address of a human who manages RT. RT will send
+# errors generated by the mail gateway to this address. This address
+# should _not_ be an address that's managed by your RT instance.
+
+Set($OwnerEmail , 'root');
+
+# If $LoopsToRTOwner is defined, RT will send mail that it believes
+# might be a loop to $RT::OwnerEmail
+
+Set($LoopsToRTOwner , 1);
+
+# If $StoreLoopss is defined, RT will record messages that it believes
+# to be part of mail loops.
+# As it does this, it will try to be careful not to send mail to the
+# sender of these messages
+
+Set($StoreLoops , undef);
+
+# $MaxAttachmentSize sets the maximum size (in bytes) of attachments stored
+# in the database.
+
+# For mysql and oracle, we set this size at 10 megabytes.
+# If you're running a postgres version earlier than 7.1, you will need
+# to drop this to 8192. (8k)
+
+Set($MaxAttachmentSize , 10000000);
+
+# $TruncateLongAttachments: if this is set to a non-undef value,
+# RT will truncate attachments longer than MaxAttachmentLength.
+
+Set($TruncateLongAttachments , undef);
+
+# $DropLongAttachments: if this is set to a non-undef value,
+# RT will silently drop attachments longer than MaxAttachmentLength.
+
+Set($DropLongAttachments , undef);
+
+# If $ParseNewMessageForTicketCcs is true, RT will attempt to divine
+# Ticket 'Cc' watchers from the To and Cc lines of incoming messages
+# Be forewarned that if you have _any_ addresses which forward mail to
+# RT automatically and you enable this option without modifying
+# "RTAddressRegexp" below, you will get yourself into a heap of trouble.
+
+Set($ParseNewMessageForTicketCcs , undef);
+
+# RTAddressRegexp is used to make sure RT doesn't add itself as a ticket CC if
+# the setting above is enabled.
+
+Set($RTAddressRegexp , '^rt\@example.com$');
+
+# RT provides functionality which allows the system to rewrite
+# incoming email addresses. In its simplest form,
+# you can substitute the value in CanonicalizeEmailAddressReplace
+# for the value in CanonicalizeEmailAddressMatch
+# (These values are passed to the CanonicalizeEmailAddress subroutine in RT/User.pm)
+# By default, that routine performs a s/$Match/$Replace/gi on any address passed to it
+
+Set($CanonicalizeEmailAddressMatch , 'subdomain.example.com$');
+Set($CanonicalizeEmailAddressReplace , 'example.com');
+
+# If $SenderMustExistInExternalDatabase is true, RT will refuse to
+# create non-privileged accounts for unknown users if you are using
+# the "LookupSenderInExternalDatabase" option.
+# Instead, an error message will be mailed and RT will forward the
+# message to $RTOwner.
+#
+# If you are not using $LookupSenderInExternalDatabase, this option
+# has no effect.
+#
+# If you define an AutoRejectRequest template, RT will use this
+# template for the rejection message.
+
+Set($SenderMustExistInExternalDatabase , undef);
+
+# }}}
+
+# {{{ Outgoing mail configuration
+
+# RT is designed such that any mail which already has a ticket-id associated
+# with it will get to the right place automatically.
+
+# $CorrespondAddress and $CommentAddress are the default addresses
+# that will be listed in From: and Reply-To: headers of correspondence
+# and comment mail tracked by RT, unless overridden by a queue-specific
+# address.
+
+Set($CorrespondAddress , 'RT_CorrespondAddressNotSet');
+
+Set($CommentAddress , 'RT_CommentAddressNotSet');
+
+#Sendmail Configuration
+
+# $MailCommand defines which method RT will use to try to send mail
+# We know that 'sendmailpipe' works fairly well.
+# If 'sendmailpipe' doesn't work well for you, try 'sendmail'
+#
+# Note that you should remove the '-t' from $SendmailArguments
+# if you use 'sendmail rather than 'sendmailpipe'
+
+Set($MailCommand , 'sendmailpipe');
+
+# $SendmailArguments defines what flags to pass to $Sendmail
+# assuming you picked 'sendmail' or 'sendmailpipe' as the $MailCommand above.
+# If you picked 'sendmailpipe', you MUST add a -t flag to $SendmailArguments
+
+# These options are good for most sendmail wrappers and workalikes
+Set($SendmailArguments , "-oi -t");
+
+# These arguments are good for sendmail brand sendmail 8 and newer
+#Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m");
+
+# If you selected 'sendmailpipe' above, you MUST specify the path
+# to your sendmail binary in $SendmailPath.
+# !! If you did not # select 'sendmailpipe' above, this has no effect!!
+Set($SendmailPath , "/usr/sbin/sendmail");
+
+# By default, RT sets the outgoing mail's "From:" header to
+# "SenderName via RT". Setting this option to 0 disables it.
+
+Set($UseFriendlyFromLine , 1);
+
+# sprintf() format of the friendly 'From:' header; its arguments
+# are SenderName and SenderEmailAddress.
+Set($FriendlyFromLineFormat , "\"%s via RT\" <%s>");
+
+# RT can optionally set a "Friendly" 'To:' header when sending messages to
+# Ccs or AdminCcs (rather than having a blank 'To:' header.
+
+# This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL
+# If you are using sendmail, rather than postfix, qmail, exim or some other MTA,
+# you _must_ disable this option.
+
+Set($UseFriendlyToLine , 0);
+
+# sprintf() format of the friendly 'From:' header; its arguments
+# are WatcherType and TicketId.
+Set($FriendlyToLineFormat, "\"%s of $RT::rtname Ticket #%s\":;");
+
+# By default RT doesn't notify the person who performs an update, as they
+# already know what they've done. If you'd like to change this behaviour,
+# Set $NotifyActor to 1
+
+Set($NotifyActor, 0);
+
+
+# }}}
+
+# {{{ Logging
+
+# Logging. The default is to log anything except debugging
+# information to syslog. Check the Log::Dispatch POD for
+# information about how to get things by syslog, mail or anything
+# else, get debugging info in the log, etc.
+
+# It might generally make
+# sense to send error and higher by email to some administrator.
+# If you do this, be careful that this email isn't sent to this RT instance.
+
+# the minimum level error that will be logged to the specific device.
+# levels from lowest to highest:
+# debug info notice warning error critical alert emergency
+
+# Mail loops will generate a critical log message.
+Set($LogToSyslog , 'debug');
+Set($LogToScreen , 'error');
+Set($LogToFile , undef);
+Set($LogDir, '/opt/rt3/var/log');
+Set($LogToFileNamed , "rt.log"); #log to rt.log
+
+# }}}
+
+# {{{ Web interface configuration
+
+# Define the directory name to be used for images in rt web
+# documents.
+
+# If you're putting the web ui somewhere other than at the root of
+# your server
+# $WebPath requires a leading / but no trailing /
+
+Set($WebPath , "");
+
+# This is the Scheme, server and port for constructing urls to webrt
+# $WebBaseURL doesn't need a trailing /
+
+Set($WebBaseURL , "http://RT::WebBaseURL.not.configured:80");
+
+Set($WebURL , $WebBaseURL . $WebPath . "/");
+
+# $WebImagesURL points to the base URL where RT can find its images.
+
+Set($WebImagesURL , $WebURL . "NoAuth/images/");
+
+# $RTLogoURL points to the URL of the RT logo displayed in the web UI
+
+Set($LogoURL , $WebImagesURL . "rt.jpg");
+
+# For message boxes, set the entry box width and what type of wrapping
+# to use.
+#
+# Default width: 72
+Set($MessageBoxWidth , 72);
+
+# Default wrapping: "HARD" (choices "SOFT", "HARD")
+Set($MessageBoxWrap, "HARD");
+
+# if TrustHTMLAttachments is not defined, we will display them
+# as text. This prevents malicious HTML and javascript from being
+# sent in a request (although there is probably more to it than that)
+Set($TrustHTMLAttachments , undef);
+
+# If $WebExternalAuth is defined, RT will defer to the environment's
+# REMOTE_USER variable.
+
+Set($WebExternalAuth , undef);
+
+# If $WebFallbackToInternalAuth is undefined, the user is allowed a chance
+# of fallback to the login screen, even if REMOTE_USER failed.
+
+Set($WebFallbackToInternalAuth , undef);
+
+# $WebExternalGecos means to match 'gecos' field as the user identity);
+# useful with mod_auth_pwcheck and IIS Integrated Windows logon.
+
+Set($WebExternalGecos , undef);
+
+# $WebExternalAuto will create users under the same name as REMOTE_USER
+# upon login, if it's missing in the Users table.
+
+Set($WebExternalAuto , undef);
+
+# $WebSessionClass is the class you wish to use for managing Sessions.
+# It defaults to use your SQL database, but if you are using MySQL 3.x and
+# plans to use non-ascii Queue names, uncomment and add this line to
+# RT_SiteConfig.pm will prevent session corruption.
+
+# Set($WebSessionClass , 'Apache::Session::File');
+
+# $MaxInlineBody is the maximum attachment size that we want to see
+# inline when viewing a transaction. 13456 is a random sane-sounding
+# default.
+
+Set($MaxInlineBody, 13456);
+
+# $MyTicketsLength is the length of the table on the front page.
+# For some people, the default of 10 isn't big enough to get a feel for
+# how much work needs to be done before you get some time off.
+
+Set($MyTicketsLength, 10);
+
+# @MasonParameters is the list of parameters for the constructor of
+# HTML::Mason's Apache or CGI Handler. This is normally only useful
+# for debugging, eg. profiling individual components with
+# (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
+
+@MasonParameters = () unless (@MasonParameters);
+
+# }}}
+
+# {{{ RT UTF-8 Settings
+
+# An array that contains languages supported by RT's internationalization
+# interface. Defaults to all *.po lexicons; set it to qw(en ja) will make
+# RT bilingual instead of multilingual, but will save same memory.
+
+@LexiconLanguages = qw(*) unless (@LexiconLanguages);
+
+# An array that contains default encodings used to guess which charset
+# an attachment uses if not specified. Must be recognized by
+# Encode::Guess.
+
+@EmailInputEncodings = qw(utf-8 iso-8859-1 us-ascii) unless (@EmailInputEncodings);
+
+# The charset for localized email. Must be recognized by Encode.
+
+Set($EmailOutputEncoding , 'utf-8');
+
+# }}}
+
+# {{{ RT Date Handling Options (for Time::ParseDate)
+
+# Set this to 1 if your local date convention looks like "dd/mm/yy"
+# instead of "mm/dd/yy".
+
+Set($DateDayBeforeMonth , 1);
+
+# Should "Tuesday" default to meaning "Next Tuesday" or "Last Tuesday"?
+# Set to 0 for "Next" or 1 for "Last".
+
+Set($AmbiguousDayInPast , 1);
+
+# }}}
+
+1;
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
new file mode 100644
index 0000000..8271a77
--- /dev/null
+++ b/rt/etc/RT_Config.pm.in
@@ -0,0 +1,374 @@
+#
+# WARNING: NEVER EDIT RT_Config.pm. Instead, copy any sections you want to change to RT_SiteConfig.pm
+# and edit them there.
+#
+
+package RT;
+
+=head1 NAME
+
+RT::Config
+
+=for testing
+
+use RT::Config;
+
+=cut
+
+# {{{ Base Configuration
+
+# $rtname the string that RT will look for in mail messages to
+# figure out what ticket a new piece of mail belongs to
+
+# Your domain name is recommended, so as not to pollute the namespace.
+# once you start using a given tag, you should probably never change it.
+# (otherwise, mail for existing tickets won't get put in the right place
+
+Set($rtname , "example.com");
+
+# You should set this to your organization's DNS domain. For example,
+# fsck.com or asylum.arkham.ma.us. It's used by the linking interface to
+# guarantee that ticket URIs are unique and easy to construct.
+
+Set($Organization , "example.com");
+
+# $user_passwd_min defines the minimum length for user passwords. Setting
+# it to 0 disables this check
+Set($MinimumPasswordLength , "5");
+
+# $Timezone is used to convert times entered by users into GMT and back again
+# It should be set to a timezone recognized by your local unix box.
+Set($Timezone , 'US/Eastern');
+
+# }}}
+
+# }}}
+
+# {{{ Database Configuration
+
+# Database driver beeing used. Case matters
+# Valid types are "mysql", "Oracle" and "Pg"
+
+Set($DatabaseType , '@DB_TYPE@');
+
+# The domain name of your database server
+# If you're running mysql and it's on localhost,
+# leave it blank for enhanced performance
+Set($DatabaseHost , '@DB_HOST@');
+Set($DatabaseRTHost , '@DB_RT_HOST@');
+
+# The port that your database server is running on. Ignored unless it's
+# a positive integer. It's usually safe to leave this blank
+Set($DatabasePort , '@DB_PORT@');
+
+#The name of the database user (inside the database)
+Set($DatabaseUser , '@DB_RT_USER@');
+
+# Password the DatabaseUser should use to access the database
+Set($DatabasePassword , '@DB_RT_PASS@');
+
+# The name of the RT's database on your database server
+Set($DatabaseName , '@DB_DATABASE@');
+
+# If you're using Postgres and have compiled in SSL support,
+# set DatabaseRequireSSL to 1 to turn on SSL communication
+Set($DatabaseRequireSSL , undef);
+
+# }}}
+
+# {{{ Incoming mail gateway configuration
+
+# OwnerEmail is the address of a human who manages RT. RT will send
+# errors generated by the mail gateway to this address. This address
+# should _not_ be an address that's managed by your RT instance.
+
+Set($OwnerEmail , 'root');
+
+# If $LoopsToRTOwner is defined, RT will send mail that it believes
+# might be a loop to $RT::OwnerEmail
+
+Set($LoopsToRTOwner , 1);
+
+# If $StoreLoopss is defined, RT will record messages that it believes
+# to be part of mail loops.
+# As it does this, it will try to be careful not to send mail to the
+# sender of these messages
+
+Set($StoreLoops , undef);
+
+# $MaxAttachmentSize sets the maximum size (in bytes) of attachments stored
+# in the database.
+
+# For mysql and oracle, we set this size at 10 megabytes.
+# If you're running a postgres version earlier than 7.1, you will need
+# to drop this to 8192. (8k)
+
+Set($MaxAttachmentSize , 10000000);
+
+# $TruncateLongAttachments: if this is set to a non-undef value,
+# RT will truncate attachments longer than MaxAttachmentLength.
+
+Set($TruncateLongAttachments , undef);
+
+# $DropLongAttachments: if this is set to a non-undef value,
+# RT will silently drop attachments longer than MaxAttachmentLength.
+
+Set($DropLongAttachments , undef);
+
+# If $ParseNewMessageForTicketCcs is true, RT will attempt to divine
+# Ticket 'Cc' watchers from the To and Cc lines of incoming messages
+# Be forewarned that if you have _any_ addresses which forward mail to
+# RT automatically and you enable this option without modifying
+# "RTAddressRegexp" below, you will get yourself into a heap of trouble.
+
+Set($ParseNewMessageForTicketCcs , undef);
+
+# RTAddressRegexp is used to make sure RT doesn't add itself as a ticket CC if
+# the setting above is enabled.
+
+Set($RTAddressRegexp , '^rt\@example.com$');
+
+# RT provides functionality which allows the system to rewrite
+# incoming email addresses. In its simplest form,
+# you can substitute the value in CanonicalizeEmailAddressReplace
+# for the value in CanonicalizeEmailAddressMatch
+# (These values are passed to the CanonicalizeEmailAddress subroutine in RT/User.pm)
+# By default, that routine performs a s/$Match/$Replace/gi on any address passed to it
+
+Set($CanonicalizeEmailAddressMatch , 'subdomain.example.com$');
+Set($CanonicalizeEmailAddressReplace , 'example.com');
+
+# If $SenderMustExistInExternalDatabase is true, RT will refuse to
+# create non-privileged accounts for unknown users if you are using
+# the "LookupSenderInExternalDatabase" option.
+# Instead, an error message will be mailed and RT will forward the
+# message to $RTOwner.
+#
+# If you are not using $LookupSenderInExternalDatabase, this option
+# has no effect.
+#
+# If you define an AutoRejectRequest template, RT will use this
+# template for the rejection message.
+
+Set($SenderMustExistInExternalDatabase , undef);
+
+# }}}
+
+# {{{ Outgoing mail configuration
+
+# RT is designed such that any mail which already has a ticket-id associated
+# with it will get to the right place automatically.
+
+# $CorrespondAddress and $CommentAddress are the default addresses
+# that will be listed in From: and Reply-To: headers of correspondence
+# and comment mail tracked by RT, unless overridden by a queue-specific
+# address.
+
+Set($CorrespondAddress , 'RT_CorrespondAddressNotSet');
+
+Set($CommentAddress , 'RT_CommentAddressNotSet');
+
+#Sendmail Configuration
+
+# $MailCommand defines which method RT will use to try to send mail
+# We know that 'sendmailpipe' works fairly well.
+# If 'sendmailpipe' doesn't work well for you, try 'sendmail'
+#
+# Note that you should remove the '-t' from $SendmailArguments
+# if you use 'sendmail rather than 'sendmailpipe'
+
+Set($MailCommand , 'sendmailpipe');
+
+# $SendmailArguments defines what flags to pass to $Sendmail
+# assuming you picked 'sendmail' or 'sendmailpipe' as the $MailCommand above.
+# If you picked 'sendmailpipe', you MUST add a -t flag to $SendmailArguments
+
+# These options are good for most sendmail wrappers and workalikes
+Set($SendmailArguments , "-oi -t");
+
+# These arguments are good for sendmail brand sendmail 8 and newer
+#Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m");
+
+# If you selected 'sendmailpipe' above, you MUST specify the path
+# to your sendmail binary in $SendmailPath.
+# !! If you did not # select 'sendmailpipe' above, this has no effect!!
+Set($SendmailPath , "/usr/sbin/sendmail");
+
+# By default, RT sets the outgoing mail's "From:" header to
+# "SenderName via RT". Setting this option to 0 disables it.
+
+Set($UseFriendlyFromLine , 1);
+
+# sprintf() format of the friendly 'From:' header; its arguments
+# are SenderName and SenderEmailAddress.
+Set($FriendlyFromLineFormat , "\"%s via RT\" <%s>");
+
+# RT can optionally set a "Friendly" 'To:' header when sending messages to
+# Ccs or AdminCcs (rather than having a blank 'To:' header.
+
+# This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL
+# If you are using sendmail, rather than postfix, qmail, exim or some other MTA,
+# you _must_ disable this option.
+
+Set($UseFriendlyToLine , 0);
+
+# sprintf() format of the friendly 'From:' header; its arguments
+# are WatcherType and TicketId.
+Set($FriendlyToLineFormat, "\"%s of $RT::rtname Ticket #%s\":;");
+
+# By default RT doesn't notify the person who performs an update, as they
+# already know what they've done. If you'd like to change this behaviour,
+# Set $NotifyActor to 1
+
+Set($NotifyActor, 0);
+
+
+# }}}
+
+# {{{ Logging
+
+# Logging. The default is to log anything except debugging
+# information to syslog. Check the Log::Dispatch POD for
+# information about how to get things by syslog, mail or anything
+# else, get debugging info in the log, etc.
+
+# It might generally make
+# sense to send error and higher by email to some administrator.
+# If you do this, be careful that this email isn't sent to this RT instance.
+
+# the minimum level error that will be logged to the specific device.
+# levels from lowest to highest:
+# debug info notice warning error critical alert emergency
+
+# Mail loops will generate a critical log message.
+Set($LogToSyslog , 'debug');
+Set($LogToScreen , 'error');
+Set($LogToFile , undef);
+Set($LogDir, '@RT_LOG_PATH@');
+Set($LogToFileNamed , "rt.log"); #log to rt.log
+
+# }}}
+
+# {{{ Web interface configuration
+
+# Define the directory name to be used for images in rt web
+# documents.
+
+# If you're putting the web ui somewhere other than at the root of
+# your server
+# $WebPath requires a leading / but no trailing /
+
+Set($WebPath , "");
+
+# This is the Scheme, server and port for constructing urls to webrt
+# $WebBaseURL doesn't need a trailing /
+
+Set($WebBaseURL , "http://RT::WebBaseURL.not.configured:80");
+
+Set($WebURL , $WebBaseURL . $WebPath . "/");
+
+# $WebImagesURL points to the base URL where RT can find its images.
+
+Set($WebImagesURL , $WebURL . "NoAuth/images/");
+
+# $RTLogoURL points to the URL of the RT logo displayed in the web UI
+
+Set($LogoURL , $WebImagesURL . "rt.jpg");
+
+# For message boxes, set the entry box width and what type of wrapping
+# to use.
+#
+# Default width: 72
+Set($MessageBoxWidth , 72);
+
+# Default wrapping: "HARD" (choices "SOFT", "HARD")
+Set($MessageBoxWrap, "HARD");
+
+# if TrustHTMLAttachments is not defined, we will display them
+# as text. This prevents malicious HTML and javascript from being
+# sent in a request (although there is probably more to it than that)
+Set($TrustHTMLAttachments , undef);
+
+# If $WebExternalAuth is defined, RT will defer to the environment's
+# REMOTE_USER variable.
+
+Set($WebExternalAuth , undef);
+
+# If $WebFallbackToInternalAuth is undefined, the user is allowed a chance
+# of fallback to the login screen, even if REMOTE_USER failed.
+
+Set($WebFallbackToInternalAuth , undef);
+
+# $WebExternalGecos means to match 'gecos' field as the user identity);
+# useful with mod_auth_pwcheck and IIS Integrated Windows logon.
+
+Set($WebExternalGecos , undef);
+
+# $WebExternalAuto will create users under the same name as REMOTE_USER
+# upon login, if it's missing in the Users table.
+
+Set($WebExternalAuto , undef);
+
+# $WebSessionClass is the class you wish to use for managing Sessions.
+# It defaults to use your SQL database, but if you are using MySQL 3.x and
+# plans to use non-ascii Queue names, uncomment and add this line to
+# RT_SiteConfig.pm will prevent session corruption.
+
+# Set($WebSessionClass , 'Apache::Session::File');
+
+# $MaxInlineBody is the maximum attachment size that we want to see
+# inline when viewing a transaction. 13456 is a random sane-sounding
+# default.
+
+Set($MaxInlineBody, 13456);
+
+# $MyTicketsLength is the length of the table on the front page.
+# For some people, the default of 10 isn't big enough to get a feel for
+# how much work needs to be done before you get some time off.
+
+Set($MyTicketsLength, 10);
+
+# @MasonParameters is the list of parameters for the constructor of
+# HTML::Mason's Apache or CGI Handler. This is normally only useful
+# for debugging, eg. profiling individual components with
+# (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
+
+@MasonParameters = () unless (@MasonParameters);
+
+# }}}
+
+# {{{ RT UTF-8 Settings
+
+# An array that contains languages supported by RT's internationalization
+# interface. Defaults to all *.po lexicons; set it to qw(en ja) will make
+# RT bilingual instead of multilingual, but will save same memory.
+
+@LexiconLanguages = qw(*) unless (@LexiconLanguages);
+
+# An array that contains default encodings used to guess which charset
+# an attachment uses if not specified. Must be recognized by
+# Encode::Guess.
+
+@EmailInputEncodings = qw(utf-8 iso-8859-1 us-ascii) unless (@EmailInputEncodings);
+
+# The charset for localized email. Must be recognized by Encode.
+
+Set($EmailOutputEncoding , 'utf-8');
+
+# }}}
+
+# {{{ RT Date Handling Options (for Time::ParseDate)
+
+# Set this to 1 if your local date convention looks like "dd/mm/yy"
+# instead of "mm/dd/yy".
+
+Set($DateDayBeforeMonth , 1);
+
+# Should "Tuesday" default to meaning "Next Tuesday" or "Last Tuesday"?
+# Set to 0 for "Next" or 1 for "Last".
+
+Set($AmbiguousDayInPast , 1);
+
+# }}}
+
+1;
diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm
new file mode 100644
index 0000000..572a2ba
--- /dev/null
+++ b/rt/etc/RT_SiteConfig.pm
@@ -0,0 +1,13 @@
+$RT::rtname = '%%%RT_DOMAIN%%%';
+$RT::Organization = '%%%RT_DOMAIN%%%';
+
+$RT::Timezone = '%%%RT_TIMEZONE%%%';
+
+$RT::WebBaseURL = '';
+$RT::WebPath = '/freeside/rt';
+
+$RT::WebExternalAuth = 1;
+$RT::WebFallbackToInternal = 1; #no
+$RT::WebExternalAuto = 1;
+
+1;
diff --git a/rt/etc/acl.Informix b/rt/etc/acl.Informix
new file mode 100644
index 0000000..bca0408
--- /dev/null
+++ b/rt/etc/acl.Informix
@@ -0,0 +1,5 @@
+sub acl {
+return (
+"GRANT RESOURCE TO ${RT::DatabaseUser};");
+}
+1;
diff --git a/rt/etc/acl.Oracle b/rt/etc/acl.Oracle
new file mode 100644
index 0000000..ac29215
--- /dev/null
+++ b/rt/etc/acl.Oracle
@@ -0,0 +1,10 @@
+sub acl {
+return (
+"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword} ".
+"default tablespace USERS " .
+"temporary tablespace TEMP " .
+"quota unlimited on USERS" ,
+"grant connect, resource to ${RT::DatabaseUser}"
+);
+}
+1;
diff --git a/rt/etc/acl.Pg b/rt/etc/acl.Pg
new file mode 100755
index 0000000..16ea71b
--- /dev/null
+++ b/rt/etc/acl.Pg
@@ -0,0 +1,63 @@
+sub acl {
+ my $dbh = shift;
+
+ my @acls;
+
+ my @tables = qw (
+
+ attachments_id_seq
+ Attachments
+ queues_id_seq
+ Queues
+ links_id_seq
+ Links
+ principals_id_seq
+ Principals
+ groups_id_seq
+ Groups
+ scripconditions_id_seq
+ ScripConditions
+ transactions_id_seq
+ Transactions
+ scrips_id_seq
+ Scrips
+ acl_id_seq
+ ACL
+ groupmembers_id_seq
+ GroupMembers
+ cachedgroupmembers_id_seq
+ CachedGroupMembers
+ users_id_seq
+ Users
+ tickets_id_seq
+ Tickets
+ scripactions_id_seq
+ ScripActions
+ templates_id_seq
+ Templates
+ ticketcustomfieldvalues_id_s
+ TicketCustomFieldValues
+ customfields_id_seq
+ CustomFields
+ customfieldvalues_id_seq
+ CustomFieldValues
+ sessions
+ );
+
+ # if there's already an rt_user, drop it.
+ my @row =
+ $dbh->selectrow_array( "select usename from pg_user where usename = '" . $RT::DatabaseUser."'" );
+ if ( $row[0] ) {
+ push @acls, "drop user ${RT::DatabaseUser};",;
+ }
+
+ push @acls, "create user ${RT::DatabaseUser} with password '${RT::DatabasePassword}' NOCREATEDB NOCREATEUSER;";
+ foreach my $table (@tables) {
+ push @acls,
+ "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
+ . $RT::DatabaseUser . ";";
+
+ }
+ return (@acls);
+}
+1;
diff --git a/rt/etc/acl.mysql b/rt/etc/acl.mysql
new file mode 100755
index 0000000..0ecaa3b
--- /dev/null
+++ b/rt/etc/acl.mysql
@@ -0,0 +1,8 @@
+sub acl {
+return (
+"USE mysql;",
+"DELETE FROM user WHERE user = '${RT::DatabaseUser}';",
+"DELETE FROM db where db = '${RT::DatabaseName}';",
+"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@${RT::DatabaseRTHost} IDENTIFIED BY '${RT::DatabasePassword}';");
+}
+1;
diff --git a/rt/etc/constraints.mysql b/rt/etc/constraints.mysql
new file mode 100644
index 0000000..fd557d5
--- /dev/null
+++ b/rt/etc/constraints.mysql
@@ -0,0 +1,42 @@
+ ALTER TABLE Links ADD FOREIGN KEY (LocalBase) REFERENCES Tickets(id) ;
+ ALTER TABLE Links ADD FOREIGN KEY (LocalTarget) REFERENCES Tickets(id);
+ ALTER TABLE Tickets ADD FOREIGN KEY (Queue) REFERENCES Queues(id);
+ ALTER TABLE Tickets ADD FOREIGN KEY (EffectiveId) REFERENCES Tickets(id);
+ ALTER TABLE Tickets ADD FOREIGN KEY (Owner) REFERENCES Principals(id);
+ ALTER TABLE Tickets ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE Tickets ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE Transactions ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE Transactions ADD FOREIGN KEY (Ticket) REFERENCES Tickets(id);
+ ALTER TABLE Transactions ADD FOREIGN KEY (EffectiveTicket) REFERENCES Tickets(id);
+ ALTER TABLE Attachments ADD FOREIGN KEY (TransactionId) REFERENCES Transactions(id);
+ ALTER TABLE Attachments ADD FOREIGN KEY (Parent) REFERENCES Attachments(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (ScripCondition) REFERENCES ScripConditions(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (ScripAction) REFERENCES ScripActions(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (Template) REFERENCES Templates(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (Queue) REFERENCES Queues(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE Scrips ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE ACL ADD FOREIGN KEY (PrincipalId) REFERENCES Principals(id);
+ ALTER TABLE ACL ADD FOREIGN KEY (DelegatedBy) REFERENCES Principals(id);
+ ALTER TABLE ACL ADD FOREIGN KEY (DelegatedFrom) REFERENCES ACL(id);
+ ALTER TABLE GroupMembers ADD FOREIGN KEY (GroupId) REFERENCES Groups(id);
+ ALTER TABLE GroupMembers ADD FOREIGN KEY (MemberId) REFERENCES Principals(id);
+ ALTER TABLE CachedGroupMembers ADD FOREIGN KEY (ImmediateParentId) REFERENCES Principals(id);
+ ALTER TABLE CachedGroupMembers ADD FOREIGN KEY (GroupId) REFERENCES Principals(id);
+ ALTER TABLE CachedGroupMembers ADD FOREIGN KEY (MemberId) REFERENCES Principals(id);
+ ALTER TABLE CachedGroupMembers ADD FOREIGN KEY (Via) REFERENCES CachedGroupMembers(id);
+ ALTER TABLE ScripActions ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE ScripActions ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE Templates ADD FOREIGN KEY (Queue) REFERENCES Queues(id);
+ ALTER TABLE Templates ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE Templates ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE CustomFields ADD FOREIGN KEY (Queue) REFERENCES Queues(id);
+ ALTER TABLE CustomFields ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE CustomFields ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE TicketCustomFieldValues ADD FOREIGN KEY (Ticket) REFERENCES Tickets(id);
+ ALTER TABLE TicketCustomFieldValues ADD FOREIGN KEY (CustomField) REFERENCES CustomFields(id);
+ ALTER TABLE TicketCustomFieldValues ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE TicketCustomFieldValues ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
+ ALTER TABLE CustomFieldValues ADD FOREIGN KEY (CustomField) REFERENCES CustomFields(id);
+ ALTER TABLE CustomFieldValues ADD FOREIGN KEY (Creator) REFERENCES Users(id);
+ ALTER TABLE CustomFieldValues ADD FOREIGN KEY (LastUpdatedBy) REFERENCES Users(id);
diff --git a/rt/etc/drop.Informix b/rt/etc/drop.Informix
new file mode 100644
index 0000000..ce7cc01
--- /dev/null
+++ b/rt/etc/drop.Informix
@@ -0,0 +1,19 @@
+DROP TABLE ACL;
+DROP TABLE ATTACHMENTS;
+DROP TABLE CACHEDGROUPMEMBERS;
+DROP TABLE CUSTOMFIELDS;
+DROP TABLE CUSTOMFIELDVALUES;
+DROP TABLE GROUPMEMBERS;
+DROP TABLE GROUPS;
+DROP TABLE LINKS;
+DROP TABLE PRINCIPALS;
+DROP TABLE QUEUES;
+DROP TABLE SCRIPACTIONS;
+DROP TABLE SCRIPCONDITIONS;
+DROP TABLE SCRIPS;
+DROP TABLE SESSIONS;
+DROP TABLE TEMPLATES;
+DROP TABLE TICKETCUSTOMFIELDVALUES;
+DROP TABLE TICKETS;
+DROP TABLE TRANSACTIONS;
+DROP TABLE USERS;
diff --git a/rt/etc/drop.Oracle b/rt/etc/drop.Oracle
new file mode 100644
index 0000000..dd11376
--- /dev/null
+++ b/rt/etc/drop.Oracle
@@ -0,0 +1,37 @@
+DROP TABLE ACL;
+DROP TABLE ATTACHMENTS;
+DROP TABLE CACHEDGROUPMEMBERS;
+DROP TABLE CUSTOMFIELDS;
+DROP TABLE CUSTOMFIELDVALUES;
+DROP TABLE GROUPMEMBERS;
+DROP TABLE GROUPS;
+DROP TABLE LINKS;
+DROP TABLE PRINCIPALS;
+DROP TABLE QUEUES;
+DROP TABLE SCRIPACTIONS;
+DROP TABLE SCRIPCONDITIONS;
+DROP TABLE SCRIPS;
+DROP TABLE SESSIONS;
+DROP TABLE TEMPLATES;
+DROP TABLE TICKETCUSTOMFIELDVALUES;
+DROP TABLE TICKETS;
+DROP TABLE TRANSACTIONS;
+DROP TABLE USERS;
+DROP SEQUENCE ACL_seq;
+DROP SEQUENCE ATTACHMENTS_seq;
+DROP SEQUENCE CACHEDGROUPMEMBERS_seq;
+DROP SEQUENCE CUSTOMFIELDS_seq;
+DROP SEQUENCE CUSTOMFIELDVALUES_seq;
+DROP SEQUENCE GROUPMEMBERS_seq;
+DROP SEQUENCE GROUPS_seq;
+DROP SEQUENCE LINKS_seq;
+DROP SEQUENCE PRINCIPALS_seq;
+DROP SEQUENCE QUEUES_seq;
+DROP SEQUENCE SCRIPACTIONS_seq;
+DROP SEQUENCE SCRIPCONDITIONS_seq;
+DROP SEQUENCE SCRIPS_seq;
+DROP SEQUENCE TEMPLATES_seq;
+DROP SEQUENCE TICKETCUSTOMFIELDVALUES_seq;
+DROP SEQUENCE TICKETS_seq;
+DROP SEQUENCE TRANSACTIONS_seq;
+DROP SEQUENCE USERS_seq;
diff --git a/rt/etc/initialdata b/rt/etc/initialdata
new file mode 100644
index 0000000..e360c5d
--- /dev/null
+++ b/rt/etc/initialdata
@@ -0,0 +1,569 @@
+# Initial data for a fresh RT3 Installation.
+
+@Users = (
+ { Name => 'Nobody',
+ RealName => 'Nobody in particular',
+ Comments => 'Do not delete or modify this user. It is integral '
+ . 'to RT\'s internal data structures',
+ Privileged => '0', },
+
+ { Name => 'root',
+ Gecos => 'root',
+ RealName => 'Enoch Root',
+ Password => 'password',
+ EmailAddress => "root\@localhost",
+ Comments => 'SuperUser',
+ Privileged => '1', } );
+
+@Groups = (
+ { Name => '',
+ Type => 'Everyone', # loc
+ Domain => 'SystemInternal',
+ Instance => '',
+ Description => 'Pseudogroup for internal use', # loc
+ },
+ { Type => 'Privileged', # loc
+ Domain => 'SystemInternal',
+ Instance => '',
+ Name => '',
+ Description => 'Pseudogroup for internal use', # loc
+ },
+ { Name => '',
+ Type => 'Unprivileged', # loc
+ Domain => 'SystemInternal',
+ Instance => '',
+ Description => 'Pseudogroup for internal use', # loc
+ },
+ { Name => '',
+ Type => 'Owner', # loc
+ Domain => 'RT::System-Role',
+ Instance => '',
+ Description => 'SystemRolegroup for internal use', # loc
+ },
+ { Name => '',
+ Type => 'Requestor', # loc
+ Domain => 'RT::System-Role',
+ Instance => '',
+ Description => 'SystemRolegroup for internal use', # loc
+ },
+ { Name => '',
+ Type => 'Cc', # loc
+ Domain => 'RT::System-Role',
+ Instance => '',
+ Description => 'SystemRolegroup for internal use', # loc
+ },
+ { Name => '',
+ Type => 'AdminCc', # loc
+ Domain => 'RT::System-Role',
+ Instance => '',
+ Description => 'Pseudogroup for internal use', # loc
+ }, );
+
+@Queues = ({ Name => 'General',
+ Description => 'The default queue',
+ CorrespondAddress => "",
+ CommentAddress => "", },
+ { Name => '___Approvals',
+ Description => 'A system-internal queue for the approvals system',
+ Disabled => 2, } );
+
+@ScripActions = (
+
+ { Name => 'Autoreply To Requestors', # loc
+ Description =>
+'Always sends a message to the requestors independent of message sender' , # loc
+ ExecModule => 'Autoreply',
+ Argument => 'Requestor' },
+ { Name => 'Notify Requestors', # loc
+ Description => 'Sends a message to the requestors', # loc
+ ExecModule => 'Notify',
+ Argument => 'Requestor' },
+ { Name => 'Notify Owner as Comment', # loc
+ Description => 'Sends mail to the owner', # loc
+ ExecModule => 'NotifyAsComment',
+ Argument => 'Owner' },
+ { Name => 'Notify Owner', # loc
+ Description => 'Sends mail to the owner', # loc
+ ExecModule => 'Notify',
+ Argument => 'Owner' },
+ { Name => 'Notify AdminCcs as Comment', # loc
+ Description => 'Sends mail to the administrative Ccs as a comment', # loc
+ ExecModule => 'NotifyAsComment',
+ Argument => 'AdminCc' },
+ { Name => 'Notify AdminCcs', # loc
+ Description => 'Sends mail to the administrative Ccs', # loc
+ ExecModule => 'Notify',
+ Argument => 'AdminCc' },
+
+ { Name => 'Notify Requestors and Ccs as Comment', # loc
+ Description => 'Send mail to requestors and Ccs as a comment', # loc
+ ExecModule => 'NotifyAsComment',
+ Argument => 'Requestor,Cc' },
+
+ { Name => 'Notify Requestors and Ccs', # loc
+ Description => 'Send mail to requestors and Ccs', # loc
+ ExecModule => 'Notify',
+ Argument => 'Requestor,Cc' },
+
+ { Name => 'Notify Requestors, Ccs and AdminCcs as Comment', # loc
+ Description => 'Send mail to all watchers as a "comment"', # loc
+ ExecModule => 'NotifyAsComment',
+ Argument => 'All' },
+ { Name => 'Notify Requestors, Ccs and AdminCcs', # loc
+ Description => 'Send mail to all watchers', # loc
+ ExecModule => 'Notify',
+ Argument => 'All' },
+ { Name => 'Notify Other Recipients as Comment', # loc
+ Description => 'Sends mail to explicitly listed Ccs and Bccs', # loc
+ ExecModule => 'NotifyAsComment',
+ Argument => 'OtherRecipients' },
+ { Name => 'Notify Other Recipients', # loc
+ Description => 'Sends mail to explicitly listed Ccs and Bccs', # loc
+ ExecModule => 'Notify',
+ Argument => 'OtherRecipients' },
+ { Name => 'User Defined', # loc
+ Description => 'Perform a user-defined action', # loc
+ ExecModule => 'UserDefined', },
+ { Name => 'Create Tickets', # loc
+ Description =>
+ 'Create new tickets based on this scrip\'s template', # loc
+ ExecModule => 'CreateTickets', },
+ { Name => 'Open Tickets',
+ Description => 'Open tickets on correspondence', # loc
+ ExecModule => 'AutoOpen' },
+);
+
+@ScripConditions = (
+ { Name => 'On Create', # loc
+ Description => 'When a ticket is created', # loc
+ ApplicableTransTypes => 'Create',
+ ExecModule => 'AnyTransaction', },
+
+ { Name => 'On Transaction', # loc
+ Description => 'When anything happens', # loc
+ ApplicableTransTypes => 'Any',
+ ExecModule => 'AnyTransaction', },
+ {
+
+ Name => 'On Correspond', # loc
+ Description => 'Whenever correspondence comes in', # loc
+ ApplicableTransTypes => 'Correspond',
+ ExecModule => 'AnyTransaction', },
+
+ {
+
+ Name => 'On Comment', # loc
+ Description => 'Whenever comments come in', # loc
+ ApplicableTransTypes => 'Comment',
+ ExecModule => 'AnyTransaction' },
+ {
+
+ Name => 'On Status Change', # loc
+ Description => 'Whenever a ticket\'s status changes', # loc
+ ApplicableTransTypes => 'Status',
+ ExecModule => 'AnyTransaction',
+
+ },
+ {
+
+ Name => 'On Owner Change', # loc
+ Description => 'Whenever a ticket\'s owner changes', # loc
+ ApplicableTransTypes => 'Any',
+ ExecModule => 'OwnerChange',
+
+ },
+ {
+
+ Name => 'On Queue Change', # loc
+ Description => 'Whenever a ticket\'s queue changes', # loc
+ ApplicableTransTypes => 'Set',
+ ExecModule => 'QueueChange',
+
+ },
+ { Name => 'On Resolve', # loc
+ Description => 'Whenever a ticket is resolved', # loc
+ ApplicableTransTypes => 'Status',
+ ExecModule => 'StatusChange',
+ Argument => 'resolved'
+
+ },
+
+ { Name => 'User Defined', # loc
+ Description => 'Whenever a user-defined condition occurs', # loc
+ ApplicableTransTypes => 'Any',
+ ExecModule => 'UserDefined'
+
+ },
+
+);
+
+@Templates = (
+ { Queue => '0',
+ Name => 'Blank', # loc
+ Description => 'A blank template', # loc
+ Content => '', },
+ { Queue => '0',
+ Name => 'Autoreply', # loc
+ Description => 'Default Autoresponse template', # loc
+ Content => 'Subject: AutoReply: {$Ticket->Subject}
+
+
+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 [{$rtname} #{$Ticket->id()}].
+
+Please include the string:
+
+ [{$rtname} #{$Ticket->id}]
+
+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()}
+'
+ },
+
+ { Queue => '0',
+ Name => 'Transaction', # loc
+ Description => 'Default transaction template', # loc
+ Content => 'RT-Attach-Message: yes
+
+
+{$Transaction->CreatedAsString}: Request {$Ticket->id} was acted upon.
+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: {$RT::WebURL}Ticket/Display.html?id={$Ticket->id} >
+
+
+{$Transaction->Content()}
+'
+ },
+
+ {
+
+ Queue => '0',
+ Name => 'Admin Correspondence', # loc
+ Description => 'Default admin correspondence template', # loc
+ Content => 'RT-Attach-Message: yes
+
+
+<URL: {$RT::WebURL}Ticket/Display.html?id={$Ticket->id} >
+
+{$Transaction->Content()}
+'
+ },
+
+ { Queue => '0',
+ Name => 'Correspondence', # loc
+ Description => 'Default correspondence template', # loc
+ Content => 'RT-Attach-Message: yes
+
+{$Transaction->Content()}
+'
+ },
+
+ { Queue => '0',
+ Name => 'Admin Comment', # loc
+ Description => 'Default admin comment template', # loc
+ Content =>
+'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject); $s =~ s/\\[Comment\\]//g; $comment =~ s/^Re//i; $s;}
+
+
+{$RT::WebURL}Ticket/Display.html?id={$Ticket->id}
+This is a comment. It is not sent to the Requestor(s):
+
+{$Transaction->Content()}
+'
+ },
+
+ { Queue => '0',
+ Name => 'Status Change', # loc
+ Description => 'Ticket status changed', # loc
+ Content => 'Subject: Status Changed to: {$Transaction->NewValue}
+
+
+{$RT::WebURL}Ticket/Display.html?id={$Ticket->id}
+
+{$Transaction->Content()}
+'
+ },
+
+ {
+
+ Queue => '0',
+ Name => 'Resolved', # loc
+ Description => 'Ticket Resolved', # loc
+ Content => 'Subject: Resolved: {$Ticket->Subject}
+
+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", # loc
+ Description =>
+ "Notify Owners and AdminCcs of new items pending their approval", # loc
+ Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}",
+a summary of which appears below.
+
+Please visit {$RT::WebURL}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {$RT::WebURL}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Passed", # loc
+ Description =>
+ "Notify Owner of their ticket has been approved by some approver", # loc
+ Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Other approvals may be pending.
+'
+ },
+ { Queue => '___Approvals',
+ Name => "All Approvals Passed", # loc
+ Description =>
+ "Notify Owner of their ticket has been approved by all approvers", # loc
+ Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved. Its Owner may now start to act on it.
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Rejected", # loc
+ Description =>
+ "Notify Owner of their rejected ticket", # loc
+ Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+'
+ },
+);
+# }}}
+
+@Scrips = (
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Open Tickets',
+ Template => 'Blank' },
+ { ScripCondition => 'On Create',
+ ScripAction => 'AutoReply To Requestors',
+ Template => 'AutoReply' },
+ { ScripCondition => 'On Create',
+ ScripAction => 'Notify AdminCcs',
+ Template => 'Transaction' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify AdminCcs',
+ Template => 'Admin Correspondence' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify Requestors And Ccs',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify Other Recipients',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Comment',
+ ScripAction => 'Notify AdminCcs As Comment',
+ Template => 'Admin Comment' },
+ { ScripCondition => 'On Comment',
+ ScripAction => 'Notify Other Recipients As Comment',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Resolve',
+ ScripAction => 'Notify Requestors',
+ Template => 'Resolved' },
+ { Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'User Defined',
+ CustomIsApplicableCode => q[
+ $self->TicketObj->Type eq 'approval' and
+ $self->TransactionObj->Field eq 'Status' and
+ $self->TransactionObj->NewValue eq 'open' and
+ eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
+ ],
+ ScripAction => 'Notify Owner',
+ Template => 'New Pending Approval' },
+ { Description => "If an approval is rejected, reject the original and delete pending approvals", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Status Change',
+ ScripAction => 'User Defined',
+ CustomPrepareCode => q[
+# ------------------------------------------------------------------- #
+return(0) unless ( lc($self->TransactionObj->NewValue) eq "rejected" or
+ lc($self->TransactionObj->NewValue) eq "deleted" );
+
+my $rejected = 0;
+my $links = $self->TicketObj->DependedOnBy;
+foreach my $link (@{ $links->ItemsArrayRef }) {
+ my $obj = $link->BaseObj;
+ if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
+ if ($obj->Type eq 'ticket') {
+ $obj->Comment(
+ Content => $self->loc("Your request was rejected."),
+ );
+ $obj->SetStatus(
+ Status => 'rejected',
+ Force => 1,
+ );
+
+ $T::Approval = $self->TicketObj; # so we can access it inside templates
+ $self->{TicketObj} = $obj; # we want the original id in the token line
+ $rejected = 1;
+ }
+ else {
+ $obj->SetStatus(
+ Status => 'deleted',
+ Force => 1,
+ );
+ }
+ }
+}
+
+$links = $self->TicketObj->DependsOn;
+foreach my $link (@{ $links->ItemsArrayRef }) {
+ my $obj = $link->TargetObj;
+ if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
+ $obj->SetStatus(
+ Status => 'deleted',
+ Force => 1,
+ );
+ }
+}
+
+# Now magically turn myself into a Requestor Notify object...
+require RT::Action::Notify; bless($self, 'RT::Action::Notify');
+$self->{Argument} = 'Requestor'; $self->Prepare;
+
+return $rejected;
+# ------------------------------------------------------------------- #
+ ],
+ CustomCommitCode => '"never needed"',
+ Template => 'Approval Rejected', },
+ { Description => "When a ticket has been approved by any approver, add correspondence to the original ticket", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Resolve',
+ ScripAction => 'User Defined',
+ CustomPrepareCode => q[
+# ------------------------------------------------------------------- #
+return(0) unless ($self->TicketObj->Type eq 'approval');
+
+my $note;
+my $t = $self->TicketObj->Transactions;
+while (my $o = $t->Next) {
+ $note .= $o->Content . "\n" if $o->ContentObj
+ and $o->Content !~ /Default Approval/;
+}
+
+foreach my $obj ($self->TicketObj->AllDependedOnBy( Type => 'ticket' )) {
+ $obj->Comment(
+ Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
+ $self->TransactionObj->CreatorObj->Name,
+ ) . "\n" . $self->loc( "Approver's notes: [_1]", # loc
+ $note
+ ),
+ );
+ $T::Approval = $self->TicketObj; # so we can access it inside templates
+ $self->{TicketObj} = $obj; # we want the original id in the token line
+}
+
+# Now magically turn myself into a Requestor Notify object...
+require RT::Action::Notify; bless($self, 'RT::Action::Notify');
+$self->{Argument} = 'Requestor'; $self->Prepare;
+
+return 1;
+# ------------------------------------------------------------------- #
+ ],
+ CustomCommitCode => '"never needed"',
+ Template => 'Approval Passed' },
+ { Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Resolve',
+ ScripAction => 'User Defined',
+ CustomPrepareCode => q[
+# ------------------------------------------------------------------- #
+# Find all the tickets that depend on this (that this is approving)
+
+my $Ticket = $self->TicketObj;
+my @TOP = $Ticket->AllDependedOnBy( Type => 'ticket' );
+my $links = $Ticket->DependedOnBy;
+my $passed = 0;
+
+while (my $link = $links->Next) {
+ my $obj = $link->BaseObj;
+ next if ($obj->HasUnresolvedDependencies( Type => 'approval' ));
+
+ if ($obj->Type eq 'ticket') {
+ $obj->Comment(
+ Content => $self->loc("Your request has been approved."),
+ );
+ $T::Approval = $Ticket; # so we can access it inside templates
+ $self->{TicketObj} = $obj; # we want the original id in the token line
+ $passed = 1;
+ }
+ elsif ($obj->Type eq 'approval') {
+ $obj->SetStatus( Status => 'open', Force => 1 );
+ }
+ elsif ($RT::UseCodeTickets and $obj->Type eq 'code') {
+ my $code = $obj->Transactions->First->Content;
+ my $rv;
+
+ foreach my $TOP (@TOP) {
+ local $@;
+ $rv++ if eval $code;
+ $RT::Logger->error("Cannot eval code: $@") if $@;
+ }
+
+ if ($rv or !@TOP) {
+ $obj->SetStatus( Status => 'resolved', Force => 1,);
+ }
+ else {
+ $obj->SetStatus( Status => 'rejected', Force => 1,);
+ }
+ }
+}
+
+# Now magically turn myself into a Requestor Notify object...
+require RT::Action::Notify; bless($self, 'RT::Action::Notify');
+$self->{Argument} = 'Requestor'; $self->Prepare;
+
+return 0; # ignore $passed;
+# ------------------------------------------------------------------- #
+ ],
+ CustomCommitCode => '"never needed"',
+ Template => 'All Approvals Passed', },
+
+);
+
+@ACL = (
+ { UserId => 'Nobody', # - principalId
+ Right => 'OwnTicket', },
+
+ { UserId => 'root', # - principalid
+ Right => 'SuperUser', },
+
+);
diff --git a/rt/etc/rt.spec b/rt/etc/rt.spec
new file mode 100644
index 0000000..14200c1
--- /dev/null
+++ b/rt/etc/rt.spec
@@ -0,0 +1,137 @@
+Summary: rt Request Tracker
+
+Name: rt
+Version: 2.0.9pre5
+Release: 1
+Group: Applications/Web
+Packager: Jesse Vincent <jesse@bestpractical.com>
+Vendor: http://www.fsck.com/projects/rt
+Requires: perl
+Requires: mod_perl > 1.22
+Requires: perl-DBI >= 1.18
+Requires: perl-DBIx-DataSource >= 0.02
+Requires: perl-DBIx-SearchBuilder >= 0.47
+Requires: perl-HTML-Parser
+Requires: perl-MLDBM
+Requires: perl-libnet
+Requires: perl-CGI.pm >= 2.78
+Requires: perl-Params-Validate >= 0.02
+Requires: perl-HTML-Mason >= 0.896
+Requires: perl-libapreq
+Requires: perl-Apache-Session >= 1.53
+Requires: perl-MIME-tools >= 5.411
+Requires: perl-MailTools >= 1.20
+Requires: perl-Getopt-Long >= 2.24
+Requires: perl-Tie-IxHash
+Requires: perl-TimeDate
+Requires: perl-Time-HiRes
+Requires: perl-Text-Wrapper
+Requires: perl-Text-Template
+Requires: perl-File-Spec >= 0.8
+Requires: perl-FreezeThaw
+Requires: perl-Storable
+Requires: perl-File-Temp
+Requires: perl-Log-Dispatch >= 1.6
+
+Source: http://www.fsck.com/pub/rt/release/%{name}.tar.gz
+
+Copyright: GPL
+BuildRoot: /var/tmp/rt-root
+
+%description
+RT is an industrial-grade ticketing system. It lets a group
+of people intelligently and efficiently manage requests
+submitted by a community of users. RT is used by systems
+administrators, customer support staffs, NOCs, developers
+and even marketing departments at over a thousand sites
+around the world.
+
+%prep
+groupadd rt || true
+%setup -q -n %{name}
+
+%build
+
+%install
+
+if [ x$RPM_BUILD_ROOT != x ]; then
+rm -rf $RPM_BUILD_ROOT
+fi
+
+#
+# Perform all the non-site specfic steps whilst building the package
+#
+make dirs libs-install html-install bin-install DESTDIR=$RPM_BUILD_ROOT
+#
+# fixperms needs these, so make fake empty files
+touch $RPM_BUILD_ROOT/opt/rt2/etc/insertdata $RPM_BUILD_ROOT/opt/rt2/etc/config.pm
+make fixperms insert-install WEB_USER=www DESTDIR=$RPM_BUILD_ROOT
+
+#
+# Copy in the files needed again after install
+#
+mkdir -p $RPM_BUILD_ROOT/opt/rt2/postinstall/bin
+cp -rp Makefile etc tools $RPM_BUILD_ROOT/opt/rt2/postinstall
+cp -rp bin/initacls.* $RPM_BUILD_ROOT/opt/rt2/postinstall/bin
+
+# logging in /var/log/rt2
+mkdir -p $RPM_BUILD_ROOT/var/log/rt2
+chown www $RPM_BUILD_ROOT/var/log/rt2
+chgrp rt $RPM_BUILD_ROOT/var/log/rt2
+chmod ug=rwx,o= $RPM_BUILD_ROOT/var/log/rt2
+
+%clean
+if [ x$RPM_BUILD_ROOT != x ]; then
+rm -rf $RPM_BUILD_ROOT
+fi
+
+#
+# A new rt groups is required
+#
+%pre
+groupadd rt || true
+
+#
+# Show the user the site specific steps required after install
+#
+%post
+cat <<EOF
+-----------------------------------------------------------------------
+rt2 installation is complete. Now create the rt2 database by running:
+-----------------------------------------------------------------------
+
+# cd /opt/rt2/postinstall
+# make config-replace initialize.mysql insert RT_LOG_PATH=/var/log/rt2 DB_RT_PASS=new_rt_user_password
+
+Choose your own new_rt_user_password. You will need the mysql root password.
+You can try Pg or Oracle instead of mysql - untested.
+
+Review and configure your site specific details in /opt/rt2/etc/config.pm
+EOF
+
+%preun
+
+%files
+%dir /opt/rt2
+/opt/rt2/bin
+/opt/rt2/WebRT
+/opt/rt2/lib
+/opt/rt2/local
+/opt/rt2/man
+/opt/rt2/postinstall
+%dir /opt/rt2/etc
+/opt/rt2/etc/insertdata
+%config /opt/rt2/etc/config.pm
+%dir /var/log/rt2
+
+%changelog
+* Mon Sep 24 2001 Jesse Vincent <jesse@bestpractical.com>
+ Switch to rt DESTDIR support
+* Fri Sep 14 2001 Cris Bailiff <c.bailiff@devsecure.com>
+ Fix permissions on created /var/log/rt2 and roll in 2.0.7
+* Tue Sep 4 2001 Cris Bailiff <c.bailiff@devsecure.com>
+- created initial spec file
+* Tue Sep 4 2001 Cris Bailiff <c.bailiff@devsecure.com>
+- created initial spec file
+* Tue Sep 4 2001 Cris Bailiff <c.bailiff@devsecure.com>
+- created initial spec file
diff --git a/rt/etc/schema.Informix b/rt/etc/schema.Informix
new file mode 100644
index 0000000..ca6173f
--- /dev/null
+++ b/rt/etc/schema.Informix
@@ -0,0 +1,342 @@
+-- This schema was adopted from the oracle schema by
+-- Andre Koppel.
+-- Version 0.2 Date 2003.10.21
+-- The work is still in progress
+
+CREATE TABLE Attachments (
+ id SERIAL,
+ TransactionId INTEGER NOT NULL,
+ Parent INTEGER DEFAULT 0 NOT NULL,
+ MessageId VARCHAR(160),
+ Subject VARCHAR(255),
+ Filename VARCHAR(255),
+ ContentType VARCHAR(80),
+ ContentEncoding VARCHAR(80),
+ Content BYTE,
+ Headers BYTE,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+CREATE INDEX Attachments1 ON Attachments (Parent);
+CREATE INDEX Attachments2 ON Attachments (TransactionId);
+CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId);
+
+
+CREATE TABLE Queues (
+ id SERIAL,
+ Name VARCHAR(200) DEFAULT '' NOT NULL,
+ Description VARCHAR(255) DEFAULT NULL,
+ CorrespondAddress VARCHAR(120) DEFAULT NULL,
+ CommentAddress VARCHAR(120) DEFAULT NULL,
+ InitialPriority INTEGER DEFAULT 0 NOT NULL,
+ FinalPriority INTEGER DEFAULT 0 NOT NULL,
+ DefaultDueIn INTEGER DEFAULT 0 NOT NULL,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ Disabled INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE UNIQUE INDEX Queues1 ON Queues (Name);
+CREATE INDEX Queues2 ON Queues (Disabled);
+
+
+CREATE TABLE Links (
+ id SERIAL,
+ Base VARCHAR(240) DEFAULT NULL,
+ Target VARCHAR(240) DEFAULT NULL,
+ Type VARCHAR(20) DEFAULT '' NOT NULL,
+ LocalTarget INTEGER DEFAULT 0 NOT NULL,
+ LocalBase INTEGER DEFAULT 0 NOT NULL,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+-- CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type);
+CREATE INDEX Links2 ON Links (Base, Type);
+CREATE INDEX Links3 ON Links (Target, Type);
+CREATE INDEX Links4 ON Links(Type,LocalBase);
+
+
+CREATE TABLE Principals (
+ id SERIAL,
+ PrincipalType VARCHAR(16) DEFAULT '' NOT NULL,
+ ObjectId INTEGER DEFAULT 0,
+ Disabled INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX Principals2 ON Principals (ObjectId);
+
+
+CREATE TABLE Groups (
+ id SERIAL,
+ Name VARCHAR(200) DEFAULT NULL,
+ Description VARCHAR(255) DEFAULT NULL,
+ Domain VARCHAR(64) DEFAULT '',
+ Type VARCHAR(64) DEFAULT '',
+ Instance INTEGER DEFAULT 0 NOT NULL,
+-- Instance VARCHAR(64) DEFAULT '' NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX Groups1 ON Groups (Domain, Instance, Type, id);
+CREATE INDEX Groups2 ON Groups (Type, Instance, Domain);
+
+
+CREATE TABLE ScripConditions (
+ id SERIAL,
+ Name VARCHAR(200),
+ Description VARCHAR(255),
+ ExecModule VARCHAR(60),
+ Argument VARCHAR(255),
+ ApplicableTransTypes VARCHAR(60),
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+
+CREATE TABLE Transactions (
+ id SERIAL,
+ EffectiveTicket INTEGER DEFAULT 0 NOT NULL,
+ Ticket INTEGER DEFAULT 0 NOT NULL,
+ TimeTaken INTEGER DEFAULT 0 NOT NULL,
+ Type VARCHAR(20),
+ Field VARCHAR(40),
+ OldValue VARCHAR(255),
+ NewValue VARCHAR(255),
+ Data VARCHAR(255),
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+CREATE INDEX Transactions1 ON Transactions (Ticket);
+CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+
+
+CREATE TABLE Scrips (
+ id SERIAL,
+ Description VARCHAR(255) DEFAULT '',
+ ScripCondition INTEGER DEFAULT 0 NOT NULL,
+ ScripAction INTEGER DEFAULT 0 NOT NULL,
+ ConditionRules BYTE,
+ ActionRules BYTE,
+ CustomIsApplicableCode BYTE,
+ CustomPrepareCode BYTE,
+ CustomCommitCode BYTE,
+ Stage VARCHAR(32),
+ Queue INTEGER DEFAULT 0 NOT NULL,
+ Template INTEGER DEFAULT 0 NOT NULL,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+
+CREATE TABLE ACL (
+ id SERIAL,
+ PrincipalType VARCHAR(25) NOT NULL,
+ PrincipalId INTEGER NOT NULL,
+ RightName VARCHAR(25) NOT NULL,
+ ObjectType VARCHAR(25) NOT NULL,
+ ObjectId INTEGER DEFAULT 0 NOT NULL,
+ DelegatedBy INTEGER DEFAULT 0 NOT NULL,
+ DelegatedFrom INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX ACL1 ON ACL(RightName, ObjectType, ObjectId, PrincipalType, PrincipalId);
+
+
+CREATE TABLE GroupMembers (
+ id SERIAL,
+ GroupId INTEGER DEFAULT 0 NOT NULL,
+ MemberId INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers (GroupId, MemberId);
+
+
+CREATE TABLE CachedGroupMembers (
+ id SERIAL,
+ GroupId INTEGER DEFAULT 0,
+ MemberId INTEGER DEFAULT 0,
+ Via INTEGER DEFAULT 0,
+ ImmediateParentId INTEGER DEFAULT 0,
+ Disabled INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled);
+CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId);
+
+
+CREATE TABLE Users (
+ id SERIAL,
+ Name VARCHAR(200) NOT NULL,
+ Password VARCHAR(40),
+ Comments BYTE,
+ Signature BYTE,
+ EmailAddress VARCHAR(120),
+ FreeFormContactInfo BYTE,
+ Organization VARCHAR(200),
+ RealName VARCHAR(120),
+ NickName VARCHAR(16),
+ Lang VARCHAR(16),
+ EmailEncoding VARCHAR(16),
+ WebEncoding VARCHAR(16),
+ ExternalContactInfoId VARCHAR(100),
+ ContactInfoSystem VARCHAR(30),
+ ExternalAuthId VARCHAR(100),
+ AuthSystem VARCHAR(30),
+ Gecos VARCHAR(16),
+ HomePhone VARCHAR(30),
+ WorkPhone VARCHAR(30),
+ MobilePhone VARCHAR(30),
+ PagerPhone VARCHAR(30),
+ Address1 VARCHAR(200),
+ Address2 VARCHAR(200),
+ City VARCHAR(100),
+ State VARCHAR(100),
+ Zip VARCHAR(16),
+ Country VARCHAR(50),
+ Timezone VARCHAR(50),
+ PGPKey BYTE,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+-- 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);
+
+
+CREATE TABLE Tickets (
+ id SERIAL,
+ EffectiveId INTEGER DEFAULT 0 NOT NULL,
+ Queue INTEGER DEFAULT 0 NOT NULL,
+ Type VARCHAR(16),
+ IssueStatement INTEGER DEFAULT 0 NOT NULL,
+ Resolution INTEGER DEFAULT 0 NOT NULL,
+ Owner INTEGER DEFAULT 0 NOT NULL,
+ Subject VARCHAR(200) DEFAULT '[no subject]',
+ InitialPriority INTEGER DEFAULT 0 NOT NULL,
+ FinalPriority INTEGER DEFAULT 0 NOT NULL,
+ Priority INTEGER DEFAULT 0 NOT NULL,
+ TimeEstimated INTEGER DEFAULT 0 NOT NULL,
+ TimeWorked INTEGER DEFAULT 0 NOT NULL,
+ Status VARCHAR(10),
+ TimeLeft INTEGER DEFAULT 0 NOT NULL,
+ Told DATETIME YEAR TO SECOND,
+ Starts DATETIME YEAR TO SECOND,
+ Started DATETIME YEAR TO SECOND,
+ Due DATETIME YEAR TO SECOND,
+ Resolved DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ Disabled INTEGER DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+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);
+CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type);
+
+
+CREATE TABLE ScripActions (
+ id SERIAL,
+ Name VARCHAR(200),
+ Description VARCHAR(255),
+ ExecModule VARCHAR(60),
+ Argument VARCHAR(255),
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+
+CREATE TABLE Templates (
+ id SERIAL,
+ Queue INTEGER DEFAULT 0 NOT NULL,
+ Name VARCHAR(200) NOT NULL,
+ Description VARCHAR(255),
+ Type VARCHAR(16),
+ Language VARCHAR(16),
+ TranslationOf INTEGER DEFAULT 0 NOT NULL,
+ Content BYTE,
+ LastUpdated DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+
+CREATE TABLE TicketCustomFieldValues (
+ id SERIAL,
+ Ticket INTEGER NOT NULL,
+ CustomField INTEGER NOT NULL,
+ Content VARCHAR(255),
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content);
+CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket);
+
+CREATE TABLE CustomFields (
+ id SERIAL,
+ Name VARCHAR(200),
+ Type VARCHAR(200),
+ Queue INTEGER DEFAULT 0 NOT NULL,
+ Description VARCHAR(255),
+ SortOrder INTEGER DEFAULT 0 NOT NULL,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ Disabled SMALLINT DEFAULT 0 NOT NULL,
+ PRIMARY KEY (id)
+);
+CREATE INDEX CustomFields1 ON CustomFields (Disabled, Queue);
+
+
+CREATE TABLE CustomFieldValues (
+ id SERIAL,
+ CustomField INTEGER NOT NULL,
+ Name VARCHAR(200),
+ Description VARCHAR(255),
+ SortOrder INTEGER DEFAULT 0 NOT NULL,
+ Creator INTEGER DEFAULT 0 NOT NULL,
+ Created DATETIME YEAR TO SECOND,
+ LastUpdatedBy INTEGER DEFAULT 0 NOT NULL,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+
+CREATE TABLE sessions (
+ id VARCHAR(32) NOT NULL,
+ a_session BYTE,
+ LastUpdated DATETIME YEAR TO SECOND,
+ PRIMARY KEY (id)
+);
+
diff --git a/rt/etc/schema.Pg b/rt/etc/schema.Pg
new file mode 100755
index 0000000..085c615
--- /dev/null
+++ b/rt/etc/schema.Pg
@@ -0,0 +1,578 @@
+------------------------------------------------------------------
+-- My2Pg 1.23 translated dump
+--
+------------------------------------------------------------------
+
+
+
+--
+-- Sequences for table ATTACHMENTS
+--
+
+CREATE SEQUENCE attachments_id_seq;
+
+-- {{{ Attachments
+
+CREATE TABLE Attachments (
+ id INTEGER DEFAULT nextval('attachments_id_seq'),
+ TransactionId integer NOT NULL ,
+ Parent integer NOT NULL DEFAULT 0 ,
+ MessageId varchar(160) NULL ,
+ Subject varchar(255) NULL ,
+ Filename varchar(255) NULL ,
+ ContentType varchar(80) NULL ,
+ ContentEncoding varchar(80) NULL ,
+ Content text NULL ,
+ Headers text NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX Attachments1 ON Attachments (Parent) ;
+CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
+CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
+-- }}}
+
+-- {{{ Queues
+
+
+--
+-- Sequences for table QUEUES
+--
+
+CREATE SEQUENCE queues_id_seq;
+
+CREATE TABLE Queues (
+ id INTEGER DEFAULT nextval('queues_id_seq'),
+ Name varchar(200) NOT NULL ,
+ Description varchar(255) NULL ,
+ CorrespondAddress varchar(120) NULL ,
+ CommentAddress varchar(120) NULL ,
+ InitialPriority integer NOT NULL DEFAULT 0 ,
+ FinalPriority integer NOT NULL DEFAULT 0 ,
+ DefaultDueIn integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+
+);
+CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
+
+-- }}}
+
+-- {{{ Links
+
+
+
+--
+-- Sequences for table LINKS
+--
+
+CREATE SEQUENCE links_id_seq;
+
+CREATE TABLE Links (
+ id INTEGER DEFAULT nextval('links_id_seq'),
+ Base varchar(240) NULL ,
+ Target varchar(240) NULL ,
+ Type varchar(20) NOT NULL ,
+ LocalTarget integer NOT NULL DEFAULT 0 ,
+ LocalBase integer NOT NULL DEFAULT 0 ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ;
+CREATE INDEX Links4 ON Links(Type,LocalBase);
+
+-- }}}
+
+-- {{{ Principals
+
+
+
+--
+-- Sequences for table PRINCIPALS
+--
+
+CREATE SEQUENCE principals_id_seq;
+
+CREATE TABLE Principals (
+ id INTEGER DEFAULT nextval('principals_id_seq') not null,
+ PrincipalType VARCHAR(16) not null,
+ ObjectId integer,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX Principals2 ON Principals (ObjectId);
+
+
+-- }}}
+
+-- {{{ Groups
+
+
+
+--
+-- Sequences for table GROUPS
+--
+
+CREATE SEQUENCE groups_id_seq;
+
+CREATE TABLE Groups (
+ id INTEGER DEFAULT nextval('groups_id_seq'),
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ Domain varchar(64),
+ Type varchar(64),
+ Instance integer,
+ PRIMARY KEY (id)
+
+);
+CREATE UNIQUE INDEX Groups1 ON Groups (Domain,Instance,Type,id, Name);
+CREATE INDEX Groups2 On Groups (Type, Instance, Domain);
+
+
+-- }}}
+
+-- {{{ ScripConditions
+
+
+
+--
+-- Sequences for table SCRIPCONDITIONS
+--
+
+CREATE SEQUENCE scripconditions_id_seq;
+
+CREATE TABLE ScripConditions (
+ id INTEGER DEFAULT nextval('scripconditions_id_seq'),
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ ExecModule varchar(60) NULL ,
+ Argument varchar(255) NULL ,
+ ApplicableTransTypes varchar(60) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ Transactions
+
+
+--
+-- Sequences for table TRANSACTIONS
+--
+
+CREATE SEQUENCE transactions_id_seq;
+
+CREATE TABLE Transactions (
+ id INTEGER DEFAULT nextval('transactions_id_seq'),
+ EffectiveTicket integer NOT NULL DEFAULT 0 ,
+ Ticket integer NOT NULL DEFAULT 0 ,
+ TimeTaken integer NOT NULL DEFAULT 0 ,
+ Type varchar(20) NULL ,
+ Field varchar(40) NULL ,
+ OldValue varchar(255) NULL ,
+ NewValue varchar(255) NULL ,
+ Data varchar(255) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+CREATE INDEX Transactions1 ON Transactions (Ticket);
+CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+
+-- }}}
+
+-- {{{ Scrips
+
+
+
+--
+-- Sequences for table SCRIPS
+--
+
+CREATE SEQUENCE scrips_id_seq;
+
+CREATE TABLE Scrips (
+ id INTEGER DEFAULT nextval('scrips_id_seq'),
+ Description varchar(255),
+ ScripCondition integer NOT NULL DEFAULT 0 ,
+ ScripAction integer NOT NULL DEFAULT 0 ,
+ ConditionRules text NULL ,
+ ActionRules text NULL ,
+ CustomIsApplicableCode text NULL ,
+ CustomPrepareCode text NULL ,
+ CustomCommitCode text NULL ,
+ Stage varchar(32) NULL ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Template integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ ACL
+
+
+--
+-- Sequences for table ACL
+--
+
+CREATE SEQUENCE acl_id_seq;
+
+CREATE TABLE ACL (
+ id INTEGER DEFAULT nextval('acl_id_seq'),
+ PrincipalType varchar(25) NOT NULL,
+
+ PrincipalId integer NOT NULL ,
+ RightName varchar(25) NOT NULL ,
+ ObjectType varchar(25) NOT NULL ,
+ ObjectId integer NOT NULL DEFAULT 0,
+ DelegatedBy integer NOT NULL DEFAULT 0,
+ DelegatedFrom integer NOT NULL DEFAULT 0,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX ACL1 on ACL(RightName, ObjectType, ObjectId,PrincipalType,PrincipalId);
+
+
+-- }}}
+
+-- {{{ GroupMembers
+
+
+
+--
+-- Sequences for table GROUPMEMBERS
+--
+
+CREATE SEQUENCE groupmembers_id_seq;
+
+CREATE TABLE GroupMembers (
+ id INTEGER DEFAULT nextval('groupmembers_id_seq'),
+ GroupId integer NOT NULL DEFAULT 0,
+ MemberId integer NOT NULL DEFAULT 0,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ GroupMembersCache
+
+
+
+--
+-- Sequences for table CACHEDGROUPMEMBERS
+--
+
+CREATE SEQUENCE cachedgroupmembers_id_seq;
+
+CREATE TABLE CachedGroupMembers (
+ id int DEFAULT nextval('cachedgroupmembers_id_seq'),
+ GroupId int,
+ MemberId int,
+ Via int,
+ ImmediateParentId int,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX CachedGroupMembers2 on CachedGroupMembers (MemberId);
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (GroupId);
+CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled);
+
+-- }}}
+
+-- {{{ Users
+
+
+
+--
+-- Sequences for table USERS
+--
+
+CREATE SEQUENCE users_id_seq;
+
+CREATE TABLE Users (
+ id INTEGER DEFAULT nextval('users_id_seq'),
+ Name varchar(200) NOT NULL ,
+ Password varchar(40) NULL ,
+ Comments text NULL ,
+ Signature text NULL ,
+ EmailAddress varchar(120) NULL ,
+ FreeformContactInfo text NULL ,
+ Organization varchar(200) NULL ,
+ RealName varchar(120) NULL ,
+ NickName varchar(16) NULL ,
+ Lang varchar(16) NULL ,
+ EmailEncoding varchar(16) NULL ,
+ WebEncoding varchar(16) NULL ,
+ ExternalContactInfoId varchar(100) NULL ,
+ ContactInfoSystem varchar(30) NULL ,
+ ExternalAuthId varchar(100) NULL ,
+ AuthSystem varchar(30) NULL ,
+ Gecos varchar(16) NULL ,
+ HomePhone varchar(30) NULL ,
+ WorkPhone varchar(30) NULL ,
+ MobilePhone varchar(30) NULL ,
+ PagerPhone varchar(30) NULL ,
+ Address1 varchar(200) NULL ,
+ Address2 varchar(200) NULL ,
+ City varchar(100) NULL ,
+ State varchar(100) NULL ,
+ Zip varchar(16) NULL ,
+ Country varchar(50) NULL ,
+ Timezone varchar(50) NULL ,
+ PGPKey text NULL,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+
+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);
+
+
+-- }}}
+
+-- {{{ Tickets
+
+
+
+--
+-- Sequences for table TICKETS
+--
+
+CREATE SEQUENCE tickets_id_seq;
+
+CREATE TABLE Tickets (
+ id INTEGER DEFAULT nextval('tickets_id_seq'),
+ EffectiveId integer NOT NULL DEFAULT 0 ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Type varchar(16) NULL ,
+ IssueStatement integer NOT NULL DEFAULT 0 ,
+ Resolution integer NOT NULL DEFAULT 0 ,
+ Owner integer NOT NULL DEFAULT 0 ,
+ Subject varchar(200) NULL DEFAULT '[no subject]' ,
+ InitialPriority integer NOT NULL DEFAULT 0 ,
+ FinalPriority integer NOT NULL DEFAULT 0 ,
+ Priority integer NOT NULL DEFAULT 0 ,
+ TimeEstimated integer NOT NULL DEFAULT 0 ,
+ TimeWorked integer NOT NULL DEFAULT 0 ,
+ Status varchar(10) NULL ,
+ TimeLeft integer NOT NULL DEFAULT 0 ,
+ Told TIMESTAMP NULL ,
+ Starts TIMESTAMP NULL ,
+ Started TIMESTAMP NULL ,
+ Due TIMESTAMP NULL ,
+ Resolved TIMESTAMP NULL ,
+
+
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+
+);
+
+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) ;
+
+-- }}}
+
+-- {{{ ScripActions
+
+
+
+--
+-- Sequences for table SCRIPACTIONS
+--
+
+CREATE SEQUENCE scripactions_id_seq;
+
+CREATE TABLE ScripActions (
+ id INTEGER DEFAULT nextval('scripactions_id_seq'),
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ ExecModule varchar(60) NULL ,
+ Argument varchar(255) NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ Templates
+
+
+
+--
+-- Sequences for table TEMPLATES
+--
+
+CREATE SEQUENCE templates_id_seq;
+
+CREATE TABLE Templates (
+ id INTEGER DEFAULT nextval('templates_id_seq'),
+ Queue integer NOT NULL DEFAULT 0 ,
+ Name varchar(200) NOT NULL ,
+ Description varchar(255) NULL ,
+ Type varchar(16) NULL ,
+ Language varchar(16) NULL ,
+ TranslationOf integer NOT NULL DEFAULT 0 ,
+ Content text NULL ,
+ LastUpdated TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ TicketCustomFieldValues
+
+
+
+--
+-- Sequences for table TICKETCUSTOMFIELDVALUES
+--
+
+CREATE SEQUENCE ticketcustomfieldvalues_id_s;
+
+CREATE TABLE TicketCustomFieldValues (
+ id INTEGER DEFAULT nextval('ticketcustomfieldvalues_id_s'),
+ Ticket int NOT NULL ,
+ CustomField int NOT NULL ,
+ Content varchar(255) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content);
+CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket);
+
+-- }}}
+
+-- {{{ CustomFields
+
+
+
+--
+-- Sequences for table CUSTOMFIELDS
+--
+
+CREATE SEQUENCE customfields_id_seq;
+
+CREATE TABLE CustomFields (
+ id INTEGER DEFAULT nextval('customfields_id_seq'),
+ Name varchar(200) NULL ,
+ Type varchar(200) NULL ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Description varchar(255) 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 ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
+-- {{{ CustomFieldValues
+
+
+
+--
+-- Sequences for table CUSTOMFIELDVALUES
+--
+
+CREATE SEQUENCE customfieldvalues_id_seq;
+
+CREATE TABLE CustomFieldValues (
+ id INTEGER DEFAULT nextval('customfieldvalues_id_seq'),
+ CustomField int NOT NULL ,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ SortOrder integer NOT NULL DEFAULT 0 ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created TIMESTAMP NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated TIMESTAMP NULL ,
+ PRIMARY KEY (id)
+
+);
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+
+-- }}}
+
+-- {{{ Sessions
+
+-- sessions is used by Apache::Session to keep sessions in the database.
+-- We should have a reaper script somewhere.
+
+CREATE TABLE sessions (
+ id char(32) NOT NULL,
+ a_session bytea,
+ LastUpdated TIMESTAMP not null default current_timestamp,
+ PRIMARY KEY (id)
+
+);
+
+-- }}}
+
diff --git a/rt/etc/schema.SQLite b/rt/etc/schema.SQLite
new file mode 100644
index 0000000..b10ff46
--- /dev/null
+++ b/rt/etc/schema.SQLite
@@ -0,0 +1,390 @@
+--- {{{ Attachments
+
+CREATE TABLE Attachments (
+ id INTEGER PRIMARY KEY ,
+ TransactionId INTEGER ,
+ Parent integer NULL ,
+ MessageId varchar(160) NULL ,
+ Subject varchar(255) NULL ,
+ Filename varchar(255) NULL ,
+ ContentType varchar(80) NULL ,
+ ContentEncoding varchar(80) NULL ,
+ Content LONGTEXT NULL ,
+ Headers LONGTEXT NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL
+
+) ;
+
+CREATE INDEX Attachments1 ON Attachments (Parent) ;
+CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
+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 ,
+ InitialPriority integer NULL ,
+ FinalPriority integer NULL ,
+ DefaultDueIn integer NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0
+
+) ;
+CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
+
+--- }}}
+
+--- {{{ Links
+
+CREATE TABLE Links (
+ id INTEGER PRIMARY KEY ,
+ Base varchar(240) NULL ,
+ Target varchar(240) NULL ,
+ Type varchar(20) NOT NULL ,
+ LocalTarget integer NULL ,
+ LocalBase integer NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL
+
+) ;
+CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ;
+CREATE INDEX Links4 ON Links(Type,LocalBase);
+
+--- }}}
+
+--- {{{ Principals
+
+CREATE TABLE Principals (
+ id INTEGER PRIMARY KEY,
+ PrincipalType VARCHAR(16) not null,
+ ObjectId integer,
+ Disabled int2 NOT NULL DEFAULT 0
+
+) ;
+
+--- }}}
+
+--- {{{ Groups
+
+CREATE TABLE Groups (
+ id INTEGER ,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ Domain varchar(64),
+ Type varchar(64),
+ Instance integer
+
+) ;
+
+CREATE UNIQUE INDEX Groups1 ON Groups (Name,Domain,Type,Instance) ;
+
+--- }}}
+
+--- {{{ ScripConditions
+
+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 ,
+
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+--- }}}
+
+--- {{{ Transactions
+CREATE TABLE Transactions (
+ id INTEGER PRIMARY KEY ,
+ EffectiveTicket integer NULL ,
+ Ticket integer NULL ,
+ TimeTaken integer NULL ,
+ Type varchar(20) NULL ,
+ Field varchar(40) NULL ,
+ OldValue varchar(255) NULL ,
+ NewValue varchar(255) NULL ,
+ Data varchar(255) NULL ,
+
+ Creator integer NULL ,
+ Created DATETIME NULL
+
+) ;
+CREATE INDEX Transactions1 ON Transactions (Ticket);
+CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+
+--- }}}
+
+--- {{{ Scrips
+
+CREATE TABLE Scrips (
+ id INTEGER PRIMARY KEY ,
+ Description varchar(255),
+ ScripCondition integer NULL ,
+ ScripAction integer NULL ,
+ ConditionRules text NULL ,
+ ActionRules text NULL ,
+ CustomIsApplicableCode text NULL ,
+ CustomPrepareCode text NULL ,
+ CustomCommitCode text NULL ,
+ Stage varchar(32) NULL ,
+ Queue integer NULL ,
+ Template integer NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+--- }}}
+
+--- {{{ ACL
+CREATE TABLE ACL (
+ id INTEGER PRIMARY KEY ,
+ PrincipalType varchar(25) NOT NULL,
+
+ PrincipalId INTEGER,
+ RightName varchar(25) NOT NULL ,
+ ObjectType varchar(25) NOT NULL ,
+ ObjectId INTEGER default 0,
+ DelegatedBy integer NOT NULL default 0,
+ DelegatedFrom integer NOT NULL default 0
+
+) ;
+
+
+--- }}}
+
+--- {{{ GroupMembers
+
+CREATE TABLE GroupMembers (
+ id INTEGER PRIMARY KEY ,
+ GroupId integer NULL,
+ MemberId integer NULL
+
+) ;
+
+--- }}}
+
+--- {{{ CachedGroupMembers
+
+create table CachedGroupMembers (
+ id integer primary key ,
+ GroupId int,
+ MemberId int,
+ Via int,
+ ImmediateParentId int,
+ Disabled int2 NOT NULL DEFAULT 0 # if this cached group member is a member of this group by way of a disabled
+ # group or this group is disabled, this will be set to 1
+ # this allows us to not find members of disabled subgroups when listing off
+ # group members recursively.
+ # Also, this allows us to have the ACL system elide members of disabled groups
+
+
+) ;
+
+--- }}}
+
+--- {{{ Users
+
+CREATE TABLE Users (
+ id INTEGER ,
+ Name varchar(200) NOT NULL ,
+ Password varchar(40) NULL ,
+ Comments blob NULL ,
+ Signature blob NULL ,
+ EmailAddress varchar(120) NULL ,
+ FreeformContactInfo blob NULL ,
+ Organization varchar(200) NULL ,
+ RealName varchar(120) NULL ,
+ NickName varchar(16) NULL ,
+ Lang varchar(16) NULL ,
+ EmailEncoding varchar(16) NULL ,
+ WebEncoding varchar(16) NULL ,
+ ExternalContactInfoId varchar(100) NULL ,
+ ContactInfoSystem varchar(30) NULL ,
+ ExternalAuthId varchar(100) NULL ,
+ AuthSystem varchar(30) NULL ,
+ Gecos varchar(16) NULL ,
+ HomePhone varchar(30) NULL ,
+ WorkPhone varchar(30) NULL ,
+ MobilePhone varchar(30) NULL ,
+ PagerPhone varchar(30) NULL ,
+ Address1 varchar(200) NULL ,
+ Address2 varchar(200) NULL ,
+ City varchar(100) NULL ,
+ State varchar(100) NULL ,
+ Zip varchar(16) NULL ,
+ Country varchar(50) NULL ,
+ Timezone char(50) NULL ,
+ PGPKey text NULL,
+
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+
+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);
+
+
+--- }}}
+
+--- {{{ Tickets
+
+CREATE TABLE Tickets (
+ id INTEGER PRIMARY KEY ,
+ EffectiveId integer NULL ,
+ Queue integer NULL ,
+ Type varchar(16) NULL ,
+ IssueStatement integer NULL ,
+ Resolution integer NULL ,
+ Owner integer NULL ,
+ Subject varchar(200) NULL DEFAULT '[no subject]' ,
+ InitialPriority integer NULL ,
+ FinalPriority integer NULL ,
+ Priority integer NULL ,
+ TimeEstimated integer NULL ,
+ TimeWorked integer NULL ,
+ Status varchar(10) NULL ,
+ TimeLeft integer NULL ,
+ Told DATETIME NULL ,
+ Starts DATETIME NULL ,
+ Started DATETIME NULL ,
+ Due DATETIME NULL ,
+ Resolved DATETIME NULL ,
+
+
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0
+
+) ;
+
+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) ;
+
+--- }}}
+
+--- {{{ ScripActions
+
+CREATE TABLE ScripActions (
+ id INTEGER PRIMARY KEY ,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ ExecModule varchar(60) NULL ,
+ Argument varchar(255) NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+--- }}}
+
+--- {{{ Templates
+
+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 ,
+ Content blob NULL ,
+ LastUpdated DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ Creator integer NULL ,
+ Created DATETIME NULL
+
+) ;
+
+--- }}}
+
+--- {{{ TicketCustomFieldValues
+
+CREATE TABLE TicketCustomFieldValues (
+ id INTEGER PRIMARY KEY ,
+ Ticket int NOT NULL ,
+ CustomField int NOT NULL ,
+ Content varchar(255) NULL ,
+
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content);
+CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket);
+
+--- }}}
+
+--- {{{ CustomFields
+
+CREATE TABLE CustomFields (
+ id INTEGER PRIMARY KEY ,
+ Name varchar(200) NULL ,
+ Type varchar(200) NULL ,
+ Queue int NULL ,
+ Description varchar(255) NULL ,
+ SortOrder integer NULL ,
+
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0
+
+) ;
+
+--- }}}
+
+--- {{{ CustomFieldValues
+
+CREATE TABLE CustomFieldValues (
+ id INTEGER PRIMARY KEY ,
+ CustomField int NOT NULL ,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ SortOrder integer NULL ,
+
+ Creator integer NULL ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NULL ,
+ LastUpdated DATETIME NULL
+
+) ;
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+
+--- }}}
diff --git a/rt/etc/schema.mysql b/rt/etc/schema.mysql
new file mode 100755
index 0000000..14e9223
--- /dev/null
+++ b/rt/etc/schema.mysql
@@ -0,0 +1,422 @@
+# {{{ Attachments
+
+CREATE TABLE Attachments (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ TransactionId integer NOT NULL ,
+ Parent integer NOT NULL DEFAULT 0 ,
+ MessageId varchar(160) NULL ,
+ Subject varchar(255) NULL ,
+ Filename varchar(255) NULL ,
+ ContentType varchar(80) NULL ,
+ ContentEncoding varchar(80) NULL ,
+ Content LONGTEXT NULL ,
+ Headers LONGTEXT NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Attachments1 ON Attachments (Parent) ;
+CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
+CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
+# }}}
+
+# {{{ Queues
+CREATE TABLE Queues (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NOT NULL ,
+ Description varchar(255) NULL ,
+ CorrespondAddress varchar(120) NULL ,
+ CommentAddress varchar(120) NULL ,
+ InitialPriority integer NOT NULL DEFAULT 0 ,
+ FinalPriority integer NOT NULL DEFAULT 0 ,
+ DefaultDueIn integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
+CREATE INDEX Queues2 ON Queues (Disabled) ;
+
+# }}}
+
+# {{{ Links
+
+CREATE TABLE Links (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Base varchar(240) NULL ,
+ Target varchar(240) NULL ,
+ Type varchar(20) NOT NULL ,
+ LocalTarget integer NOT NULL DEFAULT 0 ,
+ LocalBase integer NOT NULL DEFAULT 0 ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ;
+CREATE INDEX Links2 ON Links (Base, Type) ;
+CREATE INDEX Links3 ON Links (Target, Type) ;
+CREATE INDEX Links4 ON Links(Type,LocalBase);
+
+# }}}
+
+# {{{ Principals
+
+CREATE TABLE Principals (
+ id INTEGER AUTO_INCREMENT not null,
+ PrincipalType VARCHAR(16) not null,
+ ObjectId integer, # foreign key to Users or Groups, depending
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Principals2 ON Principals (ObjectId);
+
+# }}}
+
+# {{{ Groups
+
+CREATE TABLE Groups (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ Domain varchar(64),
+ Type varchar(64),
+ Instance integer,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id);
+CREATE INDEX Groups2 On Groups (Type, Instance, Domain);
+
+# }}}
+
+# {{{ ScripConditions
+
+CREATE TABLE ScripConditions (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ ExecModule varchar(60) NULL ,
+ Argument varchar(255) NULL ,
+ ApplicableTransTypes varchar(60) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ Transactions
+CREATE TABLE Transactions (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ EffectiveTicket integer NOT NULL DEFAULT 0 ,
+ Ticket integer NOT NULL DEFAULT 0 ,
+ TimeTaken integer NOT NULL DEFAULT 0 ,
+ Type varchar(20) NULL ,
+ Field varchar(40) NULL ,
+ OldValue varchar(255) NULL ,
+ NewValue varchar(255) NULL ,
+ Data varchar(255) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE INDEX Transactions1 ON Transactions (Ticket);
+CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
+
+# }}}
+
+# {{{ Scrips
+
+CREATE TABLE Scrips (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Description varchar(255),
+ ScripCondition integer NOT NULL DEFAULT 0 ,
+ ScripAction integer NOT NULL DEFAULT 0 ,
+ ConditionRules text NULL ,
+ ActionRules text NULL ,
+ CustomIsApplicableCode text NULL ,
+ CustomPrepareCode text NULL ,
+ CustomCommitCode text NULL ,
+ Stage varchar(32) NULL ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Template integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ ACL
+CREATE TABLE ACL (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ PrincipalType varchar(25) NOT NULL, #"User" "Group", "Owner", "Cc" "AdminCc", "Requestor", "Requestor"
+
+ PrincipalId integer NOT NULL , #Foreign key to principals
+ RightName varchar(25) NOT NULL ,
+ ObjectType varchar(25) NOT NULL ,
+ ObjectId integer NOT NULL default 0,
+ DelegatedBy integer NOT NULL default 0, #foreign key to principals with a userid
+ DelegatedFrom integer NOT NULL default 0, #foreign key to ACL
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX ACL1 on ACL(RightName, ObjectType, ObjectId,PrincipalType,PrincipalId);
+
+# }}}
+
+# {{{ GroupMembers
+
+CREATE TABLE GroupMembers (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ GroupId integer NOT NULL DEFAULT 0,
+ MemberId integer NOT NULL DEFAULT 0, #Foreign key to Principals
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE UNIQUE INDEX GroupMembers1 on GroupMembers (GroupId, MemberId);
+
+
+# }}}
+
+# {{{ GroupMembersCache
+
+create table CachedGroupMembers (
+ id int auto_increment,
+ GroupId int, # foreign key to Principals
+ MemberId int, # foreign key to Principals
+ Via int, #foreign key to CachedGroupMembers. (may point to $self->id)
+ ImmediateParentId int, #foreign key to prinicpals.
+ # this points to the group that the member is
+ # a member of, for ease of deletes.
+ Disabled int2 NOT NULL DEFAULT 0 , # if this cached group member is a member of this group by way of a disabled
+ # group or this group is disabled, this will be set to 1
+ # this allows us to not find members of disabled subgroups when listing off
+ # group members recursively.
+ # Also, this allows us to have the ACL system elide members of disabled groups
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled);
+CREATE INDEX GrouMem on CachedGroupMembers (GroupId,MemberId);
+
+# }}}
+
+# {{{ Users
+
+CREATE TABLE Users (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NOT NULL ,
+ Password varchar(40) NULL ,
+ Comments blob NULL ,
+ Signature blob NULL ,
+ EmailAddress varchar(120) NULL ,
+ FreeformContactInfo blob NULL ,
+ Organization varchar(200) NULL ,
+ RealName varchar(120) NULL ,
+ NickName varchar(16) NULL ,
+ Lang varchar(16) NULL ,
+ EmailEncoding varchar(16) NULL ,
+ WebEncoding varchar(16) NULL ,
+ ExternalContactInfoId varchar(100) NULL ,
+ ContactInfoSystem varchar(30) NULL ,
+ ExternalAuthId varchar(100) NULL ,
+ AuthSystem varchar(30) NULL ,
+ Gecos varchar(16) NULL ,
+ HomePhone varchar(30) NULL ,
+ WorkPhone varchar(30) NULL ,
+ MobilePhone varchar(30) NULL ,
+ PagerPhone varchar(30) NULL ,
+ Address1 varchar(200) NULL ,
+ Address2 varchar(200) NULL ,
+ City varchar(100) NULL ,
+ State varchar(100) NULL ,
+ Zip varchar(16) NULL ,
+ Country varchar(50) NULL ,
+ Timezone varchar(50) NULL ,
+ PGPKey text NULL,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+
+CREATE UNIQUE INDEX Users1 ON Users (Name) ;
+CREATE INDEX Users2 ON Users (Name);
+CREATE INDEX Users3 ON Users (id, EmailAddress);
+CREATE INDEX Users4 ON Users (EmailAddress);
+
+
+# }}}
+
+# {{{ Tickets
+
+CREATE TABLE Tickets (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ EffectiveId integer NOT NULL DEFAULT 0 ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Type varchar(16) NULL ,
+ IssueStatement integer NOT NULL DEFAULT 0 ,
+ Resolution integer NOT NULL DEFAULT 0 ,
+ Owner integer NOT NULL DEFAULT 0 ,
+ Subject varchar(200) NULL DEFAULT '[no subject]' ,
+ InitialPriority integer NOT NULL DEFAULT 0 ,
+ FinalPriority integer NOT NULL DEFAULT 0 ,
+ Priority integer NOT NULL DEFAULT 0 ,
+ TimeEstimated integer NOT NULL DEFAULT 0 ,
+ TimeWorked integer NOT NULL DEFAULT 0 ,
+ Status varchar(10) NULL ,
+ TimeLeft integer NOT NULL DEFAULT 0 ,
+ Told DATETIME NULL ,
+ Starts DATETIME NULL ,
+ Started DATETIME NULL ,
+ Due DATETIME NULL ,
+ Resolved DATETIME NULL ,
+
+
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Tickets1 ON Tickets (Queue, Status) ;
+CREATE INDEX Tickets2 ON Tickets (Owner) ;
+CREATE INDEX Tickets3 ON Tickets (EffectiveId) ;
+CREATE INDEX Tickets4 ON Tickets (id, Status) ;
+CREATE INDEX Tickets5 ON Tickets (id, EffectiveId) ;
+CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type) ;
+
+# }}}
+
+# {{{ ScripActions
+
+CREATE TABLE ScripActions (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ ExecModule varchar(60) NULL ,
+ Argument varchar(255) NULL ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ Templates
+
+CREATE TABLE Templates (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Name varchar(200) NOT NULL ,
+ Description varchar(255) NULL ,
+ Type varchar(16) NULL ,
+ Language varchar(16) NULL ,
+ TranslationOf integer NOT NULL DEFAULT 0 ,
+ Content blob NULL ,
+ LastUpdated DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ TicketCustomFieldValues
+
+CREATE TABLE TicketCustomFieldValues (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Ticket int NOT NULL ,
+ CustomField int NOT NULL ,
+ Content varchar(255) NULL ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX TicketCustomFieldValues1 ON TicketCustomFieldValues (CustomField,Ticket,Content);
+CREATE INDEX TicketCustomFieldValues2 ON TicketCustomFieldValues (CustomField,Ticket);
+
+# }}}
+
+# {{{ CustomFields
+
+CREATE TABLE CustomFields (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ Name varchar(200) NULL ,
+ Type varchar(200) NULL ,
+ Queue integer NOT NULL DEFAULT 0 ,
+ Description varchar(255) NULL ,
+ SortOrder integer NOT NULL DEFAULT 0 ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ Disabled int2 NOT NULL DEFAULT 0 ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX CustomFields1 on CustomFields (Disabled, Queue);
+
+
+# }}}
+
+# {{{ CustomFieldValues
+
+CREATE TABLE CustomFieldValues (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ CustomField int NOT NULL ,
+ Name varchar(200) NULL ,
+ Description varchar(255) NULL ,
+ SortOrder integer NOT NULL DEFAULT 0 ,
+
+ Creator integer NOT NULL DEFAULT 0 ,
+ Created DATETIME NULL ,
+ LastUpdatedBy integer NOT NULL DEFAULT 0 ,
+ LastUpdated DATETIME NULL ,
+ PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+
+# }}}
+
+# {{{ Sessions
+
+# sessions is used by Apache::Session to keep sessions in the database.
+# We should have a reaper script somewhere.
+
+CREATE TABLE sessions (
+ id char(32) NOT NULL,
+ a_session LONGTEXT,
+ LastUpdated TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+# }}}
diff --git a/rt/etc/upgrade/2.1.71 b/rt/etc/upgrade/2.1.71
new file mode 100644
index 0000000..cb89a3a
--- /dev/null
+++ b/rt/etc/upgrade/2.1.71
@@ -0,0 +1,211 @@
+@Queues = ( {
+ Name => '___Approvals',
+ Description => 'A system-internal queue for the approvals system',
+ Disabled => 2,
+ }
+);
+
+
+
+
+
+# {{{ Templates
+@Templates = (
+ {
+ Queue => '___Approvals',
+ Name => "New Pending Approval", # loc
+ Description => "Notify Owners and AdminCcs of new items pending their approval", # loc
+ Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}",
+a summary of which appears below.
+
+Please visit {$RT::WebURL}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {$RT::WebURL}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+ },
+);
+
+# }}}
+
+1;
+
+@ScripActions = (
+ { Name => 'Open Tickets',
+ Description => 'Open tickets on correspondence',
+ ExecModule => 'AutoOpen' },
+
+);
+
+ @Scrips = (
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Open Tickets',
+ Template => 'Blank',
+ Queue => '0'
+ },
+ { ScripCondition => 'On Create',
+ ScripAction => 'AutoReply To Requestors',
+ Template => 'AutoReply' },
+ { ScripCondition => 'On Create',
+ ScripAction => 'Notify AdminCcs',
+ Template => 'Transaction' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify AdminCcs',
+ Template => 'Admin Correspondence' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify Requestors And Ccs',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Correspond',
+ ScripAction => 'Notify Other Recipients',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Comment',
+ ScripAction => 'Notify AdminCcs As Comment',
+ Template => 'Admin Comment' },
+ { ScripCondition => 'On Comment',
+ ScripAction => 'Notify Other Recipients As Comment',
+ Template => 'Correspondence' },
+ { ScripCondition => 'On Resolve',
+ ScripAction => 'Notify Requestors',
+ Template => 'Resolved' },
+
+
+ {
+ Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Create',
+ ScripAction => 'Notify AdminCcs',
+ Template => 'New Pending Approval'
+ },
+ {
+ Description => "If an approval is rejected, reject the original and delete pending approvals", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Status Change',
+ ScripAction => 'User Defined',
+ CustomCommitCode => q[
+# ------------------------------------------------------------------- #
+return(1) unless ( lc($self->TransactionObj->NewValue) eq "rejected" or
+ lc($self->TransactionObj->NewValue) eq "deleted" );
+
+my $links = $self->TicketObj->DependedOnBy;
+foreach my $link (@{ $links->ItemsArrayRef }) {
+ my $obj = $link->BaseObj;
+ if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
+ if ($obj->Type eq 'ticket') {
+ $obj->Correspond(
+ Content => $self->loc("Your request was rejected."),
+ );
+ $obj->SetStatus(
+ Status => 'rejected',
+ Force => 1,
+ );
+ }
+ else {
+ $obj->SetStatus(
+ Status => 'deleted',
+ Force => 1,
+ );
+ }
+ }
+}
+
+$links = $self->TicketObj->DependsOn;
+foreach my $link (@{ $links->ItemsArrayRef }) {
+ my $obj = $link->TargetObj;
+ if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
+ $obj->SetStatus(
+ Status => 'deleted',
+ Force => 1,
+ );
+ }
+}
+
+return 1;
+# ------------------------------------------------------------------- #
+ ],
+ CustomPrepareCode => '1',
+ Template => 'Admin Comment',
+ },
+ {
+ Description => "When a ticket has been approved by any approver, add correspondence to the original ticket", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Resolve',
+ ScripAction => 'User Defined',
+ CustomPrepareCode => 'return(1);',
+ CustomCommitCode => q[
+# ------------------------------------------------------------------- #
+return(1) unless ($self->TicketObj->Type eq 'approval');
+
+foreach my $obj ($self->TicketObj->AllDependedOnBy( Type => 'ticket' )) {
+ $obj->Correspond(
+ Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
+ $self->TransactionObj->CreatorObj->Name,
+ ) . "\n" . $self->loc( "Approver's notes: [_1]", # loc
+ $self->TicketObj->Transactions->Last->Content,
+ ),
+ _reopen => 0,
+ );
+}
+
+return 1;
+# ------------------------------------------------------------------- #
+ ],
+ Template => 'Admin Comment'
+ },
+ {
+ Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket", # loc
+ Queue => '___Approvals',
+ ScripCondition => 'On Resolve',
+ ScripAction => 'User Defined',
+ CustomPrepareCode => 'return(1);',
+ CustomCommitCode => q[
+# ------------------------------------------------------------------- #
+# Find all the tickets that depend on this (that this is approving)
+
+my $Ticket = $self->TicketObj;
+my @TOP = $Ticket->AllDependedOnBy( Type => 'ticket' );
+my $links = $Ticket->DependedOnBy;
+
+while (my $link = $links->Next) {
+ my $obj = $link->BaseObj;
+ next if ($obj->HasUnresolvedDependencies( Type => 'approval' ));
+
+ if ($obj->Type eq 'ticket') {
+ $obj->Correspond(
+ Content => $self->loc("Your request has been approved."),
+ _reopen => 0,
+ );
+ }
+ elsif ($obj->Type eq 'code') {
+ my $code = $obj->Transactions->First->Content;
+ my $rv;
+
+ foreach my $TOP (@TOP) {
+ local $@;
+ $rv++ if eval $code;
+ $RT::Logger->error("Cannot eval code: $@") if $@;
+ }
+
+ if ($rv or !@TOP) {
+ $obj->SetStatus( Status => 'resolved', Force => 1,);
+ }
+ else {
+ $obj->SetStatus( Status => 'rejected', Force => 1,);
+ }
+ }
+}
+
+return 1;
+# ------------------------------------------------------------------- #
+ ],
+ Template => 'Admin Comment',
+ },
+);
+
+# }}}
+
diff --git a/rt/html/Admin/Elements/AddCustomFieldValue b/rt/html/Admin/Elements/AddCustomFieldValue
new file mode 100644
index 0000000..8850734
--- /dev/null
+++ b/rt/html/Admin/Elements/AddCustomFieldValue
@@ -0,0 +1,44 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<b><&|/l&>Add Value</&></b></b></b></b>
+<TABLE BORDER="0">
+<TR><TD><small>
+<&|/l&>Sort</&>:<br>
+<input name="CustomField-<% $CustomField->Id %>-AddValue-SortOrder" size="5">
+</TD>
+<TD><small>
+<&|/l&>Name</&>:<br>
+<input size=20 name="CustomField-<% $CustomField->Id %>-AddValue-Name">
+</TD>
+<TD><small>
+<&|/l&>Description</&>:<br>
+<input size="60" name="CustomField-<% $CustomField->Id %>-AddValue-Description">
+</TD></TR>
+</TABLE>
+
+<%init>
+</%init>
+<%args>
+$CustomField => undef
+</%args>
diff --git a/rt/html/Admin/Elements/CreateUserCalled b/rt/html/Admin/Elements/CreateUserCalled
new file mode 100644
index 0000000..8ceccca
--- /dev/null
+++ b/rt/html/Admin/Elements/CreateUserCalled
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<FORM METHOD=get ACTION="<% $RT::WebPath %>/Admin/Users/Create.html">
+<&|/l&>New user called</&> <INPUT NAME="Name" size=10><input type=submit value="<&|/l&>Create</&>">
+</form>
diff --git a/rt/html/Admin/Elements/EditCustomField b/rt/html/Admin/Elements/EditCustomField
new file mode 100644
index 0000000..7baed16
--- /dev/null
+++ b/rt/html/Admin/Elements/EditCustomField
@@ -0,0 +1,127 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/ListActions, actions => \@results &>
+
+
+<FORM METHOD=POST ACTION="CustomField.html">
+<INPUT TYPE=HIDDEN NAME="CustomField" VALUE="<%$id %>">
+<INPUT TYPE=HIDDEN name="Queue" value="<%$Queue%>">
+
+<TABLE WIDTH="100%" BORDER="0">
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Name</&>:
+</TD><TD>
+<input name="Name" VALUE="<%$CustomFieldObj->Name%>" SIZE=20>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Description</&>:
+</TD><TD>
+<input name="Description" VALUE="<%$CustomFieldObj->Description%>" SIZE=80>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Type</&>:
+</TD><TD>
+<& /Admin/Elements/SelectCustomFieldType, Name => "Type", Default => $CustomFieldObj->Type &>
+</TD></TR>
+<TR><TD>
+</TD><TD>
+<INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this custom field)</&>
+</TD></TR>
+</TABLE>
+
+<P>
+% if ($CustomFieldObj->Id and $CustomFieldObj->Type =~ /Select/) {
+<h2><&|/l&>Values</&></h2>
+<font size=-1>
+<& /Admin/Elements/EditCustomFieldValues, CustomField => $CustomFieldObj &>
+<& /Admin/Elements/AddCustomFieldValue, CustomField => $CustomFieldObj &>
+</font>
+% }
+<&/Elements/Submit&>
+</FORM>
+
+
+
+<%INIT>
+
+my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+my $EnabledChecked = "CHECKED";
+my (@results);
+
+if (! $CustomField ) {
+ $title = loc("Create a CustomField");
+ $id = 'new';
+} else {
+
+ if ($CustomField eq 'new') {
+ my ($val, $msg) = $CustomFieldObj->Create(Queue => $Queue,
+ Name => $Name,
+ Type => $Type,
+ Description => $Description,
+ );
+ Abort(loc("Could not create CustomField", $msg)) unless ($val);
+ push @results, $msg;
+ $CustomFieldObj->SetSortOrder($CustomFieldObj->id);
+ $title = loc('Created CustomField [_1]', $CustomFieldObj->Name());
+ } else {
+ $CustomFieldObj->Load($CustomField) || Abort(loc('No CustomField'));
+ $title = loc('Editing CustomField [_1]', $CustomFieldObj->Name());
+
+ my @aresults = ProcessCustomFieldUpdates (
+ CustomFieldObj => $CustomFieldObj,
+ ARGSRef => \%ARGS );
+ push @results, @aresults;
+ }
+
+
+$id = $CustomFieldObj->id;
+
+ #we're asking about enabled on the web page but really care about disabled.
+ my $Disabled = ($Enabled ? 0 : 1);
+
+ if ( ($SetEnabled) and ( $Disabled != $CustomFieldObj->Disabled) ) {
+ my ($code, $msg) = $CustomFieldObj->SetDisabled($Disabled);
+ push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+ }
+
+ if ($CustomFieldObj->Disabled()) {
+ $EnabledChecked ="";
+ }
+
+}
+
+
+</%INIT>
+<%ARGS>
+$id => undef
+$title => undef
+$Queue => undef
+$CustomField => undef
+$Type => undef
+$Description => undef
+$Name => undef
+$SetEnabled => undef
+$Enabled => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/EditCustomFieldValues b/rt/html/Admin/Elements/EditCustomFieldValues
new file mode 100644
index 0000000..2c9e6d0
--- /dev/null
+++ b/rt/html/Admin/Elements/EditCustomFieldValues
@@ -0,0 +1,42 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<i><&|/l&>(Check box to delete)</&></i>
+<ul>
+% while (my $v = $values->Next) {
+<li>
+<INPUT TYPE="text" SIZE="2" NAME="CustomField-<%$CustomField->Id%>-SortOrder<%$v->Id()%>" VALUE="<%$v->SortOrder()%>">
+<input type="checkbox" name="CustomField-<%$CustomField->Id%>-DeleteValue" value="<%$v->id%>">
+<%$v->Name%>
+% if ($v->Description) {
+<i>(<%$v->Description%>)</i>
+% }
+</li>
+% }
+</ul>
+<%init>
+my $values = $CustomField->Values();
+</%init>
+<%args>
+$CustomField => undef
+</%args>
diff --git a/rt/html/Admin/Elements/EditCustomFields b/rt/html/Admin/Elements/EditCustomFields
new file mode 100644
index 0000000..81c984d
--- /dev/null
+++ b/rt/html/Admin/Elements/EditCustomFields
@@ -0,0 +1,213 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/ListActions, actions => \@actions &>
+
+<TABLE>
+<TR>
+<TD VALIGN=TOP>
+<h2><%$caption%></h2>
+</TD></TR></TABLE>
+% if ($CustomFields->Count == 0 ) {
+<P><i><&|/l&>(No custom fields)</&></i></P>
+% } else {
+<TABLE cellspacing=0 cellpadding=2>
+% my $count;
+% while (my $CustomFieldObj = $CustomFields->Next) {
+<TR>
+ <TD valign="TOP">
+% if ($CustomFieldObj->Name) {
+ <A HREF="CustomField.html?Queue=<%$id%>&CustomField=<%$CustomFieldObj->id()%>"><b><%$CustomFieldObj->Name%></b></a><br>
+% } else {
+ <A HREF="CustomField.html?Queue=<%$id%>&CustomField=<%$CustomFieldObj->id()%>"><i>(<%loc("no name")%>)</i></a><br>
+% }
+ <%$CustomFieldObj->Description%>
+ </TD>
+ <TD valign="TOP">
+ <i><% $CustomFieldObj->FriendlyType %></i>
+ </TD>
+% # show 'move up' unless it's the first item
+% if ($count++) {
+ <TD valign="TOP">
+ <a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=-1"><&|/l&>Move up</&></a>
+% } else {
+ <TD valign="TOP" ALIGN=RIGHT>
+% }
+
+% # show 'move down' unless it's the last item
+% if (!$CustomFields->IsLast) {
+% $m->print(' | ') if $count > 1;
+ <a href="CustomFields.html?id=<%$id%>&CustomField=<%$CustomFieldObj->id%>&Move=1"><&|/l&>Move down</&></a>
+% }
+</TD>
+</TR>
+% }
+
+</TABLE>
+% }
+<FORM METHOD=GET ACTION="CustomFields.html">
+% if ($id) {
+<INPUT TYPE="Hidden" NAME="id" VALUE="<%$id%>">
+% }
+<input type="checkbox" name="FindDisabledCustomFields"> <&|/l&>Include disabled custom fields in listing.</&>
+<input type=submit value="<&|/l&>Go!</&>">
+</FORM>
+
+
+<%INIT>
+my $CustomFields = RT::CustomFields->new($session{'CurrentUser'});
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+my $caption;
+
+if ($id) {
+ $QueueObj->Load($id);
+}
+
+if ($QueueObj->id) {
+ $CustomFields->LimitToQueue($id);
+}
+else {
+ $CustomFields->LimitToGlobal();
+}
+
+if ($FindDisabledCustomFields) {
+ $caption = loc("All Custom Fields");
+ $CustomFields->{'find_disabled_rows'} = 1;
+} else {
+ $caption = loc("Enabled Custom Fields");
+}
+
+# {{{ deal with moving sortorder of custom fields
+if ($CustomField and $Move) {
+ my $SourceObj = RT::CustomField->new($session{'CurrentUser'});
+ $SourceObj->Load($CustomField) || Abort(loc('No CustomField'));
+
+ my $TargetObj;
+ my $target_order = $SourceObj->SortOrder + $Move;
+ while (my $CustomFieldObj = $CustomFields->Next) {
+ my $this_order = $CustomFieldObj->SortOrder;
+
+ # if we have an exact match, finish the loop now
+ ($TargetObj = $CustomFieldObj, last) if $this_order == $target_order;
+
+ # otherwise, we need to apropos toward the general direction
+ # ... first, check the sign is correct
+ next unless ($this_order - $SourceObj->SortOrder) * $Move > 0;
+
+ # ... next, see if we already have a candidate
+ if ($TargetObj) {
+ # ... if yes, compare the delta and choose the smaller one
+ my $orig_delta = abs($TargetObj->SortOrder - $target_order);
+ my $this_delta = abs($this_order - $target_order);
+ next if $orig_delta < $this_delta;
+ }
+
+ $TargetObj = $CustomFieldObj;
+ }
+
+ if ($TargetObj) {
+ # swap their sort order
+ my ($s, $t) = ($SourceObj->SortOrder, $TargetObj->SortOrder);
+ $TargetObj->SetSortOrder($s);
+ $SourceObj->SetSortOrder($t);
+ # because order changed, we must redo search for subsequent uses
+ $CustomFields->RedoSearch;
+ }
+
+ $CustomFields->GotoFirstItem;
+}
+# }}}
+
+# {{{ now process the 'copy queue' action
+my @actions;
+if ($Source and $Source ne $id) {
+ my $SourceQueue = RT::Queue->new($session{'CurrentUser'});
+ $SourceQueue->Load($Source) || Abort(loc("Couldn't load queue"));
+ my $SourceCustomFields = RT::CustomFields->new($session{'CurrentUser'});
+ $SourceCustomFields->LimitToQueue($SourceQueue->id);
+
+ # delete old fields
+ foreach my $CustomFieldObj ( @{$CustomFields->ItemsArrayRef} ) {
+ $CustomFieldObj->Delete;
+ }
+
+ # add new fields
+ while (my $SourceCustomFieldObj = $SourceCustomFields->Next) {
+ my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+ my ($val, $msg) = $CustomFieldObj->Create(
+ id => $SourceCustomFieldObj->id,
+ Queue => $id,
+ Name => $SourceCustomFieldObj->Name,
+ Type => $SourceCustomFieldObj->Type,
+ Description => $SourceCustomFieldObj->Description,
+ );
+ Abort(loc("Could not create CustomField") . ": $msg") unless ($val);
+ push @actions, $msg;
+
+ $CustomFieldObj->SetSortOrder($SourceCustomFieldObj->SortOrder);
+
+ # add new values
+ my $values = $SourceCustomFieldObj->Values();
+ while (my $v = $values->Next) {
+ my ( $addval, $addmsg ) = $CustomFieldObj->AddValue(
+ Name => $v->Name,
+ Description => $v->Description,
+ SortOrder => $v->SortOrder
+ );
+ }
+ }
+
+ # because content changed, we must redo search for subsequent uses
+ $CustomFields->RedoSearch;
+ $CustomFields->GotoFirstItem;
+}
+# }}}
+
+# {{{ deal with deleting existing custom fields
+foreach my $key (keys %ARGS) {
+ # {{{ if we're trying to delete the custom field
+ if ($key =~ /^DeleteCustomField-(\d+)/) {
+ my $id = $1;
+ my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+ $CustomFieldObj->Load($id);
+ my ($retval, $msg) = $CustomFieldObj->Delete;
+ if ($retval) {
+ push @actions, loc("Custom field deleted");
+ }
+ else {
+ push @actions, $msg;
+ }
+ }
+ # }}}
+}
+# }}}
+
+</%INIT>
+<%ARGS>
+$id => 0
+$title => undef
+$Move => undef
+$Source => undef
+$CustomField => undef
+$FindDisabledCustomFields => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/EditQueueWatchers b/rt/html/Admin/Elements/EditQueueWatchers
new file mode 100644
index 0000000..db39bfb
--- /dev/null
+++ b/rt/html/Admin/Elements/EditQueueWatchers
@@ -0,0 +1,55 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%if ($Members->Count == 0 ) {
+<ul>
+<li><i><&|/l&>none</&></i>
+% } else {
+<i><&|/l&>(Check box to delete)</&></i><br><BR>
+<ul>
+% while (my $watcher=$Members->Next) {
+<li>
+<INPUT TYPE=CHECKBOX
+ NAME="Queue-<%$QueueObj->Id%>-DelWatcher-Type-<%$Watchers->Type%>-Principal-<%$watcher->MemberId%>"
+ UNCHECKED>
+% if ($watcher->MemberObj->IsUser) {
+<a href="<%$RT::WebPath%>/Admin/Users/Modify.html?id=<%$watcher->MemberObj->ObjectId%>">
+% } else {
+<a href="<%$RT::WebPath%>/Admin/Groups/Modify.html?id=<%$watcher->MemberObj->ObjectId%>">
+% }
+<%$watcher->MemberObj->Object->Name%></a>
+% }
+% }
+</ul>
+
+<%INIT>
+my $Members = $Watchers->MembersObj;
+</%INIT>
+
+<%ARGS>
+$QueueObj => undef
+$Watchers => undef
+</%ARGS>
+
+
+
diff --git a/rt/html/Admin/Elements/EditScrip b/rt/html/Admin/Elements/EditScrip
new file mode 100644
index 0000000..1f186c2
--- /dev/null
+++ b/rt/html/Admin/Elements/EditScrip
@@ -0,0 +1,158 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/ListActions, actions => \@actions &>
+
+
+<FORM METHOD=POST ACTION="Scrip.html">
+<input type="hidden" name="id" value="<%$id%>">
+<input type="hidden" name="Queue" value="<%$Queue%>">
+<TABLE>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:
+</TD>
+<TD>
+<input Name="Scrip-<%$id%>-Description" value="<%$scrip->Description%>">
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Condition</&>:
+</TD>
+<TD>
+<& /Admin/Elements/SelectScripCondition, Name => "Scrip-$id-ScripCondition", Default => $scrip->ConditionObj->Id &><BR>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Custom condition</&>:
+</TD>
+<TD>
+<TEXTAREA COLS=80 ROWS=5 NAME="Scrip-<%$id%>-CustomIsApplicableCode"><%$scrip->CustomIsApplicableCode%></TEXTAREA>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Action</&>:
+</TD>
+<TD>
+<& /Admin/Elements/SelectScripAction, Name => "Scrip-$id-ScripAction", Default => $scrip->ActionObj->Id &>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Custom action preparation code</&>:
+</TD>
+<TD>
+<TEXTAREA COLS=80 ROWS=5 NAME="Scrip-<%$id%>-CustomPrepareCode"><%$scrip->CustomPrepareCode%></TEXTAREA>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Custom action cleanup code</&>:
+</TD>
+<TD>
+<TEXTAREA COLS=80 ROWS=5 NAME="Scrip-<%$id%>-CustomCommitCode"><%$scrip->CustomCommitCode%></TEXTAREA>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Stage</&>:
+</TD>
+<TD>
+<& /Admin/Elements/SelectStage, Name => "Scrip-$id-Stage", Default => $scrip->Stage &>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Template</&>:
+</TD>
+<TD>
+<& /Admin/Elements/SelectTemplate, Name => "Scrip-$id-Template", Default => $scrip->TemplateObj->Id, Queue => $Queue &>
+</TD>
+</TR>
+
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</FORM>
+<%init>
+my (@actions);
+
+
+my $scrip = new RT::Scrip($session{'CurrentUser'});
+
+if ( $id eq 'new' ) {
+
+ my ( $retval, $msg ) = $scrip->Create(
+ Queue => $Queue,
+ ScripAction => $ARGS{"Scrip-new-ScripAction"},
+ ScripCondition => $ARGS{"Scrip-new-ScripCondition"},
+ Template => $ARGS{"Scrip-new-Template"},
+ Description => $ARGS{"Scrip-new-Description"},
+ CustomPrepareCode => $ARGS{"Scrip-new-CustomPrepareCode"},
+ CustomCommitCode => $ARGS{"Scrip-new-CustomCommitCode"},
+ CustomIsApplicableCode => $ARGS{"Scrip-new-CustomIsApplicableCode"},
+ );
+ if ( defined $retval ) {
+ push @actions, $msg;
+ }
+ else {
+ Abort( $msg);
+ }
+}
+elsif ($id) {
+ my ($val,$msg) =$scrip->Load($id);
+ if ($val) {
+ $id = $scrip->id;
+ } else {
+ Abort ($msg);
+ }
+ my @attribs = qw (
+ Queue
+ ScripAction
+ ScripCondition
+ Template
+ Stage
+ Description
+ CustomPrepareCode
+ CustomCommitCode
+ CustomIsApplicableCode
+ );
+ my @results = UpdateRecordObject( AttributesRef => \@attribs,
+ AttributePrefix => 'Scrip-'.$scrip->Id,
+ Object => $scrip,
+ ARGSRef => \%ARGS );
+ push (@actions, @results);
+}
+
+elsif ($ARGS{'create'}) {
+ $id = 'new';
+}
+
+# }}}
+</%init>
+
+<%ARGS>
+$id => undef
+$title => undef
+$Queue => 0
+</%ARGS>
diff --git a/rt/html/Admin/Elements/EditScrips b/rt/html/Admin/Elements/EditScrips
new file mode 100644
index 0000000..07a57f4
--- /dev/null
+++ b/rt/html/Admin/Elements/EditScrips
@@ -0,0 +1,99 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/ListActions, actions => \@actions &>
+
+<form action="Scrips.html" method="post">
+<input type="hidden" name="id" value="<%$id%>">
+
+<P><&|/l&>Current Scrips</&>:</P>
+% if ($Scrips->Count == 0 ) {
+<P><i><&|/l&>(No scrips)</&></i></P>
+% } else {
+<TABLE>
+<P><i><&|/l&>(Check box to delete)</&></i></P>
+
+% while (my $scrip = $Scrips->Next ) {
+<TR>
+<TD>
+<input type="checkbox" name="DeleteScrip-<%$scrip->Id%>">
+</TD>
+<TD>
+<a href="Scrip.html?id=<%$scrip->Id%>&Queue=<%$id%>"><% $scrip->Description || "<i>(".loc('no value').")</i>" |n %></a><br>
+<small><&|/l, loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name) &>[_1] [_2] with template [_3]</&></small>
+</TD>
+</TR>
+% }
+
+</TABLE>
+
+% }
+<& /Elements/Submit,
+ Caption => loc("Delete selected scrips"),
+ Label => loc("Delete") &>
+</form>
+<%init>
+my (@actions);
+
+my $Scrips = RT::Scrips->new($session{'CurrentUser'});
+
+
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+if ($id) {
+ $QueueObj->Load($id);
+}
+
+if ($QueueObj->id) {
+ $Scrips->LimitToQueue($id);
+}
+else {
+ $Scrips->LimitToGlobal();
+}
+
+
+
+
+# {{{ deal with modifying and deleting existing scrips
+foreach my $key (keys %ARGS) {
+ # {{{ if we're trying to delete the scrip
+ if ($key =~ /^DeleteScrip-(\d+)/) {
+ my $id = $1;
+ my $scrip = new RT::Scrip($session{'CurrentUser'});
+ $scrip->Load($id);
+ my ($retval, $msg) = $scrip->Delete;
+ if ($retval) {
+ push @actions, loc("Scrip deleted");
+ }
+ else {
+ push @actions, $msg;
+ }
+ }
+ # }}}
+}
+# }}}
+</%init>
+
+<%ARGS>
+$id => undef
+$title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/EditTemplates b/rt/html/Admin/Elements/EditTemplates
new file mode 100644
index 0000000..12677ca
--- /dev/null
+++ b/rt/html/Admin/Elements/EditTemplates
@@ -0,0 +1,104 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/ListActions, actions => \@actions &>
+
+<FORM METHOD=GET ACTION="Templates.html">
+<INPUT TYPE="Hidden" NAME="id" VALUE="<%$id%>">
+
+% if ($Templates->Count == 0 ) {
+<P><i><&|/l&>(No templates)</&></i></P>
+% } else {
+<TABLE>
+<TR>
+<TH>
+<i><&|/l&>(Check box to delete)</&></i>
+</TH>
+<TH>
+</TH>
+</TR>
+% my $count;
+% while (my $TemplateObj = $Templates->Next) {
+<TR>
+<TD>
+<input type="checkbox" name="DeleteTemplate-<%$TemplateObj->Id%>">
+</TD>
+<TD>
+<A HREF="Template.html?Queue=<%$id%>&Template=<%$TemplateObj->id()%>">
+<B><% loc($TemplateObj->Name) %></B></A>
+<br><% loc($TemplateObj->Description) %>
+</TD>
+</TR>
+
+% }
+</TABLE>
+% }
+
+<& /Elements/Submit &>
+</FORM>
+
+<%INIT>
+my $Templates = RT::Templates->new($session{'CurrentUser'});
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+my @actions;
+
+if ($id) {
+ $QueueObj->Load($id);
+}
+
+if ($QueueObj->id) {
+ $Templates->LimitToQueue($id);
+}
+else {
+ $Templates->LimitToGlobal();
+}
+
+# Now let callbacks add their extra limits
+$m->comp('/Elements/Callback', Templates => $Templates, %ARGS);
+
+# {{{ deal with deleting existing templates
+foreach my $key (keys %ARGS) {
+ # {{{ if we're trying to delete the template
+ if ($key =~ /^DeleteTemplate-(\d+)/) {
+ my $id = $1;
+ my $TemplateObj = RT::Template->new($session{'CurrentUser'});
+ $TemplateObj->Load($id);
+ my ($retval, $msg) = $TemplateObj->Delete;
+ if ($retval) {
+ push @actions, loc("Template deleted");
+ }
+ else {
+ push @actions, $msg;
+ }
+ }
+ # }}}
+}
+# }}}
+</%INIT>
+<%ARGS>
+$id => 0
+$title => undef
+$Move => undef
+$Source => undef
+$Template => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/EditUserComments b/rt/html/Admin/Elements/EditUserComments
new file mode 100644
index 0000000..f791876
--- /dev/null
+++ b/rt/html/Admin/Elements/EditUserComments
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => "Comments about $name" &>
+<&|/l&>These comments aren't generally visible to the user</&>:<br>
+<input type="hidden" name="id" value="<%$id%>">
+<TEXTAREA COLS=60 ROWS=15 WRAP=SOFT NAME="Comments"><% $UserObj->Comments %></TEXTAREA>
+</FORM>
+
+<%ARGS>
+$UserObj => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/GroupTabs b/rt/html/Admin/Elements/GroupTabs
new file mode 100644
index 0000000..8737782
--- /dev/null
+++ b/rt/html/Admin/Elements/GroupTabs
@@ -0,0 +1,76 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Tabs,
+ subtabs => $tabs,
+ current_tab => 'Admin/Groups/',
+ current_subtab => $current_tab,
+ Title => $Title &>
+<%INIT>
+my $tabs;
+
+if ( $GroupObj and $GroupObj->id ) {
+$tabs->{"this"} = { class => "currentnav",
+ path => "Admin/Groups/Modify.html?id=" . $GroupObj->id,
+ title => $GroupObj->Name,
+ current_subtab => $current_subtab,
+ subtabs => {
+ C => { title => loc('Basics'),
+ path => "Admin/Groups/Modify.html?id=" . $GroupObj->id },
+
+ D => { title => loc('Members'),
+ path => "Admin/Groups/Members.html?id=" . $GroupObj->id },
+
+ F => { title => loc('Group Rights'),
+ path => "Admin/Groups/GroupRights.html?id=" . $GroupObj->id, },
+ G => { title => loc('User Rights'),
+ path => "Admin/Groups/UserRights.html?id=" . $GroupObj->id, },
+ }
+}
+}
+$tabs->{"A"} = { title => loc('Select group'),
+ path => "Admin/Groups/", };
+$tabs->{"B"} = { title => loc('New group'),
+ path => "Admin/Groups/Modify.html?Create=1",
+ separator => 1, };
+
+# Now let callbacks add their extra tabs
+$m->comp( '/Elements/Callback', tabs => $tabs, %ARGS );
+foreach my $tab ( sort keys %{$tabs->{'this'}->{'subtabs'}} ) {
+ if ( $tabs->{'this'}->{'subtabs'}->{$tab}->{'path'} eq $current_tab ) {
+ $tabs->{'this'}->{'subtabs'}->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{'this'}->{'subtabs'}->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+}
+ $tabs->{'this'}->{"current_subtab"} = $current_tab;
+ $current_tab = "Admin/Groups/Modify.html?id=".$GroupObj->id if $GroupObj;
+
+</%INIT>
+<%ARGS>
+$GroupObj => undef
+$subtabs => undef
+$current_subtab => undef
+$current_tab => undef
+$Title => undef
+</%ARGS>
+
diff --git a/rt/html/Admin/Elements/Header b/rt/html/Admin/Elements/Header
new file mode 100644
index 0000000..92a7c54
--- /dev/null
+++ b/rt/html/Admin/Elements/Header
@@ -0,0 +1,28 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $Title &>
+
+<%ARGS>
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/ListGlobalCustomFields b/rt/html/Admin/Elements/ListGlobalCustomFields
new file mode 100644
index 0000000..032f680
--- /dev/null
+++ b/rt/html/Admin/Elements/ListGlobalCustomFields
@@ -0,0 +1,37 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% my $count = 0;
+% while (my $CustomFieldObj = $CustomFields->Next) {
+% $count++;
+<font size="-1"><%$CustomFieldObj->id%>/<% loc($CustomFieldObj->Type) %>/<%$CustomFieldObj->Name%>: <%$CustomFieldObj->Description%></font>
+<BR>
+% }
+% if (!$count) {
+<font size="-1"><&|/l&>(No custom fields)</&></font>
+% }
+
+<%init>
+my $CustomFields = new RT::CustomFields ($session{'CurrentUser'});
+$CustomFields->LimitToGlobal();
+</%INIT>
diff --git a/rt/html/Admin/Elements/ListGlobalScrips b/rt/html/Admin/Elements/ListGlobalScrips
new file mode 100644
index 0000000..8dba3b6
--- /dev/null
+++ b/rt/html/Admin/Elements/ListGlobalScrips
@@ -0,0 +1,37 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% my $count = 0;
+% while (my $scrip = $Scrips->Next ) {
+% $count++;
+<font size="-1"><&|/l, loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name) &>[_1] [_2] with template [_3]</&></font>
+<BR>
+% }
+% if (!$count) {
+<font size="-1"><&|/l&>(No scrips)</&></font>
+% }
+
+<%init>
+my $Scrips = new RT::Scrips ($session{'CurrentUser'});
+$Scrips->LimitToGlobal();
+</%INIT>
diff --git a/rt/html/Admin/Elements/ModifyQueue b/rt/html/Admin/Elements/ModifyQueue
new file mode 100644
index 0000000..e5761df
--- /dev/null
+++ b/rt/html/Admin/Elements/ModifyQueue
@@ -0,0 +1,78 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => loc('Editing Configuration for queue [_1]', $QueueObj->Id) &>
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Queues/Modify.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$QueueObj->Id%>">
+<TABLE>
+<TR><TD ALIGN=RIGHT>
+<&|/l&>Queue Name</&>:
+</TD>
+<TD><INPUT name="Name" value="<%$QueueObj->Name%>"></TD>
+</TR><TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:</TD><TD COLSPAN=3><INPUT name="Description" value="<%$QueueObj->Description%>" size=60></TD></TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Correspondence Address</&>:
+</TD><TD>
+<INPUT name="CorrespondAddress" value="<%$QueueObj->CorrespondAddress%>">
+</TD>
+<TD ALIGN=RIGHT>
+
+<&|/l&>Comment Address</&>: </TD><TD>
+<INPUT NAME="CommentAddress" value="<%$QueueObj->CommentAddress%>">
+</TD>
+</TR><TR>
+
+<TD ALIGN=RIGHT>
+<&|/l&>Priority starts at</&>:
+</TD><TD><INPUT NAME="InitialPriority" value="<%$QueueObj->InitialPriority %>">
+</TD>
+<TD ALIGN=RIGHT>
+<&|/l&>Over time, priority moves toward</&>:
+</TD><TD><INPUT NAME="FinalPriority" value="<%$QueueObj->FinalPriority %>">
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Requests should be due in</&>:
+</TD><TD>
+<INPUT NAME="DefaultDueIn" VALUE="<%$QueueObj->DefaultDueIn%>"> <&|/l&>days</&>.
+</TD>
+</TR>
+</TABLE>
+<& /Elements/Submit &>
+</form>
+<& /Elements/TitleBoxEnd &>
+
+<%INIT>
+
+</%INIT>
+
+<%ARGS>
+
+
+$QueueObj => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/ModifyTemplate b/rt/html/Admin/Elements/ModifyTemplate
new file mode 100644
index 0000000..5f75bac
--- /dev/null
+++ b/rt/html/Admin/Elements/ModifyTemplate
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Name</&>:
+</TD>
+<TD>
+<input name="Name" VALUE="<%$Name%>" SIZE=20><BR>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:
+</TD>
+<TD>
+<input name="Description" VALUE="<%$Description%>" SIZE=80><BR>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=TOP>
+<&|/l&>Content</&>:<BR>
+</TD>
+<TD>
+<TEXTAREA NAME=Content ROWS=25 COLS=80 WRAP=SOFT>
+<%$Content%></TEXTAREA>
+</TD>
+</TR>
+</TABLE>
+
+<%INIT>
+
+</%INIT>
+
+<%ARGS>
+$Name => undef
+$Description => undef
+$Content => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/ModifyUser b/rt/html/Admin/Elements/ModifyUser
new file mode 100644
index 0000000..876e8a7
--- /dev/null
+++ b/rt/html/Admin/Elements/ModifyUser
@@ -0,0 +1,99 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => loc('Editing Configuration for user [_1]', $UserObj->Name) &>
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Users/Modify.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$UserObj->Id%>">
+
+<&|/l&>Name</&>: <input name="Name" value="<%$UserObj->Name%>">
+<BR>
+<&|/l&>New Password</&>: <input type=password name="Pass1"><BR>
+<&|/l&>Retype Password</&>: <input type=password name="Pass2"><BR>
+
+<&|/l&>Comments</&>: <TEXTAREA name="Comments" COLS=80 ROWS=5 WRAP=VIRTUAL>
+<%$UserObj->Comments%></TEXTAREA>
+
+<BR>
+<&|/l&>Signature</&>: <TEXTAREA COLS=80 ROWS=5 name="Signature" WRAP=HARD>
+<%$UserObj->Signature%></TEXTAREA>
+<BR>
+<&|/l&>EmailAddress</&>: <input name="EmailAddress" value="<%$UserObj->EmailAddress%>">
+<BR>
+<&|/l&>FreeformContactInfo</&>: <input name="FreeformContactInfo" value="<%$UserObj->FreeformContactInfo%>">
+<BR>
+<&|/l&>Organization</&>: <input name="Organization" value="<%$UserObj->Organization%>">
+<BR>
+<&|/l&>RealName</&>: <input name="RealName" value="<%$UserObj->RealName%>">
+<BR>
+<&|/l&>NickName</&>: <input name="NickName" value="<%$UserObj->NickName%>">
+<BR>
+<&|/l&>Lang</&>: <input name="Lang" value="<%$UserObj->Lang%>">
+<BR>
+<&|/l&>EmailEncoding</&>: <input name="EmailEncoding" value="<%$UserObj->EmailEncoding%>">
+<BR>
+<&|/l&>WebEncoding</&>: <input name="WebEncoding" value="<%$UserObj->WebEncoding%>">
+<BR>
+<&|/l&>ExternalContactInfoId</&>: <input name="ExternalContactInfoId" value="<%$UserObj->ExternalContactInfoId%>">
+<BR>
+<&|/l&>ContactInfoSystem</&>: <input name="ContactInfoSystem" value="<%$UserObj->ContactInfoSystem%>">
+<BR>
+<&|/l&>UnixUsername</&>: <input name="Gecos" value="<%$UserObj->Gecos%>">
+<BR>
+<&|/l&>ExternalAuthId</&>: <input name="ExternalAuthId" value="<%$UserObj->ExternalAuthId%>">
+<BR>
+<&|/l&>AuthSystem</&>: <input name="AuthSystem" value="<%$UserObj->AuthSystem%>">
+<BR>
+<&|/l&>HomePhone</&>: <input name="HomePhone" value="<%$UserObj->HomePhone%>">
+<BR>
+<&|/l&>WorkPhone</&>: <input name="WorkPhone" value="<%$UserObj->WorkPhone%>">
+<BR>
+<&|/l&>MobilePhone</&>: <input name="MobilePhone" value="<%$UserObj->MobilePhone%>">
+<BR>
+<&|/l&>PagerPhone</&>: <input name="PagerPhone" value="<%$UserObj->PagerPhone%>">
+<BR>
+<&|/l&>Address1</&>: <input name="Address1" value="<%$UserObj->Address1%>">
+<BR>
+<&|/l&>Address2</&>: <input name="Address2" value="<%$UserObj->Address2%>">
+<BR>
+<&|/l&>City</&>: <input name="City" value="<%$UserObj->City%>">
+<BR>
+<&|/l&>State</&>: <input name="State" value="<%$UserObj->State%>">
+<BR>
+<&|/l&>Zip</&>: <input name="Zip" value="<%$UserObj->Zip%>">
+<BR>
+<&|/l&>Country</&>: <input name="Country" value="<%$UserObj->Country%>">
+<BR>
+<& /Elements/Submit &>
+</form>
+<& /Elements/TitleBoxEnd &>
+
+<%INIT>
+
+</%INIT>
+
+<%ARGS>
+
+
+$UserObj => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/QueueRightsForUser b/rt/html/Admin/Elements/QueueRightsForUser
new file mode 100644
index 0000000..05bb511
--- /dev/null
+++ b/rt/html/Admin/Elements/QueueRightsForUser
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<UL>
+%while(my $ACE = $ACL->Next) {
+
+<LI><checkbox name="delete_ace_<%$ACE->id%>"> <% loc($ACE->RightName) %> (<%$ACE->UserObj->RealName%>)
+
+%}
+</UL>
+
+<%INIT>
+my $ACL = new RT::ACL($session{'CurrentUser'});
+$ACL->LimitToQueue($QueueObj->id);
+$ACL->LimitPrincipalToUser($PrincipalId);
+</%INIT>
+<%ARGS>
+$PrincipalId => undef
+$QueueObj => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/QueueTabs b/rt/html/Admin/Elements/QueueTabs
new file mode 100644
index 0000000..3b4805a
--- /dev/null
+++ b/rt/html/Admin/Elements/QueueTabs
@@ -0,0 +1,93 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Tabs,
+ subtabs => $tabs,
+ current_tab => 'Admin/Queues/',
+ current_subtab => $current_tab,
+ Title => $Title &>
+
+<%INIT>
+my $tabs;
+if ($id) {
+ $tabs->{'this'} = {
+ title => $QueueObj->Name,
+ path => "Admin/Queues/Modify.html?id=".$id,
+ current_subtab => $current_tab,
+ subtabs => {
+ C => { title => loc('Basics'),
+ path => "Admin/Queues/Modify.html?id=".$id,
+ },
+ D => { title => loc('Watchers'),
+ path => "Admin/Queues/People.html?id=".$id,
+ },
+
+ E => { title => loc('Scrips'),
+ path => "Admin/Queues/Scrips.html?id=".$id,
+ },
+ F => { title => loc('Templates'),
+ path => "Admin/Queues/Templates.html?id=".$id,
+ },
+
+ G => { title => loc('Custom Fields'),
+ path => 'Admin/Queues/CustomFields.html?id='.$id,
+ },
+
+ H => { title => loc('Group Rights'),
+ path => "Admin/Queues/GroupRights.html?id=".$id,
+ },
+ I => { title => loc('User Rights'),
+ path => "Admin/Queues/UserRights.html?id=".$id,
+ }
+ }
+ };
+}
+if ($session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminQueue')) {
+ $tabs->{"A"} = { title => loc('Select queue'),
+ path => "Admin/Queues/",
+ };
+ $tabs->{"B"} = { title => loc('New queue'),
+ path => "Admin/Queues/Modify.html?Create=1",
+ separator => 1,
+ };
+}
+
+ # Now let callbacks add their extra tabs
+ $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+foreach my $tab ( sort keys %{$tabs->{'this'}->{'subtabs'}} ) {
+ if ( $tabs->{'this'}->{'subtabs'}->{$tab}->{'path'} eq $current_tab ) {
+ $tabs->{'this'}->{'subtabs'}->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{'this'}->{'subtabs'}->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+}
+ $current_tab = "Admin/Queues/Modify.html?id=".$id if $id;
+</%INIT>
+
+<%ARGS>
+$QueueObj => undef
+$id => undef
+$subtabs => undef
+$current_subtab => undef
+$current_tab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectCustomFieldType b/rt/html/Admin/Elements/SelectCustomFieldType
new file mode 100644
index 0000000..b5f4c07
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectCustomFieldType
@@ -0,0 +1,36 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+%for my $option ($cf->Types) {
+<OPTION VALUE="<%$option%>" <%$option eq $Default && "SELECTED"%>><% $cf->FriendlyType($option) %></OPTION>
+%}
+</SELECT>
+<%INIT>
+my $cf = RT::CustomField->new($session{'CurrentUser'});
+
+</%INIT>
+<%ARGS>
+$Default=>undef
+$Name => 'Type'
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectGroups b/rt/html/Admin/Elements/SelectGroups
new file mode 100644
index 0000000..3cc909b
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectGroups
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT MULTIPLE NAME="<%$Name%>" SIZE=10>
+%while (my $group = $groups->Next) {
+<OPTION VALUE="<%$group->id%>"><%$group->Name%>
+%}
+</SELECT>
+
+<%INIT>
+my $groups = new RT::Groups($session{'CurrentUser'});
+$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => $Domain);
+
+</%INIT>
+<%ARGS>
+$Name => 'groups'
+$Domain => 'UserDefined';
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectModifyGroup b/rt/html/Admin/Elements/SelectModifyGroup
new file mode 100644
index 0000000..47978d3
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectModifyGroup
@@ -0,0 +1,33 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%while ( $Group = $Groups->Next) {
+<A HREF="Modify.html?id=<%$Group->id%>"><%$Group->id%>: <%$Group->Name%></a><BR>
+%}
+<%INIT>
+my ($Group);
+my $Groups = new RT::Groups($session{'CurrentUser'});
+$Groups->UnLimit;
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectModifyQueue b/rt/html/Admin/Elements/SelectModifyQueue
new file mode 100644
index 0000000..c5152ac
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectModifyQueue
@@ -0,0 +1,33 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%while ( $queue = $queues->Next) {
+<A HREF="Modify.html?id=<%$queue->id%>"><%$queue->id%>: <%$queue->Name%></a><BR>
+%}
+<%INIT>
+my ($queue);
+my $queues = new RT::Queues($session{'CurrentUser'});
+$queues->UnLimit;
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectModifyUser b/rt/html/Admin/Elements/SelectModifyUser
new file mode 100644
index 0000000..9e7789b
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectModifyUser
@@ -0,0 +1,49 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%while ( $user = $users->Next) {
+<A HREF="Modify.html?id=<%$user->id%>"><%$user->id%>: <%$user->Name%></a><BR>
+%}
+<%INIT>
+my ($user);
+my $users = new RT::Users($session{'CurrentUser'});
+$users->Limit(FIELD => 'id',
+ VALUE => $RT::SystemUser->id,
+ OPERATOR => '!=' );
+
+if (defined $IdLike) {
+$users->Limit(FIELD => 'Name',
+ VALUE => $IdLike,
+ OPERATOR => 'LIKE' );
+}
+if (defined $EmailLike) {
+$users->Limit(FIELD => 'EmailAddress',
+ VALUE => $EmailLike,
+ OPERATOR => 'LIKE');
+
+}
+</%INIT>
+<%ARGS>
+$IdLike => undef
+$EmailLike => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectNewGroupMembers b/rt/html/Admin/Elements/SelectNewGroupMembers
new file mode 100644
index 0000000..e5c28e9
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectNewGroupMembers
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if ($Show ne 'Groups') {
+<b><&|/l&>Users</&></b>
+<SELECT MULTIPLE NAME="<%$Name%>Users" SIZE=10>
+%while (my $user = $users->Next) {
+<OPTION VALUE="User-<%$user->id%>"><%$user->Name%></OPTION>
+%}
+</SELECT>
+<br>
+% }
+% if ($Show ne 'Users') {
+<b><&|/l&>Groups</&></b>
+<SELECT MULTIPLE NAME="<%$Name%>Groups" SIZE=10>
+%while (my $group = $groups->Next) {
+<OPTION VALUE="Group-<%$group->id%>"><%$group->Name%></OPTION>
+%}
+</SELECT>
+% }
+
+<%INIT>
+my $users = new RT::Users($session{'CurrentUser'});
+
+$users->Limit(FIELD => 'id', VALUE => $RT::SystemUser->id, OPERATOR => '!=' );
+$users->Limit(FIELD => 'id', VALUE => $RT::Nobody->id, OPERATOR => '!=' );
+$users->LimitToPrivileged();
+
+my $groups = new RT::Groups($session{'CurrentUser'});
+
+# self-recursive group membership considered harmful!
+$groups->Limit(FIELD => 'id', VALUE => $Group->id, OPERATOR => '!=' );
+$groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined');
+
+
+</%INIT>
+<%ARGS>
+$Name => 'Users'
+$Show => 'All'
+$Group
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectRights b/rt/html/Admin/Elements/SelectRights
new file mode 100644
index 0000000..8d87ac9
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectRights
@@ -0,0 +1,91 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<INPUT TYPE=HIDDEN NAME="CheckACL" VALUE="<%$ACLDesc%>">
+ <TABLE BORDER=0>
+<TR>
+<TD valign=top width="180" align="left">
+<h3><&|/l&>Current rights</&></h3>
+% if ($ACLObj->Count() > 0) {
+<i>(<&|/l&>Check box to revoke right</&>)</i> <BR>
+% } else {
+<i><&|/l&>No rights granted.</&></i> <BR>
+% }
+% while (my $right = $ACLObj->Next()) {
+% if ($right->RightName) {
+<input type=checkbox value="<%$right->Id%>" name="RevokeRight-<%$ACLDesc%>-<%$right->RightName%>"> <% loc($right->RightName) %><br>
+% }
+% }
+</TD>
+<TD valign=top>
+<h3><&|/l&>New rights</&></h3>
+<SELECT SIZE=5 MULTIPLE NAME="GrantRight-<%$ACLDesc%>">
+% foreach $right (sort keys %Rights) {
+ <OPTION VALUE="<%$right%>"
+ ><% loc($right) %></OPTION>
+% }
+<OPTION VALUE="" SELECTED><&|/l&>(no value)</&></OPTION>
+</SELECT>
+</TD>
+</TR>
+</TABLE>
+<%INIT>
+ my ($right, $ACLDesc, $AppliesTo, %Rights);
+
+ # if the principal id points to a user, we really want to point
+ # to their ACL equivalence group. The machinations we're going through
+ # lead me to start to suspect that we really want users and groups
+ # to just be the same table. or _maybe_ that we want an object db.
+ my $princ = RT::Principal->new($RT::SystemUser);
+ $princ->Load($PrincipalId);
+ if ($princ->PrincipalType eq 'User') {
+ my $group = RT::Group->new($RT::SystemUser);
+ $group->LoadACLEquivalenceGroup($princ);
+ $PrincipalId = $group->PrincipalId;
+ }
+
+
+ my $ACLObj = new RT::ACL($session{'CurrentUser'});
+ my $ACE = new RT::ACE($session{'CurrentUser'});
+
+
+ $ACLObj->LimitToObject( $Object);
+ $ACLObj->LimitToPrincipal( Id => $PrincipalId);
+ $ACLObj->OrderBy(FIELD=>'RightName');
+
+ if (ref($Object) && UNIVERSAL::can($Object, 'AvailableRights')) {
+ %Rights = %{$Object->AvailableRights};
+ }
+
+ else {
+ %Rights = { loc('System Error') => loc("No rights found")};
+ }
+
+ $ACLDesc = "$PrincipalId-".ref($Object)."-".$Object->Id;
+</%INIT>
+
+<%ARGS>
+$PrincipalType => undef
+$PrincipalId => undef
+$Object =>undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectScrip b/rt/html/Admin/Elements/SelectScrip
new file mode 100644
index 0000000..18e4098
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectScrip
@@ -0,0 +1,48 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME=<%$Name%>>
+<OPTION VALUE=""
+<% $Default eq undef && 'SELECTED' %>
+>-</OPTION>
+%while (my $Scrip = $Scrips->Next) {
+<OPTION VALUE=<% $Scrip->Id %>
+<% $Scrip->Id == $Default && 'SELECTED' %>
+><% loc($Scrip->Name) %>
+</OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my $Scrips = RT::Scrips->new($session{'CurrentUser'});
+$Scrips->UnLimit;
+
+
+
+</%INIT>
+<%ARGS>
+
+$Default => undef
+$Name => 'Scrip'
+
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectScripAction b/rt/html/Admin/Elements/SelectScripAction
new file mode 100644
index 0000000..0d7f8cc
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectScripAction
@@ -0,0 +1,48 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME=<%$Name%>>
+<OPTION VALUE=""
+<% $Default eq undef && 'SELECTED' %>
+>-</OPTION>
+%while (my $ScripAction = $ScripActions->Next) {
+<OPTION VALUE=<%$ScripAction->Id%>
+<% $ScripAction->Id == $Default && 'SELECTED' %>
+><% loc($ScripAction->Name) %>
+</OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my $ScripActions = RT::ScripActions->new($session{'CurrentUser'});
+$ScripActions->UnLimit;
+
+
+
+</%INIT>
+<%ARGS>
+
+$Default => undef
+$Name => 'ScripAction'
+
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectScripCondition b/rt/html/Admin/Elements/SelectScripCondition
new file mode 100644
index 0000000..aeb366a
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectScripCondition
@@ -0,0 +1,48 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME=<%$Name%>>
+<OPTION VALUE=""
+<% $Default eq undef && 'SELECTED' %>
+>-</OPTION>
+%while (my $ScripCondition = $ScripConditions->Next) {
+<OPTION VALUE=<%$ScripCondition->Id%>
+<% $ScripCondition->Id == $Default && 'SELECTED' %>
+><% loc($ScripCondition->Name) %>
+</OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my $ScripConditions = RT::ScripConditions->new($session{'CurrentUser'});
+$ScripConditions->UnLimit;
+
+
+
+</%INIT>
+<%ARGS>
+
+$Default => undef
+$Name => 'ScripCondition'
+
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectSingleOrMultiple b/rt/html/Admin/Elements/SelectSingleOrMultiple
new file mode 100644
index 0000000..98e9ee7
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectSingleOrMultiple
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+ <select name="<%$Name%>">
+ <option value="1" <%$SingleDefault%>><&|/l&>Single</&></option>
+ <option value="0" <%$MultipleDefault%>><&|/l&>Multiple</&></option>
+ </select>
+
+
+<%INIT>
+my ($SingleDefault, $MultipleDefault);
+if ($Default == 1) {
+ $SingleDefault = "SELECTED";
+}
+elsif ($Default == 0 ) {
+ $MultipleDefault = "SELECTED";
+}
+
+</%INIT>
+<%ARGS>
+$Name => 'Single'
+$Default => 1
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectStage b/rt/html/Admin/Elements/SelectStage
new file mode 100644
index 0000000..b62964b
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectStage
@@ -0,0 +1,39 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME=<%$Name%>>
+% foreach my $stage (qw(TransactionCreate TransactionBatch)) {
+<OPTION VALUE=<%$stage%>
+<% ($stage eq $Default) && 'SELECTED' %>
+><% loc($stage) %>
+</OPTION>
+% }
+<%INIT>
+if ($Default eq '') {
+ $Default = 'TransactionCreate';
+}
+</%INIT>
+<%ARGS>
+$Default => 'TransactionCreate'
+$Name => 'Stage'
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectTemplate b/rt/html/Admin/Elements/SelectTemplate
new file mode 100644
index 0000000..70ff4d1
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectTemplate
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME=<%$Name%>>
+<OPTION VALUE=""
+<% $Default eq 'none' && 'SELECTED' %>
+>-</OPTION>
+%while (my $Template = $PrimaryTemplates->Next) {
+<OPTION VALUE=<%$Template->Id%>
+<% ($Template->Id == $Default) && 'SELECTED' %>
+><% loc($Template->Name) %>
+</OPTION>
+%}
+%while (my $Template = $OtherTemplates->Next) {
+<OPTION VALUE=<%$Template->Id%>
+<% ($Template->Id == $Default) && 'SELECTED'%>
+><&|/l, loc($Template->Name) &>Global template: [_1]</&>
+</OPTION>
+%}
+</SELECT>
+
+<%INIT>
+
+
+my $PrimaryTemplates = RT::Templates->new($session{'CurrentUser'});
+if ($Queue != 0) {
+$PrimaryTemplates->LimitToQueue($Queue);
+}
+
+my $OtherTemplates = RT::Templates->new($session{'CurrentUser'});
+$OtherTemplates->LimitToGlobal($DefaultQueue);
+
+</%INIT>
+<%ARGS>
+
+$Queue => undef
+$Default => 'none'
+$DefaultQueue => undef
+$Name => 'Template'
+
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SelectUsers b/rt/html/Admin/Elements/SelectUsers
new file mode 100644
index 0000000..d4c8a85
--- /dev/null
+++ b/rt/html/Admin/Elements/SelectUsers
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT MULTIPLE NAME="<%$Name%>" SIZE=10>
+%while (my $user = $users->Next) {
+<OPTION VALUE="<%$user->id%>"><%$user->Name%>
+%}
+</SELECT>
+
+<%INIT>
+my $users = new RT::Users($session{'CurrentUser'});
+
+$users->Limit(FIELD => 'id', VALUE => $RT::SystemUser->id, OPERATOR => '!=' );
+$users->Limit(FIELD => 'id', VALUE => $RT::Nobody->id, OPERATOR => '!=' );
+$users->LimitToPrivileged();
+
+</%INIT>
+<%ARGS>
+$Name => 'Users'
+</%ARGS>
diff --git a/rt/html/Admin/Elements/SystemTabs b/rt/html/Admin/Elements/SystemTabs
new file mode 100644
index 0000000..f38febd
--- /dev/null
+++ b/rt/html/Admin/Elements/SystemTabs
@@ -0,0 +1,70 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Tabs, subtabs => $tabs,
+ current_tab => 'Admin/Global/',
+ current_subtab => $current_tab,
+ Title => $Title &>
+
+<%INIT>
+ my $tabs = {
+
+ A => { title => loc('Scrips'),
+ path => 'Admin/Global/Scrips.html',
+ },
+ B => { title => loc('Templates'),
+ path => 'Admin/Global/Templates.html',
+ },
+
+ F => { title => loc('Custom Fields'),
+ path => 'Admin/Global/CustomFields.html',
+ },
+
+ G => { title => loc('Group Rights'),
+ path => 'Admin/Global/GroupRights.html',
+ },
+ H => { title => loc('User Rights'),
+ path => 'Admin/Global/UserRights.html',
+ }
+
+};
+
+ # Now let callbacks add their extra tabs
+ $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+
+ foreach my $tab (sort keys %{$tabs}) {
+ if ($tabs->{$tab}->{'path'} eq $current_tab) {
+ $tabs->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+ }
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+$current_tab => undef
+$subtabs => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/Tabs b/rt/html/Admin/Elements/Tabs
new file mode 100644
index 0000000..8fa2708
--- /dev/null
+++ b/rt/html/Admin/Elements/Tabs
@@ -0,0 +1,63 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Tabs,
+ tabs => $tabs,
+ current_toptab => 'Admin/',
+ current_tab => $current_tab,
+ Title => $Title &>
+
+<%INIT>
+ my $tabs = { A => { title => loc('Users'),
+ path => 'Admin/Users/',
+ },
+ B => { title => loc('Groups'),
+ path => 'Admin/Groups/',
+ },
+ C => { title => loc('Queues'),
+ path => 'Admin/Queues/',
+ },
+ D => { 'title' => loc('Global'),
+ path => 'Admin/Global/',
+ },
+ };
+
+ # Now let callbacks add their extra tabs
+ $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+
+ foreach my $tab (sort keys %{$tabs}) {
+ if ($tabs->{$tab}->{'path'} eq $current_tab) {
+ $tabs->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+ }
+
+</%INIT>
+
+
+<%ARGS>
+$subtabs => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Elements/UserTabs b/rt/html/Admin/Elements/UserTabs
new file mode 100644
index 0000000..764fdfc
--- /dev/null
+++ b/rt/html/Admin/Elements/UserTabs
@@ -0,0 +1,74 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Tabs,
+ subtabs => $tabs,
+ current_tab => 'Admin/Users/',
+ current_subtab => $current_subtab,
+ Title => $Title &>
+<%INIT>
+my $tabs;
+my $subtabs;
+if ($id) {
+$tabs->{'this'} = { title => eval { $UserObj->Name },
+
+ path => "Admin/Users/Modify.html?id=".$id,
+subtabs => {
+ Queues => { title => loc('Basics'),
+ path => "Admin/Users/Modify.html?id=".$id
+ },
+# Scrips => { title => loc('Rights'),
+# path => "Admin/Users/Rights.html?id=".$id
+# }
+
+ }
+}
+}
+if ($session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'AdminUsers')) {
+ $tabs->{"A"} = { title => loc('Select user'),
+ path => "Admin/Users/",
+ };
+ $tabs->{"B"} = { title => loc('New user'),
+ path => "Admin/Users/Modify.html?Create=1",
+ separator => 1,
+ };
+}
+
+ # Now let callbacks add their extra tabs
+ $m->comp('/Elements/Callback', tabs => $tabs, %ARGS);
+
+foreach my $tab ( sort keys %{$tabs} ) {
+ if ( $tabs->{$tab}->{'path'} eq $current_subtab ) {
+ $tabs->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+}
+</%INIT>
+
+
+<%ARGS>
+$UserObj => undef
+$id => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Admin/Global/CustomField.html b/rt/html/Admin/Global/CustomField.html
new file mode 100644
index 0000000..0974af5
--- /dev/null
+++ b/rt/html/Admin/Global/CustomField.html
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/CustomFields.html',
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditCustomField, title => $title, %ARGS &>
+
+<%INIT>
+my ($title, $current_subtab);
+
+my $subtabs = {
+ A => { title => loc('Select custom field'),
+ path => "Admin/Global/CustomFields.html"
+ },
+ B => { title => loc('New custom field'),
+ path => "Admin/Global/CustomField.html?create=1&Queue=0",
+ separator => 1,
+ }
+ };
+if ( $ARGS{'create'} ) {
+ $current_subtab = "Admin/Global/CustomField.html?create=1&Queue=0";
+ $title = loc('Create a CustomField which applies to all queues');
+}
+else {
+ $current_subtab =
+ "Admin/Global/CustomField.html?CustomField=" . $CustomField . "&Queue=0";
+ $title = loc('Modify a CustomField which applies to all queues');
+ $subtabs->{"C"} = {
+ title => loc( 'Custom Field #[_1]', $CustomField ),
+ path => "Admin/Global/CustomField.html?CustomField=" . $CustomField . "&Queue=0"
+ };
+}
+</%INIT>
+<%ARGS>
+$CustomField => undef
+</%ARGS>
diff --git a/rt/html/Admin/Global/CustomFields.html b/rt/html/Admin/Global/CustomFields.html
new file mode 100644
index 0000000..f6bbddf
--- /dev/null
+++ b/rt/html/Admin/Global/CustomFields.html
@@ -0,0 +1,47 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/CustomFields.html',
+ current_subtab => 'Admin/Global/CustomFields.html',
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditCustomFields, title => $title, %ARGS &>
+
+<%INIT>
+my $subtabs = {
+ A => { title => loc('Select custom field'),
+ path => "Admin/Global/CustomFields.html"
+ },
+ B => { title => loc('New custom field'),
+ path => "Admin/Global/CustomField.html?create=1&Queue=0",
+ separator => 1,
+ }
+ };
+my $title = loc("Modify Custom Fields which apply to all queues");
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Global/GroupRights.html b/rt/html/Admin/Global/GroupRights.html
new file mode 100644
index 0000000..150e83f
--- /dev/null
+++ b/rt/html/Admin/Global/GroupRights.html
@@ -0,0 +1,99 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify global group rights') &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/GroupRights.html',
+ Title => loc('Modify global group rights') &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="GroupRights.html">
+
+<& /Elements/TitleBoxStart, title => loc('Modify global group rights.')&>
+
+<h1><&|/l&>System groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToSystemInternalGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% loc($Group->Type) %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object =>$RT::System &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<h1><&|/l&>Roles</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToRolesForSystem();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% loc($Group->Type) %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object => $RT::System &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<h1><&|/l&>User defined groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToUserDefinedGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $Group->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object => $RT::System &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+
+ <& /Elements/TitleBoxEnd &>
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+
+my $Groups;
+
+</%INIT>
+
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/Admin/Global/Scrip.html b/rt/html/Admin/Global/Scrip.html
new file mode 100644
index 0000000..8b9cf6d
--- /dev/null
+++ b/rt/html/Admin/Global/Scrip.html
@@ -0,0 +1,56 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/Scrips.html',
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditScrip, title => $title, %ARGS &>
+
+<%init>
+my ($title, $current_subtab);
+my $subtabs = {
+ A => { title => loc('Select scrip'),
+ path => "Admin/Global/Scrips.html",
+ },
+ B => { title => loc('New scrip'),
+ path => "Admin/Global/Scrip.html?create=1&Queue=0",
+ separator => 1,
+ }
+ };
+
+if ($ARGS{'id'}) {
+ $current_subtab = "Admin/Global/Scrip.html?id=".$ARGS{'id'}."&Queue=0";
+ $title = loc("Modify a scrip which applies to all queues");
+ $subtabs->{"C"} = { title => loc('Scrip #[_1]', $ARGS{'id'}),
+ path => "Admin/Global/Scrip.html?id=".$ARGS{'id'}."&Queue=0"
+ }
+}
+else {
+ $current_subtab = "Admin/Global/Scrip.html?create=1&Queue=0";
+ $title = loc("Add a scrip which will apply to all queues");
+}
+</%init>
diff --git a/rt/html/Admin/Global/Scrips.html b/rt/html/Admin/Global/Scrips.html
new file mode 100644
index 0000000..7631980
--- /dev/null
+++ b/rt/html/Admin/Global/Scrips.html
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/Scrips.html',
+ current_subtab => 'Admin/Global/Scrips.html',
+ subtabs => $subtabs,
+ Title => $title &>
+<& /Admin/Elements/EditScrips, title => $title, id => $id, %ARGS &>
+</form>
+<%init>
+
+my $subtabs = {
+ A => { title => loc('Select scrip'),
+ path => "Admin/Global/Scrips.html",
+ },
+ B => { title => loc('New scrip'),
+ path => "Admin/Global/Scrip.html?create=1&Queue=0",
+ separator => 1,
+ }
+ };
+my $title = loc("Modify scrips which apply to all queues");
+
+my (@actions);
+
+</%init>
+
+
+
+<%ARGS>
+$id => 0
+</%ARGS>
diff --git a/rt/html/Admin/Global/Template.html b/rt/html/Admin/Global/Template.html
new file mode 100644
index 0000000..71f77e9
--- /dev/null
+++ b/rt/html/Admin/Global/Template.html
@@ -0,0 +1,101 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc("Modify template [_1]", $TemplateObj->id) &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/Templates.html',
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => loc("Modify template [_1]", $TemplateObj->id) &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+<FORM METHOD=POST ACTION="Template.html">
+%if ($Create ) {
+<INPUT TYPE=HIDDEN NAME="Template" VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME="Template" VALUE="<%$TemplateObj->Id%>">
+% }
+
+%# hang onto the queue id
+<INPUT TYPE=HIDDEN name="Queue" value="<%$Queue%>">
+
+<& /Admin/Elements/ModifyTemplate, Name => $TemplateObj->Name, Description => $TemplateObj->Description, Content => $TemplateObj->Content &>
+
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</FORM>
+
+
+
+<%INIT>
+
+my $TemplateObj = new RT::Template($session{'CurrentUser'});
+my ($title, @results, $current_subtab);
+
+my $subtabs = {
+ A => { title => loc('Select template'),
+ path => "Admin/Global/Templates.html"
+ },
+ B => { title => loc('New template'),
+ path => "Admin/Global/Template.html?Create=1&Queue=0",
+ separator => 1,
+ }
+ };
+
+
+if ($Create) {
+ $current_subtab = "Admin/Global/Template.html?Create=1&Queue=0";
+ $title = loc("Create a template");
+}
+
+else {
+ if ($Template eq 'new') {
+ my ($val, $msg) = $TemplateObj->Create(Queue => $Queue, Name => $Name);
+ Abort(loc("Could not create template: [_1]", $msg)) unless ($val);
+ push @results, $msg;
+ }
+ else {
+ $TemplateObj->Load($Template) || Abort(loc('No Template'));
+ }
+ $title = loc('Modify template [_1]', loc($TemplateObj->Name()));
+
+
+}
+if ($TemplateObj->Id()) {
+ my @attribs = qw( Description Content Queue Name);
+ my @aresults = UpdateRecordObject( AttributesRef => \@attribs,
+ Object => $TemplateObj,
+ ARGSRef => \%ARGS);
+ $current_subtab = "Admin/Global/Template.html?Queue=0&Template=".$TemplateObj->Id();
+ $subtabs->{"C"} = { title => loc('Template #[_1]', $TemplateObj->Id()),
+ path => "Admin/Global/Template.html?Queue=0&Template=".$TemplateObj->Id(),
+ };
+ push @results, @aresults;
+}
+</%INIT>
+<%ARGS>
+$Queue => undef
+$Template => undef
+$Create => undef
+$Name => undef
+</%ARGS>
diff --git a/rt/html/Admin/Global/Templates.html b/rt/html/Admin/Global/Templates.html
new file mode 100644
index 0000000..77aab07
--- /dev/null
+++ b/rt/html/Admin/Global/Templates.html
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/Templates.html',
+ current_subtab => 'Admin/Global/Templates.html',
+ subtabs => $subtabs,
+ Title => $title &>
+<& /Admin/Elements/EditTemplates, title => $title, %ARGS &>
+</form>
+<%init>
+
+my $subtabs = {
+ A => { title => loc('Select template'),
+ path => "Admin/Global/Templates.html"
+ },
+ B => { title => loc('New template'),
+ path => "Admin/Global/Template.html?Create=1&Queue=0",
+ separator => 1,
+ }
+ };
+my $title = loc("Modify templates which apply to all queues");
+
+my (@actions);
+
+</%init>
+
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Global/UserRights.html b/rt/html/Admin/Global/UserRights.html
new file mode 100644
index 0000000..aee82d1
--- /dev/null
+++ b/rt/html/Admin/Global/UserRights.html
@@ -0,0 +1,77 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify global user rights') &>
+<& /Admin/Elements/SystemTabs,
+ current_tab => 'Admin/Global/UserRights.html',
+ Title => loc('Modify global user rights') &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="UserRights.html">
+
+<& /Elements/TitleBoxStart, title => loc('Modify global user rights.') &>
+
+<TABLE>
+
+% while (my $Member = $Users->Next()) {
+% my $UserObj = $Member->MemberObj->Object();
+% my $group = RT::Group->new($session{'CurrentUser'});
+% $group->LoadACLEquivalenceGroup($Member->MemberObj);
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $UserObj->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $group->PrincipalId,
+ Object => $RT::System &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+
+ <& /Elements/TitleBoxEnd &>
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+# {{{ Deal with setting up the display of current rights.
+
+
+# Find out which users we want to display ACL selects for
+my $Privileged = RT::Group->new($session{'CurrentUser'});
+$Privileged->LoadSystemInternalGroup('Privileged');
+my $Users = $Privileged->MembersObj();
+
+
+
+# }}}
+
+</%INIT>
+
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/Admin/Global/index.html b/rt/html/Admin/Global/index.html
new file mode 100644
index 0000000..1749f4f
--- /dev/null
+++ b/rt/html/Admin/Global/index.html
@@ -0,0 +1,64 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Callback, tabs => $tabs, %ARGS &>
+<& /Admin/Elements/Header, Title => loc('Admin/Global configuration') &>
+<& /Admin/Elements/SystemTabs,
+ Title => loc('Admin/Global configuration') &>
+
+<ul>
+% foreach my $key (sort keys %$tabs) {
+<li><font size="+2"><a href="<% $tabs->{$key}{path} %>"><% $tabs->{$key}{title} %></a></font><br>
+<% $tabs->{$key}{text} %>
+</li>
+% }
+</ul>
+
+<%INIT>
+ my $tabs = {
+
+ A => { title => loc('Scrips'),
+ text => loc('Modify scrips which apply to all queues'),
+ path => 'Scrips.html',
+ },
+ B => { title => loc('Templates'),
+ text => loc('Edit system templates'),
+ path => 'Templates.html',
+ },
+
+ F => { title => loc('Custom Fields'),
+ text => loc('Modify Custom Fields which apply to all queues'),
+ path => 'CustomFields.html',
+ },
+
+ G => { title => loc('Group Rights'),
+ text => loc('Modify global group rights'),
+ path => 'GroupRights.html',
+ },
+ H => { title => loc('User Rights'),
+ text => loc('Modify global user rights'),
+ path => 'UserRights.html',
+ }
+
+};
+</%INIT>
diff --git a/rt/html/Admin/Groups/GroupRights.html b/rt/html/Admin/Groups/GroupRights.html
new file mode 100644
index 0000000..6220259
--- /dev/null
+++ b/rt/html/Admin/Groups/GroupRights.html
@@ -0,0 +1,95 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify group rights for group [_1]', $GroupObj->Name) &>
+<& /Admin/Elements/GroupTabs,
+ GroupObj => $GroupObj,
+ current_tab => 'Admin/Groups/GroupRights.html?id='.$id,
+ Title => loc('Modify group rights for group [_1]', $GroupObj->Name) &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="GroupRights.html">
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<% $GroupObj->id %>">
+
+<& /Elements/TitleBoxStart, title => loc('Modify group rights for group [_1]', $GroupObj->Name) &>
+
+<h1><&|/l&>System groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToSystemInternalGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% loc($Group->Type) %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ PrincipalType => 'Group',
+ Object => $GroupObj &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<h1><&|/l&>User defined groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToUserDefinedGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $Group->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ PrincipalType => 'Group',
+ Object => $GroupObj &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+
+ <& /Elements/TitleBoxEnd &>
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+
+if (!defined $id) {
+ Abort(loc("No Group defined"));
+}
+
+my $GroupObj = RT::Group->new($session{'CurrentUser'});
+$GroupObj->Load($id) || Abort(loc("Couldn't load group [_1]",$id));
+
+my $Groups;
+
+</%INIT>
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Groups/Members.html b/rt/html/Admin/Groups/Members.html
new file mode 100644
index 0000000..6e66966
--- /dev/null
+++ b/rt/html/Admin/Groups/Members.html
@@ -0,0 +1,134 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => "RT/Admin/Edit the group ". $Group->Name &>
+<& /Admin/Elements/GroupTabs, GroupObj => $Group,
+ current_tab => 'Admin/Groups/Members.html?id='.$id,
+ Title => "RT/Admin/Edit the group ". $Group->Name &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+<& /Elements/TitleBoxStart, title => loc('Editing membership for group [_1]', $Group->Name) &>
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Groups/Members.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Group->Id%>">
+<TABLE WIDTH="100%">
+<TR>
+<TD>
+<h3><&|/l&>Current members</&></h3>
+</TD>
+<TD>
+<h3><&|/l&>Add members</&></h3>
+</TD>
+</TR>
+
+<TR>
+<TD VALIGN=TOP>
+
+% if ($Group->MembersObj->Count == 0 ) {
+<i><&|/l&>(No members)</&></i>
+% } else {
+<i><&|/l&>(Check box to delete)</&></i>
+<br>
+<br>
+<&|/l&>Users</&>
+% my $UserMembers = $Group->MembersObj;
+% $UserMembers->LimitToUsers();
+<UL>
+% while (my $member = $UserMembers->Next()) {
+<LI><INPUT TYPE=CHECKBOX Name="DeleteMember-<%$member->MemberId%>">
+<%$member->MemberObj->Object->Name%> (<%$member->MemberObj->Object->RealName%>)
+% }
+</ul>
+<&|/l&>Groups</&>
+<ul>
+% my $GroupMembers = $Group->MembersObj;
+% $GroupMembers->LimitToGroups();
+% while (my $member = $GroupMembers->Next()) {
+<LI><INPUT TYPE=CHECKBOX Name="DeleteMember-<%$member->MemberId%>">
+<%$member->MemberObj->Object->Name%>
+% }
+% }
+</UL>
+</TD>
+<TD VALIGN=TOP>
+<& /Admin/Elements/SelectNewGroupMembers, Name => "AddMembers", Group => $Group &>
+</TD>
+</TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</form>
+
+
+<%INIT>
+
+my $Group = new RT::Group($session{'CurrentUser'});
+$Group->Load($id) || Abort(loc('Could not load group'));
+
+my (@results);
+
+my $key;
+foreach $key (keys %ARGS) {
+
+if ($key =~ /^DeleteMember-(\d+)$/) {
+ my $id = $1;
+ my ($val,$msg) = $Group->DeleteMember($id);
+ push (@results, $msg);
+}
+}
+
+# Make sure AddMembers is always an array
+my @AddMembers = (
+ ((ref $AddMembersUsers eq 'ARRAY') ? @{$AddMembersUsers} : ($AddMembersUsers)),
+ ((ref $AddMembersGroups eq 'ARRAY') ? @{$AddMembersGroups} : ($AddMembersGroups)),
+);
+
+foreach my $member (@AddMembers) {
+ next unless ($member);
+
+ my $principal;
+
+ if ($member =~ /^Group-(\d+)$/) {
+ $principal = RT::Group->new($session{'CurrentUser'});
+ $principal->Load($1);
+ } elsif ($member =~ /^User-(\d+)$/) {
+ $principal = RT::User->new($session{'CurrentUser'});
+ $principal->Load($1);
+ } else {
+ next;
+ }
+
+
+ my ($val, $msg) = $Group->AddMember($principal->PrincipalId);
+ push (@results, $msg);
+}
+
+
+</%INIT>
+
+<%ARGS>
+$AddMembersUsers => undef
+$AddMembersGroups => undef
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Groups/Modify.html b/rt/html/Admin/Groups/Modify.html
new file mode 100644
index 0000000..c5e9158
--- /dev/null
+++ b/rt/html/Admin/Groups/Modify.html
@@ -0,0 +1,134 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+
+<& /Admin/Elements/GroupTabs,
+ GroupObj => $Group,
+ current_tab => $current_tab,
+ Title => $title &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Groups/Modify.html" METHOD=POST>
+
+%unless ($Group->Id) {
+<INPUT TYPE=HIDDEN NAME=id VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Group->Id%>">
+% }
+<TABLE>
+<TR><TD ALIGN=RIGHT>
+<&|/l&>Name</&>:
+</TD>
+<TD><INPUT name="Name" value="<%$Group->Name%>"></TD>
+</TR><TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:</TD><TD COLSPAN=3><INPUT name="Description" value="<%$Group->Description%>" size=60></TD>
+</TR><TR>
+<TD COLSPAN=2>
+<INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this group)</&><BR>
+</TR>
+</TABLE>
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</form>
+<%INIT>
+
+my $current_tab;
+my ($title, @results, $Disabled, $EnabledChecked);
+
+my $Group = RT::Group->new($session{'CurrentUser'});
+
+if ($Create) {
+ $current_tab = 'Admin/Groups/Modify.html?Create=1';
+ $title = loc("Create a new group");
+}
+
+else {
+ $current_tab = 'Admin/Groups/Modify.html?id='.$id;
+ if ($id eq 'new' ) {
+
+ my ($create_id, $create_msg) = $Group->CreateUserDefinedGroup(Name =>
+ "$Name");
+ unless ($create_id) {
+ Abort (loc("Group could not be created: [_1]", $create_msg));
+ }
+ $id = $Group->Id;
+ }
+ else {
+ $Group->Load($id) || Abort('Could not load group');
+ }
+
+
+ if ($id) {
+ $title = loc("Modify the group [_1]", $Group->Name);
+
+ }
+
+ # If the create failed
+ else {
+ $title = loc("Create a new group");
+ $Create = 1;
+ }
+
+}
+
+if ($id) {
+
+ my @fields = qw(Description Name );
+ my @fieldresults = UpdateRecordObject ( AttributesRef => \@fields,
+ Object => $Group,
+ ARGSRef => \%ARGS );
+ push (@results,@fieldresults);
+}
+
+#we're asking about enabled on the web page but really care about disabled.
+if ($Enabled == 1) {
+ $Disabled = 0;
+}
+else {
+ $Disabled = 1;
+}
+if ( ($SetEnabled) and ( $Disabled != $Group->Disabled) ) {
+ my ($code, $msg) = $Group->SetDisabled($Disabled);
+ push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+}
+
+unless ($Group->Disabled()) {
+ $EnabledChecked ="CHECKED";
+}
+
+
+</%INIT>
+
+
+<%ARGS>
+$Create => undef
+$Name => undef
+$Description => undef
+$SetEnabled => undef
+$Enabled => undef
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Groups/UserRights.html b/rt/html/Admin/Groups/UserRights.html
new file mode 100644
index 0000000..0a87ef8
--- /dev/null
+++ b/rt/html/Admin/Groups/UserRights.html
@@ -0,0 +1,92 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify user rights for group [_1]', $GroupObj->Name) &>
+<& /Admin/Elements/GroupTabs,
+ GroupObj => $GroupObj,
+ current_tab => 'Admin/Groups/UserRights.html?id='.$id,
+ Title => loc('Modify user rights for group [_1]', $GroupObj->Name) &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="UserRights.html">
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<% $GroupObj->id %>">
+
+<& /Elements/TitleBoxStart, title => loc('Modify user rights for group [_1]', $GroupObj->Name) &>
+
+<TABLE>
+
+% while (my $Member = $Users->Next()) {
+% my $UserObj = $Member->MemberObj->Object();
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $UserObj->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Member->MemberObj->Id,
+ PrincipalType => 'User',
+ Object => $GroupObj &>
+ </TD>
+ </TR>
+% }
+ </TABLE>
+
+ <& /Elements/TitleBoxEnd &>
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+# {{{ Deal with setting up the display of current rights.
+
+
+#Define vars used in html above
+
+
+if (!defined $id) {
+ Abort(loc("No Group defined"));
+}
+
+my $GroupObj = RT::Group->new($session{'CurrentUser'});
+$GroupObj->Load($id) || Abort(loc("Couldn't load group [_1]",$id));
+
+# Find out which users we want to display ACL selects for
+my $Privileged = RT::Group->new($session{'CurrentUser'});
+$Privileged->LoadSystemInternalGroup('Privileged');
+my $Users = $Privileged->MembersObj();
+
+
+
+# }}}
+
+</%INIT>
+
+<%ARGS>
+$id => undef
+$UserString => undef
+$UserOp => undef
+$UserField => undef
+</%ARGS>
diff --git a/rt/html/Admin/Groups/index.html b/rt/html/Admin/Groups/index.html
new file mode 100644
index 0000000..57c86c9
--- /dev/null
+++ b/rt/html/Admin/Groups/index.html
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/GroupTabs, current_tab => 'Admin/Groups/',
+ current_subtab => 'Admin/Groups/',
+ Title => $title &>
+
+
+<UL>
+%while ( my $Group = $Groups->Next) {
+<LI><A HREF="Modify.html?id=<%$Group->id%>"><%$Group->Name || loc('(empty)')%></a><BR>
+%}
+</UL>
+
+<%INIT>
+my $Groups = RT::Groups->new($session{'CurrentUser'});
+$Groups->LimitToUserDefinedGroups();
+my $title = loc('Select a group');
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/Admin/Queues/CustomField.html b/rt/html/Admin/Queues/CustomField.html
new file mode 100644
index 0000000..2515c3e
--- /dev/null
+++ b/rt/html/Admin/Queues/CustomField.html
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $QueueObj->Id,
+ QueueObj => $QueueObj,
+ current_tab => 'Admin/Queues/CustomFields.html?id='.$QueueObj->id,
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditCustomField, title => $title, %ARGS &>
+
+<%INIT>
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+
+my ($title, $current_subtab);
+
+unless($QueueObj->id) {
+ Abort(loc("Queue [_1] not found", $Queue));
+}
+if ($CustomField) {
+ $title = loc('Modify a CustomField for queue [_1]', $QueueObj->Name());
+}else {
+ $current_subtab = "Admin/Queues/CustomField.html?create=1&Queue=".$QueueObj->id;
+ $title = loc('Create a CustomField for queue [_1]', $QueueObj->Name());
+}
+
+my $subtabs = {
+ A => { title => loc('New custom field'),
+ path => "Admin/Queues/CustomField.html?create=1&Queue=".$QueueObj->id
+ }
+ };
+
+</%INIT>
+<%ARGS>
+$CustomField => undef
+$Queue => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/CustomFields.html b/rt/html/Admin/Queues/CustomFields.html
new file mode 100644
index 0000000..ddf39d7
--- /dev/null
+++ b/rt/html/Admin/Queues/CustomFields.html
@@ -0,0 +1,49 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $Queue->id,
+ current_tab => 'Admin/Queues/CustomFields.html?id='.$id,
+ QueueObj => $Queue,
+ subtabs => $subtabs,
+ Title => $title
+ &>
+
+<& /Admin/Elements/EditCustomFields, title => $title, %ARGS &>
+<%INIT>
+my $Queue = new RT::Queue($session{'CurrentUser'});
+$Queue->Load($id) || Abort(loc("Couldn't load queue", $id));
+
+my $CustomFields = RT::CustomFields->new($RT::SystemUser);
+$CustomFields->LimitToQueue($Queue->Id);
+my $subtabs = {
+ A => { title => loc('New custom field'),
+ path => "Admin/Queues/CustomField.html?create=1&Queue=".$id,
+ }
+ };
+
+my $title= loc('Edit Custom Fields for [_1]', $Queue->Name);
+</%INIT>
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/GroupRights.html b/rt/html/Admin/Queues/GroupRights.html
new file mode 100644
index 0000000..a1ac709
--- /dev/null
+++ b/rt/html/Admin/Queues/GroupRights.html
@@ -0,0 +1,110 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify group rights for queue [_1]', $QueueObj->Name) &>
+<& /Admin/Elements/QueueTabs, id => $id,
+ QueueObj => $QueueObj,
+ current_tab => $current_tab,
+ Title => loc('Modify group rights for queue [_1]', $QueueObj->Name) &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="GroupRights.html">
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<% $QueueObj->id %>">
+
+
+<h1><&|/l&>System groups</&></h1>
+<TABLE>
+<& /Elements/Callback, QueueObj => $QueueObj, results => \@results, %ARGS &>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToSystemInternalGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% loc($Group->Type) %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object => $QueueObj &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<h1><&|/l&>Roles</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToRolesForQueue($QueueObj->Id);
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% loc($Group->Type) %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object => $QueueObj &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+<h1><&|/l&>User defined groups</&></h1>
+<TABLE>
+% $Groups = RT::Groups->new($session{'CurrentUser'});
+% $Groups->LimitToUserDefinedGroups();
+% while (my $Group = $Groups->Next()) {
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $Group->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId => $Group->PrincipalId,
+ Object => $QueueObj &>
+ </TD>
+ </TR>
+% }
+</TABLE>
+
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+
+if (!defined $id) {
+ Abort(loc("No Queue defined"));
+}
+
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+$QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
+
+my $Groups;
+my $current_tab;
+$current_tab = 'Admin/Queues/GroupRights.html?id='.$QueueObj->id;
+
+</%INIT>
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/Modify.html b/rt/html/Admin/Queues/Modify.html
new file mode 100644
index 0000000..46608eb
--- /dev/null
+++ b/rt/html/Admin/Queues/Modify.html
@@ -0,0 +1,163 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Admin/Queue/Basics') &>
+<& /Admin/Elements/QueueTabs, id => $QueueObj->id,
+ QueueObj => $QueueObj,
+ current_tab => $current_tab,
+ Title => loc('Admin/Queue/Basics') &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Queues/Modify.html" METHOD=POST>
+%if ($Create ) {
+<INPUT TYPE=HIDDEN NAME=id VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$QueueObj->Id%>">
+% }
+
+<TABLE>
+<TR><TD ALIGN=RIGHT>
+<&|/l&>Queue Name</&>:
+</TD>
+<TD><INPUT name="Name" value="<% ($Create) ? "" : $QueueObj->Name %>"></TD>
+</TR><TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:</TD><TD COLSPAN=3><INPUT name="Description" value="<% ($Create) ? "" : $QueueObj->Description %>" size=60></TD></TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Correspondence Address</&>:
+</TD><TD>
+<INPUT name="CorrespondAddress" value="<% ($Create) ? "" : $QueueObj->CorrespondAddress %>">
+<BR><font size="-1"><i><&|/l , $RT::CorrespondAddress&>(If left blank, will default to [_1]</&></i></font>
+</TD>
+<TD ALIGN=RIGHT>
+
+<&|/l&>Comment Address</&>: </TD><TD>
+<INPUT NAME="CommentAddress" value="<% ($Create) ? "" : $QueueObj->CommentAddress %>">
+<BR><font size="-1"><i><&|/l , $RT::CommentAddress&>(If left blank, will default to [_1]</&></i></font>
+</TD>
+</TR><TR>
+
+<TD ALIGN=RIGHT>
+<&|/l&>Priority starts at</&>:
+</TD><TD><INPUT NAME="InitialPriority" value="<% ($Create) ? "" : $QueueObj->InitialPriority %>">
+</TD>
+<TD ALIGN=RIGHT>
+<&|/l&>Over time, priority moves toward</&>:
+</TD><TD><INPUT NAME="FinalPriority" value="<% ($Create) ? "" : $QueueObj->FinalPriority %>">
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Requests should be due in</&>:
+</TD><TD>
+<INPUT NAME="DefaultDueIn" VALUE="<% ($Create) ? "" : $QueueObj->DefaultDueIn%>"> <&|/l&>days</&>.
+</TD>
+</TR>
+<TR>
+<TD>
+</TD>
+<TD COLSPAN=4><INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this queue)</&><BR>
+<& /Elements/Callback, QueueObj => $QueueObj, results => \@results, %ARGS &>
+</TD>
+</TR>
+
+</TABLE>
+<& /Elements/Submit &>
+</form>
+
+
+
+<%INIT>
+my $current_tab;
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($id);
+my ($title, @results, $Disabled, $EnabledChecked);
+$EnabledChecked = "CHECKED";
+
+if ($Create) {
+ $current_tab = 'Admin/Queues/Modify.html?Create=1';
+ $title = loc("Create a queue");
+} else {
+ if ($id eq 'new') {
+ my ($val, $msg) = $QueueObj->Create(Name => $Name);
+ delete $session{'create_in_queues'};
+ if ($val == 0 ) {
+ Abort("$msg");
+ }
+ else {
+ push @results, $msg;
+ }
+ }
+ else {
+ $QueueObj->Load($id) || $QueueObj->Load($Name) || Abort("Couldn't load queue '$Name'");
+ }
+ $title = loc('Editing Configuration for queue [_1]', $QueueObj->Name);
+
+ $current_tab = 'Admin/Queues/Modify.html?id='.$QueueObj->id;
+}
+if ($QueueObj->Id()) {
+ delete $session{'create_in_queues'};
+my @attribs= qw(Description CorrespondAddress CommentAddress Name
+ InitialPriority FinalPriority DefaultDueIn);
+
+ @results = UpdateRecordObject( AttributesRef => \@attribs,
+ Object => $QueueObj,
+ ARGSRef => \%ARGS);
+
+ #we're asking about enabled on the web page but really care about disabled.
+ if ($Enabled == 1) {
+ $Disabled = 0;
+ }
+ else {
+ $Disabled = 1;
+ }
+ if ( ($SetEnabled) and ( $Disabled != $QueueObj->Disabled) ) {
+ my ($code, $msg) = $QueueObj->SetDisabled($Disabled);
+ push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+ }
+
+ if ($QueueObj->Disabled()) {
+ $EnabledChecked ="";
+ }
+}
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+$result => undef
+$Name => undef
+$Create => undef
+$Description => undef
+$CorrespondAddress => undef
+$CommentAddress => undef
+$InitialPriority => undef
+$FinalPriority => undef
+$DefaultDueIn => undef
+$SetEnabled => undef
+$Enabled => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/People.html b/rt/html/Admin/Queues/People.html
new file mode 100644
index 0000000..e0a7345
--- /dev/null
+++ b/rt/html/Admin/Queues/People.html
@@ -0,0 +1,186 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc('Modify people related to queue [_1]', $QueueObj->Name) &>
+<& /Admin/Elements/QueueTabs, id => $id,
+ QueueObj => $QueueObj,
+ current_tab => $current_tab,
+ Title => loc('Modify people related to queue [_1]', $QueueObj->Name) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+
+<FORM METHOD=POST ACTION="People.html">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$QueueObj->Id%>">
+
+<TABLE WIDTH=100%>
+<TR>
+<TD VALIGN=TOP >
+
+<h3><&|/l&>Current watchers</&></h3>
+
+
+<&|/l&>Cc</&>:
+
+<& /Admin/Elements/EditQueueWatchers, QueueObj => $QueueObj, Watchers => $QueueObj->Cc &>
+
+<&|/l&>Administrative Cc</&>:
+
+<& /Admin/Elements/EditQueueWatchers, QueueObj => $QueueObj, Watchers => $QueueObj->AdminCc &>
+
+
+</TD>
+<TD VALIGN=TOP>
+<h3><&|/l&>New watchers</&></h3>
+
+<&|/l&>Find people whose</&><BR>
+<& /Elements/SelectUsers &>
+<input type=submit name="OnlySearchForPeople" value="<&|/l&>Go!</&>">
+<BR>
+<&|/l&>Find group whose</&><BR>
+<& /Elements/SelectGroups &>
+<input type=submit name="OnlySearchForGroup" value="<&|/l&>Go!</&>">
+
+<p>
+<&|/l&>Add new watchers</&>:<br>
+<p>
+<b><&|/l&>Users</&></b>
+% if ($user_msg) {
+<br>
+<i><%$user_msg%></i>
+% } elsif ($Users) {
+<ul>
+% while (my $u = $Users->Next ) {
+<li><&/Elements/SelectWatcherType, Scope=>'queue', Name =>
+"Queue-AddWatcher-Principal-".$u->PrincipalId &> <%$u->Name%>
+(<%$u->RealName%>)
+% }
+</ul>
+% }
+
+<p>
+<b><&|/l&>Groups</&></b>
+
+% if ($group_msg) {
+<br>
+<i><%$group_msg%></i>
+% } elsif ($Groups) {
+<ul>
+% while (my $g = $Groups->Next ) {
+<li><&/Elements/SelectWatcherType, Scope=>'queue', Name =>
+"Queue-AddWatcher-Principal-".$g->PrincipalId &> <%$g->Name%>
+(<%$g->Description%>)
+% }
+</ul>
+% }
+
+</TD>
+</TR>
+</TABLE>
+
+
+
+
+<& /Elements/Submit, Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), Reset => 1 &>
+</form>
+
+<%INIT>
+
+my $current_tab;
+my ($field, @results, $User, $Users, $Groups, $watcher, $user_msg, $group_msg);
+
+# {{{ Load the queue
+#If we get handed two ids, mason will make them an array. bleck.
+# We want teh first one. Just because there's no other sensible way
+# to deal
+
+
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($id) || Abort(loc("Couldn't load queue", $id));
+# }}}
+
+# {{{ Delete deletable watchers
+
+foreach my $key (keys %ARGS) {
+ my $id = $QueueObj->Id;
+
+ if (($key =~ /^Queue-$id-DelWatcher-Type-(.*?)-Principal-(\d*)$/)) {;
+ my ($code, $msg) = $QueueObj->DeleteWatcher(Type => $1,
+ PrincipalId => $2);
+ push @results, $msg;
+ }
+}
+# }}}
+
+# {{{ Add new watchers
+foreach my $key (keys %ARGS) {
+ #They're in this order because otherwise $1 gets clobbered :/
+ if ( ($ARGS{$key} =~ /^(AdminCc|Cc)$/) and
+ ($key =~ /^Queue-AddWatcher-Principal-(\d*)$/) ) {
+ $RT::Logger->debug("Adding a watcher $1 to ".$ARGS{$key}."\n");
+ my ($code, $msg) = $QueueObj->AddWatcher(Type => $ARGS{$key},
+ PrincipalId => $1);
+ push @results, $msg;
+ }
+}
+
+# }}}
+
+
+
+if (!length $ARGS{'UserString'}) {
+$user_msg = loc("No principals selected.");
+ }
+else {
+ $Users = new RT::Users($session{'CurrentUser'});
+ $Users->Limit(FIELD => $ARGS{'UserField'},
+ VALUE => $ARGS{'UserString'},
+ OPERATOR => $ARGS{'UserOp'});
+ }
+
+if (!length $ARGS{'GroupString'}) {
+$group_msg = loc("No principals selected.");
+ }
+else {
+$Groups = new RT::Groups($session{'CurrentUser'});
+$Groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined');
+$Groups->Limit(FIELD => $ARGS{'GroupField'},
+ VALUE => $ARGS{'GroupString'},
+ OPERATOR => $ARGS{'GroupOp'});
+ }
+
+$current_tab = 'Admin/Queues/People.html?id='.$QueueObj->id;
+</%INIT>
+
+<%ARGS>
+$UserField => 'Name'
+$UserOp => '='
+$UserString => undef
+$GroupField => 'Name'
+$GroupOp => '='
+$GroupString => undef
+$Type => undef
+$id => undef
+</%ARGS>
+
diff --git a/rt/html/Admin/Queues/Scrip.html b/rt/html/Admin/Queues/Scrip.html
new file mode 100644
index 0000000..edbfcd6
--- /dev/null
+++ b/rt/html/Admin/Queues/Scrip.html
@@ -0,0 +1,67 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $QueueObj->Id,
+ QueueObj => $QueueObj,
+ current_tab => 'Admin/Queues/Scrips.html?id='.$QueueObj->id,
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditScrip, title => $title, %ARGS &>
+<%init>
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue);
+
+my ($title, $current_subtab);
+my $subtabs = {
+ A => { title => loc('Select scrip'),
+ path => "Admin/Queues/Scrips.html?id=".$QueueObj->id,
+ },
+ B => { title => loc('New scrip'),
+ path => "Admin/Queues/Scrip.html?create=1&Queue=".$QueueObj->id,
+ separator => 1,
+ },
+ };
+
+unless($QueueObj->id) {
+ Abort(loc("Queue [_1] not found",$id));
+}
+if ($id) {
+ $current_subtab = "Admin/Queues/Scrip.html?id=".$id."&Queue=".$QueueObj->id;
+ $title = loc("Modify a scrip for queue [_1]", $QueueObj->Name);
+ $subtabs->{"C"} = { title => loc("Scrip #[_1]",$QueueObj->id),
+ path => "Admin/Queues/Scrip.html?id=$id&Queue=".$QueueObj->id };
+} else {
+ $current_subtab = "Admin/Queues/Scrip.html?create=1&Queue=".$QueueObj->id;
+ $title = loc("Create a scrip for queue [_1]", $QueueObj->Name);
+}
+
+
+</%init>
+
+<%ARGS>
+$id => undef
+$Queue => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/Scrips.html b/rt/html/Admin/Queues/Scrips.html
new file mode 100644
index 0000000..60b2831
--- /dev/null
+++ b/rt/html/Admin/Queues/Scrips.html
@@ -0,0 +1,63 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $QueueObj->id,
+ QueueObj => $QueueObj,
+ current_tab => 'Admin/Queues/Scrips.html?id='.$id,
+ current_subtab => 'Admin/Queues/Scrips.html?id='.$id,
+ subtabs => $subtabs,
+ Title => $title &>
+
+% if (!$QueueObj->Disabled) { # Global scrips does not apply to disabled queues
+<h2><&|/l&>Scrips which apply to all queues</&></h2>
+<& /Admin/Elements/ListGlobalScrips &>
+<BR>
+% }
+<& /Admin/Elements/EditScrips, title => $title, %ARGS &>
+<%init>
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($id);
+
+my $title;
+
+if ($QueueObj->id) {
+ $title = loc("Modify scrips for queue [_1]", $QueueObj->Name);
+} else {
+ Abort(loc("Queue [_1] not found",$id));
+}
+
+my $subtabs = {
+ A => { title => loc('Select scrip'),
+ path => "Admin/Queues/Scrips.html?id=".$id,
+ },
+ B => { title => loc('New scrip'),
+ path => "Admin/Queues/Scrip.html?create=1&Queue=".$id,
+ separator => 1,
+ }
+ };
+</%init>
+
+<%ARGS>
+$id => undef #some identifier that a Queue could
+</%ARGS>
diff --git a/rt/html/Admin/Queues/Template.html b/rt/html/Admin/Queues/Template.html
new file mode 100644
index 0000000..994de61
--- /dev/null
+++ b/rt/html/Admin/Queues/Template.html
@@ -0,0 +1,101 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $Queue,
+ QueueObj => $TemplateObj->QueueObj,
+ current_tab => 'Admin/Queues/Templates.html?id='.$Queue,
+ current_subtab => $current_subtab,
+ subtabs => $subtabs,
+ Title => $title &>
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM METHOD=POST ACTION="Template.html">
+%if ($Create ) {
+<INPUT TYPE=HIDDEN NAME="Template" VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME="Template" VALUE="<%$TemplateObj->Id%>">
+% }
+
+%# hang onto the queue id
+<INPUT TYPE=HIDDEN name="Queue" value="<%$Queue%>">
+<& /Admin/Elements/ModifyTemplate, Name => $TemplateObj->Name, Description =>
+$TemplateObj->Description, Content => $TemplateObj->Content &>
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</FORM>
+
+
+<%INIT>
+
+my $TemplateObj = new RT::Template($session{'CurrentUser'});
+my ($title, @results, $current_subtab);
+
+my $subtabs = {
+ A => { title => loc('Select template'),
+ path => "Admin/Queues/Templates.html?id=$Queue"
+ },
+ B => { title => loc('New template'),
+ path => "Admin/Queues/Template.html?Create=1&Queue=$Queue",
+ separator => 1,
+ }
+ };
+
+if ($Create) {
+ $title = loc("Create a template");
+ $current_subtab = "Admin/Queues/Template.html?create=1&Queue=".$Queue;
+}
+
+else {
+ if ($Template eq 'new') {
+ my ($val, $msg) = $TemplateObj->Create(Queue => $Queue, Name => $Name);
+ Abort(loc("Could not create template: [_1]", $msg)) unless ($val);
+ push @results, $msg;
+ }
+ else {
+ $TemplateObj->Load($Template) || Abort(loc('No Template'));
+ }
+ $title = loc('Modify template [_1]', loc($TemplateObj->Name()));
+
+
+}
+if ($TemplateObj->Id()) {
+ $Queue = $TemplateObj->Queue;
+
+ my @attribs = qw( Description Content Queue Name);
+ my @aresults = UpdateRecordObject( AttributesRef => \@attribs,
+ Object => $TemplateObj,
+ ARGSRef => \%ARGS);
+ $current_subtab = "Admin/Queues/Template.html?Queue=$Queue&Template=".$TemplateObj->Id();
+ $subtabs->{"C"} = { title => loc('Template #[_1]', $TemplateObj->Id()),
+ path => "Admin/Queues/Template.html?Queue=$Queue&Template=".$TemplateObj->Id(),
+ };
+ push @results, @aresults;
+}
+
+</%INIT>
+<%ARGS>
+$Queue => undef
+$Template => undef
+$Create => undef
+$Name => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/Templates.html b/rt/html/Admin/Queues/Templates.html
new file mode 100644
index 0000000..98bdf24
--- /dev/null
+++ b/rt/html/Admin/Queues/Templates.html
@@ -0,0 +1,57 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/QueueTabs, id => $QueueObj->id,
+ current_tab => 'Admin/Queues/Templates.html?id='.$id,
+ current_subtab => 'Admin/Queues/Templates.html?id='.$id,
+ QueueObj => $QueueObj,
+ subtabs => $subtabs,
+ Title => $title &>
+
+<& /Admin/Elements/EditTemplates, title => $title, %ARGS &>
+
+<%INIT>
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($id);
+
+my ($title, $current_subtab);
+
+if ($QueueObj->id) {
+ $title = loc("Edit Templates for queue [_1]", $QueueObj->Name);
+} else {
+ Abort(loc("Queue [_1] not found",$id));
+}
+my $subtabs = {
+ A => { title => loc('Select template'),
+ path => "Admin/Queues/Templates.html?id=".$id,
+ },
+ B => { title => loc('New template'),
+ path => "Admin/Queues/Template.html?Create=1&Queue=".$id,
+ }
+ };
+
+</%INIT>
+<%ARGS>
+$id => undef #some identifier that a Queue could
+</%ARGS>
diff --git a/rt/html/Admin/Queues/UserRights.html b/rt/html/Admin/Queues/UserRights.html
new file mode 100644
index 0000000..aeb55c7
--- /dev/null
+++ b/rt/html/Admin/Queues/UserRights.html
@@ -0,0 +1,90 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Modify user rights for queue [_1]', $QueueObj->Name) &>
+<& /Admin/Elements/QueueTabs, id => $id,
+ QueueObj => $QueueObj,
+ current_tab => $current_tab,
+ Title => loc('Modify user rights for queue [_1]', $QueueObj->Name) &>
+<& /Elements/ListActions, actions => \@results &>
+
+ <FORM METHOD=POST ACTION="UserRights.html">
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<% $QueueObj->id %>">
+
+
+<TABLE>
+<& /Elements/Callback, QueueObj => $QueueObj, results => \@results, %ARGS &>
+% while (my $Member = $Users->Next()) {
+% my $UserObj = $Member->MemberObj->Object();
+% my $group = RT::Group->new($session{'CurrentUser'});
+% $group->LoadACLEquivalenceGroup($Member->MemberObj);
+ <TR ALIGN=RIGHT>
+ <TD VALIGN=TOP>
+ <% $UserObj->Name %>
+ </TD>
+ <TD>
+ <& /Admin/Elements/SelectRights, PrincipalId=> $group->PrincipalId,
+ Object => $QueueObj &>
+ </TD>
+ </TR>
+% }
+ </TABLE>
+
+ <& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+
+ </FORM>
+
+<%INIT>
+
+ #Update the acls.
+ my @results = ProcessACLChanges(\%ARGS);
+
+# {{{ Deal with setting up the display of current rights.
+
+
+
+if (!defined $id) {
+ Abort(loc("No Queue defined"));
+}
+
+my $QueueObj = RT::Queue->new($session{'CurrentUser'});
+$QueueObj->Load($id) || Abort(loc("Couldn't load queue [_1]",$id));
+
+# Find out which users we want to display ACL selects for
+my $Privileged = RT::Group->new($session{'CurrentUser'});
+$Privileged->LoadSystemInternalGroup('Privileged');
+my $Users = $Privileged->MembersObj();
+
+
+
+# }}}
+my $current_tab;
+$current_tab = 'Admin/Queues/UserRights.html?id='.$QueueObj->id;
+</%INIT>
+
+<%ARGS>
+$id => undef
+$UserString => undef
+$UserOp => undef
+$UserField => undef
+</%ARGS>
diff --git a/rt/html/Admin/Queues/index.html b/rt/html/Admin/Queues/index.html
new file mode 100644
index 0000000..78a1d5d
--- /dev/null
+++ b/rt/html/Admin/Queues/index.html
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc("Admin queues") &>
+<& /Admin/Elements/QueueTabs, current_tab => 'Admin/Queues/',
+ current_subtab => 'Admin/Queues/',
+ Title => loc("Admin queues") &>
+
+
+
+<%$caption%>:<BR>
+<UL>
+%if ($queues->Count == 0) {
+<LI> <i><&|/l&>No queues matching search criteria found.</&></i>
+% }
+%while ( $queue = $queues->Next) {
+<LI><A HREF="Modify.html?id=<%$queue->id%>"><%$queue->Name%></a></LI>
+%}
+</UL>
+<BR>
+<FORM METHOD=POST ACTION="<% $RT::WebPath %>/Admin/Queues/index.html">
+<input type="checkbox" name="FindDisabledQueues"> <&|/l&>Include disabled queues in listing.</&>
+<div align=right><input type=submit value="<&|/l&>Go!</&>"></div>
+</FORM>
+
+<%INIT>
+my ($queue, $caption);
+my $queues = new RT::Queues($session{'CurrentUser'});
+$queues->UnLimit();
+
+if ($FindDisabledQueues) {
+ $caption = loc("All Queues");
+ $queues->{'find_disabled_rows'} = 1;
+} else {
+ $caption = loc("Enabled Queues");
+}
+
+</%INIT>
+<%ARGS>
+$FindDisabledQueues => 0
+</%ARGS>
diff --git a/rt/html/Admin/Users/Modify.html b/rt/html/Admin/Users/Modify.html
new file mode 100644
index 0000000..b424ae9
--- /dev/null
+++ b/rt/html/Admin/Users/Modify.html
@@ -0,0 +1,349 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => $title &>
+<& /Admin/Elements/UserTabs,
+ id => $id,
+ UserObj => $UserObj,
+ current_subtab => $current_tab,
+ Title => $title &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM ACTION="<%$RT::WebPath%>/Admin/Users/Modify.html" METHOD=POST>
+%if ($Create) {
+<INPUT TYPE=HIDDEN NAME=id VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$UserObj->Id%>">
+% }
+<TABLE WIDTH=100% BORDER=0>
+<TR>
+
+<TD VALIGN=TOP ROWSPAN=2>
+<& /Elements/TitleBoxStart, title => loc('Identity') &>
+
+<TABLE>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Username</&>:
+</TD><TD>
+<input name="Name" value="<%$UserObj->Name%>"> <b><&|/l&>(required)</&></b>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Email</&>:
+</TD><TD>
+<input name="EmailAddress" value="<%$UserObj->EmailAddress%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Real Name</&>:
+</TD><TD>
+<input name="RealName" value="<%$UserObj->RealName%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Nickname</&>:
+</TD><TD>
+<input name="NickName" value="<%$UserObj->NickName%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Unix login</&>:
+</TD><TD>
+<input name="Gecos" value="<%$UserObj->Gecos%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Extra info</&>:
+</TD><TD>
+<textarea name="FreeformContactInfo" cols=20 rows=5><%$UserObj->FreeformContactInfo%></TEXTAREA>
+</TD></TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<br>
+<& /Elements/TitleBoxStart, title => loc('Access control') &>
+<INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>>
+<&|/l&>Let this user access RT</&><BR>
+
+
+<INPUT TYPE=HIDDEN NAME="SetPrivileged" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Privileged" VALUE="1" <%$PrivilegedChecked%>> <&|/l&>Let this user be granted rights</&><BR>
+
+% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
+<TABLE>
+<TR>
+<TD ALIGN=RIGHT>
+<&|/l&>New Password</&>:
+</TD>
+<TD ALIGN=LEFT>
+<input type=password name="Pass1">
+</TD>
+</TR>
+<TR><TD ALIGN=RIGHT>
+<&|/l&>Retype Password</&>:
+</TD>
+<TD>
+<input type=password name="Pass2">
+</TD>
+</TR>
+</TABLE>
+% }
+<& /Elements/TitleBoxEnd &>
+</TD>
+</TR>
+<TR>
+
+<TD VALIGN=TOP>
+<& /Elements/TitleBoxStart, title => loc('Location') &>
+<TABLE>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Organization</&>:
+</TD><TD>
+<input name="Organization" value="<%$UserObj->Organization%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Address1</&>:
+</TD><TD>
+<input name="Address1" value="<%$UserObj->Address1%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Address2</&>:
+</TD><TD>
+<input name="Address2" value="<%$UserObj->Address2%>">
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>City</&>:
+</TD><TD>
+<input name="City" value="<%$UserObj->City%>" size=14>
+
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>State</&>:
+</TD><TD>
+<input name="State" value="<%$UserObj->State%>" size=3>
+
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Zip</&>:
+</TD><TD>
+<input name="Zip" value="<%$UserObj->Zip%>" size=9>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Country</&>:
+</TD><TD>
+<input name="Country" value="<%$UserObj->Country%>">
+</TD></TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<br>
+<& /Elements/TitleBoxStart, title => loc('Phone numbers') &>
+<TABLE>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Residence</&>:
+</TD><TD>
+<input name="HomePhone" value="<%$UserObj->HomePhone%>" size=13><br>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Work</&>:
+</TD><TD>
+<input name="WorkPhone" value="<%$UserObj->WorkPhone%>" size=13><br>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Mobile</&>:
+</TD><TD>
+<input name="MobilePhone" value="<%$UserObj->MobilePhone%>" size=13><br>
+</TD></TR>
+<TR><TD ALIGN="RIGHT">
+<&|/l&>Pager</&>:
+</TD><TD>
+<input name="PagerPhone" value="<%$UserObj->PagerPhone%>" size=13><br>
+<& /Elements/TitleBoxEnd &>
+</TD>
+
+<TR>
+</TR>
+</TABLE>
+<TR>
+<TD colspan="2">
+<& /Elements/TitleBoxStart, title => loc('Comments about this user') &>
+<TEXTAREA name="Comments" COLS=80 ROWS=5 WRAP=VIRTUAL><%$UserObj->Comments%>
+</TEXTAREA>
+<& /Elements/TitleBoxEnd &>
+%if ($UserObj->Privileged) {
+<BR>
+<& /Elements/TitleBoxStart, title => loc('Signature') &>
+<TEXTAREA COLS=80 ROWS=5 name="Signature" WRAP=HARD>
+<%$UserObj->Signature%></TEXTAREA>
+<& /Elements/TitleBoxEnd &>
+% }
+
+</TD>
+</TR>
+</TABLE>
+
+<& /Elements/Submit &>
+</form>
+
+<%INIT>
+
+my $current_tab;
+my $UserObj = new RT::User($session{'CurrentUser'});
+my ($title, $PrivilegedChecked, $EnabledChecked, $Disabled, $result, @results);
+
+my ($val, $msg);
+
+if ($Create) {
+ $current_tab = 'Admin/Users/Modify.html?Create=1';
+ $title = loc("Create a new user");
+}
+else {
+
+ $current_tab = 'Admin/Users/Modify.html?id='.$id;
+ if ($id eq 'new') {
+ ($val, $msg) = $UserObj->Create( Name => $Name,
+ EmailAddress => $ARGS{'EmailAddress'}
+ );
+ if ($val) {
+ push @results, $msg;
+ } else {
+ push @results, loc('User could not be created: [_1]', $msg);
+ }
+
+ # set the id, so the the menu will have the right info
+ $id = $UserObj->Id;
+
+ } else {
+ $UserObj->Load($id) || $UserObj->Load($Name) || Abort("Couldn't load user '$Name'");
+ $val = $UserObj->Id();
+ }
+
+ if ($val) {
+ $title = loc("Modify the user [_1]", $UserObj->Name);
+ }
+
+ # If the create failed
+ else {
+ $title = loc("Create a new user");
+ $Create = 1;
+ }
+
+
+
+}
+
+
+
+
+# If we have a user to modify, lets try.
+if ($UserObj->Id) {
+
+ my @fields = qw(Name Comments Signature EmailAddress FreeformContactInfo
+ Organization RealName NickName Lang EmailEncoding WebEncoding
+ ExternalContactInfoId ContactInfoSystem Gecos ExternalAuthId
+ AuthSystem HomePhone WorkPhone MobilePhone PagerPhone Address1
+ Address2 City State Zip Country
+ );
+
+ my @fieldresults = UpdateRecordObject ( AttributesRef => \@fields,
+ Object => $UserObj,
+ ARGSRef => \%ARGS );
+ push (@results,@fieldresults);
+
+
+# {{{ Deal with special fields: Privileged, Enabled and Password
+if ( ($SetPrivileged) and ( $Privileged != $UserObj->Privileged) ) {
+my ($code, $msg) = $UserObj->SetPrivileged($Privileged);
+ push @results, loc('Privileged status: [_1]', loc_fuzzy($msg));
+}
+
+#we're asking about enabled on the web page but really care about disabled.
+if ($Enabled == 1) {
+ $Disabled = 0;
+}
+else {
+ $Disabled = 1;
+}
+if ( ($SetEnabled) and ( $Disabled != $UserObj->Disabled) ) {
+ my ($code, $msg) = $UserObj->SetDisabled($Disabled);
+ push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+}
+
+
+#TODO: make this report errors properly
+if ((defined $Pass1) and ($Pass1 ne '') and ($Pass1 eq $Pass2) and (!$UserObj->IsPassword($Pass1))) {
+ my ($code, $msg);
+ ($code, $msg) = $UserObj->SetPassword($Pass1);
+ push @results, loc('Password: [_1]', loc_fuzzy($msg));
+} elsif ( $Pass1 && ($Pass1 ne $Pass2)) {
+ push @results, loc("Passwords do not match.");
+}
+
+# }}}
+}
+
+
+# {{{ Do some setup for the ui
+unless ($UserObj->Disabled()) {
+ $EnabledChecked ="CHECKED";
+}
+
+if ($UserObj->Privileged()) {
+ $PrivilegedChecked = "CHECKED";
+}
+
+# }}}
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+$Name => undef
+$Comments => undef
+$Signature => undef
+$EmailAddress => undef
+$FreeformContactInfo => undef
+$Organization => undef
+$RealName => undef
+$NickName => undef
+$Privileged => undef
+$SetPrivileged => undef
+$Enabled => undef
+$SetEnabled => undef
+$Lang => undef
+$EmailEncoding => undef
+$WebEncoding => undef
+$ExternalContactInfoId => undef
+$ContactInfoSystem => undef
+$Gecos => undef
+$ExternalAuthId => undef
+$AuthSystem => undef
+$HomePhone => undef
+$WorkPhone => undef
+$MobilePhone => undef
+$PagerPhone => undef
+$Address1 => undef
+$Address2 => undef
+$City => undef
+$State => undef
+$Zip => undef
+$Country => undef
+$Pass1 => undef
+$Pass2=> undef
+$Create=> undef
+</%ARGS>
diff --git a/rt/html/Admin/Users/Prefs.html b/rt/html/Admin/Users/Prefs.html
new file mode 100644
index 0000000..0bba9fa
--- /dev/null
+++ b/rt/html/Admin/Users/Prefs.html
@@ -0,0 +1,122 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("User view") &>
+
+<& /Elements/ViewUser, User=>$u &>
+
+<h2 class="title"><%loc("User view")%></h2>
+
+%if ($session{CurrentUser} && ($session{CurrentUser}->Id == $id)) {
+ <& /Elements/TitleBoxStart, title => loc('Signature') &>
+<form method=post>
+<input type="hidden" name="id" value=<%$id%>>
+<TEXTAREA COLS=72 ROWS=4 WRAP=HARD NAME="Signature"><% $u->Signature %></TEXTAREA><br><br>
+<input type="submit" value="<&|/l&>Update signature</&>">
+</form>
+ <& /Elements/TitleBoxEnd &>
+ <form method=post>
+ <&|/l&>Open tickets (from listing) in another window</&>: <input type="checkbox" name="NewWindowOption" <%exists $session{NewWindowOption} && "CHECKED"%>><br>
+ <&|/l&>Open tickets (from listing) in a new window</&>: <input type="checkbox" name="AlwaysNewWindowOption" <%exists $session{AlwaysNewWindowOption} && "CHECKED"%>><br>
+ <input type="submit" name="NewWindowSetting" value="<&|/l&>New window setting</&>">
+ </form>
+%}
+
+ <& /Elements/TitleBoxStart, title => loc('Email') &>
+<form method=post>
+<input type="hidden" name="id" value="<%$id%>">
+<input name="Email" value="<% $u->EmailAddress %>"><input type="submit" value="<&|/l&>Update email</&>">
+</form>
+ <& /Elements/TitleBoxEnd &>
+ <& /Elements/TitleBoxStart, title => loc('Real Name') &>
+<form method=post>
+<input type="hidden" name="id" value="<%$id%>">
+<input name="RealName" value="<% $u->RealName %>"><input type="submit" value="<&|/l&>Update name</&>">
+</form>
+ <& /Elements/TitleBoxEnd &>
+
+ <& /Elements/TitleBoxStart, title => loc('User ID') &>
+<form method=post>
+<input type="hidden" name="id" value="<%$id%>">
+<input name="Name" value="<% $u->Name %>"><input type="submit" value="<&|/l&>Update ID</&>">
+</form>
+ <& /Elements/TitleBoxEnd &>
+
+%# TODO: alternative email addresses + merging users
+
+<%ARGS>
+$id => $session{CurrentUser} ? $session{CurrentUser}->Id : 0
+$Signature => undef
+$Email => undef
+$RealName => undef
+$Name => undef
+</%ARGS>
+
+<%INIT>
+require RT::User;
+my $u=RT::User->new($session{CurrentUser});
+$u->Load($id) || die loc("Couldn't load that user ([_1])", $id);
+if ($Signature) {
+my ($val, $msg)=$u->SetSignature($Signature);
+$RT::Logger->log(level=>($val ? 'info' : 'error'), message=>$msg);
+}
+
+if ($Email) {
+my ($val, $msg)=$u->SetEmailAddress($Email);
+$RT::Logger->log(level=>($val ? 'info' : 'error'), message=>$msg);
+}
+
+if ($RealName) {
+my ($val, $msg)=$u->SetRealName($RealName);
+$RT::Logger->log(level=>($val ? 'info' : 'error'), message=>$msg);
+}
+
+if ($Name) {
+my ($val, $msg)=$u->SetName($Name);
+$RT::Logger->log(level=>($val ? 'info' : 'error'), message=>$msg);
+}
+
+if ($ARGS{NewWindowSetting}) {
+if ($ARGS{NewWindowOption}) {
+$session{NewWindowOption}=1;
+} else {
+delete $session{NewWindowOption};
+}
+if ($ARGS{AlwaysNewWindowOption}) {
+$session{NewWindowOption}=1;
+$session{AlwaysNewWindowOption}=1;
+} else {
+delete $session{AlwaysNewWindowOption};
+}
+}
+
+</%INIT>
+
+
+
+
+
+
+
+
+
diff --git a/rt/html/Admin/Users/index.html b/rt/html/Admin/Users/index.html
new file mode 100644
index 0000000..7dc9af6
--- /dev/null
+++ b/rt/html/Admin/Users/index.html
@@ -0,0 +1,81 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('Select a user') &>
+<& /Admin/Elements/UserTabs, current_tab => 'Admin/Users/',
+ current_subtab => 'Admin/Users/',
+ Title => loc('Select a user') &>
+
+
+
+
+<%$caption%>:<BR>
+<UL>
+%if ($users->Count == 0) {
+<LI> <i><&|/l&>No users matching search criteria found.</&></i>
+% }
+%while ( $user = $users->Next) {
+<LI><A HREF="Modify.html?id=<%$user->id%>"><%$user->Name || loc('(no name listed)')%></a></LI>
+%}
+
+</UL>
+<br><br>
+<FORM METHOD=POST ACTION="<% $RT::WebPath %>/Admin/Users/index.html">
+
+<&|/l&>Find people whose</&> <& /Elements/SelectUsers &><BR>
+<input type="checkbox" name="FindDisabledUsers"> <&|/l&>Include disabled users in search.</&>
+<BR>
+<div align=right><input type=submit value="<&|/l&>Go!</&>"></div>
+</FORM>
+
+<%INIT>
+my ($user, $caption);
+my $users = new RT::Users($session{'CurrentUser'});
+
+if ($FindDisabledUsers) {
+ $users->{'find_disabled_rows'} = 1;
+}
+
+unless (defined $UserString) {
+ $users->LimitToPrivileged();
+ $caption = loc("Privileged users");
+}
+else {
+ $caption = loc("Users matching search criteria");
+
+ if ($UserString) {
+ $users->Limit( FIELD => $UserField,
+ OPERATOR => $UserOp,
+ VALUE => $UserString);
+
+}
+}
+</%INIT>
+<%ARGS>
+$UserString => undef
+$UserOp => '='
+$UserField => 'Name'
+$IdLike => undef
+$EmailLike => undef
+$FindDisabledUsers => 0
+</%ARGS>
diff --git a/rt/html/Admin/index.html b/rt/html/Admin/index.html
new file mode 100644
index 0000000..522ade8
--- /dev/null
+++ b/rt/html/Admin/index.html
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Admin/Elements/Header, Title => loc('RT Administration') &>
+<& /Admin/Elements/Tabs, Title => loc('RT Administration') &>
+
+<ul>
+<li><font size="+2"><a href="Users/"><&|/l&>Users</&></a></font><br>
+<&|/l&>Manage users and passwords</&>
+</li>
+<li><font size="+2"><a href="Groups/"><&|/l&>Groups</&></a></font><br>
+<&|/l&>Manage groups and group membership</&>
+</li>
+<li><font size="+2"><a href="Queues/"><&|/l&>Queues</&></a></font><br>
+<&|/l&>Manage queues and queue-specific properties</&>
+</li>
+<li><font size="+2"><a href="Global/"><&|/l&>Global</&></a></font><br>
+<&|/l&>Manage properties and configuration which apply to all queues</&>
+</li>
+</ul>
diff --git a/rt/html/Approvals/Display.html b/rt/html/Approvals/Display.html
new file mode 100644
index 0000000..6a265e2
--- /dev/null
+++ b/rt/html/Approvals/Display.html
@@ -0,0 +1,50 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title &>
+
+<& Elements/Tabs,
+ current_tab => "Approvals/Display.html",
+ Title => $title &>
+<form method=post action="<%$RT::WebPath%>/Approvals/index.html">
+
+<& /Elements/TitleBoxStart, title => $title &>
+<& /Ticket/Elements/ShowHistory , Ticket => $Ticket, Collapsed => 0, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowTitleBarCommands => 0 &>
+<hr>
+<table width=100%>
+<& Elements/Approve, ticket => $Ticket, ShowApproving => 0 &>
+</table>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit&>
+</form>
+<& Elements/ShowDependency, Ticket => $Ticket &>
+
+<%init>
+my $Ticket = LoadTicket($id);
+
+my $title = loc("Approval #[_1]: [_2]", $Ticket->Id, $Ticket->Subject);
+
+</%init>
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Approvals/Elements/Approve b/rt/html/Approvals/Elements/Approve
new file mode 100644
index 0000000..6a7cfa3
--- /dev/null
+++ b/rt/html/Approvals/Elements/Approve
@@ -0,0 +1,56 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<tr bgcolor="#b9b9ff">
+<td colspan=2><font size="3">
+<a href="<%$RT::WebPath%>/Approvals/Display.html?id=<%$ticket->Id%>"><% loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject) %></a> (<%loc($ticket->Status)%>)</font></td>
+</tr>
+% if ($ShowApproving) {
+% foreach my $approving ( $ticket->AllDependedOnBy( Type => 'ticket' ) ) {
+<tr bgcolor="#e9e9e9">
+<td colspan=2>
+<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<% $approving->Id %>"><&|/l, $approving->Id, $approving->Subject &>Originating ticket: #[_1]</&></a>
+</td>
+</tr>
+<tr><td colspan=2>
+<& /Ticket/Elements/ShowCustomFields, Ticket => $approving &>
+<& /Ticket/Elements/ShowHistory, Ticket => $approving, Collapsed => 0, ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowTitleBarCommands => 0 &>
+</td></tr>
+% }
+% }
+<tr <%$class && "class=\"$class\""%>>
+<td valign=top>
+<input type="radio" name="Approval-<%$ticket->Id%>-Action" value="approve"><&|/l&>Approve</&><br>
+<input type="radio" name="Approval-<%$ticket->Id%>-Action" value="deny"><&|/l&>Deny</&><br>
+<input type="radio" name="Approval-<%$ticket->Id%>-Action" value="none" checked><&|/l&>No action</&>
+</td>
+<td>
+<&|/l&>Notes</&><br>
+<textarea name="Approval-<%$ticket->Id%>-Notes" rows=2 cols=70></textarea>
+</td>
+</tr>
+<%ARGS>
+$ShowApproving => 1
+$ticket => undef
+$class => undef
+</%ARGS>
diff --git a/rt/html/Approvals/Elements/PendingMyApproval b/rt/html/Approvals/Elements/PendingMyApproval
new file mode 100644
index 0000000..b5cf007
--- /dev/null
+++ b/rt/html/Approvals/Elements/PendingMyApproval
@@ -0,0 +1,87 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table width="100%">
+% my ($i, $class);
+% my %done;
+% foreach ($tickets, $group_tickets) {
+% while (my $ticket = $_->Next() ) {
+% next if !$ARGS{'ShowDependent'} and $ticket->HasUnresolvedDependencies( Type => 'approval' );
+% next if $done{$ticket->Id}++; # don't show duplicate tickets
+% $i++;
+% $class = ($i%2) ? "oddline" : "evenline";
+<& Approve, ticket => $ticket, class => $class &>
+% }
+% }
+</table>
+
+<& /Elements/TitleBoxStart, title => loc("Search for approvals") &>
+<input type=checkbox value="1" name="ShowPending"
+ <%((!$ARGS{'ShowRejected'} && !$ARGS{'ShowResolved'}) ||
+ $ARGS{'ShowPending'})
+ && "checked"%>> <&|/l&>Show pending requests</&><br>
+<input type=checkbox value="1" name="ShowResolved" <%$ARGS{'ShowResolved'} && "checked"%>> <&|/l&>Show approved requests</&><br>
+<input type=checkbox value="1" name="ShowRejected" <%$ARGS{'ShowRejected'} && "checked"%>> <&|/l&>Show denied requests</&><br>
+<input type=checkbox value="1" name="ShowDependent" <%$ARGS{'ShowDependent'} && "checked"%>> <&|/l&>Show requests awaiting other approvals</&><br>
+
+<&|/l,"<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>"&>Only show approvals for requests created before [_1]</&><br>
+
+<&|/l, "<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>"&>Only show approvals for requests created after [_1]</&>
+<& /Elements/TitleBoxEnd &>
+
+<%init>
+my $tickets = RT::Tickets->new( $session{'CurrentUser'} );
+$tickets->LimitOwner( VALUE => $session{'CurrentUser'}->Id );
+
+# also consider AdminCcs as potential approvers.
+my $group_tickets = RT::Tickets->new( $session{'CurrentUser'} );
+
+my $created_before = RT::Date->new( $session{'CurrentUser'} );
+my $created_after = RT::Date->new( $session{'CurrentUser'} );
+
+foreach ($tickets, $group_tickets) {
+ $_->Limit( FIELD => 'Type', VALUE => 'approval' );
+
+ if ( $ARGS{'ShowResolved'} ) {
+ $_->LimitStatus( VALUE => 'resolved' );
+ }
+ if ( $ARGS{'ShowRejected'} ) {
+ $_->LimitStatus( VALUE => 'rejected' );
+ }
+ if ( $ARGS{'ShowPending'} || ( !$ARGS{'ShowRejected'} && !$ARGS{'Resolved'} ) ) {
+ $_->LimitStatus( VALUE => 'open' );
+ $_->LimitStatus( VALUE => 'new' );
+ $_->LimitStatus( VALUE => 'stalled' );
+ }
+
+ if ( $ARGS{'CreatedBefore'} ) {
+ $created_before->Set( Format => 'unknown', Value => $ARGS{'CreatedBefore'} );
+ $_->LimitCreated( OPERATOR => "<=", VALUE => $created_before->ISO );
+ }
+ if ( $ARGS{'CreatedAfter'} ) {
+ $created_after->Set( Format => 'unknown', Value => $ARGS{'CreatedAfter'} );
+ $_->LimitCreated( OPERATOR => ">=", VALUE => $created_after->ISO );
+ }
+}
+
+</%init>
diff --git a/rt/html/Approvals/Elements/ShowDependency b/rt/html/Approvals/Elements/ShowDependency
new file mode 100644
index 0000000..417cad1
--- /dev/null
+++ b/rt/html/Approvals/Elements/ShowDependency
@@ -0,0 +1,85 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% my $approving = $Ticket->DependedOnBy();
+% if ($approving->Count) {
+<h3><&|/l&>Tickets which depend on this approval:</&></h3>
+
+<table width=100%>
+<TR>
+<TD WIDTH="25" bgcolor="#999999">&nbsp;</TD><TD>
+<%PERL>
+my %show;
+while (my $link = $approving->Next()) {
+ next unless ($link->BaseURI->IsLocal());
+ my $text = '<a name="' . $link->BaseObj->Id . '">';
+ my $head = '';
+ my $type = $link->BaseObj->Type;
+ my $dep = $m->scomp('ShowDependency', Ticket => $link->BaseObj, _seen => $_seen);
+
+ if ($type eq 'approval') {
+ $head .= $m->scomp('/Elements/TitleBoxStart', title => loc("Approval #[_1]: [_2]", $link->BaseObj->Id, $link->BaseObj->Subject));
+ $text .= $head;
+ $text .= $m->scomp('/Ticket/Elements/ShowCustomFields', Ticket => $link->BaseObj);
+ } elsif ($type eq 'ticket') {
+ $head .= $m->scomp('/Elements/TitleBoxStart', title => loc("Ticket #[_1]: [_2]", $link->BaseObj->Id, $link->BaseObj->Subject));
+ $text .= $head;
+ $text .= $m->scomp('/Ticket/Elements/ShowSummary', Ticket => $link->BaseObj);
+ } else {
+ $head .= $m->scomp('/Elements/TitleBoxStart', title => loc("#[_1]: [_2]", $link->BaseObj->Id, $link->BaseObj->Subject));
+ $text .= $head;
+ }
+
+ $text .= $m->scomp('/Ticket/Elements/ShowHistory' , Ticket => $link->BaseObj, Collapsed => ($type ne 'ticket'), ShowTitle => 0, ShowHeaders => 0, ShowDisplayModes => 0, ShowTitleBarCommands => 0);
+
+ $head .= $m->scomp('/Elements/TitleBoxEnd');
+ $text .= $m->scomp('/Elements/TitleBoxEnd');
+ $text .= $dep;
+ $text .= '</a>';
+ $show{$link->BaseObj->Id} = {
+ text => $text,
+ head => $head,
+ };
+}
+
+my $refer;
+foreach my $id (sort keys %show) {
+ if ($_seen->{$id}++) {
+ $refer .= "<a href='#txn-$id'>" . $show{$id}{head} . "</a>";
+ next;
+ }
+
+ $m->print($show{$id}{text});
+}
+$m->print($refer);
+
+</%PERL>
+</TD>
+</TR>
+</TABLE>
+
+% }
+<%ARGS>
+$Ticket
+$_seen => {}
+</%ARGS>
diff --git a/rt/html/Approvals/Elements/Tabs b/rt/html/Approvals/Elements/Tabs
new file mode 100644
index 0000000..648ff75
--- /dev/null
+++ b/rt/html/Approvals/Elements/Tabs
@@ -0,0 +1,34 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Tabs,
+ tabs => $tabs,
+ current_toptab => 'Approvals/',
+ current_tab => $current_tab,
+ Title => $Title &>
+
+<%ARGS>
+$tabs => undef
+$current_tab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Approvals/index.html b/rt/html/Approvals/index.html
new file mode 100644
index 0000000..b4156f3
--- /dev/null
+++ b/rt/html/Approvals/index.html
@@ -0,0 +1,66 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("My approvals") &>
+<& /Approvals/Elements/Tabs, Title => loc("My approvals") &>
+
+<& /Elements/ListActions, actions => \@actions &>
+<form method="post">
+<& Elements/PendingMyApproval, %ARGS &>
+<& /Elements/Submit &>
+</form>
+<%init>
+
+my (@actions);
+foreach my $arg ( keys %ARGS ) {
+
+ next unless ( $arg =~ /Approval-(\d+)-Action/ );
+
+ my ( $notesval, $notesmsg );
+
+ my $ticket = LoadTicket($1);
+
+ if ( $ARGS{ "Approval-" . $ticket->Id . "-Notes" } ) {
+ my $notes = MIME::Entity->build(
+ Data => [ $ARGS{ "Approval-" . $ticket->Id . "-Notes" } ]
+ );
+ RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+ my ( $notesval, $notesmsg ) = $ticket->Correspond( MIMEObj => $notes );
+ if ($notesval) {
+ push ( @actions, loc("Approval #[_1]: Notes recorded",$ticket->Id ));
+ } else {
+ push ( @actions, loc("Approval #[_1]: Notes not recorded due to a system error",$ticket->Id ));
+ }
+ }
+
+ my ($val, $msg);
+ if ( $ARGS{$arg} eq 'deny' ) {
+ ( $val, $msg ) = $ticket->SetStatus('rejected');
+ }
+ elsif ( $ARGS{$arg} eq 'approve' ) {
+ ( $val, $msg ) = $ticket->SetStatus('resolved');
+ }
+ push ( @actions, loc("Approval #[_1]: [_2]",$ticket->id, $msg )) if ($msg);
+}
+</%init>
diff --git a/rt/html/Elements/BevelBoxRaisedEnd b/rt/html/Elements/BevelBoxRaisedEnd
new file mode 100644
index 0000000..ebf45df
--- /dev/null
+++ b/rt/html/Elements/BevelBoxRaisedEnd
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+ </TD>
+</TR>
+</table>
diff --git a/rt/html/Elements/BevelBoxRaisedStart b/rt/html/Elements/BevelBoxRaisedStart
new file mode 100644
index 0000000..c4e6c55
--- /dev/null
+++ b/rt/html/Elements/BevelBoxRaisedStart
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table cellspacing=0 cellpadding=0 width=100% height=100%>
+ <TR>
+ <TD width=100% height=100%>
diff --git a/rt/html/Elements/Callback b/rt/html/Elements/Callback
new file mode 100644
index 0000000..79157e7
--- /dev/null
+++ b/rt/html/Elements/Callback
@@ -0,0 +1,66 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%once>
+my (%cache, $check);
+</%once>
+<%init>
+# checks for inode change time for each callback directory
+my $new_check = join(
+ $;, map { $_->[1] => (stat("$_->[1]/Callbacks"))[10] } $m->interp->resolver->comp_root_array
+) or return;
+
+$Page = $m->callers(1)->path unless ($Page);
+
+my $callbacks;
+if ($new_check eq $check) {
+ $callbacks = $cache{$Page,$_CallbackName};
+}
+else {
+ $check = $new_check;
+}
+
+if (!$callbacks) {
+ my $path = "/Callbacks/*$Page/$_CallbackName";
+ $callbacks = [ $m->interp->resolver->glob_path($path) ];
+ @$callbacks = grep !/^\.|~$/, @$callbacks; #skip backup files
+
+ #skip files without a package
+ my $invalid_base = "/Callbacks/$Page/$_CallbackName";
+ @$callbacks = grep !/^$invalid_base$/, @$callbacks;
+
+
+
+ $cache{$Page,$_CallbackName} = $callbacks;
+}
+
+my @rv;
+foreach my $comp (sort @$callbacks) {
+ push @rv, $m->comp($comp, %ARGS) if $m->comp_exists($comp);
+}
+return @rv;
+</%init>
+<%args>
+$_CallbackName => 'Default'
+$Page => undef
+</%args>
diff --git a/rt/html/Elements/Checkbox b/rt/html/Elements/Checkbox
new file mode 100644
index 0000000..ae3d765
--- /dev/null
+++ b/rt/html/Elements/Checkbox
@@ -0,0 +1,39 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<INPUT TYPE="Checkbox" NAME ="<%$Name%>" <%$IsChecked%>>
+
+<%ARGS>
+$Name => undef
+$Default => undef
+$True => undef
+$False => undef
+$IsChecked => undef
+</%ARGS>
+
+<%INIT>
+$IsChecked =
+ ($Default && $Default =~ /checked/i)
+ ? " CHECKED " : "";
+1;
+</%INIT>
diff --git a/rt/html/Elements/CreateTicket b/rt/html/Elements/CreateTicket
new file mode 100644
index 0000000..7e1025d
--- /dev/null
+++ b/rt/html/Elements/CreateTicket
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<FORM ACTION="<% $RT::WebPath%>/Ticket/Create.html">
+<&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" value="New ticket in">&nbsp;[_1]</&>
+</FORM>
diff --git a/rt/html/Elements/Error b/rt/html/Elements/Error
new file mode 100644
index 0000000..dc44f12
--- /dev/null
+++ b/rt/html/Elements/Error
@@ -0,0 +1,62 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Callback, %ARGS, error => $error &>
+<& /Elements/Header, Code => $Code, Why => $Why &>
+<& /Elements/Tabs &>
+<& /Elements/TitleBoxStart, class=> "error", title => $Title &>
+<%$Why%>
+<br>
+<font size=-1>
+<%$Details%>
+</font>
+<& /Elements/TitleBoxEnd &>
+</body>
+</HTML>
+
+
+<%args>
+$Code => undef
+$Details => undef
+$Title => loc("RT Error")
+$Why => loc("the calling component did not specify why")
+</%args>
+
+<%INIT>
+my $error = "WebRT: $Why ($Details)";
+
+# TODO: Log::Dispatch isn't UTF-8 safe. Autrijus needs to talk to dave rolsky about getting this fixed
+if ($] >= 5.007001) {
+ require Encode;
+ Encode::_utf8_off($error);
+}
+
+$RT::Logger->error($error);
+
+if ( $session{'SessionType'} eq 'REST' ) {
+ $r->content_type('text/plain');
+ $m->out( "Error: " . $Why . "\n" );
+ $m->out( $Details . "\n" );
+ $m->abort();
+}
+</%INIT>
diff --git a/rt/html/Elements/Footer b/rt/html/Elements/Footer
new file mode 100644
index 0000000..5c833f8
--- /dev/null
+++ b/rt/html/Elements/Footer
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if ($Menu) {
+</td>
+</tr>
+<tr>
+<td>
+% }
+<& /Elements/Callback, %ARGS &>
+<div class="bpscredits">
+&#187;&#124;&#171; <&|/l, $RT::VERSION &>RT [_1] from <a href="http://bestpractical.com">Best Practical Solutions, LLC</a>.</&>
+</div>
+% if ($Debug) {
+<HR>
+<b><&|/l&>Time to display</&>: <%Time::HiRes::tv_interval( $m->{'rt_base_time'} )%></b>
+% }
+% if ($Debug >= 2 ) {
+% require Data::Dumper;
+% my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
+<pre>
+<%$d->Dump() %>
+</pre>
+% }
+% if ($Menu) {
+</TD>
+</TR>
+</TABLE>
+</TD>
+</TR>
+</TABLE>
+% }
+</BODY>
+</HTML>
+% $m->abort();
+
+<%ARGS>
+$Debug => 0
+$Menu => 1
+</%ARGS>
diff --git a/rt/html/Elements/GotoTicket b/rt/html/Elements/GotoTicket
new file mode 100644
index 0000000..bb0c04d
--- /dev/null
+++ b/rt/html/Elements/GotoTicket
@@ -0,0 +1,24 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<FORM ACTION="<%$RT::WebPath%>/Ticket/Display.html"><input type=submit value="<&|/l&>Goto ticket</&>">&nbsp;<input size=5 name=id accesskey="0"></FORM>
diff --git a/rt/html/Elements/Header b/rt/html/Elements/Header
new file mode 100644
index 0000000..0fd91a2
--- /dev/null
+++ b/rt/html/Elements/Header
@@ -0,0 +1,82 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML>
+<HEAD>
+<TITLE><%$Title%></TITLE>
+% if ($Refresh > 0) {
+<META HTTP-EQUIV="REFRESH" CONTENT="<%$Refresh%>">
+% }
+
+<link rel="shortcut icon" href="<%$RT::WebImagesURL%>/favicon.png" type="image/png">
+<link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/webrt.css" type="text/css">
+</HEAD>
+<BODY BGCOLOR="<%$BgColor%>"
+% if ($Focus) {
+ONLOAD="
+ var tmp = (document.getElementsByName('<% $Focus %>'));
+ if (tmp.length > 0) tmp[tmp.length-1].focus();
+"
+% }
+>
+<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF">
+ <tr>
+ <td colspan=2><a href="http://bestpractical.com"><img src="<%$RT::WebImagesURL%>/bplogo.gif" alt="" width="230" height="50"></a></td>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td width="50%" align="right">
+% if ($session{'CurrentUser'} && $session{'CurrentUser'}->Id && $LoggedIn) {
+<SPAN STYLE="display: none"><A HREF="#skipnav"><&|/l&>Skip Menu</&></A> |</SPAN>
+<A HREF="<%$RT::WebPath%><% $Prefs %>" ><&|/l&>Preferences</&></A>
+<& /Elements/Callback, %ARGS &>
+% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
+| <A HREF="<%$RT::WebPath%>/NoAuth/Logout.html<%$URL && "?URL=".$URL%>"><&|/l&>Logout</&></a>
+% }
+<BR>
+<&|/l, "<b>".$session{'CurrentUser'}->Name."</b>" &>Logged in as [_1]</&>
+% } else {
+<&|/l&>Not logged in.</&>
+% }
+</font>
+ </td>
+ </tr>
+</table>
+<%INIT>
+
+$r->header_out('Pragma' => 'no-cache');
+$r->header_out('Cache-control' => 'no-cache');
+</%INIT>
+
+<%ARGS>
+$Prefs => '/User/Prefs.html'
+$Focus => 'focus'
+$Title => undef
+$Code => undef
+$Refresh => 0
+$Why => undef
+$BgColor => '#ffffff'
+$ShowBar => 1
+$LoggedIn => 1
+$URL => undef
+</%ARGS>
diff --git a/rt/html/Elements/ListActions b/rt/html/Elements/ListActions
new file mode 100644
index 0000000..ffa09e2
--- /dev/null
+++ b/rt/html/Elements/ListActions
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if ($actions[0] ) {
+<& /Elements/TitleBoxStart, title => loc('Results') &>
+<UL>
+% foreach my $action (@actions) {
+% next unless ($action);
+% my $skip = 0;
+% $m->comp('/Elements/Callback', _CallbackName => 'ModifyRow', row => \$action, skip => \$skip, %ARGS);
+% next if $skip;
+<LI><%$action%></LI>
+% }
+</UL>
+<& /Elements/TitleBoxEnd &>
+<BR>
+% }
+<%init>
+@actions = grep (/./,@actions);
+</%init>
+<%ARGS>
+@actions => undef
+</%ARGS>
diff --git a/rt/html/Elements/Login b/rt/html/Elements/Login
new file mode 100644
index 0000000..42c49c4
--- /dev/null
+++ b/rt/html/Elements/Login
@@ -0,0 +1,101 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%INIT>
+if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
+ $r->content_type("text/plain");
+ $m->error_format("text");
+ $m->out("RT/$RT::VERSION 401 Credentials required\n");
+ $m->out("\n$Error\n") if $Error;
+ $m->abort;
+}
+</%INIT>
+
+<& /Elements/Callback, %ARGS, _CallbackName => 'Header' &>
+<& /Elements/Header, Title => loc('Login'), Focus => 'user' &>
+
+<DIV ALIGN=CENTER>
+% if ($Error) {
+<& /Elements/TitleBoxStart, title => loc('Error') &>
+<% $Error %>
+<& /Elements/TitleBoxEnd &>
+% }
+<BR>
+<& /Elements/TitleBoxStart, width=> "40%", titleright => loc("RT [_1]", $RT::VERSION), title => loc('Login') ,
+contentbg=>"#cccccc" &>
+
+% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
+<FORM METHOD=POST ACTION="<% (UNIVERSAL::can($r, 'uri') && ($r->uri) =~ m!.*/(.*)!) %>" >
+<TABLE BORDER=0 WIDTH=100%>
+<TR ALIGN=RIGHT>
+<TD ALIGN=RIGHT><&|/l&>Username</&>:</TD><TD ALIGN=LEFT><input name=user value="<%$user%>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Password</&>:</TD><TD ALIGN=LEFT><input type=password name=pass></TD></TR>
+<TR><TD colspan=2 align=right>
+<input type=submit Value="<&|/l&>Login</&>">
+</TD></TR>
+</TABLE>
+
+%# Give callbacks a chance to add more control elements
+<& /Elements/Callback, %ARGS &>
+
+<&/Elements/TitleBoxEnd&>
+% # From mason 1.0.1 forward, this doesn't work. in fact, it breaks things.
+% # But on Mason 1.15 it's fixed again, so we still use it.
+% # The code below iterates through everything in the passed in arguments
+% # Preserving all the old parameters
+% # This would be easier, except mason is 'smart' and calls multiple values
+% # arrays rather than multiple hash keys
+% my $key; my $val;
+% foreach $key (keys %ARGS) {
+% if (($key ne 'user') and ($key ne 'pass')) {
+% if (ref($ARGS{$key}) =~ /ARRAY/) {
+% foreach $val (@{$ARGS{$key}}) {
+<input type=hidden name="<%$key %>" value="<% $val %>">
+% }
+% }
+% else {
+<input type="hidden" name="<% $key %>" value="<% $ARGS{$key} %>">
+% }
+% }
+% }
+</FORM>
+% }
+</DIV>
+
+<BR>
+<!-- TODO: not yet implemented
+If you've forgotten your username or password, RT can <A
+href="/NoAuth/Reminder.html">send you a reminder</a>.
+-->
+<BR>
+<BR>
+<BR>
+<HR>
+<&|/l, '2003'&>RT is &copy; Copyright 1996-[_1] Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href="http://www.gnu.org/copyleft/gpl.html">Version 2 of the GNU General Public License.</a></&>
+
+<%ARGS>
+$user => ""
+$pass => undef
+$goto => undef
+$Error => undef
+</%ARGS>
diff --git a/rt/html/Elements/Menu b/rt/html/Elements/Menu
new file mode 100644
index 0000000..963be13
--- /dev/null
+++ b/rt/html/Elements/Menu
@@ -0,0 +1,84 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# font size depends on level
+% if ($level ge 3) {
+% $size = $basesize-(6);
+% } elsif ($level gt 0) {
+% $size = $basesize-($level * 2);
+% $padding = 2;
+% }
+% else {
+% $size = $basesize;
+% $padding = 5;
+% }
+<ul class="topnav" >
+% my $sep=0;
+% my $accesskey="1";
+% foreach $tab (sort keys %{$toptabs}) {
+% my $current = $current_toptab || "";
+% my $path = $toptabs->{$tab}->{'path'} || "";
+% $path =~ s#/index.html$##gi;
+% $current =~ s#/index.html$##gi;
+% if ( $path eq $current) {
+% $class="currenttopnav"
+% } else {
+% $class="topnav"
+% }
+% my $style="";
+% if ($sep) {
+% $style="border-top: solid #999 1px; padding-top: .1em; margin-top: .5em;";
+% } elsif ($level == 0 ) {
+% $style="border-bottom: solid white 1px; padding-top: .25em; padding-bottom: .5em;" ;
+% }
+% if ($toptabs->{$tab}->{'separator'}) {
+% $sep=1;
+% } else {
+% $sep=0;
+% }
+<li style="<%$style%>"><A HREF="<%$RT::WebPath%>/<%$toptabs->{$tab}->{'path'}|n%>" style="font-size: <%$size%>;" class="<%$class%>"
+<%($class eq 'currenttopnav') ? "name='focus'" : ""|n %>
+<% !$level && "accesskey='".$accesskey++."'" |n%>><% $toptabs->{$tab}->{'title'}%></A>
+%# Second-level items
+%# if ($current_toptab eq $toptabs->{$tab}->{'path'}) {
+%# commented out by jesse on 4 jan 2003 so that tickets/search and ticket/# can
+%# both have menu items
+% if ($toptabs->{$tab}->{'subtabs'}) {
+ <& /Elements/Menu, level => $level+1,
+ current_toptab => $toptabs->{$tab}->{'current_subtab'},
+ toptabs => $toptabs->{$tab}->{'subtabs'} &></li>
+% }
+%# }
+% }
+</ul>
+
+<%INIT>
+my ($tab, $subtab, $class, $size, $padding);
+my $basesize=16;
+</%INIT>
+
+<%ARGS>
+$current_toptab => ""
+$toptabs => undef
+$level => 0
+</%ARGS>
diff --git a/rt/html/Elements/MessageBox b/rt/html/Elements/MessageBox
new file mode 100644
index 0000000..32f4222
--- /dev/null
+++ b/rt/html/Elements/MessageBox
@@ -0,0 +1,49 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TEXTAREA COLS=<%$Width%> ROWS=15 WRAP=<%$Wrap%> NAME="<%$Name%>"><& /Elements/Callback, %ARGS &><% $Default %><%$message%><%$IncludeSignature ? $signature : ''%></TEXTAREA>
+<%INIT>
+
+my ($message);
+
+if ($QuoteTransaction) {
+ my $transaction=RT::Transaction->new($session{'CurrentUser'});
+ $transaction->Load($QuoteTransaction);
+ $message=$transaction->Content(Quote => 1);
+}
+
+my $signature = '';
+if ($session{'CurrentUser'}->UserObj->Signature) {
+ $signature = "-- \n".$session{'CurrentUser'}->UserObj->Signature;
+}
+
+</%INIT>
+<%ARGS>
+$QuoteTransaction => undef
+$Name => 'Content'
+$Default => ''
+$Width => $RT::MessageBoxWidth
+$Wrap => $RT::MessageBoxWrap
+$IncludeSignature => 1
+</%ARGS>
+
diff --git a/rt/html/Elements/MyRequests b/rt/html/Elements/MyRequests
new file mode 100644
index 0000000..05ae624
--- /dev/null
+++ b/rt/html/Elements/MyRequests
@@ -0,0 +1,78 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => loc("[_1] highest priority tickets I requested...", $rows), bodyclass=> '' &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+<TR>
+<TH align=right><&|/l&>#</&></TH>
+<TH align=left><&|/l&>Subject</&></TH>
+<TH align=left><&|/l&>Queue</&></TH>
+<TH align=left><&|/l&>Status</&></TH>
+<TH align=left><&|/l&>Owner</&></TH>
+</TR>
+% my $i;
+% while (my $Ticket = $MyTickets->Next) {
+% $i++;
+<TR class="<% $i%2 ? 'oddline' : 'evenline'%>" >
+<TD ALIGN=RIGHT>
+<%$Ticket->Id%>
+</TD>
+<TD>
+<A HREF="<% $RT::WebPath %>/Ticket/Display.html?id=<%$Ticket->Id%>">
+<%$Ticket->Subject || loc('(no subject)')%>
+</A>
+</TD>
+<TD>
+<%$Ticket->QueueObj->Name%>
+</TD>
+<TD>
+% if ($Ticket->HasUnresolvedDependencies ) {
+% if ($Ticket->HasUnresolvedDependencies( Type => 'approval' )) {
+<em><&|/l&>(pending approval)</&></em>
+% } else {
+<em><&|/l&>(pending other tickets)</&></em>
+% }
+% } else {
+<%loc($Ticket->Status)%>
+% }
+</TD>
+<TD>
+<%$Ticket->OwnerObj->Name%>
+</TD>
+</TR>
+% }
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+
+
+<%INIT>
+my $rows = 10;
+my $MyTickets;
+$MyTickets = new RT::Tickets ($session{'CurrentUser'});
+$MyTickets->LimitWatcher(TYPE => 'Requestor', VALUE => $session{'CurrentUser'}->EmailAddress);
+$MyTickets->LimitStatus(VALUE => "open");
+$MyTickets->LimitStatus(VALUE => "new");
+$MyTickets->RowsPerPage($rows);
+$MyTickets->OrderBy(FIELD => 'Priority', ORDER => 'DESC');
+
+</%INIT>
diff --git a/rt/html/Elements/MyTickets b/rt/html/Elements/MyTickets
new file mode 100644
index 0000000..52dae3b
--- /dev/null
+++ b/rt/html/Elements/MyTickets
@@ -0,0 +1,81 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => loc("[_1] highest priority tickets I own...", $rows), bodyclass=> '' &>
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+<TR>
+<TH ALIGN=RIGHT><&|/l&>#</&></TH>
+<TH ALIGN=LEFT><&|/l&>Subject</&></TH>
+<TH ALIGN=LEFT><&|/l&>Queue</&></TH>
+<TH ALIGN=LEFT><&|/l&>Status</&></TH>
+<TH ALIGN=LEFT>&nbsp;</TH>
+</TR>
+ <TR>
+% my $i;
+% while (my $Ticket = $MyTickets->Next) {
+% next if $Ticket->HasUnresolvedDependencies( Type => 'approval' );
+% last if $i++ >= $rows;
+<TR class="<% $i%2 ? 'oddline' : 'evenline'%>" >
+<TD ALIGN=RIGHT>
+<%$Ticket->Id%>
+</TD>
+<TD>
+<A HREF="<% $RT::WebPath %>/Ticket/Display.html?id=<%$Ticket->Id%>">
+<%$Ticket->Subject || loc('(no subject)')%>
+</A>
+</TD>
+<TD>
+<%$Ticket->QueueObj->Name%>
+</TD>
+<TD>
+% if ($Ticket->HasUnresolvedDependencies ) {
+% if ($Ticket->HasUnresolvedDependencies( Type => 'approval' ) or
+% $Ticket->HasUnresolvedDependencies( Type => 'code' )) {
+<em><&|/l&>(pending approval)</&></em>
+% } else {
+<em><&|/l&>(pending other tickets)</&></em>
+% }
+% } else {
+<%loc($Ticket->Status)%>
+% }
+</TD>
+<TD ALIGN=RIGHT>
+[<A HREF="<% $RT::WebPath %>/Ticket/Update.html?id=<%$Ticket->Id%>"><&|/l&>Update</&></A>]
+</TD>
+</TR>
+% }
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+
+
+<%INIT>
+my $rows = $RT::MyTicketsLength;
+my $MyTickets;
+$MyTickets = new RT::Tickets ($session{'CurrentUser'});
+$MyTickets->LimitOwner(VALUE => $session{'CurrentUser'}->Id);
+$MyTickets->LimitStatus(VALUE => "open");
+$MyTickets->LimitStatus(VALUE => "new");
+$MyTickets->RowsPerPage($rows);
+$MyTickets->OrderBy(FIELD => 'Priority', ORDER => 'DESC');
+
+</%INIT>
diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout
new file mode 100644
index 0000000..6853175
--- /dev/null
+++ b/rt/html/Elements/PageLayout
@@ -0,0 +1,99 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table class="darkblue" border=0 cellspacing=0 cellpadding=0 width="100%">
+ <th class="titlebox" align="left"><span class="rtname"><%$AppName%></span>
+ </th>
+ <span class="topactions">
+% foreach my $action (sort keys %{$topactions}) {
+ <td class="darkblueright">
+ <%$topactions->{"$action"}->{'html'} |n %>
+ </td>
+% }
+ </span>
+</table>
+<table border=0 cellspacing=0 cellpadding=0 width="100%" height="100%">
+%# Vertical menu
+<TR height="100%">
+<TD valign="top" width="140" class="blue">
+ <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &>
+</TD>
+<td valign="top">
+<table width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">
+<tr>
+ <td class="blue" valign="top">
+ <span class="title"><%$title%></span>
+</td>
+</tr>
+<tr>
+<td class="blueright" valign="top">
+ <span class="nav">
+% if ($actions) {
+% my @actions;
+% foreach my $action (sort keys %{$actions}) {
+% if ($actions->{"$action"}->{'html'}) {
+% push @actions, $actions->{"$action"}->{'html'};
+% } else {
+% push @actions, "<A class='nav' HREF=\"".$RT::WebPath."/".$actions->{$action}->{'path'}."\">".$actions->{$action}->{'title'}."</A>";
+% }
+% }
+<% join(" | ", @actions) | n %>
+% if ($subactions) {
+% my @actions;
+% foreach my $action (sort keys %{$subactions}) {
+% push @actions, $subactions->{"$action"}->{'html'};
+% }
+<% join(" | ", @actions) | n %>
+% }
+% }
+ </span>
+ </td>
+</tr>
+<TR valign="top">
+<TD valign="top" width="100%" height="100%" class="mainbody" >
+
+<%INIT>
+
+ foreach my $tab (sort keys %{$toptabs}) {
+ if ($toptabs->{$tab}->{'path'} eq $current_toptab) {
+ $toptabs->{$tab}->{"subtabs"} = $tabs;
+ $toptabs->{$tab}->{"current_subtab"} = $current_tab;
+ }
+ }
+
+if (! defined($AppName)) {
+ $AppName = loc("RT for [_1]", $RT::rtname);
+}
+
+</%INIT>
+<%ARGS>
+$current_toptab => undef
+$current_tab => undef
+$toptabs => undef
+$topactions => undef
+$tabs => undef
+$actions => undef
+$subactions => undef
+$title => $m->callers(-1)->path
+$AppName => undef
+</%ARGS>
diff --git a/rt/html/Elements/Quicksearch b/rt/html/Elements/Quicksearch
new file mode 100644
index 0000000..b1a67ab
--- /dev/null
+++ b/rt/html/Elements/Quicksearch
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => loc("Quick search"), bodyclass => "" &>
+
+<TABLE BORDER=0 cellspacing=0 cellpadding=1 WIDTH=100%>
+<tr>
+ <th align=left><&|/l&>Queue</&></th>
+ <th align=right><font size=-1><&|/l&>New</&></font></th>
+ <th align=right><font size=-1><&|/l&>Open</&></font></th>
+</tr>
+
+<%PERL>
+my $i;
+while (my $queue = $Queues->Next) {
+ $Tickets->ClearRestrictions;
+ $Tickets->LimitStatus(VALUE => "open");
+ $Tickets->LimitQueue(VALUE => $queue->Name, OPERATOR => '=');
+ my $open = $Tickets->Count();
+
+ $Tickets->ClearRestrictions;
+ $Tickets->LimitStatus(VALUE => "new");
+ $Tickets->LimitQueue(VALUE => $queue->Name, OPERATOR => '=');
+ my $new = $Tickets->Count();
+
+</%PERL>
+% $i++;
+<TR class="<% $i%2 ? 'oddline' : 'evenline'%>" >
+<td><A HREF="<% $RT::WebPath%>/Search/Listing.html?ValueOfStatus=open&ValueOfStatus=new&StatusOp=%3D&QueueOp=%3D&ValueOfQueue=<%$queue->Id%>&RowsPerPage=50&NewSearch=1" TITLE="<% $queue->Description %>"><%$queue->Name%></a></TD>
+<td align="right"><A HREF="<% $RT::WebPath%>/Search/Listing.html?ValueOfStatus=new&StatusOp=%3D&QueueOp=%3D&ValueOfQueue=<%$queue->Id%>&RowsPerPage=50&NewSearch=1"><%$new%></a></TD>
+<td align="right"><A HREF="<% $RT::WebPath%>/Search/Listing.html?ValueOfStatus=open&StatusOp=%3D&QueueOp=%3D&ValueOfQueue=<%$queue->Id%>&RowsPerPage=50&NewSearch=1"><%$open%></a></TD>
+</TR>
+% }
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+
+<%INIT>
+my $Queues = RT::Queues->new($session{'CurrentUser'});
+$Queues->UnLimit();
+my $Tickets = RT::Tickets->new($session{'CurrentUser'});
+</%INIT>
diff --git a/rt/html/Elements/Refresh b/rt/html/Elements/Refresh
new file mode 100644
index 0000000..2b5376f
--- /dev/null
+++ b/rt/html/Elements/Refresh
@@ -0,0 +1,45 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+<OPTION VALUE="-1"
+%unless ($Default) {
+ SELECTED
+%}
+><&|/l&>Don't refresh this page.</&></OPTION>
+%foreach my $value (@refreshevery) {
+<OPTION VALUE="<%$value%>"
+% if ($value == $Default) {
+SELECTED
+% }
+><&|/l, $value/60 &>Refresh this page every [_1] minutes.</&></OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my @refreshevery = qw(120 300 600 1200 3600 7200);
+</%INIT>
+<%ARGS>
+$Name => undef
+$Default => 0
+</%ARGS>
diff --git a/rt/html/Elements/Section b/rt/html/Elements/Section
new file mode 100644
index 0000000..6912358
--- /dev/null
+++ b/rt/html/Elements/Section
@@ -0,0 +1,34 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE WIDTH=100%>
+<TR>
+<TD>
+<font size=+4><%$title%></font>
+</TD>
+</TR>
+</TABLE>
+
+<%ARGS>
+$title => undef
+</%ARGS>
diff --git a/rt/html/Elements/SelectAttachmentField b/rt/html/Elements/SelectAttachmentField
new file mode 100644
index 0000000..47bc532
--- /dev/null
+++ b/rt/html/Elements/SelectAttachmentField
@@ -0,0 +1,31 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+<OPTION VALUE="Content"><&|/l&>content</&></OPTION>
+<OPTION VALUE="ContentType"><&|/l&>content-type</&></OPTION>
+<OPTION VALUE="Filename"><&|/l&>filename</&></OPTION>
+</SELECT>
+<%ARGS>
+$Name => 'AttachmentField'
+</%ARGS>
diff --git a/rt/html/Elements/SelectBoolean b/rt/html/Elements/SelectBoolean
new file mode 100644
index 0000000..8cf60dc
--- /dev/null
+++ b/rt/html/Elements/SelectBoolean
@@ -0,0 +1,46 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+<OPTION VALUE="<%$TrueVal%>" <%$TrueDefault%>><%$True%></OPTION>
+<OPTION VALUE="<%$FalseVal%>" <%$FalseDefault%>><%$False%></OPTION>
+</SELECT>
+
+<%ARGS>
+$Name => undef
+$True => loc("is")
+$Default => 'true'
+$TrueVal => 1
+$FalseVal => 0
+$False => loc("isn't")
+</%ARGS>
+
+<%INIT>
+my ($TrueDefault, $FalseDefault);
+if ($Default && $Default !~ /true/i) {
+ $FalseDefault = "SELECTED";
+}
+else {
+ $TrueDefault = "SELECTED";
+}
+</%INIT>
diff --git a/rt/html/Elements/SelectCustomFieldOperator b/rt/html/Elements/SelectCustomFieldOperator
new file mode 100644
index 0000000..e886cbe
--- /dev/null
+++ b/rt/html/Elements/SelectCustomFieldOperator
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+% while (my $option = shift @Options) {
+% my $value = shift @Values;
+<OPTION VALUE="<%$value%>"
+% if ($Default eq $value) {
+SELECTED
+% }
+><%$option%></OPTION>
+% }
+</SELECT>
+
+<%ARGS>
+$Name => undef
+@Options => ( loc('contains'), loc("doesn't contain"), loc('is'), loc("isn't"), loc('less than'), loc('greater than'))
+@Values => ('LIKE', 'NOT LIKE', '=', '!=', '<', '>')
+$Default => undef
+</%ARGS>
diff --git a/rt/html/Elements/SelectCustomFieldValue b/rt/html/Elements/SelectCustomFieldValue
new file mode 100644
index 0000000..60f65bc
--- /dev/null
+++ b/rt/html/Elements/SelectCustomFieldValue
@@ -0,0 +1,41 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Callback, %ARGS &>
+% if ($CustomField->Type =~ /Select/i) {
+% my $values = $CustomField->Values;
+<select name="<%$Name%>">
+<option value="" SELECTED>-</option>
+<option value="null"><&|/l&>(no value)</&></option>
+% while (my $value = $values->Next) {
+<option value="<%$value->Name%>"><%$value->Name%></option>
+% }
+</select>
+% }
+% else {
+<input name="<%$Name%>" size="20">
+% }
+<%args>
+$Name => undef
+$CustomField =>undef
+</%args>
diff --git a/rt/html/Elements/SelectDate b/rt/html/Elements/SelectDate
new file mode 100644
index 0000000..5f169fc
--- /dev/null
+++ b/rt/html/Elements/SelectDate
@@ -0,0 +1,48 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<INPUT NAME="<%$Name%>" VALUE="<%$Default%>" size=16>
+
+<%init>
+unless ((defined $Default) or
+ ($current <= 0)) {
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
+ localtime($current);
+ $Default = sprintf("%04d-%02d-%02d %02d:%02d",
+ $year+1900,$mon+1,$mday,
+ $hour,$min);
+}
+
+unless ($Name) {
+ $Name = $menu_prefix. "_Date";
+}
+</%init>
+
+<%args>
+
+$ShowTime => undef
+$menu_prefix=>''
+$current=>time
+$Default => undef
+$Name => undef
+</%args>
diff --git a/rt/html/Elements/SelectDateRelation b/rt/html/Elements/SelectDateRelation
new file mode 100644
index 0000000..ee26efe
--- /dev/null
+++ b/rt/html/Elements/SelectDateRelation
@@ -0,0 +1,36 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+<OPTION VALUE="&lt;"><%$Before%></OPTION>
+<OPTION VALUE="="><%$On%></OPTION>
+<OPTION VALUE="&gt;"><%$After%></OPTION>
+</SELECT>
+
+<%ARGS>
+$Name => undef
+$Default => undef
+$Before => loc('Before')
+$On => loc('On')
+$After => loc('After')
+</%ARGS>
diff --git a/rt/html/Elements/SelectDateType b/rt/html/Elements/SelectDateType
new file mode 100644
index 0000000..afb9a70
--- /dev/null
+++ b/rt/html/Elements/SelectDateType
@@ -0,0 +1,36 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+<OPTION VALUE="Created"><&|/l&>Created</&></OPTION>
+<OPTION VALUE="Started"><&|/l&>Started</&></OPTION>
+<OPTION VALUE="Resolved"><&|/l&>Resolved</&></OPTION>
+<OPTION VALUE="Told"><&|/l&>Last Contacted</&></OPTION>
+<OPTION VALUE="LastUpdated"><&|/l&>Last Updated</&></OPTION>
+<OPTION VALUE="Starts"><&|/l&>Starts</&></OPTION>
+<OPTION VALUE="Due"><&|/l&>Due</&></OPTION>
+<OPTION VALUE="Updated"><&|/l&>Updated</&></OPTION>
+</SELECT>
+<%ARGS>
+$Name => 'DateType'
+</%ARGS>
diff --git a/rt/html/Elements/SelectEqualityOperator b/rt/html/Elements/SelectEqualityOperator
new file mode 100644
index 0000000..99c60d5
--- /dev/null
+++ b/rt/html/Elements/SelectEqualityOperator
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+% while (my $option = shift @Options) {
+% my $value = shift @Values;
+<OPTION VALUE="<%$value%>"
+% if ($Default eq $value) {
+SELECTED
+% }
+><%$option%></OPTION>
+% }
+</SELECT>
+
+<%ARGS>
+$Name => undef
+@Options => (loc('less than'), loc('equal to'), loc('greater than'), loc('not equal to'))
+@Values => qw(< = > !=)
+$Default => undef
+</%ARGS>
diff --git a/rt/html/Elements/SelectGroups b/rt/html/Elements/SelectGroups
new file mode 100644
index 0000000..8f33c1e
--- /dev/null
+++ b/rt/html/Elements/SelectGroups
@@ -0,0 +1,29 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<select name="GroupField">
+<option value="Name"><&|/l&>Name</&>
+<option value="Description"><&|/l&>Description</&>
+</select>
+<& /Elements/SelectMatch, Name=> 'GroupOp' &>
+<input size=8 name="GroupString">
diff --git a/rt/html/Elements/SelectLang b/rt/html/Elements/SelectLang
new file mode 100644
index 0000000..cc2c357
--- /dev/null
+++ b/rt/html/Elements/SelectLang
@@ -0,0 +1,56 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+% if ($ShowNullOption) {
+<OPTION VALUE="">-</OPTION>
+% }
+% foreach my $lang (@lang) {
+<OPTION VALUE="<%$lang%>" <%($Default && ($lang eq $Default)) && 'SELECTED'%>><% $lang_to_desc{$lang} %>
+% if (($Verbose) and (my $description = I18N::LangTags::List::native_name($lang)) ){
+(<%$description%>)
+% }
+</OPTION>
+% }
+</SELECT>
+<%ARGS>
+$ShowNullOption => 1
+$ShowAllQueues => 1
+$Name => undef
+$Verbose => undef
+$Default => 0
+$Lite => 0
+</%ARGS>
+
+<%ONCE>
+use I18N::LangTags::List;
+my (@lang, %lang_to_desc);
+foreach my $lang (map { s/:://; s/_/-/g; $_ } grep { /^\w+::$/ } keys %RT::I18N::) {
+ next if $lang =~ /i-default|en-us/;
+ my $desc = I18N::LangTags::List::name($lang);
+ next unless ($desc);
+ $desc =~ s/(.*) (.*)/$2 ($1)/;
+ $lang_to_desc{$lang} = $desc;
+}
+@lang = sort { $lang_to_desc{$a} cmp $lang_to_desc{$b} } keys %lang_to_desc;
+</%ONCE>
diff --git a/rt/html/Elements/SelectLinkType b/rt/html/Elements/SelectLinkType
new file mode 100644
index 0000000..9ebefda
--- /dev/null
+++ b/rt/html/Elements/SelectLinkType
@@ -0,0 +1,37 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+<OPTION VALUE="MemberOf"><&|/l&>Member of</&></OPTION>
+<OPTION VALUE="DependsOn"><&|/l&>Depends on</&></OPTION>
+<OPTION VALUE="RefersTo"><&|/l&>Refers to</&></OPTION>
+</SELECT>
+
+<%ARGS>
+$Name => "LinkType"
+$Default => undef
+</%ARGS>
+
+<%INIT>
+# TODO handle Default
+</%INIT>
diff --git a/rt/html/Elements/SelectMatch b/rt/html/Elements/SelectMatch
new file mode 100644
index 0000000..d58a963
--- /dev/null
+++ b/rt/html/Elements/SelectMatch
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+<OPTION VALUE="LIKE" <%$LikeDefault%>><%$Like%></OPTION>
+<OPTION VALUE="NOT LIKE" <%$NotLikeDefault%>><%$NotLike%></OPTION>
+<OPTION VALUE="=" <%$TrueDefault%>><%$True%></OPTION>
+<OPTION VALUE="!=" <%$FalseDefault%>><%$False%></OPTION>
+</SELECT>
+
+<%ARGS>
+$Name => undef
+$Like => loc('contains')
+$NotLike => loc("doesn't contain")
+$True => loc('is')
+$False => loc("isn't")
+$Default => undef
+</%ARGS>
+<%INIT>
+my ($TrueDefault, $FalseDefault, $LikeDefault, $NotLikeDefault);
+if ($Default && $Default !~ /true/i) {
+ $FalseDefault = "SELECTED";
+}
+elsif ($Default && $Default !~ /false/i) {
+ $TrueDefault = "SELECTED";
+}
+elsif ($Default && $Default !~ /notlike/i) {
+ $NotLikeDefault = "SELECTED";
+}
+else {
+ $LikeDefault = "SELECTED";
+}
+</%INIT>
diff --git a/rt/html/Elements/SelectNewTicketQueue b/rt/html/Elements/SelectNewTicketQueue
new file mode 100644
index 0000000..a629b7b
--- /dev/null
+++ b/rt/html/Elements/SelectNewTicketQueue
@@ -0,0 +1,56 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<LABEL ACCESSKEY="9">
+<SELECT NAME ="<%$Name%>">
+% foreach my $queue (@{$session{'create_in_queues'}}) {
+<OPTION VALUE="<%$queue->{'id'}%>" <%($Default && ($queue->{'id'} == $Default)) && 'SELECTED'%>><%$queue->{'Name'}%>
+% if (($Verbose) and ($queue->{'Description'}) ){
+(<%$queue->{'Description'}%>)
+% }
+</OPTION>
+% }
+</SELECT>
+</LABEL>
+
+<%INIT>
+unless ($session{'create_in_queues'}) {
+
+@{$session{'create_in_queues'}} = ();
+my $q=new RT::Queues($session{'CurrentUser'});
+$q->UnLimit;
+while (my $queue=$q->Next) {
+ if ($queue->CurrentUserHasRight('CreateTicket')) {
+ my $ds = { Name => $queue->Name, Description => $queue->Description, id => $queue->id };
+ push (@{$session{'create_in_queues'}}, $ds);
+ }
+}
+}
+</%INIT>
+
+
+<%ARGS>
+$Name => 'Queue'
+$Verbose => undef
+$Default => undef
+</%ARGS>
diff --git a/rt/html/Elements/SelectOwner b/rt/html/Elements/SelectOwner
new file mode 100644
index 0000000..04b078d
--- /dev/null
+++ b/rt/html/Elements/SelectOwner
@@ -0,0 +1,59 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+<OPTION VALUE="">-</OPTION>
+<OPTION <% ($RT::Nobody->Id() == $Default) && "SELECTED" %> VALUE="<%$RT::Nobody->Id%>"><%$RT::Nobody->Name%></OPTION>
+%while ( my $User = $Users->Next()) {
+<OPTION VALUE="<%$User->Id()%>" <% ($User->Id() == $Default) && "SELECTED" %>><%$User->Name()%></OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my $Users = RT::Users->new($session{CurrentUser});
+my $object;
+
+
+if ($TicketObj) {
+ $object = $TicketObj;
+}
+elsif ($QueueObj) {
+ $object = $QueueObj;
+}
+if ($object) {
+ $Users->WhoHaveRight(Right => 'OwnTicket',
+ Object => $object,
+ IncludeSystemRights => 1,
+ IncludeSuperusers => 1);
+} else {
+ $Users->LimitToPrivileged;
+}
+</%INIT>
+
+<%ARGS>
+$QueueObj => undef
+$Name => undef
+$Default => undef
+$User => undef
+$TicketObj => undef
+</%ARGS>
diff --git a/rt/html/Elements/SelectQueue b/rt/html/Elements/SelectQueue
new file mode 100644
index 0000000..c45b9b5
--- /dev/null
+++ b/rt/html/Elements/SelectQueue
@@ -0,0 +1,59 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if ($Lite) {
+<INPUT NAME="<%$Name%>" size=25 DEFAULT="<%$d->Name%>">
+% } else {
+<SELECT NAME ="<%$Name%>">
+% if ($ShowNullOption) {
+<OPTION VALUE="">-</OPTION>
+% }
+% while (my $queue=$q->Next) {
+% if ($ShowAllQueues || $queue->CurrentUserHasRight('CreateTicket')) {
+<OPTION VALUE="<%$queue->Id%>" <%($Default && ($queue->Id == $Default)) && 'SELECTED'%>><%$queue->Name%>
+% if (($Verbose) and ($queue->Description) ){
+(<%$queue->Description%>)
+% }
+</OPTION>
+% }
+% }
+</SELECT>
+% }
+<%ARGS>
+$ShowNullOption => 1
+$ShowAllQueues => 1
+$Name => undef
+$Verbose => undef
+$Default => 0
+$Lite => 0
+</%ARGS>
+
+<%INIT>
+
+my $q=new RT::Queues($session{'CurrentUser'});
+$q->UnLimit;
+
+my $d = new RT::Queue($session{'CurrentUser'});
+$d->Load($Default);
+
+</%INIT>
diff --git a/rt/html/Elements/SelectResultsPerPage b/rt/html/Elements/SelectResultsPerPage
new file mode 100644
index 0000000..1bde713
--- /dev/null
+++ b/rt/html/Elements/SelectResultsPerPage
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# TODO: Better default handling
+
+<SELECT NAME ="<%$Name%>">
+% foreach my $value (@values) {
+<OPTION VALUE="<%$value%>" <% $value == $Default && 'SELECTED' %>>
+<% shift @labels %>
+</OPTION>
+% }
+</SELECT>
+
+<%INIT>
+my @values = qw(0 10 25 50 100);
+my @labels = (loc('Unlimited'), qw(10 25 50 100));
+</%INIT>
+<%ARGS>
+
+$Name => undef
+$Default => 50
+
+</%ARGS>
diff --git a/rt/html/Elements/SelectSortOrder b/rt/html/Elements/SelectSortOrder
new file mode 100644
index 0000000..0ad999a
--- /dev/null
+++ b/rt/html/Elements/SelectSortOrder
@@ -0,0 +1,41 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+%foreach my $order (@orders) {
+<OPTION VALUE="<%$order%>" <%$order eq $Default && 'SELECTED' %>>
+<% shift @order_names %>
+</OPTION>
+% }
+</SELECT>
+
+<%INIT>
+my @orders = qw (ASC DESC);
+my @order_names = (loc('Ascending'), loc('Descending'));
+
+</%INIT>
+
+<%ARGS>
+$Name => 'SortOrder'
+$Default => 'ASC'
+</%ARGS>
diff --git a/rt/html/Elements/SelectStatus b/rt/html/Elements/SelectStatus
new file mode 100644
index 0000000..16a5f29
--- /dev/null
+++ b/rt/html/Elements/SelectStatus
@@ -0,0 +1,39 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+<OPTION VALUE="">-</OPTION>
+%foreach my $status (@status) {
+%next if ($SkipDeleted && $status eq 'deleted');
+<OPTION VALUE="<%$status%>" <%($Default eq $status) && 'SELECTED'%>><%loc($status)%></OPTION>
+% }
+</SELECT>
+<%ONCE>
+my $queue = new RT::Queue($session{'CurrentUser'});
+my @status = $queue->StatusArray();
+</%ONCE>
+<%ARGS>
+$Name => undef
+$Default => undef
+$SkipDeleted => 0
+</%ARGS>
diff --git a/rt/html/Elements/SelectTicketSortBy b/rt/html/Elements/SelectTicketSortBy
new file mode 100644
index 0000000..1d0b394
--- /dev/null
+++ b/rt/html/Elements/SelectTicketSortBy
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+% foreach my $field (@sortfields) {
+<OPTION VALUE="<%$field%>" <% $field eq $Default && 'SELECTED'%>><% loc($field) %></OPTION>
+% }
+</SELECT>
+
+<%INIT>
+my $tickets = new RT::Tickets($session{'CurrentUser'});
+my @sortfields = $tickets->SortFields();
+
+</%INIT>
+<%ARGS>
+$Name => 'SortTicketsBy'
+$Default => 'id'
+</%ARGS>
diff --git a/rt/html/Elements/SelectTicketTypes b/rt/html/Elements/SelectTicketTypes
new file mode 100644
index 0000000..80aecac
--- /dev/null
+++ b/rt/html/Elements/SelectTicketTypes
@@ -0,0 +1,34 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME="<%$Name%>">
+%foreach (@Types) {
+<OPTION VALUE="<% $_ %>" <% ($_ eq $Default) && "SELECTED" %>><&|/l&><% $_ %></&>
+%}
+</SELECT>
+
+<%ARGS>
+$Name => 'TickType'
+$Default => undef
+@Types => qw(Approval Ticket)
+</%ARGS>
diff --git a/rt/html/Elements/SelectUsers b/rt/html/Elements/SelectUsers
new file mode 100644
index 0000000..7ed3835
--- /dev/null
+++ b/rt/html/Elements/SelectUsers
@@ -0,0 +1,31 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<select name="UserField">
+<option value="Name"><&|/l&>User Id</&>
+<option value="EmailAddress"><&|/l&>Email</&>
+<option value="RealName"><&|/l&>Name</&>
+<option value="Organization"><&|/l&>Organization</&>
+</select>
+<& /Elements/SelectMatch, Name=> 'UserOp' &>
+<input size=8 name="UserString">
diff --git a/rt/html/Elements/SelectWatcherType b/rt/html/Elements/SelectWatcherType
new file mode 100644
index 0000000..82aab2a
--- /dev/null
+++ b/rt/html/Elements/SelectWatcherType
@@ -0,0 +1,47 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SELECT NAME ="<%$Name%>">
+% if ($AllowNull) {
+<OPTION VALUE="">-</OPTION>
+% }
+%for my $option (@types) {
+<OPTION VALUE="<%$option%>" <%$option eq $Default && "SELECTED"%>><%loc($option)%></OPTION>
+%}
+</SELECT>
+
+<%INIT>
+my @types;
+if ($Scope =~ 'queue') {
+ @types = qw(Cc AdminCc);
+}
+else {
+ @types = qw(Requestor Cc AdminCc);
+}
+</%INIT>
+<%ARGS>
+$AllowNull => 1
+$Default=>undef
+$Scope => 'ticket'
+$Name => 'WatcherType'
+</%ARGS>
diff --git a/rt/html/Elements/SetupSessionCookie b/rt/html/Elements/SetupSessionCookie
new file mode 100644
index 0000000..7a2ad9f
--- /dev/null
+++ b/rt/html/Elements/SetupSessionCookie
@@ -0,0 +1,85 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%init>
+return if $m->is_subrequest; # avoid reentrancy, as suggested by masonbook
+
+my %cookies = CGI::Cookie->fetch();
+my $cookiename = "RT_SID_".$RT::rtname.".".$ENV{'SERVER_PORT'};
+my %backends = (
+ mysql => 'Apache::Session::MySQL',
+ Pg => 'Apache::Session::Postgres',
+# Oracle => 'Apache::Session::Oracle',
+) unless $RT::WebSessionClass;
+my $session_class = $RT::WebSessionClass || $backends{$RT::DatabaseType} || 'Apache::Session::File';
+my $pm = "$session_class.pm"; $pm =~ s|::|/|g; require $pm;
+
+ # morning bug avoidance attempt -- pdh 20030815
+ unless ($RT::Handle->dbh && $RT::Handle->dbh->ping) {
+ $RT::Handle->Connect();
+ }
+ eval {
+ tie %session, $session_class,
+ $SessionCookie || ( $cookies{$cookiename} ? $cookies{$cookiename}->value() : undef ),
+ $backends{$RT::DatabaseType} ? {
+ Handle => $RT::Handle->dbh,
+ LockHandle => $RT::Handle->dbh,
+ } : {
+ Directory => $RT::MasonSessionDir,
+ LockDirectory => $RT::MasonSessionDir,
+ };
+ };
+ if ($@) {
+
+ # If the session is invalid, create a new session.
+ if ( $@ =~ /Object does not/i ) {
+ tie %session, $session_class, undef,
+ $backends{$RT::DatabaseType} ? {
+ Handle => $RT::Handle->dbh,
+ LockHandle => $RT::Handle->dbh,
+ } : {
+ Directory => $RT::MasonSessionDir,
+ LockDirectory => $RT::MasonSessionDir,
+ };
+ undef $cookies{$cookiename};
+ }
+ else {
+ die "RT Couldn't write to session directory '$RT::MasonSessionDir': $@. Check that this dir ectory's permissions are correct.";
+ }
+ }
+
+ if ( !$cookies{$cookiename} ) {
+ my $cookie = new CGI::Cookie(
+ -name => $cookiename,
+ -value => $session{_session_id},
+ -path => '/',
+ );
+ $r->header_out('Set-Cookie', $cookie->as_string);
+
+ }
+
+ return();
+</%init>
+<%args>
+$SessionCookie => ''
+</%args>
diff --git a/rt/html/Elements/ShadedBox b/rt/html/Elements/ShadedBox
new file mode 100644
index 0000000..36b9cae
--- /dev/null
+++ b/rt/html/Elements/ShadedBox
@@ -0,0 +1,33 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+ <tr>
+ <td class="label"><%$title |n %>:</td>
+ <td class="value"><%$content |n %></td>
+ </tr>
+</table>
+<%ARGS>
+$title => undef
+$content => "&nbsp;"
+</%ARGS>
diff --git a/rt/html/Elements/ShadedInputRow b/rt/html/Elements/ShadedInputRow
new file mode 100644
index 0000000..e9fb69e
--- /dev/null
+++ b/rt/html/Elements/ShadedInputRow
@@ -0,0 +1,35 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<tr>
+ <td class="label"><%$title |n %>:</td>
+ <td class="value">
+ <input name=<%$name%> value="<%$content|h%>" SIZE=<%$size%>>
+ </td>
+</tr>
+<%ARGS>
+$title => undef
+$content => "&nbsp;"
+$name => undef
+$size => undef
+</%ARGS>
diff --git a/rt/html/Elements/ShadedRow b/rt/html/Elements/ShadedRow
new file mode 100644
index 0000000..8947fcd
--- /dev/null
+++ b/rt/html/Elements/ShadedRow
@@ -0,0 +1,31 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<tr>
+ <td class="label"><%$title |n %>:</td>
+ <td class="value"><%$content |n %></td>
+</tr>
+<%ARGS>
+$title => undef
+$content => "&nbsp;"
+</%ARGS>
diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch
new file mode 100644
index 0000000..4a0d106
--- /dev/null
+++ b/rt/html/Elements/SimpleSearch
@@ -0,0 +1,27 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<form action="<% $RT::WebPath %>/index.html">
+<input size="12" name="q" autocomplete="off" accesskey="0">
+<input type="submit" value="<&|/l&>Search</&>">&nbsp;
+</form>
diff --git a/rt/html/Elements/Submit b/rt/html/Elements/Submit
new file mode 100644
index 0000000..2c35ca0
--- /dev/null
+++ b/rt/html/Elements/Submit
@@ -0,0 +1,62 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE WIDTH=100% BGCOLOR="<%$color%>" CELLSPACING=0 BORDER=0 CELLPADDING=0 >
+<TR>
+% if ($Reset) {
+<TD>
+<FONT COLOR=#ffd800 >
+<INPUT TYPE=RESET VALUE="<%$ResetLabel%>">
+</FONT>
+</TD>
+%}
+<TD>
+&nbsp;
+</TD>
+<TD ALIGN=RIGHT VALIGN=CENTER><FONT COLOR=#ffd800>
+% if ($AlternateLabel) {
+<B><%$AlternateCaption%>
+<INPUT TYPE=SUBMIT
+%if ($Name) {
+NAME="<%$Name%>"
+%}
+VALUE='<%$AlternateLabel%>'></B>
+% }
+<B><%$Caption%> <INPUT TYPE=SUBMIT
+%if ($Name) {
+NAME="<%$Name%>"
+% }
+ VALUE='<%$Label%>'></B></FONT>
+</TD>
+</TR>
+</TABLE>
+<%ARGS>
+$color => "#336699"
+$Caption => undef
+$AlternateCaption => undef
+$AlternateLabel => undef
+$Label => loc('Submit')
+$Name => undef
+$Reset => undef
+$ResetLabel => loc('Reset')
+</%ARGS>
diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs
new file mode 100644
index 0000000..4db3849
--- /dev/null
+++ b/rt/html/Elements/Tabs
@@ -0,0 +1,82 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/PageLayout,
+ current_toptab => $current_toptab,
+ current_tab => $current_tab,
+ toptabs => $toptabs,
+ topactions => $topactions,
+ tabs => $tabs,
+ actions => $actions,
+ subactions => $subactions,
+ title => $Title
+&>
+<a name="skipnav" id="skipnav" accesskey="8"></a>
+<%INIT>
+my $action;
+my $basetopactions = {
+ A => { html => $m->scomp('/Elements/CreateTicket')
+ },
+ B => { html => $m->scomp('/Elements/SimpleSearch')
+ }
+ };
+my $basetabs = { A => { title => loc('Homepage'),
+ path => '',
+ },
+ B => { title => loc('Tickets'),
+ path => 'Search/Listing.html'
+ },
+ E => { title => loc('Configuration'),
+ path => 'Admin/'
+ },
+ K => { title => loc('Preferences'),
+ path => 'User/Prefs.html'
+ },
+ P => { title => loc('Approval'),
+ path => 'Approvals/'
+ },
+ };
+
+if (!defined $toptabs) {
+ $toptabs = $basetabs;
+}
+if (!defined $topactions) {
+ $topactions = $basetopactions;
+}
+
+ # Now let callbacks add their extra tabs
+ $m->comp('/Elements/Callback',
+ topactions => $topactions,
+ toptabs => $toptabs, %ARGS);
+
+</%INIT>
+<%ARGS>
+$current_toptab => undef
+$current_tab => undef
+$toptabs => undef
+$topactions => undef
+$tabs => undef
+$actions => undef
+$subactions => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Elements/TitleBoxEnd b/rt/html/Elements/TitleBoxEnd
new file mode 100644
index 0000000..37f3744
--- /dev/null
+++ b/rt/html/Elements/TitleBoxEnd
@@ -0,0 +1,31 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+ </TD>
+ </TR>
+</TABLE>
+<%ARGS>
+$title => undef
+$content => undef
+</%ARGS>
+
diff --git a/rt/html/Elements/TitleBoxStart b/rt/html/Elements/TitleBoxStart
new file mode 100644
index 0000000..02c76a7
--- /dev/null
+++ b/rt/html/Elements/TitleBoxStart
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE CLASS="<%$class|n%>"
+ BGCOLOR="<%$color%>"
+ CELLSPACING=0
+ BORDER=0
+ WIDTH="<%$width%>"
+ CELLPADDING="0">
+
+ <TR>
+ <TH
+ <%$color && "style=\"color: $color;\""|n%>
+ <%$class ? "class=\"$class\"" : "class=\"titlebox\""|n%>>
+ <span class="titleboxtitle">
+ <b>
+ <% $title_href && "<A CLASS=\"$title_class\" HREF=\"$title_href\">"|n%><%$title |n %><% $title_href && "</A>" |n%></b>
+ </span>
+ </TH>
+ <TH
+ <%$color && "style=\"color: $color;\""|n%>
+ <%$class ? "class=\"$class\"": "class=\"titleboxright\""|n%>>
+ <span class="titleboxright"><%$titleright ? $titleright : '&nbsp;' |n %></span>
+ </TH>
+ </TR>
+ <tr>
+ <td bgcolor="<%$contentbg%>" colspan="2" class="<%defined($bodyclass) ? $bodyclass : $class|n%>">
+<%ARGS>
+$width => "100%"
+$class => undef
+$bodyclass => undef
+$title_href => undef
+$title => undef
+$title_class => undef
+
+$titleright_href => undef
+$titleright => undef
+$contentbg => "#dddddd"
+$color => "#336699"
+</%ARGS>
diff --git a/rt/html/Elements/ViewUser b/rt/html/Elements/ViewUser
new file mode 100644
index 0000000..6572724
--- /dev/null
+++ b/rt/html/Elements/ViewUser
@@ -0,0 +1,51 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart,
+ title => "<a class='inverse' href=\"$RT::WebPath/Search/Listing.html?LimitRequestorById=1&IdOfRequestor=".$User->id."\">".loc("Tickets from [_1]", $name)."</a>",
+ titleright=> "<a class='inverse' href=\"$RT::WebPath/EditUserComments.html?id=".$User->id."\">".loc("Comments about [_1]", $name)."</a>" &>
+<TABLE WIDTH="100%">
+<tr>
+<td halign=left valign=top>
+%while (my $w=$tickets->Next) {
+<%$w->Id%>: <a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$w->id%>"><%$w->Subject%></a> (<%$w->Status%>)<BR>
+%}
+</td>
+<td align=right valign=top>
+ <% ($User->Comments || loc("No comment entered about this user")) %>
+</tr>
+</table>
+<& /Elements/TitleBoxEnd &>
+
+<%ARGS>
+$User=>undef
+</%ARGS>
+
+<%INIT>
+my $name=$User->RealName || $User->EmailAddress;
+
+my $tickets = new RT::Tickets($session{'CurrentUser'});
+$tickets->LimitWatcher(TYPE => 'Requestor', VALUE => $User->EmailAddress);
+
+
+</%INIT>
diff --git a/rt/html/NoAuth/Logout.html b/rt/html/NoAuth/Logout.html
new file mode 100644
index 0000000..a4bb997
--- /dev/null
+++ b/rt/html/NoAuth/Logout.html
@@ -0,0 +1,46 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<HTML>
+<HEAD>
+<TITLE>RT: Logout</TITLE>
+ <META HTTP-EQUIV="Refresh" CONTENT="0;URL=<%$URL%>">
+</HEAD>
+<BODY>
+<p><&|/l&>You have been logged out of RT.</&>
+
+<br>
+<br>
+<A HREF="<%$URL%>"><&|/l&>You're welcome to login again</&></a>
+
+
+<%PERL>
+if (defined %session) {
+ tied(%session)->delete;
+}
+$m->abort();
+</%PERL>
+
+<%ARGS>
+$URL => $RT::WebPath."/"
+</%ARGS>
diff --git a/rt/html/NoAuth/Reminder.html b/rt/html/NoAuth/Reminder.html
new file mode 100644
index 0000000..35da66e
--- /dev/null
+++ b/rt/html/NoAuth/Reminder.html
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, title => loc('Password Reminder') &>
+
+<&|/l&>Not yet implemented.</&>
diff --git a/rt/html/NoAuth/images/back_home.gif b/rt/html/NoAuth/images/back_home.gif
new file mode 100644
index 0000000..40b19c1
--- /dev/null
+++ b/rt/html/NoAuth/images/back_home.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/bplogo.gif b/rt/html/NoAuth/images/bplogo.gif
new file mode 100644
index 0000000..e2cf49c
--- /dev/null
+++ b/rt/html/NoAuth/images/bplogo.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/favicon.png b/rt/html/NoAuth/images/favicon.png
new file mode 100644
index 0000000..ed1ee37
--- /dev/null
+++ b/rt/html/NoAuth/images/favicon.png
Binary files differ
diff --git a/rt/html/NoAuth/images/head_requestracker.gif b/rt/html/NoAuth/images/head_requestracker.gif
new file mode 100644
index 0000000..73315e9
--- /dev/null
+++ b/rt/html/NoAuth/images/head_requestracker.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/rt.jpg b/rt/html/NoAuth/images/rt.jpg
new file mode 100644
index 0000000..a137a93
--- /dev/null
+++ b/rt/html/NoAuth/images/rt.jpg
Binary files differ
diff --git a/rt/html/NoAuth/images/space.gif b/rt/html/NoAuth/images/space.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/rt/html/NoAuth/images/space.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/spacer.gif b/rt/html/NoAuth/images/spacer.gif
new file mode 100644
index 0000000..5bfd67a
--- /dev/null
+++ b/rt/html/NoAuth/images/spacer.gif
Binary files differ
diff --git a/rt/html/NoAuth/images/squares_blue.gif b/rt/html/NoAuth/images/squares_blue.gif
new file mode 100644
index 0000000..a28da5c
--- /dev/null
+++ b/rt/html/NoAuth/images/squares_blue.gif
Binary files differ
diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css
new file mode 100644
index 0000000..159e79c
--- /dev/null
+++ b/rt/html/NoAuth/webrt.css
@@ -0,0 +1,340 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #FFFFFF;
+ text-decoration: none;
+ white-space: nowrap}
+.nav2 { font-size: 10px;
+ white-space: nowrap}
+.nav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ color: #FFFFFF;
+ text-decoration: none;
+ white-space: nowrap}
+.currentnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ font-weight: bold;
+ color: #FFFF66;
+ text-decoration: none;
+ white-space: nowrap}
+.topnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: normal;
+ color: #FFFFFF;
+ text-decoration: none;
+ white-space: nowrap}
+.currenttopnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: bold;
+ color: #FFFF66;
+ text-decoration: none;
+ white-space: nowrap}
+.topactions { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ color: #FFFFFF;
+ text-decoration: none;
+ white-space: nowrap}
+.subnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: normal;
+ color: #FFFFFF;
+ text-decoration: none;
+ white-space: nowrap}
+.currentsubnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #FFFF66;
+ text-decoration: none;
+ white-space: nowrap}
+.error { background-color: #ff0000;
+ background-position: left top;
+ vertical-align: top;
+ text-align: left;
+ }
+.oldblue { background-color: #0066CC;
+ background-position: left top;
+ vertical-align: top;
+ text-align: left;
+ }
+.blue { background-color: #4682B4;
+ background-position: left top;
+ vertical-align: top;
+ text-align: left;
+ }
+.blueright { background-color: #4682B4;
+ background-position: left top;
+ vertical-align: top;
+ text-align: right;
+ }
+.olddarkblue { background-color: #003399;
+ background-position: left top;
+ vertical-align: top;
+ text-align: left;
+ }
+.darkblue { background-color: #000080;
+ background-position: left top;
+ vertical-align: top;
+ text-align: left;
+ }
+.darkblueright { background-color: #000080;
+ background-position: left top;
+ vertical-align: top;
+ text-align: right;
+ }
+
+td { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ background-position: left top;
+ }
+.black { background-color: #000000;
+ background-position: left top;
+ }
+span.rtname { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 18px;
+ font-weight: normal;
+ color: #ffffff}
+span.title { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 20px;
+ font-weight: bold;
+ color: #ffffff}
+.header { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-weight: bold;
+ color: #0066CC}
+.subheader { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #0066CC }
+.value { font-weight: bold; }
+.entry { font-weight: normal; }
+.label { font-weight: normal;
+ text-align: right; }
+.labeltop { font-weight: normal;
+ text-align: right;
+ vertical-align: top }
+.productnav { font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #000000;
+ text-align: center;
+ vertical-align: middle;
+ text-decoration: none}
+.rtblue { background-color: #3399FF;
+ margin-top: 0.2em;
+ background-position: left top;
+ vertical-align: top }
+
+
+.currenttab { margin: 0.2em; background: #336699; }
+.othertab { margin: 0.2em; background: #efefef; }
+.oddline { background-color : #ccccee; }
+
+UL.topnav LI :focus { text-decoration: underline; }
+
+TD.mainbody {
+ padding-top: 0.5em;
+ padding-left: 0em;
+ padding-right: 1em;
+ margin-left: 1em;
+ margin-right: 1em;
+}
+
+th.ticketheader { font-size: 80%;
+ font-weight: bold;
+ color: #336699;
+ background: #cccccc;
+}
+
+th.titlebox {
+ text-align: left;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ border-top: solid black 1px;
+ border-bottom: solid black 1px;
+}
+th.titleboxright {
+ text-align: right;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ border-top: solid black 1px;
+ border-bottom: solid black 1px;
+}
+
+TD.titlebox {
+ padding-left: 1em;
+ padding-right: 1em;
+ padding-top: 1em;
+ padding-bottom: 1em;
+}
+
+SPAN.message {
+ font-size: 100%;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+
+BODY {
+ color: #000;
+ background: #FFFFFF;
+ font-family: "Helvetica", sans-serif;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-left: 0px;
+ margin-right: 0px;
+ border-top: 0px;
+ border-bottom: 0px;
+ border-left: 0px;
+ border-right: 0px;
+}
+
+
+TR.oddline {
+ background-color : #ffffff;
+}
+
+TR.evenline {
+ background-color : #ccccee;
+}
+
+H1, H2, H3 {
+ margin-top: 0.2em;
+ color: #336699;
+ font-family: "Helvetica", sans-serif;
+
+ clear: both;
+}
+
+
+DIV.endmatter { margin-left: -7% }
+.bpscredits {margin-top: 1em;
+ text-align: right;
+ color: #666666;
+ }
+
+
+A { font-weight: bold; color: #000000;
+ }
+
+.currenttab { color: #ffffff;}
+.othertab { color: #336699; }
+
+.inverse { color: #ffffff; }
+
+
+
+A:link IMG, A:visited IMG { border-style: none }
+a:focus {text-decoration: underline }
+A IMG { color: white } /* The only way to hide the border in NS 4.x */
+
+a:link { text-decoration: none}
+a:visited { text-decoration: none}
+a:hover { text-decoration: underline}
+/* a:focus { background-color: #ccccee } */
+
+.hide {
+ display: none;
+ color: white;
+}
+
+SPAN.date { font-size: 0.8em }
+
+span.title { font-size: 1.6em;
+ vertical-align: middle;
+ color: #ffffff;}
+span.productname { font-size: 2em;
+ color: #0066cc;}
+SPAN.titleboxtitle {
+ font-size: 1.1em;
+ color: #ffffff;
+ vertical-align: middle;
+ text-align: left;
+ }
+
+SPAN.titleboxright {
+ font-size: 0.8em;
+ color: #ffffff;
+ vertical-align: middle;
+ text-align: right;
+ }
+
+SPAN.attribution {
+ font-weight: bold;
+}
+
+SPAN.label { font-size: 0.8em;
+}
+
+BLOCKQUOTE {
+ font-style: italic;
+ /* color: #990; */
+}
+
+ADDRESS {
+ text-align: right;
+ font-weight: bold;
+ font-style: italic
+}
+
+BLOCKQUOTE P { /* Try to avoid space above the attribution */
+ margin-bottom: 0;
+}
+BLOCKQUOTE ADDRESS {
+ margin: 0;
+}
+
+
+.emphasized {
+ font-weight: bold
+}
+
+
+P.map-also { font-style: italic; margin-left: 15%; text-align: right }
+
+.oddline {
+ background-color : #ccccee;
+}
+
+ul.topnav {
+ list-style: none;
+ margin-left: 0;
+ margin-right: 0.25em;
+ padding-left: 0.25em;
+ padding-bottom: 0;
+ padding-top:0;
+ margin-top: 0;
+ margin-bottom:0;
+}
+
+<%flags>
+inherit => undef
+</%flags>
+<%init>
+$r->content_type('text/css');
+$r->header_out('Expires' ,'+30m');
+</%init>
diff --git a/rt/html/REST/1.0/Forms/queue/default b/rt/html/REST/1.0/Forms/queue/default
new file mode 100644
index 0000000..ce54088
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/queue/default
@@ -0,0 +1,123 @@
+%# REST/1.0/Forms/queue/default
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => {}
+</%ARGS>
+<%perl>
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $queue = new RT::Queue $session{CurrentUser};
+my @fields = qw(Name Description CorrespondAddress CommentAddress
+ InitialPriority FinalPriority DefaultDueIn);
+my %fields = map { lc $_ => $_ } @fields;
+
+if ($id ne 'new') {
+ $queue->Load($id);
+ if (!$queue->Id) {
+ return [ "# Queue $id does not exist.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ return [
+ "# Required: Name",
+ [ "id", @fields ],
+ {
+ id => 'queue/new',
+ Name => '<queue name>',
+ Description => "",
+ CommentAddress => "",
+ CorrespondAddress => "",
+ InitialPriority => "",
+ FinalPriority => "",
+ DefaultDueIn => "",
+ },
+ 0
+ ];
+ }
+ else {
+ my %v;
+ my %create = %fields;
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ }
+
+ if ($v{Name} eq '<queue name>') {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ return [
+ "# Please set the queue name.",
+ [ "id", @fields, keys %o ], $changes, 1
+ ];
+ }
+
+ $queue->Create(%v);
+ unless ($queue->Id) {
+ return [ "# Could not create queue.", [], {}, 1 ];
+ }
+
+ delete $data{id};
+ $id = $queue->Id;
+ push(@comments, "# Queue $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+if (%data == 0) {
+ my @data;
+
+ push @data, [ id => "queue/".$queue->Id ];
+ foreach my $key (@fields) {
+ push @data, [ $key => $queue->$key ];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+ $set = "Set$key";
+
+ next if $val eq $queue->$key;
+ ($n, $s) = $queue->$set($val);
+ }
+ elsif ($key ne 'id') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ @$o = ("id", @fields, keys %o);
+ $k = $changes;
+ }
+ }
+ }
+
+ push(@comments, "# Queue $id updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/queue/ns b/rt/html/REST/1.0/Forms/queue/ns
new file mode 100644
index 0000000..360eea8
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/queue/ns
@@ -0,0 +1,38 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/queue/ns
+%#
+<%ARGS>
+$id
+</%ARGS>
+<%perl>
+use RT::Queues;
+
+my $queues = new RT::Queues $session{CurrentUser};
+$queues->Limit(FIELD => 'Name', OPERATOR => '=', VALUE => $id);
+if ($queues->Count == 0) {
+ return (0, "No queue named $id exists.");
+}
+return $queues->Next->Id;
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/attachments b/rt/html/REST/1.0/Forms/ticket/attachments
new file mode 100644
index 0000000..bcb571a
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/attachments
@@ -0,0 +1,107 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/ticket/attachments
+%#
+<%ARGS>
+$id
+$args => undef
+</%ARGS>
+<%perl>
+my @data;
+my ($c, $o, $k, $e) = ("", [], {}, "");
+my $ticket = new RT::Ticket $session{CurrentUser};
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my @arglist = split('/', $args);
+my ($aid, $content);
+
+if ($arglist[1] eq 'content') {
+ $aid = $arglist[0];
+ $content = 1;
+} else {
+ $aid = $args;
+ $content = 0;
+}
+
+if ($aid) {
+ unless ($aid =~ /^\d+$/) {
+ return [ "# Invalid attachment id: $aid", [], {}, 1 ];
+ }
+ my $attachment = new RT::Attachment $session{CurrentUser};
+ $attachment->Load($aid);
+ unless ($attachment->Id eq $aid) {
+ return [ "# Invalid attachment id: $aid", [], {}, 1 ];
+ }
+ if ($content) {
+ $c = $attachment->Content;
+ } else {
+ my @data;
+ push @data, [ id => $attachment->Id ];
+ push @data, [ Subject => $attachment->Subject ];
+ push @data, [ Creator => $attachment->Creator ];
+ push @data, [ Created => $attachment->Created ];
+ push @data, [ Transaction => $attachment->TransactionId ];
+ push @data, [ Parent => $attachment->Parent ];
+ push @data, [ MessageId => $attachment->MessageId ];
+ push @data, [ Filename => $attachment->Filename ];
+ push @data, [ ContentType => $attachment->ContentType ];
+ push @data, [ ContentEncoding => $attachment->ContentEncoding ];
+ push @data, [ Headers => $attachment->Headers ];
+ push @data, [ Content => $attachment->Content ];
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+ }
+
+}
+else {
+ my @attachments;
+ my $transactions = $ticket->Transactions;
+ while (my $t = $transactions->Next) {
+ my $attachments = $t->Attachments;
+ while (my $a = $attachments->Next) {
+ next unless $a->Filename;
+ my $size = length($a->Content);
+ if ($size > 1024) { $size = int($size/102.4)/10 . "k" }
+ else { $size .= "b" }
+ push @attachments, $a->Id.": ".$a->Filename." (".$size.")";
+ }
+ }
+
+ if (@attachments) {
+ $o = [ "id", "Attachments" ];
+ $k = {
+ id => "ticket/".$ticket->Id."/attachments",
+ Attachments => \@attachments
+ };
+ }
+}
+
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/default b/rt/html/REST/1.0/Forms/ticket/default
new file mode 100644
index 0000000..fec499b
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/default
@@ -0,0 +1,253 @@
+%# REST/1.0/Forms/ticket/default
+%#
+<%ARGS>
+$id
+$changes => {}
+$fields => undef
+</%ARGS>
+<%perl>
+use MIME::Entity;
+
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $ticket = new RT::Ticket $session{CurrentUser};
+my @dates = qw(Created Starts Started Due Resolved Told);
+my @people = qw(Requestors Cc AdminCc);
+my @create = qw(Queue Requestor Subject Cc AdminCc Owner Status Priority
+ InitialPriority FinalPriority TimeEstimated TimeWorked
+ TimeLeft Starts Started Due Resolved);
+my @simple = qw(Subject Status Priority Disabled TimeEstimated TimeWorked
+ TimeLeft InitialPriority FinalPriority);
+my %dates = map {lc $_ => $_} @dates;
+my %people = map {lc $_ => $_} @people;
+my %create = map {lc $_ => $_} @create;
+my %simple = map {lc $_ => $_} @simple;
+
+# Are we dealing with an existing ticket?
+if ($id ne 'new') {
+ $ticket->Load($id);
+ if (!$ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+ }
+ elsif (!$ticket->CurrentUserHasRight('ShowTicket') ||
+ (%data && !$ticket->CurrentUserHasRight('ModifyTicket')))
+ {
+ my $act = %data ? "modify" : "display";
+ return [ "# You are not allowed to $act ticket $id.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ # GET ticket/new: Return a suitable default form.
+ # We get defaults from queue/1 (XXX: What if it isn't there?).
+ my $due = new RT::Date $session{CurrentUser};
+ my $queue = new RT::Queue $session{CurrentUser};
+ my $starts = new RT::Date $session{CurrentUser};
+ $queue->Load(1);
+ $due->SetToNow;
+ $due->AddDays($queue->DefaultDueIn) if $queue->DefaultDueIn;
+ $starts->SetToNow;
+
+ return [
+ "# Required: Queue, Requestor, Subject",
+ [ qw(id Queue Requestor Subject Cc AdminCc Owner Status Priority
+ InitialPriority FinalPriority TimeEstimated Starts Due Text) ],
+ {
+ id => "ticket/new",
+ Queue => $queue->Name,
+ Requestor => $session{CurrentUser}->Name,
+ Subject => "",
+ Cc => [],
+ AdminCc => [],
+ Owner => "",
+ Status => "new",
+ Priority => $queue->InitialPriority,
+ InitialPriority => $queue->InitialPriority,
+ FinalPriority => $queue->FinalPriority,
+ TimeEstimated => 0,
+ Starts => $starts->ISO,
+ Due => $due->ISO,
+ Text => "",
+ },
+ 0
+ ];
+ }
+ else {
+ # We'll create a new ticket, and fall through to set fields that
+ # can't be set in the call to Create().
+ my (%v, $text);
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ elsif (lc $k eq 'text') {
+ $text = delete $data{$k};
+ }
+ }
+
+ if ($text) {
+ $v{MIMEObj} =
+ MIME::Entity->build(
+ From => $session{CurrentUser}->EmailAddress,
+ Subject => $v{Subject},
+ Data => $text
+ );
+ }
+
+ $ticket->Create(%v);
+ unless ($ticket->Id) {
+ return [ "# Could not create ticket.", [], {}, 1 ];
+ }
+
+ delete $data{id};
+ $id = $ticket->Id;
+ push(@comments, "# Ticket $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+# Now we know we're dealing with an existing ticket.
+if (%data == 0) {
+ my ($time, $key, $val, @data);
+
+ push @data, [ id => "ticket/".$ticket->Id ];
+ push @data, [ Queue => $ticket->QueueObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Queue'});
+ push @data, [ Owner => $ticket->OwnerObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Owner'});
+ push @data, [ Creator => $ticket->CreatorObj->Name ]
+ if (!%$fields || exists $fields->{lc 'Creator'});
+
+ foreach (qw(Subject Status Priority InitialPriority FinalPriority)) {
+ next unless (!%$fields || (exists $fields->{lc $_}));
+ push @data, [$_ => $ticket->$_ ];
+ }
+
+ foreach $key (@people) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ push @data, [ $key => [ $ticket->$key->MemberEmailAddresses ] ];
+ }
+
+ $time = new RT::Date ($session{CurrentUser});
+ foreach $key (@dates) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ $time->Set(Format => 'sql', Value => $ticket->$key);
+ push @data, [ $key => $time->AsString ];
+ }
+
+ $time = new RT::Date ($session{CurrentUser});
+ foreach $key (qw(TimeEstimated TimeWorked TimeLeft)) {
+ next unless (!%$fields || (exists $fields->{lc $key}));
+ $val = $ticket->$key || 0;
+ $val = $time->DurationAsString($val*60) if $val;
+ push @data, [ $key => $val ];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (ref $val eq 'ARRAY') {
+ unless ($key =~ /^(?:Requestors|Cc|AdminCc)$/i) {
+ $n = 0;
+ $s = "$key may have only one value.";
+ goto SET;
+ }
+ }
+
+ if ($key =~ /^queue$/i) {
+ next if $val eq $ticket->QueueObj->Name;
+ ($n, $s) = $ticket->SetQueue($val);
+ }
+ elsif ($key =~ /^owner$/i) {
+ next if $val eq $ticket->OwnerObj->Name;
+ ($n, $s) = $ticket->SetOwner($val);
+ }
+ elsif (exists $simple{$key}) {
+ $key = $simple{$key};
+ $set = "Set$key";
+
+ next if $val eq $ticket->$key;
+ ($n, $s) = $ticket->$set($val);
+ }
+ elsif (exists $dates{$key}) {
+ $key = $dates{$key};
+ $set = "Set$key";
+
+ my $time = new RT::Date $session{CurrentUser};
+ $time->Set(Format => 'sql', Value => $ticket->$key);
+ next if ($val =~ /^not set$/i || $val eq $time->AsString);
+ ($n, $s) = $ticket->$set($val);
+ }
+ elsif (exists $people{$key}) {
+ $key = $people{$key};
+ my ($p, @msgs);
+
+ my %new = map {$_=>1} @{ vsplit($val) };
+ my %old = map {$_=>1} $ticket->$key->MemberEmailAddresses;
+ my $type = $key eq 'Requestors' ? 'Requestor' : $key;
+
+ foreach $p (keys %old) {
+ unless (exists $new{$p}) {
+ ($s, $n) = $ticket->DeleteWatcher(Type => $type,
+ Email => $p);
+ push @msgs, [ $s, $n ];
+ }
+ }
+ foreach $p (keys %new) {
+ # XXX: This is a stupid test.
+ unless ($p =~ /^[\w.+-]+\@([\w.-]+\.)*\w+.?$/) {
+ $s = 0;
+ $n = "$p is not a valid email address.";
+ push @msgs, [ $s, $n ];
+ next;
+ }
+ unless ($ticket->IsWatcher(Type => $type, Email => $p)) {
+ ($s, $n) = $ticket->AddWatcher(Type => $type,
+ Email => $p);
+ push @msgs, [ $s, $n ];
+ }
+ }
+
+ $n = 1;
+ if (@msgs = grep {$_->[0] == 0} @msgs) {
+ $n = 0;
+ $s = join "\n", map {"# ".$_->[1]} @msgs;
+ $s =~ s/^# //;
+ }
+ }
+ elsif ($key ne 'id' && $key ne 'type') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete $o{id};
+ @$o = ("id", keys %o);
+ $k = $changes;
+ }
+ }
+ }
+ push(@comments, "# Ticket ".$ticket->id." updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [$c, $o, $k, $e];
+
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/history b/rt/html/REST/1.0/Forms/ticket/history
new file mode 100644
index 0000000..f5c1dc9
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/history
@@ -0,0 +1,144 @@
+%# REST/1.0/Forms/ticket/history
+%#
+<%ARGS>
+$id
+$args => undef
+$format => undef
+$fields => undef
+</%ARGS>
+<%perl>
+my $ticket = new RT::Ticket $session{CurrentUser};
+my ($c, $o, $k, $e) = ("", [], {}, "");
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my $trans = $ticket->Transactions();
+my $total = $trans->Count();
+
+chomp $args;
+my @arglist = split('/', $args);
+my ($type, $tid);
+
+if ($arglist[0] eq 'type') {
+ $type = $arglist[1];
+} elsif ($arglist[0] eq 'id') {
+ $tid = $arglist[1];
+} else {
+ $type = $args;
+}
+
+if ($type) {
+ # Create, Set, Status, Correspond, Comment, Give, Steal, Take, Told
+ # CustomField, AddLink, DeleteLink, AddWatcher, DelWatcher
+ if ($args =~ /^links?$/) {
+ $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Link');
+ }
+ elsif ($args =~ /^watchers?$/) {
+ $trans->Limit(FIELD => 'Type', OPERATOR => 'LIKE', VALUE => '%Watcher');
+ }
+ else {
+ $trans->Limit(FIELD => 'Type', OPERATOR => '=', VALUE => $type);
+ }
+} elsif ($tid) {
+ $trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid);
+}
+
+if ($tid) {
+ my @data;
+ my $t = new RT::Transaction $session{CurrentUser};
+ $t->Load($tid);
+
+ push @data, [ id => $t->Id ];
+ push @data, [ EffectiveTicket => $t->EffectiveTicket ]
+ if (!%$fields || exists $fields->{lc 'EffectiveTicket'});
+ push @data, [ Ticket => $t->Ticket ]
+ if (!%$fields || exists $fields->{lc 'Ticket'});
+ push @data, [ TimeTaken => $t->TimeTaken ]
+ if (!%$fields || exists $fields->{lc 'TimeTaken'});
+ push @data, [ Type => $t->Type ]
+ if (!%$fields || exists $fields->{lc 'Type'});
+ push @data, [ Field => $t->Field ]
+ if (!%$fields || exists $fields->{lc 'Field'});
+ push @data, [ OldValue => $t->OldValue ]
+ if (!%$fields || exists $fields->{lc 'OldValue'});
+ push @data, [ NewValue => $t->NewValue ]
+ if (!%$fields || exists $fields->{lc 'NewValue'});
+ push @data, [ Data => $t->Data ]
+ if (!%$fields || exists $fields->{lc 'Data'});
+ push @data, [ Description => $t->Description ]
+ if (!%$fields || exists $fields->{lc 'Description'});
+ push @data, [ Content => $t->Content ]
+ if (!%$fields || exists $fields->{lc 'Content'});
+
+
+ if (!%$fields || exists $fields->{lc 'Content'}) {
+ my $creator = new RT::User $session{CurrentUser};
+ $creator->Load($t->Creator);
+ push @data, [ Creator => $creator->Name ];
+ }
+ push @data, [ Created => $t->Created ]
+ if (!%$fields || exists $fields->{lc 'Created'});
+
+ if (!%$fields || exists $fields->{lc 'Attachments'}) {
+ my $attachlist;
+ my $attachments = $t->Attachments;
+ while (my $a = $attachments->Next) {
+ my $size = length($a->Content);
+ if ($size > 1024) { $size = int($size/102.4)/10 . "k" }
+ else { $size .= "b" }
+ $attachlist .= "\n" . $a->Id.": ".($a->Filename || "untitled")." (".$size.")";
+ }
+
+ push @data, [Attachments => $attachlist];
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+
+} else {
+ my (@data, $tids);
+ $format ||= "s";
+ $format = "l" if (%$fields);
+
+ while (my $t = $trans->Next) {
+ my $tid = $t->Id;
+
+ if ($format eq "l") {
+ $tids .= "," if $tids;
+ $tids .= $tid;
+ } else {
+ push @$o, $tid;
+ $k->{$tid} = $t->Description;
+ }
+ }
+
+ if ($format eq "l") {
+ my @tid;
+ push @tid, "ticket/$id/history/id/$tids";
+ my $fieldstring;
+ foreach my $key (keys %$fields) {
+ $fieldstring .= "," if $fieldstring;
+ $fieldstring .= $key;
+ }
+ my ($content, $forms);
+
+ $m->subexec("$RT::WebPath/REST/1.0/show",
+ id => \@tid,
+ format => $format,
+ fields => $fieldstring);
+ return [ $c, $o, $k, $e ];
+ }
+}
+
+if (!$c) {
+ my $sub = $trans->Count();
+ $c = "# $sub/$total ($args/total)";
+}
+
+return [ $c, $o, $k, $e ];
+
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/ticket/links b/rt/html/REST/1.0/Forms/ticket/links
new file mode 100644
index 0000000..8ac9dc2
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/ticket/links
@@ -0,0 +1,148 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/ticket/links
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => undef
+</%ARGS>
+<%perl>
+my @data;
+my $ticket = new RT::Ticket $session{CurrentUser};
+
+$ticket->Load($id);
+if (!$ticket->Id) {
+ return [ "# Ticket $id does not exist.", [], {}, 1 ];
+}
+
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy Members MemberOf);
+my %fields = map { lc $_ => $_ } @fields;
+
+my %lfields = (
+ Members => { Type => 'MemberOf', Mode => 'Base' },
+ ReferredToBy => { Type => 'RefersTo', Mode => 'Base' },
+ DependedOnBy => { Type => 'DependsOn', Mode => 'Base' },
+ MemberOf => { Type => 'MemberOf', Mode => 'Target' },
+ RefersTo => { Type => 'RefersTo', Mode => 'Target' },
+ DependsOn => { Type => 'DependsOn', Mode => 'Target' },
+);
+
+if ($changes) {
+ my ($get, $set, $key, $val, $n, $s);
+ my %data = %$changes;
+ my @comments;
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+
+ my %old;
+ my $field = $lfields{$key}->{Mode};
+ while (my $link = $ticket->$key->Next) {
+ $old{$link->$field} = 1;
+ }
+
+ my %new;
+ foreach my $nkey (@{vsplit($val)}) {
+ if ($nkey =~ /^\d+$/) {
+ my $uri = new RT::URI $session{CurrentUser};
+ my $tick = new RT::Ticket $session{CurrentUser};
+ $tick->Load($nkey);
+ if ($tick->Id) {
+ $nkey = $uri->FromObject($tick);
+ }
+ else {
+ $n = 0;
+ $s = "Ticket $nkey does not exist.";
+ goto SET;
+ }
+ }
+ $new{$nkey} = 1;
+ }
+
+ foreach my $u (keys %old) {
+ if (exists $new{$u}) {
+ delete $new{$u};
+ }
+ else {
+ my $type = $lfields{$key}->{Type};
+ my $mode = $lfields{$key}->{Mode};
+ ($n, $s) = $ticket->DeleteLink(Type => $type, $mode => $u);
+ goto SET;
+ }
+ }
+ foreach my $u (keys %new) {
+ my $type = $lfields{$key}->{Type};
+ my $mode = $lfields{$key}->{Mode};
+ ($n, $s) = $ticket->AddLink(Type => $type, $mode => $u);
+ goto SET;
+ }
+ }
+ elsif ($key ne 'id' && $key ne 'type') {
+ $n = 0;
+ $s = "Unknown field: $key";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ @$o = ("id", @fields);
+ %$k = %data;
+ }
+ }
+ }
+
+ push(@comments, "# Links for ticket $id updated.") unless @comments;
+ $c = join("\n", @comments) if @comments;
+}
+else {
+ my @data;
+
+ push @data, [ id => "ticket/".$ticket->Id."/links" ];
+ foreach my $key (@fields) {
+ my @val;
+
+ my $field = $lfields{$key}->{Mode};
+ while (my $link = $ticket->$key->Next) {
+ push @val, $link->$field;
+ }
+ push(@val, "") if (@val == 0 && $format eq 'l');
+ push @data, [ $key => [ @val ] ] if @val;
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/user/default b/rt/html/REST/1.0/Forms/user/default
new file mode 100644
index 0000000..6b216e0
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/user/default
@@ -0,0 +1,141 @@
+%# REST/1.0/Forms/user/default
+%#
+<%ARGS>
+$id
+$format => 's'
+$changes => {}
+</%ARGS>
+<%perl>
+my @comments;
+my ($c, $o, $k, $e) = ("", [], {}, 0);
+my %data = %$changes;
+my $user = new RT::User $session{CurrentUser};
+my @fields = qw(RealName NickName Gecos Organization Address1 Address2 City
+ State Zip Country HomePhone WorkPhone MobilePhone PagerPhone
+ FreeformContactInfo Comments Signature Lang EmailEncoding
+ WebEncoding ExternalContactInfoId ContactInfoSystem
+ ExternalAuthId AuthSystem);
+my %fields = map { lc $_ => $_ } @fields;
+
+if ($id ne 'new') {
+ $user->Load($id);
+ if (!$user->Id) {
+ return [ "# User $id does not exist.", [], {}, 1 ];
+ }
+}
+else {
+ if (%data == 0) {
+ return [
+ "# Required: Name, EmailAddress",
+ [ qw(id Name EmailAddress Organization Password Comments) ],
+ {
+ id => "user/new",
+ Name => "",
+ EmailAddress => "",
+ Organization => "",
+ Password => "",
+ Comments => ""
+ },
+ 0
+ ];
+ }
+ else {
+ my %v;
+ my %create = %fields;
+ $create{name} = "Name";
+ $create{password} = "Password";
+ $create{emailaddress} = "EmailAddress";
+ $create{contactinfo} = "FreeformContactInfo";
+ # Do any fields need to be excluded here?
+
+ foreach my $k (keys %data) {
+ if (exists $create{lc $k}) {
+ $v{$create{lc $k}} = delete $data{$k};
+ }
+ }
+
+ $user->Create(%v);
+ unless ($user->Id) {
+ return [ "# Could not create user.", [], {}, 1 ];
+ }
+
+ $id = $user->Id;
+ delete $data{id};
+ push(@comments, "# User $id created.");
+ goto DONE if %data == 0;
+ }
+}
+
+if (%data == 0) {
+ my @data;
+
+ push @data, [ id => "user/".$user->Id ];
+ push @data, [ Name => $user->Name ];
+ push @data, [ Password => '********' ];
+ push @data, [ EmailAddress => $user->EmailAddress ];
+
+ foreach my $key (@fields) {
+ my $val = $user->$key;
+
+ if ($format eq 'l' || (defined $val && $val ne '')) {
+ $key = "ContactInfo" if $key eq 'FreeformContactInfo';
+ push @data, [ $key => $val ];
+ }
+ }
+
+ my %k = map {@$_} @data;
+ $o = [ map {$_->[0]} @data ];
+ $k = \%k;
+}
+else {
+ my ($get, $set, $key, $val, $n, $s);
+
+ foreach $key (keys %data) {
+ $val = $data{$key};
+ $key = lc $key;
+ $n = 1;
+
+ if ($key eq 'name' || $key eq 'emailaddress' ||
+ $key eq 'contactinfo' || exists $fields{$key})
+ {
+ if (exists $fields{$key}) {
+ $key = $fields{$key};
+ }
+ else {
+ $key = "FreeformContactInfo" if $key eq 'contactinfo';
+ $key = "EmailAddress" if $key eq 'emailaddress';
+ $key = "Name" if $key eq 'name';
+ }
+ $set = "Set$key";
+
+ next if $val eq $user->$key;
+ ($n, $s) = $user->$set($val);
+ }
+ elsif ($key eq 'password') {
+ ($n, $s) = $user->SetPassword($val) unless $val =~ /^\**$/;
+ }
+ elsif ($key ne 'id') {
+ $n = 0;
+ $s = "Unknown field.";
+ }
+
+ SET:
+ if ($n == 0) {
+ $e = 1;
+ push @comments, "# $key: $s";
+ unless (@$o) {
+ my %o = keys %$changes;
+ delete @o{"id", @fields};
+ @$o = ("id", @fields, keys %o);
+ $k = $changes;
+ }
+ }
+ }
+
+ push(@comments, "# User $id updated.") unless $n == 0;
+}
+
+DONE:
+$c ||= join("\n", @comments) if @comments;
+return [ $c, $o, $k, $e ];
+</%perl>
diff --git a/rt/html/REST/1.0/Forms/user/ns b/rt/html/REST/1.0/Forms/user/ns
new file mode 100644
index 0000000..36b3237
--- /dev/null
+++ b/rt/html/REST/1.0/Forms/user/ns
@@ -0,0 +1,41 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/Forms/user/ns
+%#
+<%ARGS>
+$id
+</%ARGS>
+<%perl>
+use RT::Users;
+
+my $field = "Name";
+$field = "EmailAddress" if $id =~ /\@/;
+
+my $users = new RT::Users $session{CurrentUser};
+$users->Limit(FIELD => $field, OPERATOR => '=', VALUE => $id);
+if ($users->Count == 0) {
+ return (0, "No user named $id exists.");
+}
+return $users->Next->Id;
+</%perl>
diff --git a/rt/html/REST/1.0/NoAuth/mail-gateway b/rt/html/REST/1.0/NoAuth/mail-gateway
new file mode 100644
index 0000000..359331f
--- /dev/null
+++ b/rt/html/REST/1.0/NoAuth/mail-gateway
@@ -0,0 +1,52 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%ARGS>
+$message
+$queue => 1
+$action => "correspond"
+$ticket => undef
+</%ARGS>
+<%init>
+use RT::Interface::Email;
+my ( $status, $error, $Ticket ) = RT::Interface::Email::Gateway(\%ARGS);
+</%init>
+<%flags>
+inherit => undef # inhibit UTF8 conversion done in /autohandler
+</%flags>
+% if ($status == -75 ) {
+temporary failure
+% }
+% elsif ($status == 1) {
+ok
+% if ( $Ticket->Id ) {
+Ticket: <% $Ticket->Id %>
+Queue: <% $Ticket->QueueObj->Name %>
+Owner: <% $Ticket->OwnerObj->Name %>
+Status: <% $Ticket->Status %>
+Subject: <% $Ticket->Subject %>
+Requestor: <% $Ticket->Requestors->MemberEmailAddressesAsString %>
+% }
+% } else {
+not ok - <%$error%>
+% }
diff --git a/rt/html/REST/1.0/autohandler b/rt/html/REST/1.0/autohandler
new file mode 100644
index 0000000..9084a1b
--- /dev/null
+++ b/rt/html/REST/1.0/autohandler
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/autohandler
+%#
+<%INIT>
+use RT::Interface::REST;
+$r->content_type('text/plain');
+$m->error_format('text');
+$m->call_next();
+$m->abort();
+</%INIT>
diff --git a/rt/html/REST/1.0/dhandler b/rt/html/REST/1.0/dhandler
new file mode 100644
index 0000000..ef5217f
--- /dev/null
+++ b/rt/html/REST/1.0/dhandler
@@ -0,0 +1,287 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/dhandler
+%#
+<%ARGS>
+@id => ()
+$fields => undef
+$format => undef
+$content => undef
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output = "";
+my $status = "200 Ok";
+my $object = $m->dhandler_arg;
+
+my $name = qr{[\w.-]+};
+my $list = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
+my $label = '[a-zA-Z0-9@_.+-]+';
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+my $labels = "(?:$label,)*$label";
+
+# We must handle requests such as the following:
+#
+# 1. http://.../REST/1.0/show (with a list of object specifications).
+# 2. http://.../REST/1.0/edit (with a self-contained list of forms).
+# 3. http://.../REST/1.0/ticket/show (implicit type specification).
+# http://.../REST/1.0/ticket/edit
+# 4. http://.../REST/1.0/ticket/nn (all possibly with a single form).
+# http://.../REST/1.0/ticket/nn/history
+# http://.../REST/1.0/ticket/nn/attachment/1
+#
+# Objects are specified by their type, and either a unique numeric ID,
+# or a unique name (e.g. ticket/1, queue/foo). Multiple objects of the
+# same type may be specified by a comma-separated list of identifiers
+# (e.g., user/ams,rai or ticket/1-3,5-7).
+#
+# Ultimately, we want a list of object specifications to operate upon.
+# The URLs in (4) provide enough information to identify an object. We
+# will assemble submitted information into that format in other cases.
+#
+my (@objects, $forms);
+my $utype;
+
+if ($object eq 'show' || # $REST/show
+ (($utype) = ($object =~ m{^($name)/show$}))) # $REST/ticket/show
+{
+ # We'll convert type/range specifications ("ticket/1-3,7-9/history")
+ # into a list of singular object specifications ("ticket/1/history").
+ # If the URL specifies a type, we'll accept only that one.
+ foreach my $id (@id) {
+ $id =~ s|^(?:$utype/)?|$utype/| if $utype;
+ if (my ($type, $oids, $extra) =
+ ($id =~ m#^($name)/($list|$labels)(?:(/.*))?$#o))
+ {
+ foreach my $oid (expand_list($oids)) {
+ if ($extra =~ m{^(?:/($name)(?:/(.*))?)?$}o) {
+ my ($attr, $args) = ($1, $2);
+ # expand transaction and attachment range specifications
+ # (if applicable)
+ my $tids;
+ if ($attr eq 'history' && $args =~ m#id/(\d.*)#o) {
+ $tids = $1;
+ }
+ if ($tids) {
+ push(@objects, "$type/$oid/$attr/id/$_") for expand_list($tids);
+ } else {
+ push(@objects, "$type/$oid$extra");
+ }
+ }
+ }
+ }
+ else {
+ $status = "400 Bad Request";
+ $output = "Invalid object ID specified: '$id'";
+ goto OUTPUT;
+ }
+ }
+}
+elsif ($object eq 'edit' || # $REST/edit
+ (($utype) = ($object =~ m{^($name)/edit$}))) # $REST/ticket/edit
+{
+ # We'll make sure each of the submitted forms is syntactically valid
+ # and sufficiently identifies an object to operate upon, then add to
+ # the object list as above.
+ my @output;
+
+ $forms = form_parse($content);
+ foreach my $form (@$forms) {
+ my ($c, $o, $k, $e) = @$form;
+
+ if ($e) {
+ push @output, [ "# Syntax error.", $o, $k, $e ];
+ }
+ else {
+ my ($type, $id);
+
+ # Look for matching types in the ID, form, and URL.
+ $type = exists $k->{type} ? $k->{type} : $utype;
+ $type =~ s|^(?:$utype)?|$utype/| if $utype;
+ $type =~ s|/$||;
+
+ if (exists $k->{id}) {
+ $id = $k->{id};
+ $id =~ s|^(?:$type/)?|$type/| if $type;
+
+ if ($id =~ m#^$name/(?:$label|\d+)(?:/.*)?#o) {
+ push @objects, $id;
+ }
+ else {
+ push @output, [ "# Invalid object ID: '$id'", $o, $k, $e ];
+ }
+ }
+ else {
+ push @output, [ "# No object ID specified.", $o, $k, $e ];
+ }
+ }
+ }
+ # If we saw any errors at this stage, we won't process any part of
+ # the submitted data.
+ if (@output) {
+ unshift @output, [ "# Please resubmit with errors corrected." ];
+ $status = "409 Syntax Error";
+ $output = form_compose(\@output);
+ goto OUTPUT;
+ }
+}
+else {
+ # We'll assume that this is in the correct format already. Otherwise
+ # it will be caught by the loop below.
+ push @objects, $object;
+
+ if ($content) {
+ $forms = form_parse($content);
+
+ if (@$forms > 1) {
+ $status = "400 Bad Request";
+ $output = "You may submit only one form to this object.";
+ goto OUTPUT;
+ }
+
+ my ($c, $o, $k, $e) = @{ $forms->[0] };
+ if ($e) {
+ $status = "409 Syntax Error";
+ $output = form_compose([ ["# Syntax error.", $o, $k, $e] ]);
+ goto OUTPUT;
+ }
+ }
+}
+
+# Make sure we have something to do.
+unless (@objects) {
+ $status = "400 Bad Request";
+ $output = "No objects specified.";
+ goto OUTPUT;
+}
+
+# Parse and validate any field specifications.
+my (%fields, @fields);
+if ($fields) {
+ unless ($fields =~ /^(?:$field,)*$field$/) {
+ $status = "400 Bad Request";
+ $output = "Invalid field specification: $fields";
+ goto OUTPUT;
+ }
+ @fields = map lc, split /,/, $fields;
+ @fields{@fields} = ();
+ unless (exists $fields{id}) {
+ unshift @fields, "id";
+ $fields{id} = ();
+ }
+}
+
+my (@comments, @output);
+
+foreach $object (@objects) {
+ my ($handler, $type, $id, $attr, $args);
+ my ($c, $o, $k, $e) = ("", ["id"], {id => $object}, 0);
+
+ my $i = 0;
+ if ($object =~ m{^($name)/(\d+|$label)(?:/($name)(?:/(.*))?)?$}o ||
+ $object =~ m{^($name)/(new)$}o)
+ {
+ ($type, $id, $attr, $args) = ($1, $2, ($3 || 'default'), $4);
+ $handler = "Forms/$type/$attr";
+
+ unless ($m->comp_exists($handler)) {
+ $args = "$attr/$args";
+ $handler = "Forms/$type/default";
+
+ unless ($m->comp_exists($handler)) {
+ $i = 2;
+ $c = "# Unknown object type: $type";
+ }
+ }
+ elsif ($id ne 'new' && $id !~ /^\d+$/) {
+ my $ns = "Forms/$type/ns";
+
+ # Can we resolve named objects?
+ unless ($m->comp_exists($ns)) {
+ $i = 3;
+ $c = "# Objects of type $type must be specified by numeric id.";
+ }
+ else {
+ my ($n, $s) = $m->comp("Forms/$type/ns", id => $id);
+ if ($n <= 0) { $i = 4; $c = "# $s"; }
+ else { $i = 0; $id = $n; }
+ }
+ }
+ else {
+ $i = 0;
+ }
+ }
+ else {
+ $i = 1;
+ $c = "# Invalid object specification: '$object'";
+ }
+
+ if ($i != 0) {
+ if ($content) {
+ (undef, $o, $k, $e) = @{ shift @$forms };
+ }
+ push @output, [ $c, $o, $k ];
+ next;
+ }
+
+ unless ($content) {
+ my $d = $m->comp($handler, id => $id, args => $args, format => $format, fields => \%fields);
+ my ($c, $o, $k, $e) = @$d;
+
+ if (!$e && @$o && keys %fields) {
+ my %lk = map { lc $_ => $_ } keys %$k;
+ @$o = map { $lk{$_} } @fields;
+ foreach my $key (keys %$k) {
+ delete $k->{$key} unless exists $fields{lc $key};
+ }
+ }
+ push(@output, [ $c, $o, $k ]) if ($c || @$o || keys %$k);
+ }
+ else {
+ my ($c, $o, $k, $e) = @{ shift @$forms };
+ my $d = $m->comp($handler, id => $id, args => $args, format => $format,
+ changes => $k);
+ ($c, $o, $k, $e) = @$d;
+
+ # We won't pass $e through to compose, trusting instead that the
+ # handler added suitable comments for the user.
+ if ($e) {
+ $status = "409 Syntax Error" if @$o;
+ push @output, [ $c, $o, $k ];
+ }
+ else {
+ push @comments, $c;
+ }
+ }
+}
+
+unshift(@output, [ join "\n", @comments ]) if @comments;
+$output = form_compose(\@output);
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/logout b/rt/html/REST/1.0/logout
new file mode 100644
index 0000000..b64938b
--- /dev/null
+++ b/rt/html/REST/1.0/logout
@@ -0,0 +1,27 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%PERL>
+tied(%session)->delete if (defined %session);
+</%PERL>
+RT/<% $RT::VERSION %> 200 Ok
diff --git a/rt/html/REST/1.0/search/dhandler b/rt/html/REST/1.0/search/dhandler
new file mode 100644
index 0000000..90b4653
--- /dev/null
+++ b/rt/html/REST/1.0/search/dhandler
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/search/dhandler
+%#
+<%INIT>
+my $status = "500 Server Error";
+my $output = "Unsupported object type.";
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/search/ticket b/rt/html/REST/1.0/search/ticket
new file mode 100644
index 0000000..2443529
--- /dev/null
+++ b/rt/html/REST/1.0/search/ticket
@@ -0,0 +1,119 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/search/ticket
+%#
+<%ARGS>
+$query
+$format => undef
+$orderby => undef
+$fields => undef
+</%ARGS>
+<%INIT>
+my $output = "";
+my $status = "200 Ok";
+my $tickets = new RT::Tickets $session{CurrentUser};
+
+# Parse and validate any field specifications.
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+my (%fields, @fields);
+if ($fields) {
+ $format = "l";
+ unless ($fields =~ /^(?:$field,)*$field$/) {
+ $status = "400 Bad Request";
+ $output = "Invalid field specification: $fields";
+ goto OUTPUT;
+ }
+ @fields = map lc, split /,/, $fields;
+ @fields{@fields} = ();
+ unless (exists $fields{id}) {
+ unshift @fields, "id";
+ $fields{id} = ();
+ }
+}
+
+$format ||= "s";
+if ($format !~ /^[isl]$/) {
+ $status = "400 Bad request";
+ $output = "Unknown listing format: $format. (Use i, s, or l.)\n";
+ goto OUTPUT;
+}
+
+my ($n, $s);
+eval {
+ ($n, $s) = $tickets->FromSQL($query);
+};
+my $sortstring = "";
+if ($orderby) {
+ $sortstring = 'FIELD => ';
+ my $order = substr($orderby, 0, 1);
+ if ($order eq '+' || $order eq '-') {
+ $sortstring .= 'substr($orderby, 1)';
+ if ($order eq '+') {
+ $sortstring .= ", ORDER => 'ASC'";
+ } elsif ($order eq '-') {
+ $sortstring .= ", ORDER => 'DESC'";
+ }
+ } else {
+ $sortstring .= '$orderby';
+ }
+ my $foo = 'FIELD => ';
+ $foo .= '$orderby';
+ $tickets->OrderBy(eval $sortstring);
+}
+if ($@ || $n == 0) {
+ $s ||= $@;
+ $status = "400 Bad request";
+ $output = "Invalid query: '$s'.\n";
+ goto OUTPUT;
+}
+
+$n = 0;
+my @output;
+while (my $ticket = $tickets->Next) {
+ $n++;
+
+ if ($format eq "i") {
+ $output .= "ticket/" . $ticket->Id . "\n";
+ }
+ elsif ($format eq "s") {
+ $output .= $ticket->Id . ": ". $ticket->Subject . "\n";
+ }
+ else {
+ my $id = $ticket->Id;
+ my $d = $m->comp("$RT::WebPath/REST/1.0/Forms/ticket/default", id => $id, format => $format, fields => \%fields);
+ my ($c, $o, $k, $e) = @$d;
+ push @output, [ $c, $o, $k ];
+ }
+}
+if ($n == 0 && $format ne "i") {
+ $output = "No matching results.\n";
+}
+
+$output = form_compose(\@output) if @output;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/comment b/rt/html/REST/1.0/ticket/comment
new file mode 100644
index 0000000..9d1b062
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/comment
@@ -0,0 +1,149 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/comment
+%#
+<%ARGS>
+$content
+</%ARGS>
+<%INIT>
+use MIME::Entity;
+use LWP::MediaTypes;
+use RT::Interface::REST;
+use File::Temp qw(tempfile);
+
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+my $status = "200 Ok";
+my $output;
+my $action;
+
+# http://.../REST/1.0/ticket/comment/1
+my ($c, $o, $k, $e) = @{ form_parse($content)->[0] };
+if ($e || !$o) {
+ if (!$o) {
+ $output = "Empty form submitted.\n";
+ }
+ else {
+ $c = "# Syntax error.";
+ $output = form_compose([[$c, $o, $k, $e]]);
+ }
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+$object =~ s#^/##;
+$object ||= $k->{Ticket};
+unless ($object =~ /^\d+/) {
+ $output = "Invalid ticket id: `$object'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+if ($k->{Ticket} && $object ne $k->{Ticket}) {
+ $output = "The submitted form and URL specify different tickets.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+($action = $k->{Action}) =~ s/^(.)(.*)$/\U$1\L$2\E/;
+unless ($action =~ /^(?:Comment|Correspond)$/) {
+ $output = "Invalid action: `$action'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+my $text = $k->{Text};
+my @atts = @{ vsplit($k->{Attachment}) };
+
+if (!$k->{Text} && @atts == 0) {
+ $status = "400 Bad Request";
+ $output = "Empty comment with no attachments submitted.\n";
+ goto OUTPUT;
+}
+
+my $cgi = $m->cgi_object;
+my $ent = MIME::Entity->build(Type => "multipart/mixed");
+$ent->attach(Data => $k->{Text}) if $k->{Text};
+
+my $i = 1;
+foreach my $att (@atts) {
+ local $/=undef;
+ my $file = $att;
+ $file =~ s#^.*[\\/]##;
+
+ my $fh = $cgi->upload("attachment_$i");
+ if ($fh) {
+ my $buf;
+ my ($w, $tmp) = tempfile();
+ my $info = $cgi->uploadInfo();
+
+ while (sysread($fh, $buf, 8192)) {
+ syswrite($w, $buf);
+ }
+
+ $ent->attach(
+ Path => $tmp,
+ Type => $info->{'Content-Type'} || guess_media_type($tmp),
+ Filename => $file,
+ Disposition => "attachment"
+ );
+ }
+ else {
+ $status = "400 Bad Request";
+ $output = "No attachment for $att.\n";
+ goto OUTPUT;
+ }
+
+ $i++;
+}
+
+$ticket->Load($object);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: `$object'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+unless ($ticket->CurrentUserHasRight('ModifyTicket') ||
+ ($action eq "Comment" &&
+ $ticket->CurrentUserHasRight("CommentOnTicket")) ||
+ ($action eq "Correspond" &&
+ $ticket->CurrentUserHasRight("ReplyToTicket")))
+{
+ $output = "You are not allowed to $action on ticket $object.\n";
+ $status = "403 Permission denied";
+ goto OUTPUT;
+}
+
+my $cc = join ", ", @{ vsplit($k->{Cc}) };
+my $bcc = join ", ", @{ vsplit($k->{Bcc}) };
+my ($n, $s) = $ticket->$action(MIMEObj => $ent,
+ CcMessageTo => $cc,
+ BccMessageTo => $bcc,
+ TimeTaken => $k->{TimeWorked} || 0);
+$output = $s;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/link b/rt/html/REST/1.0/ticket/link
new file mode 100644
index 0000000..5747625
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/link
@@ -0,0 +1,96 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/link
+%#
+<%ARGS>
+$id => undef
+$del => 0
+$rel
+$to
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output;
+my $status = "200 Ok";
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+
+my @fields = qw(DependsOn DependedOnBy RefersTo ReferredToBy HasMember MemberOf);
+my %fields = map { lc $_ => $_ } @fields;
+my %lfields = (
+ HasMember => { Type => 'MemberOf', Mode => 'Base' },
+ ReferredToBy => { Type => 'RefersTo', Mode => 'Base' },
+ DependedOnBy => { Type => 'DependsOn', Mode => 'Base' },
+ MemberOf => { Type => 'MemberOf', Mode => 'Target' },
+ RefersTo => { Type => 'RefersTo', Mode => 'Target' },
+ DependsOn => { Type => 'DependsOn', Mode => 'Target' },
+);
+
+# http://.../REST/1.0/ticket/link/1
+
+$object =~ s#^/##;
+if ($id && $object && $id != $object) {
+ $output = "Different ids in URL (`$object') and submitted form (`$id').\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$id ||= $object;
+unless ($id =~ /^\d+$/ && $to =~ /^\d+$/) {
+ my $bad = ($id !~ /^\d+$/) ? $id : $to;
+ $output = $r->path_info. "\n";
+ $output .= "Invalid ticket id: '$bad'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+unless (exists $fields{lc $rel}) {
+ $output = "Invalid relationship: '$rel'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$rel = $fields{lc $rel};
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: '$id'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+
+my $type = $lfields{$rel}->{Type};
+my $mode = $lfields{$rel}->{Mode};
+
+my $n = 1;
+my $op = $del ? "DeleteLink" : "AddLink";
+
+($n, $output) = $ticket->$op(Type => $type, $mode => $to);
+if ($n == 0) {
+ $status = "500 Error";
+}
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/REST/1.0/ticket/merge b/rt/html/REST/1.0/ticket/merge
new file mode 100644
index 0000000..9cd2a7c
--- /dev/null
+++ b/rt/html/REST/1.0/ticket/merge
@@ -0,0 +1,78 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+%# REST/1.0/ticket/merge
+%#
+<%ARGS>
+$id => undef
+$into
+</%ARGS>
+<%INIT>
+use RT::Interface::REST;
+
+my $output;
+my $status = "200 Ok";
+my $ticket = new RT::Ticket $session{CurrentUser};
+my $object = $r->path_info;
+
+# http://.../REST/1.0/ticket/merge/1
+
+$object =~ s#^/##;
+if ($id && $object && $id != $object) {
+ $output = "Different ids in URL (`$object') and submitted form (`$id').\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+$id ||= $object;
+unless ($id =~ /^\d+$/ && $into =~ /^\d+$/) {
+ my $bad = ($id !~ /^\d+$/) ? $id : $into;
+ $output = $r->path_info. "\n";
+ $output .= "Invalid ticket id: `$bad'.\n";
+ $status = "400 Bad Request";
+ goto OUTPUT;
+}
+
+$ticket->Load($id);
+unless ($ticket->Id) {
+ $output = "Couldn't load ticket id: `$id'.\n";
+ $status = "404 Ticket not found";
+ goto OUTPUT;
+}
+unless ($ticket->CurrentUserHasRight('ModifyTicket')) {
+ $output = "You are not allowed to modify ticket $id.\n";
+ $status = "403 Permission denied";
+ goto OUTPUT;
+}
+
+my ($n, $s) = $ticket->MergeInto($into);
+
+if ($n == 0) {
+ $status = "500 Error";
+}
+$output = $s;
+
+OUTPUT:
+</%INIT>
+RT/<% $RT::VERSION %> <% $status %>
+
+<% $output |n %>
diff --git a/rt/html/Search/Bulk.html b/rt/html/Search/Bulk.html
new file mode 100644
index 0000000..de9143c
--- /dev/null
+++ b/rt/html/Search/Bulk.html
@@ -0,0 +1,218 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Bulk ticket update") &>
+<& /Elements/Tabs, Title => loc("Bulk ticket update") &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM METHOD=POST>
+<TABLE WIDTH=100% border=0 cellpadding=3 CELLSPACING=0>
+<TR>
+<TH><&|/l&>Update</&></TH>
+%foreach my $col (@cols) {
+% my $colalias = $col;
+% $colalias =~ s/(Obj\-\>|)(Name|AsString)//;
+
+<TH><% loc($colalias) %>&nbsp;</TH>
+%}
+</TR>
+
+<%PERL>
+
+my $i;
+
+
+
+$session{'tickets'}->RedoSearch();
+while (my $Ticket = $session{'tickets'}->Next) {
+ $i++;
+ if ($i % 2) {
+ $bgcolor = "#dddddd";
+ }
+ else {
+ $bgcolor = "#ffffff";
+ }
+ </%PERL>
+<TR bgcolor="<%$bgcolor%>">
+<TD><input type=checkbox name="UpdateTicket<%$Ticket->Id%>" CHECKED></TD>
+%# The ticket view is controlled by config.pm, WebOptions
+%foreach my $col (@cols) {
+<TD>
+% if ($col eq 'id') {
+<A HREF="<% $RT::WebPath%>/Ticket/Display.html?id=<%$Ticket->Id%>"><%$Ticket->Id()%></A>
+% }
+%else {
+<% eval "\$Ticket->$col()" %>&nbsp;
+%}
+</TD>
+%}
+</TR>
+%}
+
+
+
+</TABLE>
+
+<HR>
+
+
+<& /Elements/TitleBoxStart, title => loc('Update selected tickets') &>
+<TABLE>
+<TR>
+<TD VALIGN=TOP>
+<table>
+<tr><td class=label> <&|/l&>Make Owner</&>: </td>
+<td class=value> <& /Elements/SelectOwner, Name => "Owner" &> (<input type=checkbox name="ForceOwnerChange"> <&|/l&>Force change</&>) </td></tr>
+<tr><td class=label> <&|/l&>Add Requestor</&>: </td>
+<td class=value> <INPUT Name="AddRequestor" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Remove Requestor</&>: </td>
+<td class=value> <INPUT Name="DeleteRequestor" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Add Cc</&>: </td>
+<td class=value> <INPUT Name="AddCc" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Remove Cc</&>: </td>
+<td class=value> <INPUT Name="DeleteCc" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Add AdminCc</&>: </td>
+<td class=value> <INPUT Name="AddAdminCc" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Remove AdminCc</&>: </td>
+<td class=value> <INPUT Name="DeleteAdminCc" SIZE=20> </td></tr>
+</table>
+</TD>
+<TD VALIGN=TOP>
+<table>
+<tr><td class=label> <&|/l&>Make subject</&>: </td>
+<td class=value> <INPUT Name="Subject" SIZE=20> </td></tr>
+<tr><td class=label> <&|/l&>Make priority</&>: </td>
+<td class=value> <INPUT Name="Priority" SIZE=4> </td></tr>
+<tr><td class=label> <&|/l&>Make queue</&>: </td>
+<td class=value> <& /Elements/SelectQueue, Name => "Queue" &> </td></tr>
+<tr><td class=label> <&|/l&>Make Status</&>: </td>
+<td class=value> <& /Elements/SelectStatus, Name => "Status" &> </td></tr>
+<tr><td class=label> <&|/l&>Make date Starts</&>: </td>
+<td class=value> <& /Elements/SelectDate, Name => "Starts_Date", ShowTime => 0, Default => '' &> </td></tr>
+<tr><td class=label> <&|/l&>Make date Started</&>: </td>
+<td class=value> <& /Elements/SelectDate, Name => "Started_Date", ShowTime => 0, Default => '' &> </td></tr>
+<tr><td class=label> <&|/l&>Make date Told</&>: </td>
+<td class=value> <& /Elements/SelectDate, Name => "Told_Date", ShowTime => 0, Default => '' &> </td></tr>
+<tr><td class=label> <&|/l&>Make date Due</&>: </td>
+<td class=value> <& /Elements/SelectDate, Name => "Due_Date", ShowTime => 0, Default => '' &> </td></tr>
+<tr><td class=label> <&|/l&>Make date Resolved</&>: </td>
+<td class=value> <& /Elements/SelectDate, Name => "Resolved_Date", ShowTime => 0, Default => '' &> </td></tr>
+</table>
+
+</TD>
+</TR>
+</table>
+<& /Elements/TitleBoxEnd&>
+<& /Elements/TitleBoxStart, title => loc('Add comments or replies to selected tickets') &>
+<table>
+<tr><td align=right><&|/l&>Update Type</&>:</td>
+<td><select name="UpdateType">
+ <option value="private" ><&|/l&>Comments (not sent to requestors)</&></option>
+<option value="response" ><&|/l&>Response to requestors</&></option>
+</select>
+</td></tr>
+<tr><td align=right><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject" size=60 value=""></td></tr>
+ <tr><td align=right><&|/l&>Attach</&>:</td><td><input name="UpdateAttachment" type="file"></td></tr>
+ <tr><td class=labeltop><&|/l&>Message</&>:</td><td>
+ <& /Elements/MessageBox, Name=>"UpdateContent"&>
+ </td></tr>
+ </table>
+<& /Elements/TitleBoxEnd &>
+
+<& /Elements/TitleBoxStart, title => loc('Edit Relationships'), color => "#336633"&>
+<i><&|/l&>Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces.</&></i><br>
+<& /Ticket/Elements/BulkLinks &>
+<& /Elements/TitleBoxEnd &>
+
+<& /Elements/Submit &>
+
+
+</FORM>
+<%INIT>
+
+# Iterate through the ARGS hash and remove anything with a null value.
+map ($ARGS{$_} =~ /^$/ && (delete $ARGS{$_}), keys %ARGS);
+
+my ($bgcolor, @results);
+my @cols = qw(id Status Priority Subject QueueObj->Name OwnerObj->Name RequestorAddresses DueAsString );
+
+Abort(loc("No search to operate on.")) unless ($session{'tickets'});
+
+
+my $do_comment_reply=0;
+# Prepare for ticket updates
+$ARGS{'UpdateContent'} =~ s/\r\n/\n/g;
+chomp ($ARGS{'UpdateContent'}) ;
+
+if ($ARGS{'UpdateContent'} &&
+ $ARGS{'UpdateContent'} ne '' &&
+ $ARGS{'UpdateContent'} ne "-- \n" .
+ $session{'CurrentUser'}->UserObj->Signature) {
+ $do_comment_reply=1;
+}
+
+#Iterate through each ticket we've been handed
+my @linkresults;
+
+$session{'tickets'}->RedoSearch();
+while (my $Ticket = $session{'tickets'}->Next) {
+ $RT::Logger->debug( "Checking Ticket ".$Ticket->Id ."\n");
+ next unless ($ARGS{"UpdateTicket".$Ticket->Id});
+ $RT::Logger->debug ("Matched\n");
+ #Update the basics.
+ my @basicresults = ProcessTicketBasics(TicketObj => $Ticket, ARGSRef => \%ARGS);
+ my @dateresults = ProcessTicketDates(TicketObj => $Ticket, ARGSRef => \%ARGS);
+ #Update the watchers
+ my @watchresults = ProcessTicketWatchers(TicketObj => $Ticket, ARGSRef => \%ARGS);
+
+ #Update the links
+ $ARGS{'id'} = $Ticket;
+ $ARGS{$Ticket->Id.'-MergeInto'} = $ARGS{'Ticket-MergeInto'};
+ $ARGS{$Ticket->Id.'-DependsOn'} = $ARGS{'Ticket-DependsOn'};
+ $ARGS{'DependsOn-'.$Ticket->Id} = $ARGS{'DependsOn-Ticket'};
+ $ARGS{$Ticket->Id.'-MemberOf'} = $ARGS{'Ticket-MemberOf'};
+ $ARGS{'MemberOf-'.$Ticket->Id} = $ARGS{'MemberOf-Ticket'};
+ $ARGS{$Ticket->Id.'-RefersTo'} = $ARGS{'Ticket-RefersTo'};
+ $ARGS{'RefersTo-'.$Ticket->Id} = $ARGS{'RefersTo-Ticket'};
+ @linkresults = ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ delete $ARGS{'id'};
+ delete $ARGS{$Ticket->Id.'-MergeInto'};
+ delete $ARGS{$Ticket->Id.'-DependsOn'};
+ delete $ARGS{'DependsOn-'.$Ticket->Id};
+ delete $ARGS{$Ticket->Id.'-MemberOf'};
+ delete $ARGS{'MemberOf-'.$Ticket->Id};
+ delete $ARGS{$Ticket->Id.'-RefersTo'};
+ delete $ARGS{'RefersTo-'.$Ticket->Id};
+
+ my @updateresults;
+ if ($do_comment_reply) {
+ ProcessUpdateMessage(TicketObj => $Ticket, ARGSRef => \%ARGS, Actions => \@updateresults);
+ }
+ my @tempresults = (@watchresults, @basicresults, @dateresults, @updateresults, @linkresults);
+ @tempresults = map { loc("Ticket [_1]: [_2]",$Ticket->Id,$_) } @tempresults;
+
+ @results = (@results, @tempresults);
+}
+
+</%INIT>
diff --git a/rt/html/Search/Elements/PickRestriction b/rt/html/Search/Elements/PickRestriction
new file mode 100644
index 0000000..0021ab2
--- /dev/null
+++ b/rt/html/Search/Elements/PickRestriction
@@ -0,0 +1,142 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<FORM ACTION="Listing.html" METHOD="GET">
+<INPUT TYPE=HIDDEN NAME="Bookmark" VALUE="<% $session{'tickets'}->FreezeLimits()|u %>">
+<& /Elements/TitleBoxStart, title => loc('Refine search')&>
+<INPUT TYPE=HIDDEN NAME="CompileRestriction" VALUE=1>
+
+<ul>
+<li><&|/l&>Owner is</&> <& /Elements/SelectBoolean, Name => "OwnerOp",
+ TrueVal=> '=',
+ FalseVal => '!='
+&>
+<& /Elements/SelectOwner, Name => "ValueOfOwner" &>
+
+<li>
+<& /Elements/SelectWatcherType, Name => "WatcherRole", AllowNull => 0 &>
+<&|/l&>email address</&>
+<& /Elements/SelectMatch, Name => "WatcherRoleOp" &>
+<INPUT Name="ValueOfWatcherRole" SIZE=20>
+
+<li>
+<&|/l&>Subject</&> <& /Elements/SelectMatch, Name => "SubjectOp" &>
+<INPUT Name="ValueOfSubject" SIZE=20>
+
+<li><&|/l&>Queue</&> <& /Elements/SelectBoolean, Name => "QueueOp" ,
+ True => loc("is"),
+ False => loc("isn't"),
+ TrueVal=> '=',
+ FalseVal => '!=' &>
+<& /Elements/SelectQueue, Name => "ValueOfQueue" &>
+
+
+<li><&|/l&>Priority</&> <& /Elements/SelectEqualityOperator, Name => "PriorityOp" &>
+
+<INPUT Name="ValueOfPriority" SIZE=5>
+
+<li>
+<& /Elements/SelectDateType, Name => 'DateType' &>
+<& /Elements/SelectDateRelation, Name=>"DateOp" &>
+<& /Elements/SelectDate, Name => "ValueOfDate", ShowTime => 0, Default => '' &>
+
+<li><&|/l&>Ticket attachment</&>
+
+<& /Elements/SelectAttachmentField, Name => 'AttachmentField' &>
+<& /Elements/SelectBoolean, Name => "AttachmentFieldOp",
+ True => loc("matches"),
+ False => loc("does not match"),
+ TrueVal => 'LIKE',
+ FalseVal => 'NOT LIKE'
+&>
+<Input Name="ValueOfAttachmentField" Size=20>
+
+<li><&|/l&>Status</&>
+<& /Elements/SelectBoolean, Name => "StatusOp",
+ True => loc("is"),
+ False => loc("isn't"),
+ TrueVal=> '=',
+ FalseVal => '!='
+&>
+<& /Elements/SelectStatus, Name => "ValueOfStatus", SkipDeleted => 1 &>
+
+
+% while ( my $CustomField = $CustomFields->Next ) {
+
+<li><% $CustomField->Name %>
+ <& /Elements/SelectCustomFieldOperator, Name => "CustomFieldOp". $CustomField->id,
+ True => loc("is"),
+ False => loc("isn't"),
+ TrueVal=> '=', FalseVal => '!=' &>
+
+<& /Elements/SelectCustomFieldValue, Name => "CustomField".$CustomField->id,
+ CustomField => $CustomField,
+ &>
+% }
+
+</UL>
+
+<& /Elements/TitleBoxEnd &>
+
+<& /Elements/TitleBoxStart, title => loc('Ordering and sorting')&>
+
+<UL>
+
+<li><&|/l&>Results per page</&> <& /Elements/SelectResultsPerPage, Name => "RowsPerPage",
+ Default => $session{'tickets_rows_per_page'} || '50'
+&>
+
+<li><&|/l&>Sort results by</&> <& /Elements/SelectTicketSortBy, Name => "TicketsSortBy",
+ Default => $session{'tickets_sort_by'}
+&>
+<& /Elements/SelectSortOrder, Name => 'TicketsSortOrder', Default => $session{'tickets_sort_order'} &>
+
+<li><input type="checkbox" name="HideResults" <%$ARGS{'HideResults'} && 'CHECKED'%>> <&|/l&>Don't show search results</&>
+<li><& /Elements/Refresh, Name => 'RefreshSearchInterval' , Default => $session{'tickets_refresh_interval'} &>
+
+</UL>
+
+
+</DIV>
+
+
+
+<& /Elements/TitleBoxEnd &>
+
+<& /Elements/Submit, Label => loc('Search'), Name => 'Action'&>
+
+</FORM>
+
+
+ <%INIT>
+my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
+ foreach ( $session{'tickets'}->RestrictionValues('Queue') ) {
+ # Gotta load up the $queue object, since queues get stored by name now.
+ my $queue = RT::Queue->new($session{'CurrentUser'});
+ $queue->Load($_);
+ $CustomFields->LimitToQueue($queue->Id);
+ }
+
+ $CustomFields->LimitToGlobal();
+
+</%INIT>
diff --git a/rt/html/Search/Elements/TicketHeader b/rt/html/Search/Elements/TicketHeader
new file mode 100644
index 0000000..ed2f60e
--- /dev/null
+++ b/rt/html/Search/Elements/TicketHeader
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TR>
+<& TicketHeaderCell , Attribute => 'id', Header => '#'&>
+<& TicketHeaderCell , Attribute => 'Subject'&>
+<& TicketHeaderCell , Attribute => 'Status'&>
+<& TicketHeaderCell , Attribute => 'Queue'&>
+<& TicketHeaderCell , Attribute => 'Owner'&>
+<& TicketHeaderCell , Attribute => 'Priority'&>
+</TR>
+<TR>
+<TH class="ticketheader">&nbsp;</TH>
+<& TicketHeaderCell , Attribute => 'Requestor(s)'&>
+<& TicketHeaderCell , Attribute => 'Created'&>
+<& TicketHeaderCell , Attribute => 'Told', Header => 'Last Contact'&>
+<& TicketHeaderCell , Attribute => 'LastUpdated', Header => 'Last Updated'&>
+<& TicketHeaderCell , Attribute => 'TimeLeft', Header => 'Left'&>
+</TR>
+%# loc('Last Notified');
diff --git a/rt/html/Search/Elements/TicketHeaderCell b/rt/html/Search/Elements/TicketHeaderCell
new file mode 100644
index 0000000..5def9ea
--- /dev/null
+++ b/rt/html/Search/Elements/TicketHeaderCell
@@ -0,0 +1,55 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%INIT>
+my ($order,$curorder);
+ $Attribute =~ s/Obj->(Name|AsString|AgeAsString)//g;
+ if ($session{'tickets_sort_order'} =~ /^asc$/i) {
+ $order = 'DESC';
+ $curorder = 'ASC';
+ } else {
+ $order = 'ASC';
+ $curorder = 'DESC';
+ }
+$Header = $Attribute unless ($Header);
+
+</%INIT>
+<th class="ticketheader">
+% if (grep (/^$Attribute$/i, $session{'tickets'}->SortFields)) {
+<A
+% if ($Attribute eq $session{'tickets_sort_by'}) {
+class="currenttab"
+HREF="<% $RT::WebPath%>/Search/Listing.html?Bookmark=<%$session{'tickets'}->FreezeLimits()|u%>&TicketsSortBy=<%$Attribute%>&TicketsSortOrder=<%$order%>&RowsPerPage=<%$session{'tickets_rows_per_page'}%>">
+% } else {
+HREF="<% $RT::WebPath%>/Search/Listing.html?Bookmark=<%$session{'tickets'}->FreezeLimits()|u%>&TicketsSortBy=<%$Attribute%>&TicketsSortOrder=<%$curorder%>&RowsPerPage=<%$session{'tickets_rows_per_page'}%>">
+% }
+<% loc($Header) %>
+</A>
+% } else {
+<% loc($Header) %>
+% }
+</th>
+<%ARGS>
+$Header => undef
+$Attribute => undef
+</%ARGS>
diff --git a/rt/html/Search/Elements/TicketRow b/rt/html/Search/Elements/TicketRow
new file mode 100644
index 0000000..5d1ad20
--- /dev/null
+++ b/rt/html/Search/Elements/TicketRow
@@ -0,0 +1,55 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<SPAN class="search">
+<TR
+% if ($i%2) {
+CLASS="oddline"
+% } else {
+CLASS="evenline"
+% }
+>
+<TD ROWSPAN="2"><B><A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=<%$Ticket->Id%>"><%$Ticket->id%></a></B></TD>
+<TD><B><A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=<%$Ticket->Id%>"><%$Ticket->Subject%></a></B></TD>
+<TD><%loc($Ticket->Status)%></TD>
+<TD><%$Ticket->QueueObj->Name%></TD>
+<TD><%$Ticket->Owner == $RT::Nobody->Id ? loc('Nobody') : $Ticket->OwnerObj->Name%></TD>
+<TD><%$Ticket->Priority%></TD>
+</TR>
+<TR
+% if ($i%2) {
+CLASS="oddline"
+% } else {
+CLASS="evenline"
+% }
+><TD><small><%$Ticket->Requestors->MemberEmailAddressesAsString%></small></TD>
+<TD><SMALL><%$Ticket->CreatedObj->AgeAsString || '-'%></SMALL></TD>
+<TD><SMALL><%$Ticket->ToldObj->AgeAsString || '-'%></SMALL></TD>
+<TD><SMALL><%$Ticket->LastUpdatedObj->AgeAsString || '-'%></SMALL></TD>
+<TD><SMALL><%$Ticket->TimeLeft%></SMALL></TD>
+</TR>
+</SPAN>
+<%ARGS>
+$Ticket => undef
+$i => undef
+</%ARGS>
diff --git a/rt/html/Search/Listing.html b/rt/html/Search/Listing.html
new file mode 100644
index 0000000..5085345
--- /dev/null
+++ b/rt/html/Search/Listing.html
@@ -0,0 +1,112 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title, Refresh => $session{'tickets_refresh_interval'} &>
+<& /Ticket/Elements/Tabs,
+ current_tab => 'Search/Listing.html',
+ Title => $title &>
+
+%if ($ticketcount && ! $ARGS{'HideResults'}) {
+<TABLE WIDTH=100% border=0 cellpadding=2 CELLSPACING=0>
+<& Elements/TicketHeader, %ARGS &>
+% my $i;
+%while (my $Ticket = $session{'tickets'}->Next) {
+% $i++;
+<& Elements/TicketRow, Ticket => $Ticket, i=> $i, %ARGS &>
+%}
+</TABLE>
+<div align=center>
+<font size=2>
+<a href="Listing.html?GotoPage=1"><&|/l&>First page</&></a>
+&nbsp;&nbsp;
+% if ( $session{'tickets'}->FirstRow >= $session{'tickets_rows_per_page'}-1 ) {
+<a href="Listing.html?GotoPage=Prev">&lt;<&|/l&>Previous page</&></a>
+&nbsp;&nbsp;
+% }
+% if ( $session{'tickets'}->FirstRow + $session{'tickets_rows_per_page'} < $ticketcount ) {
+<a href="Listing.html?GotoPage=Next"><&|/l&>Next page</&>&gt;</a>
+% }
+%#&nbsp;&nbsp;<form method=get action="Listing.html"><&|/l&>Goto page</&> <input name=GotoPage size=2></form>
+</font>
+</div>
+<!--<div align=right>-->
+<table width="100%" border=0 cellpadding=3 CELLSPACING=1>
+<tr>
+<td align=left>
+(<&|/l, ($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ) &>[_1] - [_2] shown</&>)
+</td>
+<td align=right>
+
+<a href="Bulk.html"><&|/l&>Update all these tickets at once</&></a>
+<!--</div>-->
+</td>
+</tr>
+</table>
+
+% }
+<TABLE WIDTH="100%">
+<TR>
+<TD VALIGN="TOP">
+<& /Elements/TitleBoxStart, title => loc('Current search criteria')&>
+
+%my %restrictions=$session{'tickets'}->DescribeRestrictions();
+%foreach my $row (keys %restrictions){
+<%$restrictions{"$row"}%> <A HREF="<% $RT::WebPath %>/Search/Listing.html?DeleteRestriction=<%$row%>">[<&|/l&>delete</&>]</a><br>
+%}
+<BR>
+<BR>
+<A HREF="<% $RT::WebPath%>/Search/Listing.html?Bookmark=<%$session{'tickets'}->FreezeLimits()|u%>&TicketsSortBy=<%$session{'tickets_sort_by'}%>&TicketsSortOrder=<%$session{'tickets_sort_order'}%>&RowsPerPage=<%$session{'tickets_rows_per_page'}%>"><&|/l&>Bookmarkable URL for this search</&></a>
+<& /Elements/TitleBoxEnd&>
+</TD>
+<TD>
+
+<& Elements/PickRestriction, %ARGS &>
+
+</TD>
+</TR>
+</TABLE>
+
+<%INIT>
+
+my ($title, $ticketcount);
+$session{'i'}++;
+if ($session{'tickets'}) {
+ if ($ARGS{'DeleteRestriction'}) {
+ $session{'tickets'}->DeleteRestriction($ARGS{'DeleteRestriction'});
+ }
+ if ( ($ARGS{'ClearRestrictions'}) || ($ARGS{'NewSearch'}) ) {
+ $session{'tickets'}->ClearRestrictions;
+ }
+}
+ ProcessSearchQuery(ARGS=>\%ARGS);
+ $session{'tickets'}->RedoSearch();
+ if ( $session{'tickets'}->DescribeRestrictions()) {
+ $ticketcount = $session{tickets}->CountAll();
+ $title = loc('Found [quant,_1,ticket]', $ticketcount);
+ } else {
+ $title = loc("Find tickets");
+ }
+</%INIT>
+<%CLEANUP>
+$session{'tickets'}->PrepForSerialization();
+</%CLEANUP>
diff --git a/rt/html/SelfService/Attachment/dhandler b/rt/html/SelfService/Attachment/dhandler
new file mode 100644
index 0000000..4bebbe5
--- /dev/null
+++ b/rt/html/SelfService/Attachment/dhandler
@@ -0,0 +1,27 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%init>
+$m->comp('/Ticket/Attachment/dhandler', %ARGS);
+$m->abort;
+</%init>
diff --git a/rt/html/SelfService/Closed.html b/rt/html/SelfService/Closed.html
new file mode 100644
index 0000000..b9b2ac6
--- /dev/null
+++ b/rt/html/SelfService/Closed.html
@@ -0,0 +1,27 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title => loc('Closed Tickets') &>
+
+<& /SelfService/Elements/MyRequests, status => ['rejected', 'resolved'], friendly_status =>
+loc('closed') &>
diff --git a/rt/html/SelfService/Create.html b/rt/html/SelfService/Create.html
new file mode 100644
index 0000000..7bbc88a
--- /dev/null
+++ b/rt/html/SelfService/Create.html
@@ -0,0 +1,80 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& Elements/Header, Title => loc("Create a ticket") &>
+
+<FORM ACTION="Display.html" METHOD="POST" ENCTYPE="multipart/form-data">
+<INPUT TYPE=HIDDEN Name="id" VALUE="new">
+
+<TABLE>
+<TR>
+<TD>
+<&|/l&>Queue</&>:
+</TD>
+<TD>
+<& /Elements/SelectNewTicketQueue, Verbose => 'True' &>
+</TD>
+</TR>
+<TR>
+<TD>
+<&|/l&>Requestors</&>:
+</TD>
+<TD>
+<INPUT Name="Requestors" Value="<%$session{CurrentUser}->EmailAddress%>" SIZE=20>
+</TD>
+</TR>
+<TR>
+<TD>
+<&|/l&>Cc</&>:
+</TD>
+<TD>
+ <INPUT NAME="Cc" SIZE=20>
+</TD>
+</TR>
+<TR>
+<TD>
+<&|/l&>Subject</&>:
+</TD>
+<TD>
+<INPUT Name="Subject" SIZE=60 MAXSIZE=100 value="">
+</TD>
+</TR>
+<TR>
+<TD>
+<&|/l&>Attach file</&>:
+</TD>
+<TD>
+<INPUT Name="Attach" type=file>
+</TD>
+</TR>
+<TR>
+<TD COLSPAN=2>
+<&|/l&>Describe the issue below</&>:<br>
+<& /Elements/MessageBox &>
+</TD>
+</TR>
+</TABLE>
+<& /Elements/Submit, Label => loc("Create ticket")&>
+
+
+</FORM>
diff --git a/rt/html/SelfService/Display.html b/rt/html/SelfService/Display.html
new file mode 100644
index 0000000..124ecf4
--- /dev/null
+++ b/rt/html/SelfService/Display.html
@@ -0,0 +1,180 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title => loc('#[_1]: [_2]', $Ticket->id, $Ticket->Subject) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+ <TABLE WIDTH="100%" class="ticketsummary" >
+ <TR>
+ <TD VALIGN=TOP WIDTH="50%">
+ <& /Elements/TitleBoxStart, title => loc('The Basics'),
+ title_class=> 'inverse',
+ color => "#993333" &>
+ <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+</TD>
+ <TD VALIGN=TOP WIDTH="50%">
+ <& /Elements/TitleBoxStart, title => loc("Dates"),
+ title_class=> 'inverse',
+ color => "#663366" &>
+ <& /Ticket/Elements/ShowDates, Ticket => $Ticket, UpdatedLink => 0 &>
+ <& /Elements/TitleBoxEnd &>
+</TD>
+</TR>
+</TABLE>
+
+
+
+<& /Ticket/Elements/ShowHistory, Ticket => $Ticket, AttachPath => "Attachment" &>
+
+
+
+<%INIT>
+
+my ( $field, @results );
+
+# {{{ Load the ticket
+#If we get handed two ids, mason will make them an array. bleck.
+# We want teh first one. Just because there's no other sensible way
+# to deal
+my @id = ( ref $id eq 'ARRAY' ) ? @{$id} : ($id);
+
+my $Ticket = new RT::Ticket( $session{'CurrentUser'} );
+if ( $id[0] eq 'new' ) {
+
+ # {{{ Create a new ticket
+
+ my $Queue = new RT::Queue( $session{'CurrentUser'} );
+ unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
+ $m->comp( 'Error.html', Why => loc('Queue not found') );
+ $m->abort;
+ }
+
+ unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
+ $m->comp( 'Error.html',
+ Why =>
+ loc('You have no permission to create tickets in that queue.') );
+ $m->abort;
+ }
+
+ my @Requestors = split ( /\s*,\s*/, $ARGS{'Requestors'} );
+ my @Cc = split ( /\s*,\s*/, $ARGS{'Cc'} );
+
+ my $MIMEObj = MakeMIMEEntity( Subject => $ARGS{'Subject'},
+ From => $ARGS{'From'},
+ Cc => $ARGS{'Cc'},
+ Body => $ARGS{'Content'},
+ AttachmentFieldName => 'Attach' );
+
+ #TODO in Create_Details.html: priorities and due-date
+ my ( $id, $Trans, $ErrMsg ) = $Ticket->Create( Queue => $ARGS{Queue},
+ Requestor => \@Requestors,
+ Cc => \@Cc,
+ Subject => $ARGS{Subject},
+ MIMEObj => $MIMEObj );
+ unless ( $id && $Trans ) {
+ $m->comp( 'Error.html', Why => $ErrMsg );
+ $m->abort();
+ }
+
+ push ( @results, $ErrMsg );
+
+ # }}}
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
+ delete $session{'Attachments'};
+}
+# }}}
+}
+else {
+ unless ( $Ticket->Load( $id[0] ) ) {
+ $m->comp( 'Error.html',
+ Why => loc( "Couldn't load ticket '[_1]'", $id ) );
+ $m->abort();
+ }
+}
+
+# }}}
+
+unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
+ $m->comp( 'Error.html',
+ Why => loc("No permission to display that ticket") );
+ $m->abort();
+}
+
+my ( $code, $msg );
+
+#Update the status
+if ( ( defined $ARGS{'Status'} )
+ and ( $ARGS{'Status'} ne $Ticket->Status ) ) {
+ ( $code, $msg ) = $Ticket->SetStatus( $ARGS{'Status'} );
+ push @results, "$msg";
+}
+
+# {{{ store the uploaded attachment in session
+if ($ARGS{'Attach'}) { # attachment?
+ $session{'Attachments'} = {} unless defined $session{'Attachments'};
+
+ my $subject = "$ARGS{'Attach'}";
+ # since CGI.pm deutf8izes the magic field, we need to add it back.
+ Encode::_utf8_on($subject);
+ # strip leading directories
+ $subject =~ s#^.*[\\/]##;
+
+ my $attachment = MakeMIMEEntity(
+ Subject => $subject,
+ Body => "",
+ AttachmentFieldName => 'Attach'
+ );
+
+ $session{'Attachments'} = { %{$session{'Attachments'} || {}},
+ $ARGS{'Attach'} => $attachment };
+}
+# }}}
+
+if ( $session{'Attachments'} ||
+ ( $ARGS{'UpdateContent'} ne ''
+ && $ARGS{'UpdateContent'} ne "-- \n"
+ . $session{'CurrentUser'}->UserObj->Signature )) {
+ $ARGS{UpdateAttachments} = $session{'Attachments'};
+}
+ProcessUpdateMessage( ARGSRef => \%ARGS,
+ Actions => \@results,
+ TicketObj => $Ticket );
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
+ delete $session{'Attachments'};
+}
+# }}}
+
+my $Transactions = $Ticket->Transactions;
+
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/SelfService/Elements/GotoTicket b/rt/html/SelfService/Elements/GotoTicket
new file mode 100644
index 0000000..71da8c1
--- /dev/null
+++ b/rt/html/SelfService/Elements/GotoTicket
@@ -0,0 +1,24 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<FORM ACTION="<%$RT::WebPath%>/SelfService/Display.html"><input type=submit value="<&|/l&>Goto ticket</&>">&nbsp;<input size=4 name=id></FORM>
diff --git a/rt/html/SelfService/Elements/Header b/rt/html/SelfService/Elements/Header
new file mode 100644
index 0000000..6ad1379
--- /dev/null
+++ b/rt/html/SelfService/Elements/Header
@@ -0,0 +1,25 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, %ARGS, Prefs => '/SelfService/Prefs.html' &>
+<& /SelfService/Elements/Tabs, %ARGS &>
diff --git a/rt/html/SelfService/Elements/MyRequests b/rt/html/SelfService/Elements/MyRequests
new file mode 100644
index 0000000..839359a
--- /dev/null
+++ b/rt/html/SelfService/Elements/MyRequests
@@ -0,0 +1,63 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/TitleBoxStart, title => $title &>
+<TABLE BORDER=0 cellspacing=1 cellpadding=1 BGCOLOR="#eeeeee" WIDTH=100%>
+<TR>
+<TH><&|/l&>Subject</&></TH>
+<TH><&|/l&>Status</&></TH>
+<TH><&|/l&>Owner</&></TH>
+</TR>
+<TR>
+% while (my $Ticket = $MyTickets->Next) {
+<TR>
+<TD>
+<a href="<%$RT::WebPath%>/SelfService/Display.html?id=<%$Ticket->Id%>"><%$Ticket->Id%>: <%$Ticket->Subject%></a>
+</TD>
+<TD>
+<%$Ticket->Status%>
+</TD><TD>
+<%$Ticket->OwnerObj->Name%>
+</TR>
+% }
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+
+
+<%INIT>
+$title ||= loc("My [_1] tickets", $friendly_status);
+my $MyTickets;
+$MyTickets = new RT::Tickets ($session{'CurrentUser'});
+$MyTickets->LimitWatcher(TYPE => 'Requestor', VALUE => $session{'CurrentUser'}->EmailAddress);
+$MyTickets->OrderBy(FIELD => 'id', ORDER => 'ASC');
+
+foreach my $status (@status) {
+
+ $MyTickets->LimitStatus(VALUE => $status);
+}
+</%INIT>
+<%ARGS>
+$title => undef
+$friendly_status => loc('open')
+@status => ('open', 'new', 'stalled')
+</%ARGS>
diff --git a/rt/html/SelfService/Elements/Tabs b/rt/html/SelfService/Elements/Tabs
new file mode 100644
index 0000000..efab866
--- /dev/null
+++ b/rt/html/SelfService/Elements/Tabs
@@ -0,0 +1,64 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/PageLayout,
+ current_toptab => $current_toptab,
+ current_tab => $current_tab,
+ toptabs => $tabs,
+ topactions => $actions,
+ title => $Title
+&>
+<a name="skipnav" id="skipnav" accesskey="8"></a>
+<%INIT>
+
+if ($Title) {
+$Title = loc ("RT Self Service") . " / " . $Title;
+} else {
+$Title = loc ("RT Self Service");
+
+}
+my ($tab);
+my $tabs = { A => { title => loc('Open tickets'),
+ path => 'SelfService/',
+ },
+ B => { title => loc('Closed tickets'),
+ path => 'SelfService/Closed.html',
+ },
+ C => { title => loc('New ticket'),
+ path => 'SelfService/Create.html'
+ },
+ Z => { title => loc('Preferences'),
+ path => 'SelfService/Prefs.html'
+ }
+ };
+my $actions = {
+ B => { html => $m->scomp('GotoTicket')
+ }
+ };
+</%INIT>
+<%ARGS>
+$Title => undef
+$current_toptab => undef
+$current_tab => undef
+</%ARGS>
+
diff --git a/rt/html/SelfService/Error.html b/rt/html/SelfService/Error.html
new file mode 100644
index 0000000..ac93ace
--- /dev/null
+++ b/rt/html/SelfService/Error.html
@@ -0,0 +1,46 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title => loc('Error') &>
+<h2 class="title"><%loc('Error')%></h2>
+<& /Elements/TitleBoxStart, title => $Title &>
+<%$Why%>
+<br>
+<font size=-1>
+<%$Details%>
+</font>
+<& /Elements/TitleBoxEnd &>
+</body>
+</HTML>
+
+
+<%args>
+$Code => undef
+$Details => undef
+$Title => loc("RT Error")
+$Why => loc("the calling component did not specify why")
+</%args>
+
+<%INIT>
+$RT::Logger->error("WebRT: $Why ($Details)");
+</%INIT>
diff --git a/rt/html/SelfService/Prefs.html b/rt/html/SelfService/Prefs.html
new file mode 100644
index 0000000..3bbb9b9
--- /dev/null
+++ b/rt/html/SelfService/Prefs.html
@@ -0,0 +1,68 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title => loc('Preferences') &>
+
+<& /Elements/ListActions, actions => \@results &>
+<form method=post>
+
+% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
+<& /Elements/TitleBoxStart, title => loc('Change password') &>
+<&|/l&>New password</&>: <input type=password name="NewPass1" size=16>
+<&|/l&>Confirm</&>: <input type=password name="NewPass2" size=16>
+<& /Elements/TitleBoxEnd &>
+<BR>
+% }
+<& /Elements/Submit &>
+ </form>
+
+
+<%INIT>
+my @results;
+
+if ($NewPass1) {
+ if ($NewPass1 ne $NewPass2) {
+ push (@results, "Passwords did not match.");
+ }
+ else {
+ my ($val, $msg)=$session{'CurrentUser'}->UserObj->SetPassword($NewPass1);
+ push (@results, "Password: ".$msg);
+ }
+}
+if ($Signature) {
+ $Signature =~ s/(\r\n|\r)/\n/g;
+ if ($Signature ne $session{'CurrentUser'}->UserObj->Signature) {
+ my ($val, $msg)=$session{'CurrentUser'}->UserObj->SetSignature($Signature);
+ push (@results, "Signature: ".$msg);
+ }
+}
+#A hack to make sure that session gets rewritten.
+
+$session{'i'}++;
+</%INIT>
+
+<%ARGS>
+$Signature => undef
+$NewPass1 => undef
+$NewPass2 => undef
+</%ARGS>
diff --git a/rt/html/SelfService/Update.html b/rt/html/SelfService/Update.html
new file mode 100644
index 0000000..9444aa7
--- /dev/null
+++ b/rt/html/SelfService/Update.html
@@ -0,0 +1,78 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title =>loc('Update ticket #[_1]', $Ticket->id) &>
+
+
+<FORM ACTION="Display.html" METHOD=POST ENCTYPE="multipart/form-data">
+<input type=hidden name="UpdateType" value="response">
+
+<&|/l&>Status</&>: <& /Elements/SelectStatus, Name=>"Status", Default => $DefaultStatus &><br>
+<&|/l&>Subject</&>: <input name="UpdateSubject" size=60 value="Re: <% $Ticket->Subject %>"> <br>
+<table>
+<tr>
+% if (exists $session{'Attachments'}) {
+<TD>
+<&|/l&>Attached file</&>:
+</TD>
+<TD COLSPAN=5>
+<&|/l&>Check box to delete</&><BR>
+% foreach my $attach_name (keys %{$session{'Attachments'}}) {
+<input type="checkbox" name="DeleteAttach-<%$attach_name%>"><%$attach_name%><BR>
+% } # end of foreach
+</TD>
+</TR>
+<TR>
+% } # end of if
+<tr><td align=right><&|/l&>Attach</&>:</td><td><input name="Attach" type="file"><input type="hidden" name="UpdateAttach" value="1">
+</td></tr>
+</table>
+<& /Elements/MessageBox, Name=>"UpdateContent", QuoteTransaction=>$ARGS{QuoteTransaction} &>
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<%$Ticket->Id%>"><br>
+
+
+<& /Elements/Submit &>
+ </FORM>
+
+
+
+<%INIT>
+
+my $Ticket = LoadTicket($id);
+
+my $title = loc("Update ticket #[_1]", $Ticket->id);
+
+$DefaultStatus = $Ticket->Status() unless ($DefaultStatus);
+
+
+Abort(loc("No permission to view update ticket"))
+ unless ( $Ticket->CurrentUserHasRight('ReplyToTicket') or
+ $Ticket->CurrentUserHasRight('ModifyTicket') );
+
+</%INIT>
+
+<%ARGS>
+$id => undef
+$Action => undef
+$DefaultStatus => undef
+</%ARGS>
diff --git a/rt/html/SelfService/index.html b/rt/html/SelfService/index.html
new file mode 100644
index 0000000..71dc115
--- /dev/null
+++ b/rt/html/SelfService/index.html
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /SelfService/Elements/Header, Title => undef &>
+
+<& /SelfService/Elements/MyRequests &>
diff --git a/rt/html/Ticket/Attachment/dhandler b/rt/html/Ticket/Attachment/dhandler
new file mode 100644
index 0000000..ba82b5f
--- /dev/null
+++ b/rt/html/Ticket/Attachment/dhandler
@@ -0,0 +1,70 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%perl>
+ my ($ticket, $trans,$attach, $filename);
+ my $arg = $m->dhandler_arg; # get rest of path
+ if ($arg =~ '^(\d+)/(\d+)') {
+ $trans = $1;
+ $attach = $2;
+ }
+ else {
+ Abort("Corrupted attachment URL.");
+ }
+ my $AttachmentObj = new RT::Attachment($session{'CurrentUser'});
+ $AttachmentObj->Load($attach) || Abort("Attachment '$attach' could not be loaded");
+
+
+ unless ($AttachmentObj->id) {
+ Abort("Bad attachment id. Couldn't find attachment '$attach'\n");
+ }
+ unless ($AttachmentObj->TransactionId() == $trans ) {
+ Abort("Bad transaction number for attachment. $trans should be".$AttachmentObj->TransactionId() ."\n");
+
+ }
+
+ my $content_type = $AttachmentObj->ContentType || 'text/plain';
+
+ unless ($RT::TrustHTMLAttachments) {
+ $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);
+ }
+
+ if (my $enc = $AttachmentObj->OriginalEncoding) {
+ # normalize Encode.pm convention with IANA ones
+ $enc = 'big5' if $enc eq 'big5-eten';
+ $enc = 'utf-8' if $enc eq 'utf8';
+ $content_type .= ";charset=$enc";
+ }
+
+ # unless ($RT::TrustMIMEAttachments) {
+ # $content_type = 'application/octet-stream';
+ # }
+
+ $r->content_type( $content_type );
+ $m->clear_buffer();
+ $m->out($AttachmentObj->OriginalContent);
+ $m->abort;
+</%perl>
+<%attr>
+AutoFlush => 0
+</%attr>
diff --git a/rt/html/Ticket/Create.html b/rt/html/Ticket/Create.html
new file mode 100644
index 0000000..435447a
--- /dev/null
+++ b/rt/html/Ticket/Create.html
@@ -0,0 +1,266 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Create a new ticket") &>
+<& /Elements/Tabs,
+ current_toptab => "Ticket/Create.html",
+ Title => loc("Create a new ticket") &>
+<FORM ACTION="<%$RT::WebPath%>/Ticket/Create.html" METHOD="POST" ENCTYPE="multipart/form-data">
+<INPUT TYPE=HIDDEN Name="id" VALUE="new">
+<A NAME="top">
+
+
+[<a class="currenttab"><&|/l&>Show basics</&></a>] [<A HREF="#detail"><&|/l&>Show details</&></a>]
+<BR>
+<& /Elements/TitleBoxStart, contentbg => "#cccccc", title => loc("Create a new ticket") &>
+<TABLE border=0 cellpadding=0 cellspacing=0>
+<TR><TD class=label><&|/l&>Queue</&>:</TD>
+<TD class=value><% $QueueObj->Name %>
+<INPUT TYPE=HIDDEN NAME=Queue Value="<%$QueueObj->Name%>">
+</TD>
+<TD class=label><&|/l&>Status</&>:
+</TD>
+<TD class=value>
+<& /Elements/SelectStatus, Name => "Status", Default => $ARGS{Status}||'new' &>
+</TD>
+<TD class=label>
+<&|/l&>Owner</&>:
+</TD>
+<TD class=value>
+<& /Elements/SelectOwner, Name => "Owner", QueueObj => $QueueObj, Default => $ARGS{Owner}||undef &>
+</TD>
+</TR>
+<TR>
+<TD class=label>
+<&|/l&>Requestors</&>:
+</TD>
+<TD class=value COLSPAN=5>
+<INPUT Name="Requestors" Value="<% ($ARGS{Requestors}) || $session{CurrentUser}->EmailAddress %>" SIZE=40>
+</TD>
+</TR>
+<TR>
+<TD class=labeltop>
+<&|/l&>Cc</&>:
+</TD>
+<TD class=value COLSPAN=5>
+<INPUT NAME="Cc" SIZE=40 VALUE="<% $ARGS{Cc} %>"><BR>
+<i><font size=-2>
+<&|/l&>(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)</&></font></i>
+</TD>
+</TR>
+<TR>
+<TD class=labeltop>
+<&|/l&>Admin Cc</&>:
+</TD>
+<TD class=value COLSPAN=5>
+<INPUT NAME="AdminCc" SIZE=40 VALUE="<% $ARGS{AdminCc} %>"><BR>
+<i><font size=-2>
+<&|/l&>(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)</&></font></i>
+</TD>
+</TR>
+<TR>
+<TD class=label>
+<&|/l&>Subject</&>:
+</TD>
+<TD class=value COLSPAN=5>
+<INPUT Name="Subject" SIZE=60 MAXSIZE=100 value="<%$ARGS{Subject} || ''%>">
+</TD>
+</TR>
+<TR>
+<TD COLSPAN=6>
+<& /Ticket/Elements/EditCustomFields, QueueObj => $QueueObj &>
+</TD>
+</TR>
+<TR>
+% if (exists $session{'Attachments'}) {
+<TD class=label>
+<&|/l&>Attached file</&>:
+</TD>
+<TD COLSPAN=5>
+<&|/l&>Check box to delete</&><BR>
+% foreach my $attach_name (keys %{$session{'Attachments'}}) {
+<input type="checkbox" name="DeleteAttach-<%$attach_name%>"><%$attach_name%><BR>
+% } # end of foreach
+</TD>
+</TR>
+<TR>
+% } # end of if
+<TD>
+<&|/l&>Attach file</&>:
+</TD>
+<TD class=value COLSPAN=5>
+<INPUT TYPE=FILE NAME="Attach">
+<INPUT TYPE=SUBMIT NAME="AddMoreAttach" VALUE="<&|/l&>Add More Files</&>">
+</TD>
+</TR>
+<TR>
+<TD COLSPAN=6>
+<&|/l&>Describe the issue below</&>:<br>
+% if (exists $ARGS{Content}) {
+<& /Elements/MessageBox, Default => $ARGS{Content} &>
+% } else {
+<& /Elements/MessageBox, QuoteTransaction => $QuoteTransaction &>
+%}
+
+<BR>
+</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT COLSPAN=2>
+</TD>
+</TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, Label => loc("Create")&>
+
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+
+<A NAME="detail">
+ [<A HREF="#top"><&|/l&>Show basics</&></a>] [<a class="currenttab"><&|/l&>Show details</&></a>]
+<BR>
+<TABLE WIDTH="100%" BORDER=0>
+<TR>
+<TD WIDTH="50%" VALIGN=TOP>
+
+ <& /Elements/TitleBoxStart, title => loc('The Basics'),
+ title_class=> 'inverse',
+ color => "#993333" &>
+<TABLE BORDER=0>
+<TR><TD ALIGN=RIGHT><&|/l&>Priority</&>:</TD><TD><input size=3 name="InitialPriority" value="<% $ARGS{InitialPriority} ? $ARGS{InitialPriority} : $QueueObj->InitialPriority %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Final Priority</&>:</TD><TD><input size=3 name="FinalPriority" value="<% $ARGS{FinalPriority} ? $ARGS{FinalPriority} : $QueueObj->FinalPriority %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Time Worked</&>:</TD><TD><input size=3 name="TimeWorked" value="<% $ARGS{TimeWorked} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Time Left</&>:</TD><TD><input size=3 name="TimeLeft" value="<% $ARGS{TimeLeft} %>"></TD></TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<br>
+<& /Elements/TitleBoxStart, title => loc("Dates"),
+ title_class=> 'inverse',
+ color => "#663366" &>
+
+<TABLE BORDER=0>
+<TR><TD ALIGN=RIGHT><&|/l&>Starts</&>:</TD><TD><input size=10 name="Starts" value="<% $ARGS{Starts} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Due</&>:</TD><TD><input size=10 name="Due" value="<%
+$ARGS{Due}%>"></TD></TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<BR>
+</TD>
+
+<TD VALIGN="TOP">
+<& /Elements/TitleBoxStart, title => loc('Relationships'),
+ title_class=> 'inverse',
+ titleright => '', color=> "#336633" &>
+
+<i><&|/l&>(Enter ticket ids or URLs, seperated with spaces)</&></i>
+<TABLE BORDER=0>
+<TR><TD ALIGN=RIGHT><&|/l&>Depends on</&></TD><TD><input size=10 name="new-DependsOn" value="<% $ARGS{'new-DependsOn'} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Depended on by</&></TD><TD><input size=10 name="DependsOn-new" value="<% $ARGS{'DependsOn-new'} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Parents</&></TD><TD><input size=10 name="new-MemberOf" value="<% $ARGS{'new-MemberOf'} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Children</&></TD><TD><input size=10 name="MemberOf-new" value="<% $ARGS{'MemberOf-new'} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Refers to</&></TD><TD><input size=10 name="new-RefersTo" value="<% $ARGS{'new-RefersTo'} %>"></TD></TR>
+<TR><TD ALIGN=RIGHT><&|/l&>Referred to by</&></TD><TD><input size=10 name="RefersTo-new" value="<% $ARGS{'RefersTo-new'} %>"></TD></TR>
+
+
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+<BR>
+
+</TD>
+</TR>
+</TABLE>
+<& /Elements/Submit, Label => loc("Create") &>
+</FORM>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>
+
+<%INIT>
+
+
+
+my $QueueObj = new RT::Queue($session{'CurrentUser'});
+$QueueObj->Load($Queue) || Abort(loc("Queue could not be loaded."));
+my $CFs = $QueueObj->CustomFields();
+
+if ($QueueObj->DefaultDueIn) {
+ my $default_due = RT::Date->new($session{'CurrentUser'});
+ $default_due->SetToNow();
+ $default_due->AddDays($QueueObj->DefaultDueIn);
+ $ARGS{'Due'} = $default_due->ISO();
+}
+
+# {{{ deal with deleting uploaded attachments
+foreach my $key (keys %ARGS) {
+ if ($key =~ m/^DeleteAttach-(.+)$/) {
+ delete $session{'Attachments'}{$1};
+ }
+ $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
+}
+
+# {{{ store the uploaded attachment in session
+if ($ARGS{'Attach'}) { # attachment?
+ $session{'Attachments'} = {} unless defined $session{'Attachments'};
+
+ my $subject = "$ARGS{'Attach'}";
+
+ # strip leading directories
+ $subject =~ s#^.*[\\/]##;
+
+ my $attachment = MakeMIMEEntity(
+ Subject => $subject,
+ Body => "",
+ AttachmentFieldName => 'Attach'
+ );
+
+ $session{'Attachments'} = { %{$session{'Attachments'} || {}},
+ $ARGS{'Attach'} => $attachment };
+}
+# }}}
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
+ delete $session{'Attachments'};
+}
+
+
+# }}}
+
+if ((!exists $ARGS{'AddMoreAttach'}) && ($ARGS{'id'} eq 'new')) { # new ticket?
+ $m->comp('Display.html', %ARGS);
+ $m->abort();
+}
+</%INIT>
+
+<%ARGS>
+$DependsOn => undef
+$DependedOnBy => undef
+$MemberOf => undef
+$QuoteTransaction => undef
+$Queue => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Display.html b/rt/html/Ticket/Display.html
new file mode 100644
index 0000000..276cee6
--- /dev/null
+++ b/rt/html/Ticket/Display.html
@@ -0,0 +1,118 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header,
+ Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $TicketObj,
+ current_tab => 'Ticket/Display.html?id='.$TicketObj->id,
+ Title => loc("#[_1]: [_2]", $TicketObj->Id, $TicketObj->Subject) &>
+
+<& /Elements/ListActions, actions => \@Actions &>
+
+<& /Ticket/Elements/ShowSummary, Ticket => $TicketObj &>
+
+
+<BR>
+<& /Ticket/Elements/ShowHistory ,
+ Ticket => $TicketObj,
+ Collapsed => $ARGS{'Collapsed'},
+ ShowHeaders => $ARGS{'ShowHeaders'} &>
+
+
+<%ARGS>
+$id => undef
+$Create => undef
+$ShowHeaders => undef
+$Collapsed => undef
+$TicketObj => undef
+</%ARGS>
+
+<%INIT>
+ my ($linkid, $message, $tid, @Actions);
+
+unless ($id || $TicketObj) {
+ Abort('No ticket specified');
+}
+
+if ($ARGS{'id'} eq 'new') {
+ # {{{ Create a new ticket
+
+ my $Queue = new RT::Queue($session{'CurrentUser'});
+ unless ($Queue->Load($ARGS{'Queue'})) {
+ Abort('Queue not found');
+ }
+
+ unless ($Queue->CurrentUserHasRight('CreateTicket')) {
+ Abort('You have no permission to create tickets in that queue.');
+ }
+ ($TicketObj, @Actions) =
+ CreateTicket(Attachments => $session{'Attachments'}, %ARGS);
+ delete $session{'Attachments'};
+ unless ($TicketObj->CurrentUserHasRight('ShowTicket')) {
+ Abort("No permission to view newly created ticket #".$TicketObj->id.".");
+ }
+ # }}}
+} else {
+ if (!$TicketObj) {
+
+ $TicketObj = RT::Ticket->new($session{'CurrentUser'});
+
+ $TicketObj = LoadTicket($ARGS{'id'});
+ unless ($TicketObj->CurrentUserHasRight('ShowTicket')) {
+ Abort("No permission to view ticket");
+ }
+ }
+
+ if (defined $ARGS{'Action'}) {
+ if ($ARGS{'Action'} =~ /^(Steal|Kill|Take|SetTold)$/) {
+ my $action = $1;
+ my ($res, $msg)=$TicketObj->$action();
+ push(@Actions, $msg);
+ }
+ }
+
+ if ( $ARGS{'UpdateContent'} || $session{'Attachments'}) {
+ $ARGS{'UpdateContent'} =~ s/\r\n/\n/g;
+ if ( $session{'Attachments'} ||
+ ( $ARGS{'UpdateContent'} ne ''
+ && $ARGS{'UpdateContent'} ne "-- \n"
+ . $session{'CurrentUser'}->UserObj->Signature )) {
+ $ARGS{UpdateAttachments} = $session{'Attachments'};
+ ProcessUpdateMessage( ARGSRef => \%ARGS,
+ Actions => \@Actions,
+ TicketObj => $TicketObj );
+ delete $session{'Attachments'};
+ }
+ }
+ #Process status updates
+ my @BasicActions = ProcessTicketBasics(ARGSRef => \%ARGS, TicketObj=>$TicketObj);
+ my @results = ProcessTicketLinks( TicketObj => $TicketObj, ARGSRef => \%ARGS);
+
+ push (@Actions, @BasicActions, @results);
+}
+</%INIT>
+
+
+
+
diff --git a/rt/html/Ticket/Elements/AddWatchers b/rt/html/Ticket/Elements/AddWatchers
new file mode 100644
index 0000000..96dd38f
--- /dev/null
+++ b/rt/html/Ticket/Elements/AddWatchers
@@ -0,0 +1,99 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<BR>
+<%$msg%><br>
+
+<&|/l&>Add new watchers</&>:<br>
+
+<table>
+% if ($Users and $Users->Count) {
+<tr><td>
+<&|/l&>Type</&>
+</td><td>
+<&|/l&>Username</&>
+</td></tr>
+% while (my $u = $Users->Next ) {
+<tr><td><&/Elements/SelectWatcherType, Name => "Ticket-AddWatcher-Principal-".$u->PrincipalId &></td><td><%$u->Name%> (<%$u->RealName%>)</td></tr>
+% }
+% }
+
+% if ($Groups and $Groups->Count) {
+<tr><td>
+<&|/l&>Type</&>
+</td><td>
+<&|/l&>Group</&>
+</td></tr>
+% while (my $g = $Groups->Next ) {
+<tr><td><&/Elements/SelectWatcherType, Name => "Ticket-AddWatcher-Principal-".$g->PrincipalId, Scope => 'queue' &></td><td><%$g->Name%> (<%$g->Description%>)</td></tr>
+% }
+% }
+
+<tr><td>
+<&|/l&>Type</&>
+</td><td>
+<&|/l&>Email</&>
+</td></tr>
+<tr><td>
+<&/Elements/SelectWatcherType, Name => "WatcherTypeEmail1" &>
+</td><td>
+<input name="WatcherAddressEmail1" size=15>
+</td></tr>
+<tr><td>
+<&/Elements/SelectWatcherType, Name => "WatcherTypeEmail2" &>
+</td><td>
+<input name="WatcherAddressEmail2" size=15>
+</td></tr>
+<tr><td>
+<&/Elements/SelectWatcherType, Name => "WatcherTypeEmail3" &>
+</td><td>
+<input name="WatcherAddressEmail3" size=15>
+</td></tr>
+</table>
+
+<%INIT>
+my ($msg, $Users, $Groups);
+
+if ($UserString) {
+ $Users = RT::Users->new($session{'CurrentUser'});
+ $Users->Limit(FIELD => $UserField, VALUE => $UserString, OPERATOR => $UserOp);
+ $Users->LimitToPrivileged if $PrivilegedOnly;
+ }
+
+if ($GroupString) {
+ $Groups = RT::Groups->new($session{'CurrentUser'});
+ $Groups->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined');
+ $Groups->Limit(FIELD => $GroupField, VALUE => $GroupString, OPERATOR => $GroupOp);
+ }
+
+</%INIT>
+
+<%ARGS>
+$UserField => 'Name'
+$UserOp => '='
+$UserString => undef
+$GroupField => 'Name'
+$GroupOp => '='
+$GroupString => undef
+$PrivilegedOnly => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/BulkLinks b/rt/html/Ticket/Elements/BulkLinks
new file mode 100644
index 0000000..e6b9cd5
--- /dev/null
+++ b/rt/html/Ticket/Elements/BulkLinks
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE>
+ <TR>
+ <TD class="label"><&|/l&>Merge into</&>:</TD>
+ <TD class="entry"><input name="Ticket-MergeInto"> <i><&|/l&>(only one ticket)</&></i></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Depends on</&>:</TD>
+ <TD class="entry"><input name="Ticket-DependsOn"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Depended on by</&>:</TD>
+ <TD class="entry"><input name="DependsOn-Ticket"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Parents</&>:</TD>
+ <TD class="entry"><input name="Ticket-MemberOf"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Children</&>:</TD>
+ <TD class="entry"> <input name="MemberOf-Ticket"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Refers to</&>:</TD>
+ <TD class="entry"><input name="Ticket-RefersTo"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Referred to by</&>:</TD>
+ <TD class="entry"> <input name="RefersTo-Ticket"></TD>
+ </TR>
+</TABLE>
diff --git a/rt/html/Ticket/Elements/EditBasics b/rt/html/Ticket/Elements/EditBasics
new file mode 100644
index 0000000..5d66b1f
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditBasics
@@ -0,0 +1,79 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE>
+ <TR><td>
+ <table>
+ <td class="label"><&|/l&>Subject</&>:</td>
+ <td class="value">
+ <input name=Subject value="<%$TicketObj->Subject|h%>" SIZE=50>
+ </td>
+ </table>
+ </td></TR>
+ <TR><td>
+ <table>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Status'),
+ content => $SelectStatus &>
+ </TD>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Time Worked'),
+ content => "<input name=TimeWorked value=\"".$TicketObj->TimeWorked."\" SIZE=5>" &>
+ </TD>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Time Left'),
+ content => "<input name=TimeLeft value=\"".$TicketObj->TimeLeft."\" SIZE=5>"
+&>
+ </TD>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Priority'),
+ content => "<input name=Priority value=\"".$TicketObj->Priority."\" SIZE=3>" &>
+ </TD>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Final Priority'),
+ content => "<input name=FinalPriority value=\"".$TicketObj->FinalPriority."\" SIZE=3>" &>
+ </TD>
+ <TD>
+ <& /Elements/ShadedBox,
+ title => loc('Queue'),
+ content => "$SelectQueue" &>
+ </TD>
+ </table>
+ </td></TR>
+</TABLE>
+
+<%INIT>
+#It's hard to do this inline, so we'll preload the html of the selectstatus in here.
+my $SelectStatus = $m->scomp("/Elements/SelectStatus", Name => 'Status', Default=> $TicketObj->Status);
+my $SelectQueue = $m->scomp("/Elements/SelectQueue", Name => 'Queue', Default =>$TicketObj->QueueObj->Id);
+
+</%INIT>
+<%ARGS>
+
+$TicketObj => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditCustomField b/rt/html/Ticket/Elements/EditCustomField
new file mode 100644
index 0000000..1634806
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditCustomField
@@ -0,0 +1,74 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% my $Values;
+% if ($TicketObj) {
+% $Values = $TicketObj->CustomFieldValues($CustomField->id);
+% }
+% if ($CustomField->Type eq 'FreeformSingle') {
+ <input name="<%$NamePrefix%><%$CustomField->Id%>-Value"
+ size="<%$Cols%>"
+% if ($TicketObj) {
+ value="<%$Values->Count ? $Values->First->Content : ''%>"
+% } elsif ($Default) {
+ value="<%$Default ? $Default : ''%>"
+% }
+>
+% } elsif ($CustomField->Type eq 'FreeformMultiple') {
+% my $content;
+% if ($TicketObj) {
+% while (my $value = $Values->Next ) {
+% $content .= $value->Content;
+% }
+% } elsif ($Default) {
+ value="<%$Default ? $Default : ''%>"
+% }
+<input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+<textarea cols=<%$Cols%> rows=<%$Rows%> name="<%$NamePrefix%><%$CustomField->Id%>-Values"><%$content%></textarea>
+% } elsif ($CustomField->Type =~ /^Select/) {
+ <input type="hidden" name="<%$NamePrefix%><%$CustomField->Id%>-Values-Magic" value="1">
+ <select name="<%$NamePrefix%><%$CustomField->Id%>-Values"
+ size="<%$Rows%>"
+ <%$CustomField->Type eq 'SelectMultiple' && 'MULTIPLE'%>>
+% my $CustomFieldValues = $CustomField->Values();
+% my $selected;
+% while (my $value = $CustomFieldValues->Next) {
+ <option value="<%$value->Name%>"
+% if ($TicketObj) {
+ <% $Values->HasEntry($value->Name) && ($selected = 1) && 'SELECTED' %>
+% } elsif ($Default) {
+ <% ($Default eq $value->Name) && ($selected = 1) && 'SELECTED' %>
+% }
+ ><% $value->Name%></option>
+% }
+ <option value="" <% !$selected && 'SELECTED' %>><&|/l&>(no value)</&></option>
+ </select>
+% }
+<%ARGS>
+$TicketObj => undef
+$CustomField => undef
+$NamePrefix => undef
+$Rows => 5
+$Cols=> 15
+$Default => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditCustomFields b/rt/html/Ticket/Elements/EditCustomFields
new file mode 100644
index 0000000..6b27389
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditCustomFields
@@ -0,0 +1,74 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+<tr>
+<td valign="top" width="50%">
+<table>
+
+% my @entry_fields;
+% my $i;
+% my $cfcount = $CustomFields->Count;
+% $cfcount++ if ($cfcount % 2) ; # if we have an odd number of
+% #custom fields, fudge it so we know where to put in the table break
+% while (my $CustomField = $CustomFields->Next()) {
+% if ($cfcount == 2 * $i) {
+</table>
+</td>
+<td valign="top" width="50%">
+<table>
+% }
+% $i++;
+ <tr>
+ <td class="labeltop">
+ <b><%$CustomField->Name%></b><br>
+ <i><%$CustomField->FriendlyType%></i>
+ </td>
+ <td class="entry"><& EditCustomField, TicketObj => $TicketObj, CustomField => $CustomField, NamePrefix => $NamePrefix &></td>
+ </tr>
+% }
+</table>
+</td>
+</tr>
+</table>
+
+<%INIT>
+my $CustomFields;
+my $NamePrefix;
+
+if ($TicketObj) {
+ $CustomFields = $TicketObj->QueueObj->CustomFields();
+ $NamePrefix = "Ticket-".$TicketObj->Id."-CustomField-";
+
+} else {
+ $CustomFields = $QueueObj->CustomFields();
+ $NamePrefix = "CustomField-";
+}
+
+
+
+</%INIT>
+<%ARGS>
+$TicketObj => undef
+$QueueObj => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditDates b/rt/html/Ticket/Elements/EditDates
new file mode 100644
index 0000000..1f3bf1b
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditDates
@@ -0,0 +1,53 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE>
+ <TR>
+ <TD class="label"><&|/l&>Starts</&>:</TD>
+ <TD class="entry"><& /Elements/SelectDate, menu_prefix => 'Starts', current => 0 &>
+ (<% $TicketObj->StartsObj->AsString %>)</TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Started</&>:</TD>
+ <TD class="entry"><& /Elements/SelectDate, menu_prefix => 'Started', current => 0 &> (<%$TicketObj->StartedObj->AsString %>)</TD>
+ </TR>
+
+ <TR>
+ <TD class="label">
+ <&|/l&>Last Contact</&>:
+ </TD>
+ <TD class="entry">
+ <& /Elements/SelectDate, menu_prefix => 'Told', current => 0 &> (<% $TicketObj->ToldObj->AsString %>)
+ </TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Due</&>:</TD>
+ <TD class="entry">
+ <& /Elements/SelectDate, menu_prefix => 'Due', current => 0 &> (<% $TicketObj->DueObj->AsString %>)
+ </TD>
+ </TR>
+</TABLE>
+<%ARGS>
+$TicketObj => undef
+</%ARGS>
+
diff --git a/rt/html/Ticket/Elements/EditLinks b/rt/html/Ticket/Elements/EditLinks
new file mode 100644
index 0000000..bdb8a6b
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditLinks
@@ -0,0 +1,133 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE width=100%>
+ <TR>
+ <TD VALIGN=TOP WIDTH=50%>
+ <h3><&|/l&>Current Relationships</&></h3>
+
+<table>
+ <tr>
+ <td></td>
+ <td><i><&|/l&>(Check box to delete)</&></i></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Depends on</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->DependsOn->Next) {
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
+ <& ShowLink, URI => $link->TargetURI &><br>
+% }
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Depended on by</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->DependedOnBy->Next) {
+% my $member = $link->BaseObj;
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink-<%$link->Base%>-<%$link->Type%>-">
+ <& ShowLink, URI => $link->BaseURI &><br>
+% }
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Parents</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->MemberOf->Next) {
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
+ <& ShowLink, URI => $link->TargetURI &><br>
+% }
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Children</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->Members->Next) {
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink-<%$link->Base%>-<%$link->Type%>-">
+ <& ShowLink, URI => $link->BaseURI &><br>
+% }
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Refers to</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->RefersTo->Next) {
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink--<%$link->Type%>-<%$link->Target%>">
+ <& ShowLink, URI => $link->TargetURI &><br>
+%}
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Referred to by</&>:</td>
+ <td class="value">
+% while (my $link = $Ticket->ReferredToBy->Next) {
+ <INPUT TYPE=CHECKBOX NAME="DeleteLink-<%$link->Base%>-<%$link->Type%>-">
+ <& ShowLink, URI => $link->BaseURI &><br>
+% }
+ </td>
+ </tr>
+</table>
+
+</TD>
+<TD VALIGN=TOP>
+<h3><&|/l&>New Relationships</&></h3>
+<i><&|/l&>Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces.</&></i><br>
+<TABLE>
+ <TR>
+ <TD class="label"><&|/l&>Merge into</&>:</TD>
+ <TD class="entry"><input name="<%$Ticket->Id%>-MergeInto"> <i><&|/l&>(only one ticket)</&></i></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Depends on</&>:</TD>
+ <TD class="entry"><input name="<%$Ticket->Id%>-DependsOn"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Depended on by</&>:</TD>
+ <TD class="entry"><input name="DependsOn-<%$Ticket->Id%>"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Parents</&>:</TD>
+ <TD class="entry"><input name="<%$Ticket->Id%>-MemberOf"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Children</&>:</TD>
+ <TD class="entry"> <input name="MemberOf-<%$Ticket->Id%>"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Refers to</&>:</TD>
+ <TD class="entry"><input name="<%$Ticket->Id%>-RefersTo"></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Referred to by</&>:</TD>
+ <TD class="entry"> <input name="RefersTo-<%$Ticket->Id%>"></TD>
+ </TR>
+</TABLE>
+</TD>
+</TR>
+</TABLE>
+
+
+
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditPeople b/rt/html/Ticket/Elements/EditPeople
new file mode 100644
index 0000000..a1fc011
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditPeople
@@ -0,0 +1,69 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TABLE>
+<TR>
+<TD VALIGN=TOP>
+
+<h3><&|/l&>New watchers</&></h3>
+<&|/l&>Find people whose</&><BR>
+<& /Elements/SelectUsers &>
+<input type=submit name="OnlySearchForPeople" value="<&|/l&>Go!</&>">
+<BR>
+<&|/l&>Find group whose</&><BR>
+<& /Elements/SelectGroups &>
+<input type=submit name="OnlySearchForGroup" value="<&|/l&>Go!</&>">
+
+<& AddWatchers, Ticket => $Ticket, UserString => $UserString,
+ UserOp => $UserOp, UserField => $UserField,
+ GroupString => $GroupString, GroupOp => $GroupOp,
+ GroupField => $GroupField, PrivilegedOnly => $PrivilegedOnly &>
+</TD><TD VALIGN=TOP>
+<h3><&|/l&>Owner</&></h3>
+<&|/l&>Owner</&>: <& /Elements/SelectOwner, Name => 'Owner', QueueObj => $Ticket->QueueObj, TicketObj => $Ticket, Default => $Ticket->OwnerObj->Id &>
+<h3><&|/l&>Current watchers</&></h3>
+<&|/l&>(Check box to delete)</&><br>
+
+<&|/l&>Requestors</&>:
+<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Requestors &>
+
+<&|/l&>Cc</&>:
+<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->Cc &>
+
+<&|/l&>Administrative Cc</&>:
+<& EditWatchers, TicketObj => $Ticket, Watchers => $Ticket->AdminCc &>
+
+</TD>
+</TR>
+</TABLE>
+
+<%ARGS>
+$UserField => undef
+$UserOp => undef
+$UserString => undef
+$GroupField => undef
+$GroupOp => undef
+$GroupString => undef
+$PrivilegedOnly => undef
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/EditWatchers b/rt/html/Ticket/Elements/EditWatchers
new file mode 100644
index 0000000..145071c
--- /dev/null
+++ b/rt/html/Ticket/Elements/EditWatchers
@@ -0,0 +1,52 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<ul>
+%# Print out a placeholder if there are none.
+%if ($Members->Count == 0 ) {
+<li><i><&|/l&>none</&></i>
+% }
+
+
+%while (my $watcher=$Members->Next) {
+<li>
+<INPUT TYPE=CHECKBOX NAME="Ticket-DelWatcher-Type-<%$Watchers->Type%>-Principal-<%$watcher->MemberId%>" UNCHECKED>
+%if ($watcher->MemberObj->IsUser) {
+<a href="<%$RT::WebPath%>/Admin/Users/Modify.html?id=<%$watcher->MemberObj->Object->id%>">
+<%$watcher->MemberObj->Object->Name%></a>
+%} else {
+<a href="<%$RT::WebPath%>/Admin/Groups/Modify.html?id=<%$watcher->MemberObj->Object->id%>">
+<%$watcher->MemberObj->Object->Name%></a>
+%}
+% }
+</ul>
+<%INIT>
+my $Members = $Watchers->MembersObj;
+</%INIT>
+<%ARGS>
+$TicketObj => undef
+$Watchers => undef
+</%ARGS>
+
+
+
diff --git a/rt/html/Ticket/Elements/ShowAttachments b/rt/html/Ticket/Elements/ShowAttachments
new file mode 100644
index 0000000..590a011
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowAttachments
@@ -0,0 +1,79 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if (keys %documents) {
+<& /Elements/TitleBoxStart, title => loc('Attachments'),
+ title_class=> 'inverse',
+ color => "#336699" &>
+
+% foreach my $key (keys %documents) {
+% my $fontsize='size="-1"';
+
+<%$key%><br>
+<ul>
+% foreach my $rev (@{$documents{$key}}) {
+
+<%PERL>
+my $size = $rev->ContentLength;
+
+if ($size) {
+ if ($size > 1024) {
+ $size = int($size/102.4)/10 . "k";
+ }
+ else {
+ $size = $size ."b";
+ }
+
+</%PERL>
+
+<li><font <%$fontsize%>>
+ <A HREF="<%$RT::WebPath%>/Ticket/Attachment/<%$rev->TransactionObj->Id%>/<%$rev->Id%>/<%$rev->Filename | u%>"><%$rev->CreatedAsString%> (<% $size %>)</a></font></li>
+% }
+% $fontsize='size="-2"';
+% }
+</ul>
+
+% }
+<& /Elements/TitleBoxEnd &>
+<BR>
+% }
+
+<%INIT>
+my %documents;
+my $transactions = $Ticket->Transactions();
+while (my $trans = $transactions->Next()) {
+ my $attachments = $trans->Attachments();
+ $attachments->Columns( qw( Id Filename ContentType Headers Subject Parent ContentEncoding ContentType TransactionId) );
+ $attachments->Limit(FIELD => 'Filename', OPERATOR => 'IS NOT', VALUE => 'NULL', QUOTEVALUE => 0, ENTRYAGGREGATOR => 'AND');
+ $attachments->Limit(FIELD => 'Filename', OPERATOR => '!=', VALUE => '', ENTRYAGGREGATOR => 'AND');
+ while (my $attach = $attachments->Next()) {
+ next unless ($attach->Filename());
+ # most recent at the top
+ unshift (@{$documents{$attach->Filename}}, $attach);
+ }
+}
+</%INIT>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
+
diff --git a/rt/html/Ticket/Elements/ShowBasics b/rt/html/Ticket/Elements/ShowBasics
new file mode 100644
index 0000000..ad23e8c
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowBasics
@@ -0,0 +1,54 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+ <tr>
+ <td class="label"><&|/l&>Id</&>:</td>
+ <td class="value"><%$Ticket->Id %></td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Status</&>:</td>
+ <td class="value"><&|/l&><% $Ticket->Status%></&></td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Worked</&>:</td>
+ <td class="value"><&|/l, $TimeWorked &>[_1] min</&></td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Priority</&>:</td>
+ <td class="value"><%$Ticket->Priority%>/<%$Ticket->FinalPriority %></td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Queue</&>:</td>
+ <td class="value"><%$Ticket->QueueObj->Name%></td>
+ </tr>
+</table>
+<%INIT>
+my $TimeWorked = $Ticket->TimeWorked;
+if ($Ticket->TimeLeft > 0 ) {
+ $TimeWorked = $Ticket->TimeWorked."/".$Ticket->TimeLeft;
+}
+</%INIT>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowCustomFields b/rt/html/Ticket/Elements/ShowCustomFields
new file mode 100644
index 0000000..50d28f0
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowCustomFields
@@ -0,0 +1,46 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+% my @entry_fields;
+% while (my $CustomField = $CustomFields->Next()) {
+% my $Values = $Ticket->CustomFieldValues($CustomField->Id);
+ <tr>
+ <td class="label"><%$CustomField->Name%>:</td>
+ <td class="value">
+% while (my $Value = $Values->Next()) {
+<%$Value->Content%><br>
+% }
+% unless ($Values->Count()) {
+<i><&|/l&>(no value)</&></i>
+% }
+ </td>
+ </tr>
+% }
+</table>
+<%INIT>
+my $CustomFields = $Ticket->QueueObj->CustomFields();
+</%INIT>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowDates b/rt/html/Ticket/Elements/ShowDates
new file mode 100644
index 0000000..b09b4bf
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowDates
@@ -0,0 +1,63 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+
+<TABLE>
+ <TR>
+ <TD class="label"><&|/l&>Created</&>:</TD>
+ <TD class="value"><% $Ticket->CreatedObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Starts</&>:</TD>
+ <TD class="value"><% $Ticket->StartsObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Started</&>:</TD>
+ <TD class="value"><% $Ticket->StartedObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><a href="Display.html?id=<%$Ticket->id%>&Action=SetTold"><&|/l&>Last Contact</&></a>:</TD>
+ <TD class="value"><% $Ticket->ToldObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Due</&>:</TD>
+ <TD class="value"><% $Ticket->DueObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Closed</&>:</TD>
+ <TD class="value"><% $Ticket->ResolvedObj->AsString %></TD>
+ </TR>
+ <TR>
+ <TD class="label"><&|/l&>Updated</&>:</TD>
+% my $UpdatedString = $Ticket->LastUpdated ? (loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)) : loc("Never");
+% if ($UpdatedLink) {
+ <TD class="value"><A HREF="#lasttrans"><% $UpdatedString | h %></a></TD>
+% } else {
+ <TD class="value"><% $UpdatedString | h %></TD>
+% }
+ </TR>
+</TABLE>
+<%ARGS>
+$Ticket => undef
+$UpdatedLink => 1
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowDependencies b/rt/html/Ticket/Elements/ShowDependencies
new file mode 100644
index 0000000..b7f3968
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowDependencies
@@ -0,0 +1,41 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<&|/l&>Depends on</&>:<BR>
+% while (my $Link = $Ticket->DependsOn->Next) {
+% my $member = $Link->TargetObj;
+<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$member->Id%>"><%$member->Id%></a>: (<%$member->OwnerObj->Name%>) <%$member->Subject%>
+[<%$member->Status%>]
+ <br>
+% }
+<&|/l&>Depended on by</&>:<BR>
+% while (my $Link = $Ticket->DependedOnBy->Next) {
+% my $member = $Link->TargetObj;
+<a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$member->Id%>"><%$member->Id%></a>: (<%$member->OwnerObj->Name%>) <%$member->Subject%>
+[<%$member->Status%>]
+ <br>
+% }
+
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowHistory b/rt/html/Ticket/Elements/ShowHistory
new file mode 100644
index 0000000..194be9b
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowHistory
@@ -0,0 +1,86 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%perl>
+ if ($ShowDisplayModes or $ShowTitle) {
+my $title;
+my $titleright;
+if ($ShowTitle) {
+ $title = loc('History');
+}
+else {
+ $title = '&nbsp;';
+}
+$titleright = loc('Display mode') . ":";
+if ($ShowHeaders && $ShowHeaders == $Ticket->Id ) {
+ $titleright .= "[<A HREF=\"" . $URIFile . "?id="
+ . $Ticket->id . "\">"
+ . loc("Brief headers")
+ . "</a>] <b>["
+ . loc("Full headers") . "]</b>";
+}
+else {
+ $titleright .= "<b>["
+ . loc("Brief headers")
+ . "]</b> [<A HREF=\""
+ . $URIFile
+ . "?ShowHeaders="
+ . $Ticket->Id . "&id="
+ . $Ticket->id . "\">"
+ . loc("Full headers") . "</a>]";
+}
+</%perl>
+<& /Elements/TitleBoxStart, title => $title, titleright => $titleright, bodyclass=> ''&>
+% }
+
+<TABLE WIDTH=100% CELLSPACING=0 CELLPADDING=2 BORDER=0>
+% while (my $Transaction = $Transactions->Next) {
+% my $skip = 0;
+% $m->comp('/Elements/Callback', _CallbackName => 'SkipTransaction', Transaction => $Transaction, skip => \$skip, %ARGS);
+% next if $skip;
+% $i++;
+% if ($Transactions->IsLast) {
+ <a name="lasttrans"></a>
+% }
+ <& ShowTransaction, Ticket => $Ticket, Transaction => $Transaction, ShowHeaders => $ShowHeaders, Collapsed => $Collapsed, RowNum => $i, ShowTitleBarCommands => $ShowTitleBarCommands, %ARGS &>
+% }
+</TABLE>
+% if ($ShowDisplayModes or $ShowTitle) {
+<& /Elements/TitleBoxEnd &>
+% }
+<%INIT>
+
+my $Transactions = $Ticket->Transactions;
+my $i;
+
+
+</%INIT>
+<%ARGS>
+$URIFile => 'Display.html'
+$Ticket => undef
+$ShowHeaders => undef
+$Collapsed => undef
+$ShowTitle => 1
+$ShowDisplayModes => 1
+$ShowTitleBarCommands => 1
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowLink b/rt/html/Ticket/Elements/ShowLink
new file mode 100644
index 0000000..493fd95
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowLink
@@ -0,0 +1,40 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<A href="<%$URI->Resolver->HREF%>">
+% if ($URI->IsLocal) {
+% my $member = $URI->Object;
+% if (UNIVERSAL::isa($member, "RT::Ticket")) {
+<%$member->Id%>: (<%$member->OwnerObj->Name%>) <%$member->Subject%> [<% loc($member->Status) %>]
+% } elsif ( UNIVERSAL::can($member, 'Name')) {
+<%$URI->Resolver->AsString%>: <%$member->Name%>
+% } else {
+<%$URI->Resolver->AsString%>
+% }
+% } else {
+<%$URI->Resolver->AsString%>
+% }
+</a>
+<%ARGS>
+$URI => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowLinks b/rt/html/Ticket/Elements/ShowLinks
new file mode 100644
index 0000000..f88a600
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowLinks
@@ -0,0 +1,87 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+ <tr>
+ <td class="labeltop"><&|/l&>Depends on</&>:</td>
+ <td class="value">
+<ul>
+% while (my $Link = $Ticket->DependsOn->Next) {
+<li><& ShowLink, URI => $Link->TargetURI &>
+% }
+</ul>
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Depended on by</&>:</td>
+ <td class="value">
+<ul>
+% while (my $Link = $Ticket->DependedOnBy->Next) {
+<li><& ShowLink, URI => $Link->BaseURI &>
+% }
+</ul>
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Parents</&>:</td>
+ <td class="value">
+<ul>
+% while (my $Link = $Ticket->MemberOf->Next) {
+<li><& ShowLink, URI => $Link->TargetURI &>
+% }
+</ul>
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Children</&>:</td>
+ <td class="value"><& /Ticket/Elements/ShowMembers, Ticket => $Ticket &></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Refers to</&>:</td>
+ <td class="value">
+<ul>
+% while (my $Link = $Ticket->RefersTo->Next) {
+<li><& ShowLink, URI => $Link->TargetURI &>
+% }
+</ul>
+ </td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Referred to by</&>:</td>
+ <td class="value">
+ <ul>
+% while (my $Link = $Ticket->ReferredToBy->Next) {
+<li><& ShowLink, URI => $Link->BaseURI &>
+% }
+</ul>
+ </td>
+ </tr>
+
+% # Allow people to add more rows to the table
+% $m->comp('/Elements/Callback', %ARGS );
+
+</table>
+
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMemberOf b/rt/html/Ticket/Elements/ShowMemberOf
new file mode 100644
index 0000000..79e0a3b
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowMemberOf
@@ -0,0 +1,35 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<UL>
+% my $memberof = $Ticket->MemberOf;
+% while (my $member_of = $memberof->Next) {
+<LI><a href="/Ticket/Display.html?id=<%$member_of->Id%>"><%$member_of->Id%></a>: <%$member_of->Subject%> [<%$member_of->Status%>]
+% }
+</UL>
+
+<%INIT>
+</%INIT>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMembers b/rt/html/Ticket/Elements/ShowMembers
new file mode 100644
index 0000000..e101662
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowMembers
@@ -0,0 +1,45 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if ($members->Count) {
+<UL>
+% while (my $link = $members->Next) {
+% my $member= $link->BaseObj;
+<LI><a href="<%$RT::WebPath%>/Ticket/Display.html?id=<%$member->Id%>"><%$member->Id%></a>: <%$member->Subject%> [<%loc($member->Status)%>]<br>
+% if ($depth < 8) {
+<&/Ticket/Elements/ShowMembers, Ticket => $member, depth => ($depth+1) &>
+% }
+% }
+</UL>
+% }
+
+<%INIT>
+
+my $members = $Ticket->Members;
+
+</%INIT>
+
+<%ARGS>
+$Ticket => undef
+$depth => 1
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMessageHeaders b/rt/html/Ticket/Elements/ShowMessageHeaders
new file mode 100644
index 0000000..11d873c
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowMessageHeaders
@@ -0,0 +1,32 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%$content |n%>
+<%INIT>
+my $content = $Headers;
+RT::Interface::Web::EscapeUTF8(\$content);
+$m->comp('/Elements/Callback', content => \$content, %ARGS);
+</%INIT>
+<%ARGS>
+$Headers => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowMessageStanza b/rt/html/Ticket/Elements/ShowMessageStanza
new file mode 100644
index 0000000..8e3045a
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowMessageStanza
@@ -0,0 +1,61 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% if (ref($Message)) {
+<font color="<%$colors[$Depth]%>">
+<%perl>
+foreach my $stanza (@$Message) {
+ if ( ref $stanza eq "ARRAY" ) {
+ $m->comp( 'ShowMessageStanza',
+ Depth => $Depth + 1,
+ Transaction => $Transaction,
+ Message => $stanza );
+ }
+ elsif ( ref $stanza eq "HASH" ) {
+ my $content = $stanza->{raw};
+ RT::Interface::Web::EscapeUTF8(\$content);
+ $m->comp('/Elements/Callback', content => \$content, %ARGS);
+ $content =~ s/\n/<br>/gi;
+
+</%perl>
+<%$content |n%><br>
+% }
+% } # end foreach
+</font>
+% } else {
+% my $content = $Message;
+% RT::Interface::Web::EscapeUTF8(\$content);
+% $m->comp('/Elements/Callback', content => \$content, %ARGS);
+% $content =~ s/\n/<br>/gi;
+<%$content |n%><br>
+% }
+<%INIT>
+use URI::URL;
+my $server = 'fsck.com';
+my @colors = ('#000000', '#660000', '#006600', '#000066', '#cc0000', '#00cc00', '#0000cc', '#ff0000', '#00ff00', '#0000ff');
+</%INIT>
+<%ARGS>
+$Message => undef
+$Depth => 0
+$Transaction => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowPeople b/rt/html/Ticket/Elements/ShowPeople
new file mode 100644
index 0000000..160da70
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowPeople
@@ -0,0 +1,45 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<table>
+ <tr>
+ <td class="label"><&|/l&>Owner</&>:</td>
+ <td class="value"><%$Ticket->OwnerObj->Name%></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Requestors</&>:</td>
+ <td class="value"><%$Ticket->RequestorAddresses%></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Cc</&>:</td>
+ <td class="value"><%$Ticket->CcAddresses%></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>AdminCc</&>:</td>
+ <td class="value"><%$Ticket->AdminCcAddresses%></td>
+ </tr>
+</table>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
+
diff --git a/rt/html/Ticket/Elements/ShowReferences b/rt/html/Ticket/Elements/ShowReferences
new file mode 100644
index 0000000..831923b
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowReferences
@@ -0,0 +1,50 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<UL>
+% while (my $Link = $Ticket->RefersTo->Next) {
+<LI>
+% if ($Link->TargetURI->IsLocal) {
+% my $member = $Link->TargetObj;
+
+<a href="/Ticket/Display.html?id=<%$member->Id%>"><%$member->Id%></a>: (<%$member->OwnerObj->Name%>) <%$member->Subject%> [<%$member->Status%>]<br>
+% } else {
+<A HREF="<%$Link->TargetURI->HREF%>"><%$Link->Target%></A>
+% }
+%}
+
+
+
+% while (my $Link = $Ticket->ReferredToBy->Next) {
+<LI>
+% if ($Link->BaseURI->IsLocal) {
+% my $member = $Link->BaseObj;
+<a href="/Ticket/Display.html?id=<%$member->Id%>"><%$member->Id%></a>: (<%$member->OwnerObj->Name%>) <%$member->Subject%> [<%$member->Status%>]<br>
+% } else {
+<A HREF="<%$Link->BaseURI->HREF%>"><%$Link->Base%></A>
+%}
+% }
+</UL>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowRequestor b/rt/html/Ticket/Elements/ShowRequestor
new file mode 100644
index 0000000..cc91f59
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowRequestor
@@ -0,0 +1,59 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%PERL>
+my $rows = 10;
+my $people = $Ticket->Requestors->MembersObj;
+while (my $member=$people->Next) {
+my $requestor = $member->MemberObj->Object;
+my $name=$requestor->RealName || $requestor->EmailAddress;
+my $tickets = RT::Tickets->new($session{'CurrentUser'});
+$tickets->LimitWatcher(TYPE => 'Requestor', VALUE => $requestor->EmailAddress );
+$tickets->LimitStatus( VALUE => 'open');
+$tickets->LimitStatus( VALUE => 'new');
+$tickets->RowsPerPage($rows);
+$tickets->OrderBy(FIELD => 'Priority',
+ ORDER => 'DESC');
+</%PERL>
+
+% unless ($requestor->Privileged) {
+<& /Elements/TitleBoxStart,
+ title => "<a class='inverse' href=\"$RT::WebPath/Admin/Users/Modify.html?id=".$requestor->id."\">".loc("More about [_1]", $name)."</a>" &>
+
+<&|/l&>Comments about this user</&>:<BR>
+<B><% ($requestor->Comments || loc("No comment entered about this user")) %></B><BR>
+
+<&|/l, $rows &>This user's [_1] highest priority tickets</&>:<BR>
+<UL>
+%while (my $w=$tickets->Next) {
+<LI><a href="<%$RT::WebPath%><%$DisplayPath%>?id=<%$w->id%>"><%$w->Id%>: <%$w->Subject%></a> (<%$w->Status%>)
+%}
+</UL>
+<& /Elements/TitleBoxEnd &>
+
+% }
+%}
+<%ARGS>
+$Ticket=>undef
+$DisplayPath => "/Ticket/Display.html"
+</%ARGS>
diff --git a/rt/html/Ticket/Elements/ShowSummary b/rt/html/Ticket/Elements/ShowSummary
new file mode 100644
index 0000000..6ae8758
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowSummary
@@ -0,0 +1,82 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+ <TABLE WIDTH="100%" class="ticketsummary" >
+ <TR>
+ <TD VALIGN=TOP WIDTH="50%">
+ <& /Elements/TitleBoxStart, title => loc('The Basics'),
+ title_href =>"$RT::WebPath/Ticket/Modify.html?id=".$Ticket->Id,
+ title_class=> 'inverse',
+ color => "#993333" &>
+ <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+ <br>
+% if ($Ticket->QueueObj->CustomFields()->First) {
+ <& /Elements/TitleBoxStart, title => loc('Custom Fields'),
+ title_href =>"$RT::WebPath/Ticket/Modify.html?id=".$Ticket->Id,
+ title_class=> 'inverse',
+ color => "#993333" &>
+ <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+
+<br>
+% }
+ <& /Elements/TitleBoxStart, title => loc('People'),
+ title_href =>"$RT::WebPath/Ticket/ModifyPeople.html?id=".$Ticket->Id,
+ title_class=> 'inverse',
+ color => "#333399" &>
+ <& /Ticket/Elements/ShowPeople, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+ <BR>
+ </TD>
+ <TD VALIGN=TOP WIDTH="50%">
+
+ <& /Elements/TitleBoxStart, title => loc("Dates"),
+ title_href =>"$RT::WebPath/Ticket/ModifyDates.html?id=".$Ticket->Id,
+ title_class=> 'inverse',
+ color => "#663366" &>
+ <& /Ticket/Elements/ShowDates, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+ <BR>
+ <& /Elements/TitleBoxStart, title => loc('Relationships'),
+ title_href => "$RT::WebPath/Ticket/ModifyLinks.html?id=".$Ticket->Id,
+ title_class=> 'inverse',
+ titleright => '', color=> "#336633" &>
+ <& /Ticket/Elements/ShowLinks, Ticket => $Ticket &>
+ <& /Elements/TitleBoxEnd &>
+ <BR>
+ <& /Ticket/Elements/ShowAttachments, Ticket => $Ticket &>
+
+ <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &>
+
+
+ </TD>
+ </TR>
+ </TABLE>
+<%ARGS>
+$Ticket => undef
+</%ARGS>
+
+
+
+
diff --git a/rt/html/Ticket/Elements/ShowTransaction b/rt/html/Ticket/Elements/ShowTransaction
new file mode 100644
index 0000000..2d710fc
--- /dev/null
+++ b/rt/html/Ticket/Elements/ShowTransaction
@@ -0,0 +1,181 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<TR class="<% $RowNum%2 ? 'oddline' : 'evenline'%>" >
+<TD bgcolor="<%$bgcolor%>"><A NAME="txn-<%$Transaction->Id%>" href="#txn-<%$Transaction->Id%>">#</A>&nbsp;</TD>
+<TD>&nbsp&nbsp;</TD>
+<TD><font size=-2><% $transdate|n %></font>&nbsp;</TD>
+% my $desc = $Transaction->BriefDescription;
+% $m->comp('/Elements/Callback', _CallbackName => 'ModifyDisplay', text => \$desc, Transaction => $Transaction, %ARGS);
+<TD ALIGN="LEFT"><b><%$Transaction->CreatorObj->Name%> - <%$TicketString%> <%$desc%>
+
+</b></TD>
+<TD><%$TimeTaken%>&nbsp;</TD>
+<TD ALIGN="RIGHT"><font size=-1><%$titlebar_commands|n%></font></TD>
+</TR>
+<%PERL>
+
+unless ($Collapsed) {
+ $attachments->GotoFirstItem;
+ while (my $message=$attachments->Next) {
+
+ my ($headers, $quoted);
+ if ($ShowHeaders && ($ShowHeaders == $Ticket->Id)) {
+ $headers = $message->Headers;
+ } else {
+ $headers = $message->NiceHeaders;
+ }
+ chomp $headers;
+ if ($headers) {
+ # localize the common headers (like 'Subject:'), too.
+ eval {$headers =~ s/^([^:]+)(?=:)/loc($1)/em; } # we eval here to catch errors when 5.6 panics
+ }
+ # 13456 is a random # of about the biggest size we want to see inline text
+ # It's here to catch anyone who hasn't updated RT_Config.pm since this
+ # constant was moved out there.
+ my $MAX_INLINE_BODY = $RT::MaxInlineBody || 13456;
+ if ($message->ContentType =~ m{^(text/plain|message|text$)}i &&
+ $message->ContentLength < $MAX_INLINE_BODY ) {
+ eval {
+ require Text::Quoted;
+ $quoted = Text::Quoted::extract($message->Content);
+ };
+ if ($@) {
+ $quoted = $message->Content;
+ }
+ }
+
+</%PERL>
+<TR class="<% $RowNum%2 ? 'oddline' : 'evenline'%>" >
+ <TD BGCOLOR="<%$bgcolor%>">&nbsp;&nbsp;</TD>
+ <TD>&nbsp;&nbsp;</TD>
+ <TD COLSPAN=3 VALIGN=TOP>
+<span class="message">
+ <PRE>
+<& ShowMessageHeaders, Headers => $headers, Transaction => $Transaction &>
+</PRE>
+% if (!length($quoted) && $message->ContentType =~ m#^text/#) {
+<blockquote><i><&|/l&>Message body not shown because it is too large or is not plain text.</&><br>
+<&|/l&>You can access it with the Download button on the right.</&></i></blockquote>
+% } else {
+<& ShowMessageStanza, Depth => 0, Message => $quoted, Transaction => $Transaction &>
+% }
+</span>
+ </TD>
+ <TD VALIGN=TOP ALIGN=RIGHT>
+
+% if ($message->Parent == 0 ) {
+<BR>
+% }
+<%PERL>
+my $size = $message->ContentLength or next;
+
+if ($size) {
+ if ($size > 1024) {
+ $size = loc("[_1]k", int($size/102.4)/10);
+ }
+ else {
+ $size = loc("[_1]b", $size);
+ }
+</%PERL>
+<font size=-1><A HREF="<%$AttachPath%>/<%$Transaction->Id%>/<%$message->Id%>/<%$message->Filename | u%>"><&|/l&>Download</&> <% $message->Filename|| loc('(untitled)') %></a> <% $size %></font>
+% }
+</TD>
+</TR>
+% }
+% }
+
+
+
+<%ARGS>
+$Ticket => undef
+$Transaction => undef
+$ShowHeaders => 0
+$Collapsed => undef
+$ShowTitleBarCommands => 1
+$RowNum => 1
+$AttachPath => $RT::WebPath."/Ticket/Attachment"
+</%ARGS>
+
+<%INIT>
+
+
+my ($TimeTaken, $TicketString, $bgcolor);
+
+my $transdate = $Transaction->CreatedAsString();
+$transdate =~ s/\s/&nbsp;/g;
+
+if ($Transaction->Type =~ /^(Create|Correspond|Comment$)/) {
+ if ($Transaction->IsInbound) {
+ $bgcolor="#336699";
+ }
+ else {
+ $bgcolor="#339999";
+ }
+} elsif (($Transaction->Field =~ /^Owner$/) or
+ ($Transaction->Type =~ /^(AddWatcher|DelWatcher)$/)) {
+ $bgcolor="#333399";
+
+} elsif ($Transaction->Type =~ /^(AddLink|DeleteLink)$/) {
+ $bgcolor="#336633";
+} elsif ($Transaction->Type =~ /^(Status|Set|Told)$/) {
+ if ($Transaction->Field =~ /^(Told|Starts|Started|Due)$/) {
+ $bgcolor="#663366";
+ }
+ else {
+ $bgcolor="#993333";
+ }
+}
+else {
+ $bgcolor="#cccccc";
+}
+
+if ($Ticket->Id != $Transaction->Ticket) {
+ $TicketString = "Ticket ".$Transaction->Ticket .": ";
+}
+
+if ($Transaction->TimeTaken > 0) {
+ $TimeTaken = $Transaction->TimeTaken." min"
+}
+my $attachments = $Transaction->Attachments;
+$attachments->Columns( qw( Id Filename ContentType Headers Subject Parent ContentEncoding ContentType TransactionId) );
+
+my $titlebar_commands='&nbsp;';
+
+# If the transaction has anything attached to it at all
+if ($Transaction->Attachments->First && $ShowTitleBarCommands) {
+ if ($Transaction->TicketObj->CurrentUserHasRight('ReplyToTicket')) {
+ $titlebar_commands .=
+ "[<a href=\"Update.html?id=".
+ $Transaction->Ticket . "&QuoteTransaction=".$Transaction->Id.
+ "&Action=Respond\">". loc('Reply') ."</a>]&nbsp;";
+ }
+ if ($Transaction->TicketObj->CurrentUserHasRight('CommentOnTicket')) {
+ $titlebar_commands .=
+ "[<a href=\"Update.html?id=".$Transaction->Ticket.
+ "&QuoteTransaction=".$Transaction->Id.
+ "&Action=Comment\">". loc('Comment') ."</a>]";
+ }
+}
+
+</%INIT>
diff --git a/rt/html/Ticket/Elements/Tabs b/rt/html/Ticket/Elements/Tabs
new file mode 100644
index 0000000..cba45df
--- /dev/null
+++ b/rt/html/Ticket/Elements/Tabs
@@ -0,0 +1,176 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Callback, Ticket => $Ticket, actions=> $actions, tabs => $tabs, %ARGS &>
+<& /Elements/Tabs,
+ tabs => $tabs,
+ actions => $actions,
+ current_tab => $current_tab,
+ current_toptab => $current_toptab,
+ Title => $Title &>
+<%INIT>
+
+my $tabs = {};
+my $current_toptab = "Search/Listing.html",
+ my $searchtabs = { new => { title => loc('New Search'),
+ path => 'Search/Listing.html?ClearRestrictions=1'}
+
+
+} ;
+my $actions;
+
+if ( $Ticket) {
+
+my $id = $Ticket->id();
+
+if ( defined $session{'tickets'} ) {
+
+ # we have to update session data if we get new ItemMap
+ my $updatesession = 1 unless($session{'tickets'}->{'item_map'});
+
+ my $item_map = $session{'tickets'}->ItemMap;
+
+ if ($updatesession) {
+ $session{'i'}++;
+ $session{'tickets'}->PrepForSerialization();
+ }
+
+ # Don't $current_toptab = display prev links if we're on the first ticket
+ if ($item_map->{$Ticket->Id}->{prev}) {
+ $searchtabs->{'_a'} = {
+ class => "nav",
+ path => "Ticket/Display.html?id=" . $item_map->{first},
+ title => '<< ' . loc('First') };
+ $searchtabs->{"_b"} = { class => "nav",
+ path => "Ticket/Display.html?id=" . $item_map->{$Ticket->Id}->{prev},
+ title => '< ' . loc('Prev') };
+ }
+
+
+ # Don't display next links if we're on the last ticket
+ if ($item_map->{$Ticket->Id}->{next}) {
+ $searchtabs->{'d'} = { class => "nav",
+ path => "Ticket/Display.html?id=" . $item_map->{$Ticket->Id}->{next},
+ title => loc('Next') . ' >' };
+ $searchtabs->{'e'} = {
+ class => "nav",
+ path => "Ticket/Display.html?id=" . $item_map->{last},
+ title => loc('Last') . ' >>' };
+ }
+}
+
+
+
+$tabs->{"this"} = { class => "currentnav",
+ path => "Ticket/Display.html?id=" . $Ticket->id,
+ title => "#" . $id,
+ current_subtab => $current_subtab };
+
+my $ticket_page_tabs = {
+ _A => { title => loc('Display'),
+ path => "Ticket/Display.html?id=" . $id, },
+
+ _Ab => { title => loc('History'),
+ path => "Ticket/History.html?id=" . $id, },
+ _B => { title => loc('Basics'),
+ path => "Ticket/Modify.html?id=" . $id, },
+
+ _C => { title => loc('Dates'),
+ path => "Ticket/ModifyDates.html?id=" . $id, },
+ _D =>
+ { title => loc('People'), path => "Ticket/ModifyPeople.html?id=" . $id, },
+ _E => { title => loc('Links'),
+ path => "Ticket/ModifyLinks.html?id=" . $id, },
+ _F => { title => loc('Jumbo'),
+ path => "Ticket/ModifyAll.html?id=" . $id,
+ seperator => 1
+ },
+
+};
+
+foreach my $tab ( sort keys %{$ticket_page_tabs} ) {
+ if ( $ticket_page_tabs->{$tab}->{'path'} eq $current_tab ) {
+ $ticket_page_tabs->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{'this'}->{"current_subtab"} =
+ $ticket_page_tabs->{$tab}->{"path"};
+ }
+}
+$tabs->{'this'}->{"subtabs"} = $ticket_page_tabs;
+$current_tab = "Ticket/Display.html?id=" . $id;
+
+
+
+
+
+if ( $Ticket->CurrentUserHasRight('ModifyTicket')
+ or $Ticket->CurrentUserHasRight('ReplyToTicket') ) {
+ $actions->{'A'} = { title => loc('Reply'),
+ path => "Ticket/Update.html?Action=Respond&id=" . $id,
+ };
+}
+
+if ( $Ticket->CurrentUserHasRight('ModifyTicket') ) {
+ if ( $Ticket->Status ne 'resolved' ) {
+ $actions->{'B'} = {
+
+ path => "Ticket/Update.html?Action=Comment&DefaultStatus=resolved&id=" . $id,
+ title => loc('Resolve') };
+ }
+ if ( $Ticket->Status ne 'open' ) {
+ $actions->{'C'} = { path => "Ticket/Display.html?Status=open&id=" . $id,
+ title => loc('Open it') };
+ }
+}
+
+if ( $Ticket->CurrentUserHasRight('OwnTicket') ) {
+ if ( $Ticket->OwnerObj->id == $RT::Nobody->id ) {
+ $actions->{'D'} = { path => "Ticket/Display.html?Action=Take&id=" . $id,
+ title => loc('Take') };
+ }
+ elsif ( $Ticket->OwnerObj->id != $session{CurrentUser}->id ) {
+ $actions->{'E'} = {path => "Ticket/Display.html?Action=Steal&id=" . $id,
+ title => loc('Steal') };
+ }
+}
+
+if ( $Ticket->CurrentUserHasRight('ModifyTicket')
+ or $Ticket->CurrentUserHasRight('CommentOnTicket') ) {
+ $actions->{'F'} = { title => loc('Comment'),
+ path => "Ticket/Update.html?Action=Comment&id=" . $id,
+ };
+}
+}
+$tabs->{"g"} = { path => 'Search/Listing.html',
+ title => loc('Search'),
+ separator => 1,
+ subtabs => $searchtabs };
+</%INIT>
+
+
+<%ARGS>
+$Ticket => undef
+$subtabs => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/Ticket/History.html b/rt/html/Ticket/History.html
new file mode 100644
index 0000000..cb02f1c
--- /dev/null
+++ b/rt/html/Ticket/History.html
@@ -0,0 +1,52 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Ticket History # [_1] [_2]", $Ticket->Id, $Ticket->Subject) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $Ticket, current_tab => 'Ticket/History.html?id='.$Ticket->id,
+ Title => loc("Ticket History # [_1] [_2]", $Ticket->Id, $Ticket->Subject) &>
+
+<BR>
+
+<& /Ticket/Elements/ShowHistory , Ticket => $Ticket, ShowHeaders => $ARGS{'ShowHeaders'}, URIFile => 'History.html' &>
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
+
+<%INIT>
+
+
+
+my $Ticket = LoadTicket ($id);
+
+unless ($Ticket->CurrentUserHasRight('ShowTicket')) {
+ Abort("No permission to view ticket");
+}
+
+</%INIT>
+
+
+
+
diff --git a/rt/html/Ticket/Modify.html b/rt/html/Ticket/Modify.html
new file mode 100644
index 0000000..e504a3cb
--- /dev/null
+++ b/rt/html/Ticket/Modify.html
@@ -0,0 +1,63 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc('Modify ticket #[_1]', $TicketObj->Id) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $TicketObj, current_subtab => "Ticket/Modify.html?id=".$TicketObj->Id,
+ Title => loc('Modify ticket #[_1]', $TicketObj->Id) &>
+
+<& /Elements/ListActions, actions => \@results &>
+<FORM METHOD=POST ACTION="Modify.html">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$TicketObj->Id%>">
+
+<& /Elements/TitleBoxStart, title => loc('Modify ticket #[_1]',$TicketObj->Id), color=> "#993333", width => "100%" &>
+<& Elements/EditBasics, TicketObj => $TicketObj &>
+<& Elements/EditCustomFields, TicketObj => $TicketObj &>
+<& /Elements/TitleBoxEnd &>
+
+<& /Elements/Submit, Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#993333" &>
+</form>
+<%INIT>
+
+my $TicketObj = LoadTicket($id);
+my $CustomFields = $TicketObj->QueueObj->CustomFields();
+
+# Now let callbacks have a chance at editing %ARGS
+$m->comp('/Elements/Callback', TicketObj => $TicketObj, CustomFields => $CustomFields, %ARGS);
+
+my @results = ProcessTicketBasics(TicketObj => $TicketObj, ARGSRef => \%ARGS);
+my @cf_results = ProcessTicketCustomFieldUpdates(TicketObj => $TicketObj, ARGSRef => \%ARGS);
+push (@results, @cf_results);
+
+# TODO: display the results, even if we can't display the ticket
+
+unless ($TicketObj->CurrentUserHasRight('ShowTicket')) {
+ Abort("No permission to view ticket");
+}
+
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Ticket/ModifyAll.html b/rt/html/Ticket/ModifyAll.html
new file mode 100644
index 0000000..1163f3f
--- /dev/null
+++ b/rt/html/Ticket/ModifyAll.html
@@ -0,0 +1,158 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Ticket #[_1] Jumbo update: [_2]", $Ticket->Id, $Ticket->Subject) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $Ticket,
+ current_tab => "Ticket/ModifyAll.html?id=".$Ticket->Id,
+ Title => loc("Ticket #[_1] Jumbo update: [_2]", $Ticket->Id, $Ticket->Subject) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM METHOD=POST ACTION="ModifyAll.html" ENCTYPE="multipart/form-data">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Ticket->Id%>">
+
+
+<& /Elements/TitleBoxStart, title => loc('Modify ticket # [_1]', $Ticket->Id), color=> "#993333", width => "100%" &>
+<& Elements/EditBasics, TicketObj => $Ticket &>
+<& Elements/EditCustomFields, TicketObj => $Ticket &>
+<& /Elements/TitleBoxEnd &>
+
+<BR>
+
+<& /Elements/TitleBoxStart, title => loc('Dates'), width => "100%", color => "#663366" &>
+<& Elements/EditDates, TicketObj => $Ticket &>
+<& /Elements/TitleBoxEnd &>
+
+<BR>
+
+
+<& /Elements/TitleBoxStart, title => loc('People'),width => "100%", color=> "#333399" &>
+<& Elements/EditPeople, Ticket => $Ticket, UserField => $UserField, UserString => $UserString, UserOp => $UserOp &>
+<& /Elements/TitleBoxEnd &>
+
+<BR>
+
+<& /Elements/TitleBoxStart, title => loc('Relationships'), color => "#336633"&>
+<& Elements/EditLinks, Ticket => $Ticket &>
+<& /Elements/TitleBoxEnd &>
+
+<BR>
+
+<& /Elements/TitleBoxStart, title => loc('Update ticket') &>
+<table>
+ <tr>
+ <td class="label"><&|/l&>Update Type</&>:</td>
+ <td class="entry">
+ <select name="UpdateType">
+% if ($CanComment) {
+ <option value="private" ><&|/l&>Comments (Not sent to requestors)</&></option>
+% }
+% if ($CanRespond) {
+ <option value="response"><&|/l&>Response to requestors</&></option>
+% }
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Subject</&>:</td>
+ <td class="entry"><input name="UpdateSubject" size=60 value=""></td>
+ </tr>
+ <tr>
+ <td class="label"><&|/l&>Attach</&>:</td>
+ <td class="entry"><input name="UpdateAttachment" type=file></td>
+ </tr>
+ <tr>
+ <td class="labeltop"><&|/l&>Content</&>:</td>
+ <td class="entry"><& /Elements/MessageBox, Name=>"UpdateContent", QuoteTransaction=>$ARGS{QuoteTransaction} &></td>
+ </tr>
+</table>
+<& /Elements/TitleBoxEnd &>
+
+
+<& /Elements/Submit, Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#333399" &>
+</form>
+
+<%INIT>
+
+
+
+my $Ticket = LoadTicket($id);
+
+my $CanRespond = 0;
+my $CanComment = 0;
+
+
+$CanRespond = 1 if ( $Ticket->CurrentUserHasRight('ReplyToTicket') or
+ $Ticket->CurrentUserHasRight('ModifyTicket') );
+
+$CanComment = 1 if ( $Ticket->CurrentUserHasRight('CommentOnTicket') or
+ $Ticket->CurrentUserHasRight('ModifyTicket') );
+
+
+my (@wresults, @results, @dresults, @lresults, @cf_results);
+
+unless ($OnlySearchForPeople) {
+ @wresults = ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @results = ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @cf_results = ProcessTicketCustomFieldUpdates( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @dresults = ProcessTicketDates( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @lresults = ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
+
+ $ARGS{'UpdateContent'} =~ s/\r\n/\n/g;
+
+ if ($ARGS{'UpdateContent'} &&
+ $ARGS{'UpdateContent'} ne '' &&
+ $ARGS{'UpdateContent'} ne "-- \n" .
+ $session{'CurrentUser'}->UserObj->Signature
+ ) {
+ ProcessUpdateMessage(TicketObj => $Ticket,
+ ARGSRef=>\%ARGS,
+ Actions=>\@results);
+ }
+}
+push @results, @wresults;
+push @results, @dresults;
+push @results, @lresults;
+push @results, @cf_results;
+
+# If they've gone and moved the ticket to somewhere they can't see, etc...
+# TODO: display the results, even if we can't display the ticket.
+
+unless ($Ticket->CurrentUserHasRight('ShowTicket')) {
+ Abort("No permission to view ticket");
+}
+
+
+</%INIT>
+
+
+
+<%ARGS>
+$OnlySearchForPeople => undef
+$UserField => undef
+$UserOp => undef
+$UserString => undef
+$id => undef
+</%ARGS>
+
diff --git a/rt/html/Ticket/ModifyDates.html b/rt/html/Ticket/ModifyDates.html
new file mode 100644
index 0000000..3ccae44
--- /dev/null
+++ b/rt/html/Ticket/ModifyDates.html
@@ -0,0 +1,52 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc('Modify dates for #[_1]', $TicketObj->Id) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $TicketObj,
+ current_tab => "Ticket/ModifyDates.html?id=".$TicketObj->Id,
+ Title => loc('Modify dates for #[_1]', $TicketObj->Id) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM METHOD=POST ACTION="ModifyDates.html">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$TicketObj->Id%>">
+<& /Elements/TitleBoxStart, title => loc('Modify dates for ticket # [_1]', $TicketObj->Id), width => "100%", color => "#663366" &>
+
+<& Elements/EditDates, TicketObj => $TicketObj &>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, color => "#663366" &>
+</form>
+
+
+<%INIT>
+
+my $TicketObj = LoadTicket($id);
+my @results = ProcessTicketDates( TicketObj => $TicketObj, ARGSRef => \%ARGS);
+
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Ticket/ModifyLinks.html b/rt/html/Ticket/ModifyLinks.html
new file mode 100644
index 0000000..1d05008
--- /dev/null
+++ b/rt/html/Ticket/ModifyLinks.html
@@ -0,0 +1,54 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Link ticket #[_1]", $Ticket->Id) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $Ticket,
+ current_tab => "Ticket/ModifyLinks.html?id=".$Ticket->Id,
+ Title => loc("Link ticket #[_1]", $Ticket->Id) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<form action="ModifyLinks.html" method="post">
+<input type="hidden" name="id" value="<%$Ticket->id%>">
+
+<& /Elements/TitleBoxStart, title => loc('Edit Relationships'), color => "#336633"&>
+<& Elements/EditLinks, Ticket => $Ticket &>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, color => "#336633", Caption=> loc('Save Changes') &>
+</form>
+
+
+
+
+<%INIT>
+
+my $Ticket = LoadTicket($id);
+my @results = ProcessTicketLinks( TicketObj => $Ticket, ARGSRef => \%ARGS);
+
+</%INIT>
+
+
+<%ARGS>
+$id => undef
+</%ARGS>
diff --git a/rt/html/Ticket/ModifyPeople.html b/rt/html/Ticket/ModifyPeople.html
new file mode 100644
index 0000000..debd27a
--- /dev/null
+++ b/rt/html/Ticket/ModifyPeople.html
@@ -0,0 +1,68 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc('Modify people related to ticket #[_1]', $Ticket->id) &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $Ticket,
+ current_tab => "Ticket/ModifyPeople.html?id=".$Ticket->Id,
+ Title => loc('Modify people related to ticket #[_1]', $Ticket->id) &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM METHOD=POST ACTION="ModifyPeople.html">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Ticket->Id%>">
+<& /Elements/TitleBoxStart, title => loc('Modify people related to ticket #[_1]', $Ticket->Id), width => "100%", color=> "#333399" &>
+<& Elements/EditPeople, Ticket => $Ticket, UserField => $UserField, UserString => $UserString, UserOp => $UserOp, GroupString => $GroupString, GroupOp => $GroupOp, GroupField => $GroupField &>
+<& /Elements/TitleBoxEnd &>
+<& /Elements/Submit, Label => loc('Save Changes'), Caption => loc("If you've updated anything above, be sure to"), color => "#333399" &>
+</form>
+
+<%INIT>
+
+my (@results, @wresults);
+
+my $Ticket = LoadTicket($id);
+
+# if we're trying to search for watchers and nothing else
+unless ($OnlySearchForPeople or $OnlySearchForGroup) {
+ @results = ProcessTicketBasics( TicketObj => $Ticket, ARGSRef => \%ARGS);
+ @wresults = ProcessTicketWatchers( TicketObj => $Ticket, ARGSRef => \%ARGS);
+}
+
+push @results, @wresults;
+</%INIT>
+
+
+
+<%ARGS>
+$OnlySearchForPeople => undef
+$OnlySearchForGroup => undef
+$UserField => undef
+$UserOp => undef
+$UserString => undef
+$GroupField => undef
+$GroupOp => undef
+$GroupString => undef
+$id => undef
+</%ARGS>
+
diff --git a/rt/html/Ticket/Update.html b/rt/html/Ticket/Update.html
new file mode 100644
index 0000000..ad3b217
--- /dev/null
+++ b/rt/html/Ticket/Update.html
@@ -0,0 +1,205 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title &>
+<& /Ticket/Elements/Tabs,
+ Ticket => $TicketObj,
+ Title=> $title &>
+
+<FORM ACTION="Update.html" NAME="TicketUpdate"
+ METHOD=POST enctype="multipart/form-data">
+<input type="hidden" name="QuoteTransaction" value="<% $ARGS{QuoteTransaction} %>">
+<input type="hidden" name="DefaultStatus" value="<% $DefaultStatus %>">
+<input type="hidden" name="Action" value="<% $ARGS{Action} %>">
+<font size=-1>
+
+<TABLE>
+<TR><TD>
+<a href="ModifyPeople.html?id=<%$TicketObj->Id%>"><&|/l&>Ticket watchers</&></A></TD><TD align=right>
+<&|/l&>Requestor</&>:
+</TD><TD>
+<b><% $TicketObj->RequestorAddresses %></b>
+</TD></TR>
+<TR><TD>&nbsp;</TD><TD align=right>
+<&|/l&>Cc</&>:
+</TD><TD>
+<b><% $TicketObj->CcAddresses %></b>
+</TD></TR>
+<TR><TD>&nbsp;</TD><TD align=right>
+<&|/l&>AdminCc</&>:
+</TD><TD>
+<b><% $TicketObj->AdminCcAddresses %></b>
+</TD></TR>
+</TR>
+</TABLE>
+<hr>
+
+<TABLE BORDER=0>
+
+<tr><td align=right><&|/l&>Status</&>:</td>
+<td>
+<& /Elements/SelectStatus, Name=>"Status", Default => $DefaultStatus &>
+<&|/l&>Owner</&>:
+<& /Elements/SelectOwner, Name=>"Owner", Default => ($ARGS{'Owner'} || $TicketObj->OwnerObj->Id()), QueueObj => $TicketObj->QueueObj, TicketObj => $TicketObj &>
+<&|/l&>Worked</&>: <input size=4 name="UpdateTimeWorked" value="<% $ARGS{UpdateTimeWorked}%>"> <&|/l&>minutes</&></td></tr>
+<tr><td align=right><&|/l&>Update Type</&>:</td>
+<td><select name="UpdateType">
+% if ($CanComment) {
+ <option value="private" <%$CommentDefault%>><&|/l&>Comments (Not sent to requestors)</&></option>
+% }
+% if ($CanRespond) {
+ <option value="response" <%$ResponseDefault%>><&|/l&>Response to requestors</&></option>
+% }
+</select>
+</td></tr>
+<tr><td align=right><&|/l&>Subject</&>:</td><td> <input name="UpdateSubject" size=60 value="<% ($ARGS{UpdateSubject}) ? $ARGS{UpdateSubject} : $TicketObj->Subject()%>"></td></tr>
+<tr><td align=right><&|/l&>Cc</&>:</td><td> <input name="UpdateCc" size=60
+value=<% $ARGS{UpdateCc} %>><BR>
+<i><font size=-2>
+<&|/l&>(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)</&></font></i>
+</td></tr>
+<tr><td align=right><&|/l&>Bcc</&>:</td><td> <input name="UpdateBcc" size=60 VALUE="<%$ARGS{UpdateBcc}%>"><BR>
+<i><font size=-2>
+<&|/l&>(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)</&></font></i>
+</td></tr>
+% if (exists $session{'Attachments'}) {
+<TD>
+<&|/l&>Attached file</&>:
+</TD>
+<TD COLSPAN=5>
+<&|/l&>Check box to delete</&><BR>
+% foreach my $attach_name (keys %{$session{'Attachments'}}) {
+<input type="checkbox" name="DeleteAttach-<%$attach_name%>"><%$attach_name%><BR>
+% } # end of foreach
+</TD>
+</TR>
+<TR>
+% } # end of if
+<tr><td align=right><&|/l&>Attach</&>:</td><td><input name="Attach" type="file"><INPUT TYPE=SUBMIT NAME="AddMoreAttach" VALUE="<&|/l&>Add More Files</&>"><input type="hidden" name="UpdateAttach" value="1">
+</td></tr>
+<tr><td align="right" valign="top"><&|/l&>Message</&>:</td><td>
+<& /Elements/Callback, _CallbackName => 'BeforeMessageBox', %ARGS &>
+% if (exists $ARGS{UpdateContent}) {
+% delete $ARGS{'QuoteTransaction'};
+<& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0, %ARGS&>
+% } else {
+<& /Elements/MessageBox, Name=>"UpdateContent", %ARGS &>
+% }
+</td></tr>
+ <INPUT TYPE=HIDDEN NAME=id VALUE="<%$TicketObj->Id%>"><br>
+</table>
+
+
+
+
+<& /Elements/Submit, Name => 'SubmitTicket' &>
+ </FORM>
+
+
+
+<%INIT>
+
+my $CanRespond = 0;
+my $CanComment = 0;
+my $title;
+
+my $TicketObj = LoadTicket($id);
+
+unless($DefaultStatus){
+ $DefaultStatus=($ARGS{'Status'} ||$TicketObj->Status());
+}
+
+if ($DefaultStatus =~ '^new$'){
+ $DefaultStatus='open';
+}
+
+if ($DefaultStatus eq 'resolved') {
+ $title = loc("Resolve ticket #[_1] ([_2])", $TicketObj->id, $TicketObj->Subject);
+} else {
+ $title = loc("Update ticket #[_1] ([_2])", $TicketObj->id, $TicketObj->Subject);
+}
+
+# Things needed in the template - we'll do the processing here, just
+# for the convenience:
+
+my ($CommentDefault, $ResponseDefault);
+if (($Action eq 'Comment') or ($ARGS{'UpdateType'} eq 'private')) {
+ $CommentDefault = "SELECTED";
+} else {
+ $ResponseDefault = "SELECTED";
+}
+
+
+$CanRespond = 1 if ( $TicketObj->CurrentUserHasRight('ReplyToTicket') or
+ $TicketObj->CurrentUserHasRight('ModifyTicket') );
+
+$CanComment = 1 if ( $TicketObj->CurrentUserHasRight('CommentOnTicket') or
+ $TicketObj->CurrentUserHasRight('ModifyTicket') );
+
+
+# {{{ deal with deleting uploaded attachments
+foreach my $key (keys %ARGS) {
+ if ($key =~ m/^DeleteAttach-(.+)$/) {
+ delete $session{'Attachments'}{$1};
+ }
+ $session{'Attachments'} = { %{$session{'Attachments'} || {}} };
+}
+
+# {{{ store the uploaded attachment in session
+if ($ARGS{'Attach'}) { # attachment?
+ $session{'Attachments'} = {} unless defined $session{'Attachments'};
+
+ my $subject = "$ARGS{'Attach'}";
+ # since CGI.pm deutf8izes the magic field, we need to add it back.
+ Encode::_utf8_on($subject);
+ # strip leading directories
+ $subject =~ s#^.*[\\/]##;
+
+ my $attachment = MakeMIMEEntity(
+ Subject => $subject,
+ Body => "",
+ AttachmentFieldName => 'Attach'
+ );
+
+ $session{'Attachments'} = { %{$session{'Attachments'} || {}},
+ $ARGS{'Attach'} => $attachment };
+}
+# }}}
+
+# delete temporary storage entry to make WebUI clean
+unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
+ delete $session{'Attachments'};
+}
+# }}}
+
+if ( exists $ARGS{SubmitTicket} ) {
+ $m->comp('Display.html', TicketObj => $TicketObj, %ARGS);
+ return;
+}
+</%INIT>
+
+<%ARGS>
+$id => undef
+$Action => undef
+$DefaultStatus => undef
+</%ARGS>
diff --git a/rt/html/User/Delegation.html b/rt/html/User/Delegation.html
new file mode 100644
index 0000000..c036f78
--- /dev/null
+++ b/rt/html/User/Delegation.html
@@ -0,0 +1,83 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => loc("Delegate rights") &>
+<& /User/Elements/Tabs,
+ current_tab => 'User/Delegation.html',
+ Title => loc("Delegate rights") &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<form method="post">
+<& Elements/DelegateRights, personalgroups => $personalgroups, objects => $objects, ObjectType => 'RT::System' &>
+<& Elements/DelegateRights, personalgroups => $personalgroups, objects => $objects, ObjectType => 'RT::Queue' &>
+<& Elements/DelegateRights, personalgroups => $personalgroups, objects => $objects, ObjectType => 'RT::Group' &>
+
+<& /Elements/Submit &>
+</form>
+<%INIT>
+
+my (@results, $arg);
+foreach $arg (keys %ARGS) {
+ next unless ($arg =~ /^Delegate-Existing-ACE-(\d+)-to-(\d+)-as-(\d+)$/);
+ my $parent = $1;
+ my $principal = $2;
+ my $delegation = $3;
+ unless ($ARGS{"Delegate-ACE-$1-to-$2"}) {
+ my $ace_to_del = RT::ACE->new($session{'CurrentUser'});
+ $ace_to_del->Load($delegation);
+ my ($delval, $delmsg) = $ace_to_del->Delete();
+ push (@results, $delmsg);
+ }
+}
+
+foreach $arg (keys %ARGS) {
+ next unless ($arg =~ /^Delegate-ACE-(\d+)-to-(\d+)$/);
+ my $parent = $1;
+ my $principal = $2;
+ # if we already delegate it, we just don't care
+ next if (grep /^Delegate-Existing-ACE-$parent-to-$principal-/, keys %ARGS);
+ my $ace = RT::ACE->new($session{'CurrentUser'});
+ $ace->Load($1);
+ unless ($ace->Id) {
+ push (@results, loc('Right not found'));
+ next;
+ }
+ my ($delid, $delmsg) = $ace->Delegate(PrincipalId => $principal);
+ push (@results, $delmsg);
+}
+
+my $personalgroups = RT::Groups->new($session{'CurrentUser'});
+$personalgroups->LimitToPersonalGroupsFor($session{'CurrentUser'}->PrincipalId);
+
+my $objects;
+my $acl = RT::ACL->new ($session{'CurrentUser'});
+$acl->ExcludeDelegatedRights();
+$acl->LimitToPrincipal(Id => $session{'CurrentUser'}->PrincipalId,
+ IncludeGroupMembership => 1
+ );
+
+while(my $right = $acl->Next) {
+ push @{$objects->{$right->ObjectType}{$right->ObjectId}},$right;
+}
+</%INIT>
diff --git a/rt/html/User/Elements/DelegateRights b/rt/html/User/Elements/DelegateRights
new file mode 100644
index 0000000..7ff8328
--- /dev/null
+++ b/rt/html/User/Elements/DelegateRights
@@ -0,0 +1,85 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<h2><%$sectionheading%></h2>
+<%perl>
+
+foreach my $object (keys %{$objects->{$ObjectType}}) {
+unless ($ObjectType eq 'RT::System') {
+my $object_obj = @{$objects->{$ObjectType}{$object}}[0]->Object;
+
+</%perl>
+<h3><% $object_obj->Name %></h3>
+% }
+<table width="100%" border="0" cellspacing="0" cellpadding="3">
+<tr>
+ <th width=15%><&|/l&>Personal groups:</&></th>
+% while (my $pg = $personalgroups->Next) {
+<th><%$pg->Name%></th>
+% }
+</tr>
+<%perl>
+my $i;
+foreach my $right (@{$objects->{$ObjectType}{$object}}) {
+my $delegations = RT::ACL->new($session{'CurrentUser'});
+$delegations->DelegatedBy( Id => $session{'CurrentUser'}->PrincipalId);
+$delegations->DelegatedFrom ( Id => $right->Id);
+
+my $del_hash = {};
+while ( my $delegation = $delegations->Next) {
+ $del_hash->{$delegation->PrincipalId} = $delegation;
+}
+</%perl>
+% $i++;
+%
+<tr class="<%($i%2) && 'oddline'%>">
+<td>
+<% loc($right->RightName) %><br>
+<div align=right><font size="-2" color="#999999"><&|/l, $right->PrincipalObj->Object->SelfDescription &>as granted to [_1]</&></font></div>
+ </td>
+% while (my $pg = $personalgroups->Next) {
+<td align=center>
+ <input name="Delegate-ACE-<% $right->Id %>-to-<% $pg->PrincipalId%>" type=checkbox <%$ del_hash->{$pg->PrincipalId} && 'CHECKED' %>>
+% if ( $del_hash->{$pg->PrincipalId}) {
+<input type=hidden name="Delegate-Existing-ACE-<% $right->Id %>-to-<% $pg->PrincipalId%>-as-<%$del_hash->{$pg->PrincipalId}->Id%>">
+% }
+</td>
+% }
+<td>&nbsp;</td>
+</tr>
+%}
+</table>
+% }
+<%init>
+
+my $sectionheading = loc("[_1] rights", loc($ObjectType =~ /^RT::(.*)$/));
+# 'System' # loc
+# 'Group' # loc
+# 'Queue' # loc
+
+</%init>
+<%args>
+$ObjectType => undef
+$objects => undef
+$personalgroups => undef
+</%args>
diff --git a/rt/html/User/Elements/GroupTabs b/rt/html/User/Elements/GroupTabs
new file mode 100644
index 0000000..89d7125
--- /dev/null
+++ b/rt/html/User/Elements/GroupTabs
@@ -0,0 +1,60 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /User/Elements/Tabs,
+ subtabs => $tabs,
+ current_tab => 'User/Groups/',
+ current_subtab => $current_subtab,
+ Title => $Title &>
+
+<%INIT>
+my $tabs;
+if ( $GroupObj and $GroupObj->id ) {
+ $tabs->{"this"} = {
+ title => $GroupObj->Name,
+ path => "User/Groups/Modify.html?id=" . $GroupObj->id,
+ subtabs => {
+ Basics => { title => loc('Basics'),
+ path => "User/Groups/Modify.html?id=" . $GroupObj->id
+ },
+
+ Members => { title => loc('Members'),
+ path => "User/Groups/Members.html?id=" . $GroupObj->id
+ },
+
+ } };
+ $tabs->{'this'}->{'current_subtab'} = $current_subtab;
+ $current_subtab = "User/Groups/Modify.html?id=" . $GroupObj->id,
+}
+$tabs->{"A"} = { title => loc('Select group'),
+ path => "User/Groups/index.html" };
+$tabs->{"B"} = { title => loc('New group'),
+ path => "User/Groups/Modify.html?Create=1",
+ separator => 1 };
+
+</%INIT>
+<%ARGS>
+$GroupObj => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/User/Elements/Tabs b/rt/html/User/Elements/Tabs
new file mode 100644
index 0000000..195cf1c
--- /dev/null
+++ b/rt/html/User/Elements/Tabs
@@ -0,0 +1,56 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Tabs,
+ tabs => $tabs,
+ current_toptab => 'User/Prefs.html',
+ current_tab => $current_tab,
+ Title => $Title &>
+
+<%INIT>
+ my $tabs = { a => { title => loc('About me'),
+ path => 'User/Prefs.html',
+ },
+ g => { title => loc('Personal Groups'),
+ path => 'User/Groups/',
+ },
+ h => { title => loc('Delegation'),
+ path => 'User/Delegation.html',
+ },
+ };
+
+ foreach my $tab (sort keys %{$tabs}) {
+ if ($tabs->{$tab}->{'path'} eq $current_tab) {
+ $tabs->{$tab}->{"subtabs"} = $subtabs;
+ $tabs->{$tab}->{"current_subtab"} = $current_subtab;
+ }
+ }
+</%INIT>
+
+
+<%ARGS>
+$subtabs => undef
+$current_tab => undef
+$current_subtab => undef
+$Title => undef
+</%ARGS>
diff --git a/rt/html/User/Groups/Members.html b/rt/html/User/Groups/Members.html
new file mode 100644
index 0000000..db83b8c
--- /dev/null
+++ b/rt/html/User/Groups/Members.html
@@ -0,0 +1,136 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title &>
+<& /User/Elements/GroupTabs,
+ GroupObj => $Group,
+ current_subtab => "User/Groups/Members.html?id=".$Group->id,
+ Title => $title &>
+<& /Elements/ListActions, actions => \@results &>
+
+
+
+<FORM ACTION="<%$RT::WebPath%>/User/Groups/Members.html" METHOD="POST">
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Group->Id%>">
+<TABLE WIDTH="100%">
+<TR>
+<TD>
+<&|/l&>Add members</&>
+</TD>
+<TD>
+<&|/l&>Current members</&>
+</TD>
+</TR>
+
+<TR>
+<TD VALIGN=TOP>
+<& /Admin/Elements/SelectNewGroupMembers, Name => "AddMembers", Group => $Group &>
+</TD>
+<TD VALIGN=TOP>
+
+% if ($Group->MembersObj->Count == 0 ) {
+<i><&|/l&>(No members)</&></i>
+% } else {
+<i><&|/l&>(Check box to delete)</&></i>
+<br>
+<br>
+<&|/l&>Users</&>
+% my $UserMembers = $Group->MembersObj;
+% $UserMembers->LimitToUsers();
+<UL>
+% while (my $member = $UserMembers->Next()) {
+<LI><INPUT TYPE=CHECKBOX Name="DeleteMember-<%$member->MemberId%>">
+<%$member->MemberObj->Object->Name%> (<%$member->MemberObj->Object->RealName%>)
+% }
+</ul>
+<&|/l&>Groups</&>
+<ul>
+% my $GroupMembers = $Group->MembersObj;
+% $GroupMembers->LimitToGroups();
+% while (my $member = $GroupMembers->Next()) {
+<LI><INPUT TYPE=CHECKBOX Name="DeleteMember-<%$member->MemberId%>">
+<%$member->MemberObj->Object->Name%>
+% }
+% }
+</UL>
+</TD>
+</TR>
+</TABLE>
+<& /Elements/Submit &>
+</form>
+
+
+<%INIT>
+
+my $Group = new RT::Group($session{'CurrentUser'});
+$Group->Load($id) ;
+
+unless ($Group->id) {
+ Abort(loc('Could not load group'));
+}
+
+my (@results);
+
+foreach my $key (keys %ARGS) {
+
+if ($key =~ /^DeleteMember-(\d+)$/) {
+ my $mem_id = $1;
+ my ($val,$msg) = $Group->DeleteMember($mem_id);
+ push (@results, $msg);
+}
+}
+
+# Make sure AddMembers is always an array
+my @AddMembersUsers = (ref $AddMembersUsers eq 'ARRAY') ? @{$AddMembersUsers} : ($AddMembersUsers);
+my @AddMembersGroups = (ref $AddMembersGroups eq 'ARRAY') ? @{$AddMembersGroups} : ($AddMembersGroups);
+
+foreach my $member (@AddMembersUsers, @AddMembersGroups) {
+ next unless ($member);
+
+ my $principal;
+
+ if ($member =~ /^Group-(\d+)$/) {
+ $principal = RT::Group->new($session{'CurrentUser'});
+ $principal->Load($1);
+ } elsif ($member =~ /^User-(\d+)$/) {
+ $principal = RT::User->new($session{'CurrentUser'});
+ $principal->Load($1);
+ } else {
+ next;
+ }
+
+
+ my ($val, $msg) = $Group->AddMember($principal->PrincipalId);
+ push (@results, $msg);
+}
+
+
+my $title = loc('Editing membership for personal group [_1]', $Group->Name);
+
+</%INIT>
+
+<%ARGS>
+$AddMembersUsers => undef
+$AddMembersGroups => undef
+$id => undef
+</%ARGS>
diff --git a/rt/html/User/Groups/Modify.html b/rt/html/User/Groups/Modify.html
new file mode 100644
index 0000000..f731e1a
--- /dev/null
+++ b/rt/html/User/Groups/Modify.html
@@ -0,0 +1,133 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title &>
+
+<& /User/Elements/GroupTabs,
+ GroupObj => $Group,
+ current_subtab => $current_tab,
+ Title => $title &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+
+<FORM ACTION="<%$RT::WebPath%>/User/Groups/Modify.html" METHOD=POST>
+
+%unless ($Group->Id) {
+<INPUT TYPE=HIDDEN NAME=id VALUE="new">
+% } else {
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$Group->Id%>">
+% }
+<TABLE>
+<TR><TD ALIGN=RIGHT>
+<&|/l&>Name</&>:
+</TD>
+<TD><INPUT name="Name" value="<%$Group->Name%>"></TD>
+</TR><TR>
+<TD ALIGN=RIGHT>
+<&|/l&>Description</&>:</TD><TD COLSPAN=3><INPUT name="Description" value="<%$Group->Description%>" size=60></TD>
+</TR><TR>
+<TD COLSPAN=2>
+<INPUT TYPE=HIDDEN NAME="SetEnabled" VALUE="1">
+<INPUT TYPE=CHECKBOX NAME="Enabled" VALUE="1" <%$EnabledChecked%>> <&|/l&>Enabled (Unchecking this box disables this group)</&><BR>
+</TR>
+</TABLE>
+<& /Elements/Submit, Caption => loc("Be sure to save your changes"), Reset => 1 &>
+</form>
+<%INIT>
+
+my $current_tab;
+my ($title, @results, $Disabled, $EnabledChecked);
+
+my $Group = RT::Group->new($session{'CurrentUser'});
+
+if ($Create) {
+ $current_tab = 'User/Groups/Modify.html?Create=1';
+ $title = loc("Create a new personal group");
+}
+else {
+ if ( $id eq 'new' ) {
+
+ my ( $id, $msg ) = $Group->CreatePersonalGroup(
+ Name => "$Name",
+ PrincipalId => $session{'CurrentUser'}->PrincipalId
+ );
+ unless ($id) {
+ Abort( loc("Could not create group") );
+ }
+ $id = $Group->Id;
+ }
+ else {
+ $Group->Load($id) || Abort( loc('Could not load group') );
+ }
+
+ if ($id) {
+ $title = loc( "Modify the group [_1]", $Group->Name );
+
+ }
+
+ # If the create failed
+ else {
+ $title = loc("Create a new personal group");
+ $Create = 1;
+ }
+
+ $current_tab = 'User/Groups/Modify.html?id=' . $Group->Id;
+}
+
+if ($id) {
+
+ my @fields = qw(Description Name );
+ my @fieldresults = UpdateRecordObject ( AttributesRef => \@fields,
+ Object => $Group,
+ ARGSRef => \%ARGS );
+ push (@results,@fieldresults);
+}
+
+#we're asking about enabled on the web page but really care about disabled.
+if ($Enabled == 1) {
+ $Disabled = 0;
+}
+else {
+ $Disabled = 1;
+}
+if ( ($SetEnabled) and ( $Disabled != $Group->Disabled) ) {
+ my ($code, $msg) = $Group->SetDisabled($Disabled);
+ push @results, loc('Enabled status [_1]', loc_fuzzy($msg));
+}
+
+unless ($Group->Disabled()) {
+ $EnabledChecked ="CHECKED";
+}
+
+</%INIT>
+
+
+<%ARGS>
+$Create => undef
+$Name => undef
+$Description => undef
+$SetEnabled => undef
+$Enabled => undef
+$id => undef
+</%ARGS>
diff --git a/rt/html/User/Groups/index.html b/rt/html/User/Groups/index.html
new file mode 100644
index 0000000..12b43b4
--- /dev/null
+++ b/rt/html/User/Groups/index.html
@@ -0,0 +1,43 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title => $title &>
+<& /User/Elements/GroupTabs,
+ current_subtab => 'User/Groups/index.html',
+ Title => $title &>
+
+<&|/l&>Personal groups</&>:<BR>
+<UL>
+%while ( my $Group = $Groups->Next) {
+<LI><A HREF="Modify.html?id=<%$Group->id%>"><%$Group->Name || loc('(empty)')%></a><BR>
+%}
+</UL>
+
+<%INIT>
+my $Groups = RT::Groups->new($session{'CurrentUser'});
+$Groups->LimitToPersonalGroupsFor($session{'CurrentUser'}->PrincipalId());
+my $title = loc('Personal groups');
+
+</%INIT>
+<%ARGS>
+</%ARGS>
diff --git a/rt/html/User/Prefs.html b/rt/html/User/Prefs.html
new file mode 100644
index 0000000..c2746a3
--- /dev/null
+++ b/rt/html/User/Prefs.html
@@ -0,0 +1,256 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title=>loc("Preferences") &>
+<& /User/Elements/Tabs,
+ current_tab => 'User/Prefs.html',
+ Title=>loc("Preferences") &>
+
+<& /Elements/ListActions, actions => \@results &>
+
+<FORM ACTION="<%$RT::WebPath%>/User/Prefs.html" METHOD=POST>
+<INPUT TYPE=HIDDEN NAME=id VALUE="<%$UserObj->Id%>">
+
+<TABLE WIDTH=100% BORDER=0>
+<TR>
+
+<TD VALIGN=TOP ROWSPAN=2>
+<& /Elements/TitleBoxStart, title => loc('Identity') &>
+
+<input type=hidden name="Name" value="<%$UserObj->Name%>">
+<table callspacing=0 cellpadding=0>
+ <tr>
+ <td class=label><&|/l&>Email</&>: </td>
+ <td class=value><input name="EmailAddress" value="<%$UserObj->EmailAddress%>"></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Real Name</&>:</td>
+ <td class=value><input name="RealName" value="<%$UserObj->RealName%>"></td> </tr>
+ <tr>
+ <td class=label><&|/l&>Nickname</&>:</td>
+ <td class=value><input name="NickName" value="<%$UserObj->NickName%>"></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Language</&>:</td>
+ <td class=value><& /Elements/SelectLang, Name => 'Lang', Default => $UserObj->Lang &></td>
+ </tr>
+</table>
+<& /Elements/TitleBoxEnd &>
+<br>
+<& /Elements/TitleBoxStart, title => loc('Phone numbers') &>
+<table callspacing=0 cellpadding=0>
+ <tr>
+ <td class=label><&|/l&>Residence</&>:</td>
+ <td class=value><input name="HomePhone" value="<%$UserObj->HomePhone%>" size=13></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Work</&>:</td>
+ <td class=value><input name="WorkPhone" value="<%$UserObj->WorkPhone%>" size=13></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Mobile</&>:</td>
+ <td class=value><input name="MobilePhone" value="<%$UserObj->MobilePhone%>" size=13></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Pager</&>:</td>
+ <td class=value><input name="PagerPhone" value="<%$UserObj->PagerPhone%>" size=13></td>
+ </tr>
+</table>
+<& /Elements/TitleBoxEnd &>
+</TD>
+<TD VALIGN=TOP>
+% unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) {
+<& /Elements/TitleBoxStart, title => loc('Password') &>
+<TABLE>
+<TR>
+<TD class=label>
+<&|/l&>New Password</&>:
+</TD>
+<TD class=value>
+<input type=password name="Pass1">
+</TD>
+</TR>
+<TR><TD class=label>
+<&|/l&>Retype Password</&>:
+</TD>
+<TD class=value>
+<input type=password name="Pass2">
+</TD>
+</TR>
+</TABLE>
+<& /Elements/TitleBoxEnd &>
+% }
+</TD>
+<TR>
+
+<TD VALIGN=TOP>
+<& /Elements/TitleBoxStart, title => loc('Location') &>
+<table callspacing=0 cellpadding=0>
+ <tr>
+ <td class=label><&|/l&>Organization</&>:</td>
+ <td class=value><input name="Organization" value="<%$UserObj->Organization%>"></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Address1</&>:</td>
+ <td class=value><input name="Address1" value="<%$UserObj->Address1%>"></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Address2</&>:</td>
+ <td class=value><input name="Address2" value="<%$UserObj->Address2%>"></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>City</&>:</td>
+ <td><input name="City" value="<%$UserObj->City%>" size=14></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>State</&>:</td>
+ <td class=value><input name="State" value="<%$UserObj->State%>" size=3></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Zip</&>:</td>
+ <td class=value><input name="Zip" value="<%$UserObj->Zip%>" size=9></td>
+ </tr>
+ <tr>
+ <td class=label><&|/l&>Country</&>:</td>
+ <td class=value><input name="Country" value="<%$UserObj->Country%>"></td>
+ </tr>
+</table>
+<& /Elements/TitleBoxEnd &>
+</TD>
+</TR>
+<TR>
+
+
+
+<TD COLSPAN=2 VALIGN=TOP>
+%if ($UserObj->Privileged) {
+<BR>
+<& /Elements/TitleBoxStart, title => loc('Signature') &>
+<TEXTAREA COLS=80 ROWS=5 name="Signature" WRAP=HARD>
+<%$UserObj->Signature%></TEXTAREA>
+<& /Elements/TitleBoxEnd &>
+% }
+
+</TD>
+
+</TR>
+</TABLE>
+
+
+<& /Elements/Submit &>
+</form>
+
+
+<%INIT>
+
+my $UserObj = new RT::User($session{'CurrentUser'});
+my ($title, $PrivilegedChecked, $EnabledChecked, $Disabled, $result, @results);
+
+my ($val, $msg);
+
+
+ $UserObj->Load($id) || $UserObj->Load($Name) || Abort("Couldn't load user '$Name'");
+ $val = $UserObj->Id();
+
+
+
+
+
+
+# If we have a user to modify, lets try.
+if ($UserObj->Id) {
+
+ my @fields = qw(Name Comments Signature EmailAddress FreeformContactInfo
+ Organization RealName NickName Lang EmailEncoding WebEncoding
+ ExternalContactInfoId ContactInfoSystem Gecos ExternalAuthId
+ AuthSystem HomePhone WorkPhone MobilePhone PagerPhone Address1
+ Address2 City State Zip Country Lang
+ );
+
+ my @fieldresults = UpdateRecordObject ( AttributesRef => \@fields,
+ Object => $UserObj,
+ ARGSRef => \%ARGS );
+ $session{'CurrentUser'}->LanguageHandle($Lang) if $Lang;
+ push (@results,@fieldresults);
+
+
+# {{{ Deal with special fields: Privileged, Enabled and Password
+if ( ($SetPrivileged) and ( $Privileged != $UserObj->Privileged) ) {
+my ($code, $msg) = $UserObj->SetPrivileged($Privileged);
+ push @results, loc('Privileged status: [_1]', loc_fuzzy($msg));
+}
+
+
+
+#TODO: make this report errors properly
+if ((defined $Pass1) and ($Pass1 ne '') and ($Pass1 eq $Pass2) and (!$UserObj->IsPassword($Pass1))) {
+ my ($code, $msg);
+ ($code, $msg) = $UserObj->SetPassword($Pass1);
+ push @results, loc('Password: [_1]', loc_fuzzy($msg));
+} elsif ( $Pass1 && ($Pass1 ne $Pass2)) {
+ push @results, loc("Passwords do not match. Your password has not been changed");
+}
+
+# }}}
+}
+
+
+</%INIT>
+
+
+<%ARGS>
+$id => $session{'CurrentUser'}->Id
+$Name => undef
+$Comments => undef
+$Signature => undef
+$EmailAddress => undef
+$FreeformContactInfo => undef
+$Organization => undef
+$RealName => undef
+$NickName => undef
+$Privileged => undef
+$SetPrivileged => undef
+$Enabled => undef
+$SetEnabled => undef
+$Lang => undef
+$EmailEncoding => undef
+$WebEncoding => undef
+$ExternalContactInfoId => undef
+$ContactInfoSystem => undef
+$Gecos => undef
+$ExternalAuthId => undef
+$AuthSystem => undef
+$HomePhone => undef
+$WorkPhone => undef
+$MobilePhone => undef
+$PagerPhone => undef
+$Address1 => undef
+$Address2 => undef
+$City => undef
+$State => undef
+$Zip => undef
+$Country => undef
+$Pass1 => undef
+$Pass2=> undef
+$Create=> undef
+</%ARGS>
diff --git a/rt/html/autohandler b/rt/html/autohandler
new file mode 100644
index 0000000..b2a407a
--- /dev/null
+++ b/rt/html/autohandler
@@ -0,0 +1,210 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<%INIT>
+
+# Roll back any dangling transactions from a previous failed connection
+$RT::Handle->ForceRollback() if $RT::Handle->TransactionDepth;
+
+
+local *session unless $m->is_subrequest; # avoid reentrancy, as suggested by masonbook
+
+# Disable AutoFlush using an attribute
+if ($m->request_comp->attr_exists('AutoFlush')) {
+ $m->autoflush($m->request_comp->attr('AutoFlush'));
+}
+
+%ARGS = map {
+ # if they've passed multiple values, they'll be an array. if they've
+ # passed just one, a scalar whatever they are, mark them as utf8
+ my $type = ref($_);
+ (!$type)
+ ? Encode::decode(utf8 => $_, Encode::FB_PERLQQ) :
+ ($type eq 'ARRAY')
+ ? [ map { ref($_) ? $_ : Encode::decode(utf8 => $_, Encode::FB_PERLQQ) } @$_ ] :
+ ($type eq 'HASH')
+ ? { map { ref($_) ? $_ : Encode::decode(utf8 => $_, Encode::FB_PERLQQ) } %$_ } : $_
+ } %ARGS;
+
+if ($ARGS{'Debug'}) {
+ require Time::HiRes;
+ $m->{'rt_base_time'} = [Time::HiRes::gettimeofday()];
+
+}
+else {
+ $m->{'rt_base_time'} = time;
+}
+$m->comp('/Elements/SetupSessionCookie', %ARGS);
+
+unless ($session{'CurrentUser'} && $session{'CurrentUser'}->Id) {
+ $session{'CurrentUser'} = RT::CurrentUser->new();
+}
+
+# Set the proper encoding for the current language handle
+$r->content_type("text/html; charset=utf-8");
+
+# If it's a noauth file, don't ask for auth.
+if ($m->base_comp->path =~ '^/+NoAuth/' ||
+ $m->base_comp->path =~ '^/+REST/\d+\.\d+/NoAuth/')
+{
+ $m->call_next(%ARGS);
+ $m->abort();
+}
+
+# If RT is configured for external auth, let's go through and get REMOTE_USER
+elsif ( $RT::WebExternalAuth ) {
+
+ # do we actually have a REMOTE_USER equivlent?
+ if ( RT::Interface::Web::WebCanonicalizeInfo() ) {
+
+ my $orig_user = $user;
+
+ $user = RT::Interface::Web::WebCanonicalizeInfo();
+ $session{'CurrentUser'} = RT::CurrentUser->new();
+ my $load_method = $RT::WebExternalGecos ? 'LoadByGecos' : 'Load';
+
+ if ($^O eq 'MSWin32' and $RT::WebExternalGecos) {
+ my $NodeName = Win32::NodeName();
+ $user =~ s/^\Q$NodeName\E\\//i;
+ }
+
+ $session{'CurrentUser'}->$load_method($user);
+
+ if ($RT::WebExternalAuto and !$session{'CurrentUser'}->Id() ) {
+ # Create users on-the-fly
+
+ my $UserObj = RT::User->new(RT::CurrentUser->new('root'));
+
+ my ($val, $msg) = $UserObj->Create(
+ %{ref($RT::AutoCreate) ? $RT::AutoCreate : {}},
+ Name => $user,
+ Gecos => $user,
+ );
+
+ if ($val) {
+
+ # now get user specific information, to better create our user.
+ my $new_user_info = RT::Interface::Web::WebExternalAutoInfo($user);
+
+ # set the attributes that have been defined.
+ # FIXME: this is a horrible kludge. I'm sure there's something cleaner
+ foreach my $attribute ('Name', 'Comments', 'Signature', 'EmailAddress',
+ 'PagerEmailAddress', 'FreeformContactInfo',
+ 'Organization', 'Disabled', 'Privileged',
+ 'RealName', 'NickName', 'Lang', 'EmailEncoding',
+ 'WebEncoding', 'ExternalContactInfoId',
+ 'ContactInfoSystem', 'ExternalAuthId', 'Gecos',
+ 'HomePhone', 'WorkPhone', 'MobilePhone',
+ 'PagerPhone', 'Address1', 'Address2', 'City',
+ 'State', 'Zip', 'Country') {
+
+ my $method = "Set$attribute";
+ $UserObj->$method($new_user_info->{$attribute})
+ if( defined $new_user_info->{$attribute} );
+ }
+ $session{'CurrentUser'}->Load($user);
+ }
+ else {
+ # we failed to successfully create the user. abort abort abort.
+ delete $session{'CurrentUser'};
+ $m->abort() unless $RT::WebFallbackToInternalAuth;
+ $m->comp('/Elements/Login', %ARGS,
+ Error=> loc('Cannot create user: [_1]', $msg));
+ }
+ }
+
+ unless ( $session{'CurrentUser'}->Id() ) {
+ delete $session{'CurrentUser'};
+ $user = $orig_user;
+
+ if ( $RT::WebExternalOnly ) {
+ $m->comp('/Elements/Login', %ARGS,
+ Error=> loc('You are not an authorized user'));
+ $m->abort();
+ }
+ }
+ }
+ elsif ($RT::WebFallbackToInternalAuth) {
+ unless (defined($session{'CurrentUser'})) {
+ $m->comp('/Elements/Login', %ARGS,
+ Error=> loc('XXX CHANGEME You are not an authorized user'));
+ $m->abort();
+ }
+ } else {
+ # WebExternalAuth is set, but we don't have a REMOTE_USER. abort
+ delete $session{'CurrentUser'} if defined $session{'CurrentUser'};
+ }
+}
+
+delete $session{'CurrentUser'}
+ unless $session{'CurrentUser'} and defined $session{'CurrentUser'}->Id;
+
+
+# Process per-page authentication callbacks
+$m->comp('/Elements/Callback', %ARGS, _CallbackName => 'Auth');
+
+# If the user is logging in, let's authenticate
+if (!$session{'CurrentUser'} && defined ($user) && defined ($pass) ){
+ $session{'CurrentUser'} = RT::CurrentUser->new();
+ $session{'CurrentUser'}->Load($user);
+
+ if (!$session{'CurrentUser'}->id() ||
+ !$session{'CurrentUser'}->IsPassword($pass))
+ {
+ delete $session{'CurrentUser'};
+ $m->comp('/Elements/Login', %ARGS,
+ Error => loc('Your username or password is incorrect'));
+ $m->abort();
+ }
+}
+
+# If we've got credentials, let's serve the file up.
+if ( (defined $session{'CurrentUser'}) and
+ ( $session{'CurrentUser'}->Id) ) {
+
+ # Process per-page global callbacks
+ $m->comp('/Elements/Callback', %ARGS);
+
+ # If the user isn't privileged, they can only see SelfService
+ if ((! $session{'CurrentUser'}->Privileged) and
+ ($m->base_comp->path !~ '^(/+)SelfService/') ) {
+ $m->comp('/SelfService/index.html');
+ $m->abort();
+ }
+ else {
+ $m->call_next(%ARGS);
+ }
+}
+
+# If we have no credentials
+else {
+ $m->comp('/Elements/Login', %ARGS);
+ $m->abort();
+}
+</%INIT>
+<& /Elements/Footer, %ARGS &>
+<%ARGS>
+$user => undef
+$pass => undef
+$menu => undef
+</%ARGS>
diff --git a/rt/html/index.html b/rt/html/index.html
new file mode 100644
index 0000000..798972d
--- /dev/null
+++ b/rt/html/index.html
@@ -0,0 +1,85 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+<& /Elements/Header, Title=>loc("RT at a glance"), Refresh => $session{'home_refresh_interval'} &>
+<& /Elements/Tabs,
+ current_toptab => '',
+ Title=>loc("RT at a glance") &>
+<TABLE BORDER=0 WIDTH=100%>
+<TR VALIGN=TOP>
+<TD WIDTH=70%>
+<& /Elements/MyTickets &>
+<BR>
+<& /Elements/MyRequests &>
+</TD>
+<TD>
+<& /Elements/Quicksearch &>
+<BR>
+<form method=get action="index.html">
+<& /Elements/Refresh, Name => 'HomeRefreshInterval', Default => $session {'home_refresh_interval'} &>
+<div align=right><input type=submit value="<&|/l&>Go!</&>"></div>
+</form>
+</TD>
+</TR>
+</TABLE>
+<%init>
+if ( $ARGS{'q'} ) {
+ my $query = $ARGS{'q'};
+
+ if ( $query =~ m/^\s*(\d+)\s*$/ ) {
+ $m->redirect("$RT::WebPath/Ticket/Display.html?id=$1");
+ }
+
+ $session{'tickets'} = RT::Tickets->new( $session{'CurrentUser'} );
+
+ if ( $query =~ m/\@/ ) {
+ $session{'tickets'}->LimitWatcher( VALUE => $query,
+ TYPE => 'Requestor',
+ OPERATOR => '=', );
+ $m->redirect("$RT::WebPath/Search/Listing.html");
+ }
+
+ #
+ # Any search on queue name or subject will be for new/open tickets
+ # only.
+ #
+ $session{'tickets'}->LimitStatus( VALUE => $_,
+ OPERATOR => '=', ) for qw(open new);
+
+ my $queue = RT::Queue->new( $session{'CurrentUser'} );
+ if ( $queue->Load($query) && $queue->Id ) {
+ $session{'tickets'}->LimitQueue( VALUE => $queue->Id,
+ OPERATOR => '=', );
+ $m->redirect("$RT::WebPath/Search/Listing.html");
+ }
+ $session{'tickets'}->LimitSubject( VALUE => $query,
+ OPERATOR => 'LIKE' );
+
+ $m->redirect("$RT::WebPath/Search/Listing.html");
+}
+
+if ($ARGS{'HomeRefreshInterval'}) {
+ $session{'home_refresh_interval'} = $ARGS{'HomeRefreshInterval'};
+}
+
+</%init>
diff --git a/rt/html/l b/rt/html/l
new file mode 100644
index 0000000..712e38d
--- /dev/null
+++ b/rt/html/l
@@ -0,0 +1,26 @@
+%# BEGIN LICENSE BLOCK
+%#
+%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%#
+%# (Except where explictly superceded by other copyright notices)
+%#
+%# 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.
+%#
+%# Unless otherwise specified, all modifications, corrections or
+%# extensions to this work which alter its source code become the
+%# property of Best Practical Solutions, LLC when submitted for
+%# inclusion in the work.
+%#
+%#
+%# END LICENSE BLOCK
+% my $hand = ($session{'CurrentUser'} ||= RT::CurrentUser->new)->LanguageHandle;
+% $m->print($hand->maketext($m->content,@_));
+% return(1);
diff --git a/rt/install-sh b/rt/install-sh
new file mode 100644
index 0000000..11870f1
--- /dev/null
+++ b/rt/install-sh
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. M.I.T. makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd="$cpprog"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ :
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ chmodcmd=""
+ else
+ instcmd=$mkdirprog
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f "$src" ] || [ -d "$src" ]
+ then
+ :
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ :
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ :
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+ '
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ :
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ :
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else :;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi &&
+
+# Now rename the file to the real destination.
+
+ $doit $rmcmd -f $dstdir/$dstfile &&
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm
new file mode 100644
index 0000000..7e941a2
--- /dev/null
+++ b/rt/lib/RT.pm
@@ -0,0 +1,323 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2002 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+package RT;
+use strict;
+use RT::I18N;
+use RT::CurrentUser;
+use RT::System;
+
+use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
+ $CORE_CONFIG_FILE
+ $SITE_CONFIG_FILE
+ $VENDOR_CONFIG_FILE
+ $BasePath
+ $EtcPath
+ $VarPath
+ $LocalPath
+ $LocalEtcPath
+ $LocalLexiconPath
+ $LogDir
+ $MasonComponentRoot
+ $MasonLocalComponentRoot
+ $MasonDataDir
+ $MasonSessionDir
+);
+
+$VERSION = '3.0.9';
+$CORE_CONFIG_FILE = "/opt/rt3/etc/RT_Config.pm";
+$SITE_CONFIG_FILE = "/opt/rt3/etc/RT_SiteConfig.pm";
+
+$BasePath = '/opt/rt3';
+
+$EtcPath = '/opt/rt3/etc';
+$VarPath = '/opt/rt3/var';
+$LocalPath = '/opt/rt3/local';
+$LocalEtcPath = '/opt/rt3/local/etc';
+$LocalLexiconPath = '/opt/rt3/local/po';
+
+# $MasonComponentRoot is where your rt instance keeps its mason html files
+
+$MasonComponentRoot = '/opt/rt3/share/html';
+
+# $MasonLocalComponentRoot is where your rt instance keeps its site-local
+# mason html files.
+
+$MasonLocalComponentRoot = '/opt/rt3/local/html';
+
+# $MasonDataDir Where mason keeps its datafiles
+
+$MasonDataDir = '/opt/rt3/var/mason_data';
+
+# RT needs to put session data (for preserving state between connections
+# via the web interface)
+$MasonSessionDir = '/opt/rt3/var/session_data';
+
+
+
+=head1 NAME
+
+ RT - Request Tracker
+
+=head1 SYNOPSIS
+
+ A fully featured request tracker package
+
+=head1 DESCRIPTION
+
+
+=cut
+
+=item LoadConfig
+
+Load RT's config file. First, go after the core config file.
+After that, try to load the vendor config.
+After that, go after the site config.
+
+=cut
+
+sub LoadConfig {
+ local *Set = sub { $_[0] = $_[1] unless defined $_[0] };
+ if ( -f "$SITE_CONFIG_FILE" ) {
+ require $SITE_CONFIG_FILE
+ || die ("Couldn't load RT config file '$SITE_CONFIG_FILE'\n$@");
+ }
+ require $CORE_CONFIG_FILE
+ || die ("Couldn't load RT config file '$CORE_CONFIG_FILE'\n$@");
+ RT::I18N->Init;
+}
+
+=item Init
+
+ Conenct to the database, set up logging.
+
+=cut
+
+sub Init {
+
+ #Get a database connection
+ ConnectToDatabase();
+
+ #RT's system user is a genuine database user. its id lives here
+ $SystemUser = new RT::CurrentUser();
+ $SystemUser->LoadByName('RT_System');
+
+ #RT's "nobody user" is a genuine database user. its ID lives here.
+ $Nobody = new RT::CurrentUser();
+ $Nobody->LoadByName('Nobody');
+
+ $System = RT::System->new();
+
+ InitLogging();
+}
+
+
+=head2 ConnectToDatabase
+
+Get a database connection
+
+=cut
+
+sub ConnectToDatabase {
+ require RT::Handle;
+ unless ($Handle && $Handle->dbh && $Handle->dbh->ping) {
+ $Handle = RT::Handle->new();
+ }
+ $Handle->Connect();
+}
+
+=head2 InitLogging
+
+Create the RT::Logger object.
+
+=cut
+sub InitLogging {
+
+ # We have to set the record seperator ($, man perlvar)
+ # or Log::Dispatch starts getting
+ # really pissy, as some other module we use unsets it.
+
+ $, = '';
+ use Log::Dispatch 1.6;
+
+ unless ($RT::Logger) {
+
+ $RT::Logger=Log::Dispatch->new();
+
+ if ($RT::LogToFile) {
+
+ unless (-d $RT::LogDir && -w $RT::LogDir) {
+ # localizing here would be hard when we don't have a current user yet
+ # die $self->loc("Log directory [_1] not found or couldn't be written.\n RT can't run.", $RT::LogDir);
+ die ("Log directory $RT::LogDir not found or couldn't be written.\n RT can't run.");
+ }
+
+ my $filename;
+ if ($RT::LogToFileNamed =~ m![/\\]!) {
+ # looks like an absolute path.
+ $filename = $RT::LogToFileNamed;
+ }
+ else {
+ $filename = "$RT::LogDir/$RT::LogToFileNamed";
+ }
+ require Log::Dispatch::File;
+
+
+ $RT::Logger->add(Log::Dispatch::File->new
+ ( name=>'rtlog',
+ min_level=> $RT::LogToFile,
+ filename=> $filename,
+ mode=>'append',
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+ return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"}
+
+
+
+ ));
+ }
+ if ($RT::LogToScreen) {
+ require Log::Dispatch::Screen;
+ $RT::Logger->add(Log::Dispatch::Screen->new
+ ( name => 'screen',
+ min_level => $RT::LogToScreen,
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+ return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"
+ },
+
+ stderr => 1
+ ));
+ }
+ if ($RT::LogToSyslog) {
+ require Log::Dispatch::Syslog;
+ $RT::Logger->add(Log::Dispatch::Syslog->new
+ ( name => 'syslog',
+ ident => 'RT',
+ min_level => $RT::LogToSyslog,
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+
+ # syswrite() cannot take utf8; turn it off here.
+ Encode::_utf8_off($p{message});
+
+ if ($p{level} eq 'debug') {
+
+ return "$p{message}\n" }
+ else {
+ return "$p{message} ($filename:$line)\n"}
+ },
+
+ stderr => 1
+ ));
+ }
+
+ }
+
+# {{{ Signal handlers
+
+## This is the default handling of warnings and die'ings in the code
+## (including other used modules - maybe except for errors catched by
+## Mason). It will log all problems through the standard logging
+## mechanism (see above).
+
+$SIG{__WARN__} = sub {$RT::Logger->warning($_[0])};
+
+#When we call die, trap it and log->crit with the value of the die.
+
+$SIG{__DIE__} = sub {
+ unless ($^S || !defined $^S ) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("$_[0]");
+ exit(-1);
+ }
+ else {
+ #Get out of here if we're in an eval
+ die $_[0];
+ }
+};
+
+# }}}
+
+}
+
+# }}}
+
+
+sub SystemUser {
+ return($SystemUser);
+}
+
+sub Nobody {
+ return ($Nobody);
+}
+
+
+=head2 DropSetGIDPermissions
+
+Drops setgid permissions.
+
+=cut
+
+sub DropSetGIDPermissions {
+ # Now that we got the config read in, we have the database
+ # password and don't need to be setgid
+ # make the effective group the real group
+ $) = $(;
+}
+
+
+=head1 SYNOPSIS
+
+=head1 BUGS
+
+Please report them to rt-3.0-bugs@fsck.com, if you know what's broken and have at least some idea of what needs to be fixed.
+If you're not sure what's going on, report them rt-devel@lists.fsck.com.
+
+=head1 SEE ALSO
+
+L<RT::StyleGuide>
+L<DBIx::SearchBuilder>
+
+
+
+=begin testing
+
+
+ok ($RT::Nobody->Name() eq 'Nobody', "Nobody is nobody");
+ok ($RT::Nobody->Name() ne 'root', "Nobody isn't named root");
+ok ($RT::SystemUser->Name() eq 'RT_System', "The system user is RT_System");
+ok ($RT::SystemUser->Name() ne 'noname', "The system user isn't noname");
+
+
+=end testing
+
+=cut
+
+eval "require RT_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT_Local.pm});
+
+1;
diff --git a/rt/lib/RT.pm.in b/rt/lib/RT.pm.in
new file mode 100644
index 0000000..14c0d47
--- /dev/null
+++ b/rt/lib/RT.pm.in
@@ -0,0 +1,323 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2002 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+package RT;
+use strict;
+use RT::I18N;
+use RT::CurrentUser;
+use RT::System;
+
+use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
+ $CORE_CONFIG_FILE
+ $SITE_CONFIG_FILE
+ $VENDOR_CONFIG_FILE
+ $BasePath
+ $EtcPath
+ $VarPath
+ $LocalPath
+ $LocalEtcPath
+ $LocalLexiconPath
+ $LogDir
+ $MasonComponentRoot
+ $MasonLocalComponentRoot
+ $MasonDataDir
+ $MasonSessionDir
+);
+
+$VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@';
+$CORE_CONFIG_FILE = "@CONFIG_FILE_PATH@/RT_Config.pm";
+$SITE_CONFIG_FILE = "@CONFIG_FILE_PATH@/RT_SiteConfig.pm";
+
+$BasePath = '@RT_PATH@';
+
+$EtcPath = '@RT_ETC_PATH@';
+$VarPath = '@RT_VAR_PATH@';
+$LocalPath = '@RT_LOCAL_PATH@';
+$LocalEtcPath = '@LOCAL_ETC_PATH@';
+$LocalLexiconPath = '@LOCAL_LEXICON_PATH@';
+
+# $MasonComponentRoot is where your rt instance keeps its mason html files
+
+$MasonComponentRoot = '@MASON_HTML_PATH@';
+
+# $MasonLocalComponentRoot is where your rt instance keeps its site-local
+# mason html files.
+
+$MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@';
+
+# $MasonDataDir Where mason keeps its datafiles
+
+$MasonDataDir = '@MASON_DATA_PATH@';
+
+# RT needs to put session data (for preserving state between connections
+# via the web interface)
+$MasonSessionDir = '@MASON_SESSION_PATH@';
+
+
+
+=head1 NAME
+
+ RT - Request Tracker
+
+=head1 SYNOPSIS
+
+ A fully featured request tracker package
+
+=head1 DESCRIPTION
+
+
+=cut
+
+=item LoadConfig
+
+Load RT's config file. First, go after the core config file.
+After that, try to load the vendor config.
+After that, go after the site config.
+
+=cut
+
+sub LoadConfig {
+ local *Set = sub { $_[0] = $_[1] unless defined $_[0] };
+ if ( -f "$SITE_CONFIG_FILE" ) {
+ require $SITE_CONFIG_FILE
+ || die ("Couldn't load RT config file '$SITE_CONFIG_FILE'\n$@");
+ }
+ require $CORE_CONFIG_FILE
+ || die ("Couldn't load RT config file '$CORE_CONFIG_FILE'\n$@");
+ RT::I18N->Init;
+}
+
+=item Init
+
+ Conenct to the database, set up logging.
+
+=cut
+
+sub Init {
+
+ #Get a database connection
+ ConnectToDatabase();
+
+ #RT's system user is a genuine database user. its id lives here
+ $SystemUser = new RT::CurrentUser();
+ $SystemUser->LoadByName('RT_System');
+
+ #RT's "nobody user" is a genuine database user. its ID lives here.
+ $Nobody = new RT::CurrentUser();
+ $Nobody->LoadByName('Nobody');
+
+ $System = RT::System->new();
+
+ InitLogging();
+}
+
+
+=head2 ConnectToDatabase
+
+Get a database connection
+
+=cut
+
+sub ConnectToDatabase {
+ require RT::Handle;
+ unless ($Handle && $Handle->dbh && $Handle->dbh->ping) {
+ $Handle = RT::Handle->new();
+ }
+ $Handle->Connect();
+}
+
+=head2 InitLogging
+
+Create the RT::Logger object.
+
+=cut
+sub InitLogging {
+
+ # We have to set the record seperator ($, man perlvar)
+ # or Log::Dispatch starts getting
+ # really pissy, as some other module we use unsets it.
+
+ $, = '';
+ use Log::Dispatch 1.6;
+
+ unless ($RT::Logger) {
+
+ $RT::Logger=Log::Dispatch->new();
+
+ if ($RT::LogToFile) {
+
+ unless (-d $RT::LogDir && -w $RT::LogDir) {
+ # localizing here would be hard when we don't have a current user yet
+ # die $self->loc("Log directory [_1] not found or couldn't be written.\n RT can't run.", $RT::LogDir);
+ die ("Log directory $RT::LogDir not found or couldn't be written.\n RT can't run.");
+ }
+
+ my $filename;
+ if ($RT::LogToFileNamed =~ m![/\\]!) {
+ # looks like an absolute path.
+ $filename = $RT::LogToFileNamed;
+ }
+ else {
+ $filename = "$RT::LogDir/$RT::LogToFileNamed";
+ }
+ require Log::Dispatch::File;
+
+
+ $RT::Logger->add(Log::Dispatch::File->new
+ ( name=>'rtlog',
+ min_level=> $RT::LogToFile,
+ filename=> $filename,
+ mode=>'append',
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+ return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"}
+
+
+
+ ));
+ }
+ if ($RT::LogToScreen) {
+ require Log::Dispatch::Screen;
+ $RT::Logger->add(Log::Dispatch::Screen->new
+ ( name => 'screen',
+ min_level => $RT::LogToScreen,
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+ return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"
+ },
+
+ stderr => 1
+ ));
+ }
+ if ($RT::LogToSyslog) {
+ require Log::Dispatch::Syslog;
+ $RT::Logger->add(Log::Dispatch::Syslog->new
+ ( name => 'syslog',
+ ident => 'RT',
+ min_level => $RT::LogToSyslog,
+ callbacks => sub { my %p = @_;
+ my ($package, $filename, $line) = caller(5);
+
+ # syswrite() cannot take utf8; turn it off here.
+ Encode::_utf8_off($p{message});
+
+ if ($p{level} eq 'debug') {
+
+ return "$p{message}\n" }
+ else {
+ return "$p{message} ($filename:$line)\n"}
+ },
+
+ stderr => 1
+ ));
+ }
+
+ }
+
+# {{{ Signal handlers
+
+## This is the default handling of warnings and die'ings in the code
+## (including other used modules - maybe except for errors catched by
+## Mason). It will log all problems through the standard logging
+## mechanism (see above).
+
+$SIG{__WARN__} = sub {$RT::Logger->warning($_[0])};
+
+#When we call die, trap it and log->crit with the value of the die.
+
+$SIG{__DIE__} = sub {
+ unless ($^S || !defined $^S ) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("$_[0]");
+ exit(-1);
+ }
+ else {
+ #Get out of here if we're in an eval
+ die $_[0];
+ }
+};
+
+# }}}
+
+}
+
+# }}}
+
+
+sub SystemUser {
+ return($SystemUser);
+}
+
+sub Nobody {
+ return ($Nobody);
+}
+
+
+=head2 DropSetGIDPermissions
+
+Drops setgid permissions.
+
+=cut
+
+sub DropSetGIDPermissions {
+ # Now that we got the config read in, we have the database
+ # password and don't need to be setgid
+ # make the effective group the real group
+ $) = $(;
+}
+
+
+=head1 SYNOPSIS
+
+=head1 BUGS
+
+Please report them to rt-3.0-bugs@fsck.com, if you know what's broken and have at least some idea of what needs to be fixed.
+If you're not sure what's going on, report them rt-devel@lists.fsck.com.
+
+=head1 SEE ALSO
+
+L<RT::StyleGuide>
+L<DBIx::SearchBuilder>
+
+
+
+=begin testing
+
+
+ok ($RT::Nobody->Name() eq 'Nobody', "Nobody is nobody");
+ok ($RT::Nobody->Name() ne 'root', "Nobody isn't named root");
+ok ($RT::SystemUser->Name() eq 'RT_System', "The system user is RT_System");
+ok ($RT::SystemUser->Name() ne 'noname', "The system user isn't noname");
+
+
+=end testing
+
+=cut
+
+eval "require RT_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT_Local.pm});
+
+1;
diff --git a/rt/lib/RT/ACE.pm b/rt/lib/RT/ACE.pm
new file mode 100755
index 0000000..1501a12
--- /dev/null
+++ b/rt/lib/RT/ACE.pm
@@ -0,0 +1,304 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::ACE
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::ACE;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('ACL');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(25) 'PrincipalType'.
+ int(11) 'PrincipalId'.
+ varchar(25) 'RightName'.
+ varchar(25) 'ObjectType'.
+ int(11) 'ObjectId'.
+ int(11) 'DelegatedBy'.
+ int(11) 'DelegatedFrom'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ PrincipalType => '',
+ PrincipalId => '0',
+ RightName => '',
+ ObjectType => '',
+ ObjectId => '0',
+ DelegatedBy => '0',
+ DelegatedFrom => '0',
+
+ @_);
+ $self->SUPER::Create(
+ PrincipalType => $args{'PrincipalType'},
+ PrincipalId => $args{'PrincipalId'},
+ RightName => $args{'RightName'},
+ ObjectType => $args{'ObjectType'},
+ ObjectId => $args{'ObjectId'},
+ DelegatedBy => $args{'DelegatedBy'},
+ DelegatedFrom => $args{'DelegatedFrom'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item PrincipalType
+
+Returns the current value of PrincipalType.
+(In the database, PrincipalType is stored as varchar(25).)
+
+
+
+=item SetPrincipalType VALUE
+
+
+Set PrincipalType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PrincipalType will be stored as a varchar(25).)
+
+
+=cut
+
+
+=item PrincipalId
+
+Returns the current value of PrincipalId.
+(In the database, PrincipalId is stored as int(11).)
+
+
+
+=item SetPrincipalId VALUE
+
+
+Set PrincipalId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PrincipalId will be stored as a int(11).)
+
+
+=cut
+
+
+=item RightName
+
+Returns the current value of RightName.
+(In the database, RightName is stored as varchar(25).)
+
+
+
+=item SetRightName VALUE
+
+
+Set RightName to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, RightName will be stored as a varchar(25).)
+
+
+=cut
+
+
+=item ObjectType
+
+Returns the current value of ObjectType.
+(In the database, ObjectType is stored as varchar(25).)
+
+
+
+=item SetObjectType VALUE
+
+
+Set ObjectType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectType will be stored as a varchar(25).)
+
+
+=cut
+
+
+=item ObjectId
+
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=item SetObjectId VALUE
+
+
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=item DelegatedBy
+
+Returns the current value of DelegatedBy.
+(In the database, DelegatedBy is stored as int(11).)
+
+
+
+=item SetDelegatedBy VALUE
+
+
+Set DelegatedBy to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, DelegatedBy will be stored as a int(11).)
+
+
+=cut
+
+
+=item DelegatedFrom
+
+Returns the current value of DelegatedFrom.
+(In the database, DelegatedFrom is stored as int(11).)
+
+
+
+=item SetDelegatedFrom VALUE
+
+
+Set DelegatedFrom to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, DelegatedFrom will be stored as a int(11).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ PrincipalType =>
+ {read => 1, write => 1, type => 'varchar(25)', default => ''},
+ PrincipalId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ RightName =>
+ {read => 1, write => 1, type => 'varchar(25)', default => ''},
+ ObjectType =>
+ {read => 1, write => 1, type => 'varchar(25)', default => ''},
+ ObjectId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ DelegatedBy =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ DelegatedFrom =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::ACE_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACE_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ACE_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACE_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ACE_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACE_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ACE_Overlay, RT::ACE_Vendor, RT::ACE_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ACE_Overlay.pm b/rt/lib/RT/ACE_Overlay.pm
new file mode 100644
index 0000000..65e5a9c
--- /dev/null
+++ b/rt/lib/RT/ACE_Overlay.pm
@@ -0,0 +1,907 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 SYNOPSIS
+
+ use RT::ACE;
+ my $ace = new RT::ACE($CurrentUser);
+
+
+=head1 DESCRIPTION
+
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::ACE);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+use RT::Principals;
+use RT::Queues;
+use RT::Groups;
+
+use vars qw (
+ %LOWERCASERIGHTNAMES
+ %OBJECT_TYPES
+ %TICKET_METAPRINCIPALS
+);
+
+
+# {{{ Descriptions of rights
+
+=head1 Rights
+
+# Queue rights are the sort of queue rights that can only be granted
+# to real people or groups
+
+
+=begin testing
+
+my $Queue = RT::Queue->new($RT::SystemUser);
+
+is ($Queue->AvailableRights->{'DeleteTicket'} , 'Delete tickets', "Found the delete ticket right");
+is ($RT::System->AvailableRights->{'SuperUser'}, 'Do anything and everything', "Found the superuser right");
+
+
+=end testing
+
+=cut
+
+
+
+
+# }}}
+
+# {{{ Descriptions of principals
+
+%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
+);
+
+# }}}
+
+
+# {{{ sub LoadByValues
+
+=head2 LoadByValues PARAMHASH
+
+Load an ACE by specifying a paramhash with the following fields:
+
+ PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+
+ And either:
+
+ Object => undef,
+
+ OR
+
+ ObjectType => undef,
+ ObjectId => undef
+
+=cut
+
+sub LoadByValues {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+ Object => undef,
+ ObjectId => undef,
+ ObjectType => undef,
+ @_ );
+
+ my $princ_obj;
+ ( $princ_obj, $args{'PrincipalType'} ) =
+ $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+ $args{'PrincipalType'} );
+
+ unless ( $princ_obj->id ) {
+ return ( 0,
+ $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+ );
+ }
+
+ my ($object_type, $object_id);
+
+ if ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
+ $object_type = ref($args{'Object'});
+ $object_id = $args{'Object'}->id;
+ } elsif ($args{'ObjectId'} || $args{'ObjectType'}) {
+ $object_type = $args{'ObjectType'};
+ $object_id = $args{'ObjectId'};
+ } else {
+ return ( 0, $self->loc("System error. Right not granted.") );
+ }
+
+ $self->LoadByCols( PrincipalId => $princ_obj->Id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => $object_type,
+ ObjectId => $object_id);
+
+ #If we couldn't load it.
+ unless ( $self->Id ) {
+ return ( 0, $self->loc("ACE not found") );
+ }
+
+ # if we could
+ return ( $self->Id, $self->loc("Right Loaded") );
+
+}
+
+# }}}
+
+# {{{ sub Create
+
+=head2 Create <PARAMS>
+
+PARAMS is a parameter hash with the following elements:
+
+ PrincipalId => The id of an RT::Principal object
+ PrincipalType => "User" "Group" or any Role type
+ RightName => the name of a right. in any case
+ DelegatedBy => The Principal->Id of the user delegating the right
+ DelegatedFrom => The id of the ACE which this new ACE is delegated from
+
+
+ Either:
+
+ Object => An object to create rights for. ususally, an RT::Queue or RT::Group
+ This should always be a DBIx::SearchBuilder::Record subclass
+
+ OR
+
+ ObjectType => the type of the object in question (ref ($object))
+ ObjectId => the id of the object in question $object->Id
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ PrincipalType => undef,
+ RightName => undef,
+ Object => $RT::System,
+ @_ );
+
+ # {{{ Validate the principal
+ my $princ_obj;
+ ( $princ_obj, $args{'PrincipalType'} ) =
+ $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+ $args{'PrincipalType'} );
+
+ unless ( $princ_obj->id ) {
+ return ( 0,
+ $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+ );
+ }
+
+ # }}}
+
+
+ if ($args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'})) {
+ use Carp;
+ $RT::Logger->crit(Carp::cluck("ACE::Create called with an ObjectType or an ObjectId"));
+ }
+
+
+
+ unless ($args{'Object'} && UNIVERSAL::can($args{'Object'},'id')) {
+ return ( 0, $self->loc("System error. Right not granted.") );
+ }
+ # {{{ Check the ACL
+
+ if (ref( $args{'Object'}) eq 'RT::Group' ) {
+ unless ( $self->CurrentUser->HasRight( Object => $args{'Object'},
+ Right => 'AdminGroup' )
+ ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+
+ else {
+ unless ( $self->CurrentUser->HasRight( Object => $args{'Object'}, Right => 'ModifyACL' )) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ # }}}
+
+ # {{{ Canonicalize and check the right name
+ unless ( $args{'RightName'} ) {
+ return ( 0, $self->loc('Invalid right') );
+ }
+
+ $args{'RightName'} = $self->CanonicalizeRightName( $args{'RightName'} );
+
+ #check if it's a valid RightName
+ if ( ref ($args{'Object'} eq 'RT::Queue' )) {
+ unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
+ $RT::Logger->warning("Couldn't validate right name". $args{'RightName'});
+ return ( 0, $self->loc('Invalid right') );
+ }
+ }
+ elsif ( ref ($args{'Object'} eq 'RT::Group' )) {
+ unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
+ $RT::Logger->warning("Couldn't validate group right name". $args{'RightName'});
+ return ( 0, $self->loc('Invalid right') );
+ }
+ }
+ elsif ( ref ($args{'Object'} eq 'RT::System' )) {
+ my $q = RT::Queue->new($self->CurrentUser);
+ my $g = RT::Group->new($self->CurrentUser);
+
+ unless (( exists $g->AvailableRights->{ $args{'RightName'} } )
+ || ( exists $g->AvailableRights->{ $args{'RightName'} } )
+ || ( exists $RT::System->AvailableRights->{ $args{'RightName'} } ) ) {
+ $RT::Logger->warning("Couldn't validate system right name - ". $args{'RightName'});
+ return ( 0, $self->loc('Invalid right') );
+ }
+ }
+
+ unless ( $args{'RightName'} ) {
+ return ( 0, $self->loc('Invalid right') );
+ }
+ # }}}
+
+ # Make sure the right doesn't already exist.
+ $self->LoadByCols( PrincipalId => $princ_obj->id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => ref($args{'Object'}),
+ ObjectId => $args{'Object'}->id,
+ DelegatedBy => 0,
+ DelegatedFrom => 0 );
+ if ( $self->Id ) {
+ return ( 0, $self->loc('That principal already has that right') );
+ }
+
+ my $id = $self->SUPER::Create( PrincipalId => $princ_obj->id,
+ PrincipalType => $args{'PrincipalType'},
+ RightName => $args{'RightName'},
+ ObjectType => ref( $args{'Object'} ),
+ ObjectId => $args{'Object'}->id,
+ DelegatedBy => 0,
+ DelegatedFrom => 0 );
+
+ #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
+ RT::Principal->_InvalidateACLCache();
+
+ if ( $id > 0 ) {
+ return ( $id, $self->loc('Right Granted') );
+ }
+ else {
+ return ( 0, $self->loc('System error. Right not granted.') );
+ }
+}
+
+# }}}
+
+# {{{ sub Delegate
+
+=head2 Delegate <PARAMS>
+
+This routine delegates the current ACE to a principal specified by the
+B<PrincipalId> parameter.
+
+Returns an error if the current user doesn't have the right to be delegated
+or doesn't have the right to delegate rights.
+
+Always returns a tuple of (ReturnValue, Message)
+
+=begin testing
+
+use_ok(RT::User);
+my $user_a = RT::User->new($RT::SystemUser);
+$user_a->Create( Name => 'DelegationA', Privileged => 1);
+ok ($user_a->Id, "Created delegation user a");
+
+my $user_b = RT::User->new($RT::SystemUser);
+$user_b->Create( Name => 'DelegationB', Privileged => 1);
+ok ($user_b->Id, "Created delegation user b");
+
+
+use_ok(RT::Queue);
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Create(Name =>'DelegationTest');
+ok ($q->Id, "Created a delegation test queue");
+
+
+#------ First, we test whether a user can delegate a right that's been granted to him personally
+my ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object => $RT::System, Right => 'AdminOwnPersonalGroups');
+ok($val, $msg);
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => 'OwnTicket');
+ok($val, $msg);
+
+ok($user_a->HasRight( Object => $RT::System, Right => 'AdminOwnPersonalGroups') ,"user a has the right 'AdminOwnPersonalGroups' directly");
+
+my $a_delegates = RT::Group->new($user_a);
+$a_delegates->CreatePersonalGroup(Name => 'Delegates');
+ok( $a_delegates->Id ,"user a creates a personal group 'Delegates'");
+ok( $a_delegates->AddMember($user_b->PrincipalId) ,"user a adds user b to personal group 'delegates'");
+
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to OwnTicket' in queue 'DelegationTest'");
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a has the right to 'OwnTicket' in queue 'DelegationTest'");
+ok(!$user_a->HasRight( Object => $RT::System, Right => 'DelegateRights') ,"user a does not have the right 'delegate rights'");
+
+
+my $own_ticket_ace = RT::ACE->new($user_a);
+my $user_a_equiv_group = RT::Group->new($user_a);
+$user_a_equiv_group->LoadACLEquivalenceGroup($user_a->PrincipalObj);
+ok ($user_a_equiv_group->Id, "Loaded the user A acl equivalence group");
+my $user_b_equiv_group = RT::Group->new($user_b);
+$user_b_equiv_group->LoadACLEquivalenceGroup($user_b->PrincipalObj);
+ok ($user_b_equiv_group->Id, "Loaded the user B acl equivalence group");
+$own_ticket_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $user_a_equiv_group->PrincipalId, Object=>$q, RightName => 'OwnTicket');
+
+ok ($own_ticket_ace->Id, "Found the ACE we want to test with for now");
+
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
+ok( !$val ,"user a tries and fails to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight( Right => 'DelegateRights');
+ok($val, "user a is granted the right to 'delegate rights' - $msg");
+
+ok($user_a->HasRight( Object => $RT::System, Right => 'DelegateRights') ,"user a has the right 'AdminOwnPersonalGroups' directly");
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
+
+ok( $val ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+my $delegated_ace = RT::ACE->new($user_a);
+$delegated_ace->LoadByValues ( Object => $q, RightName => 'OwnTicket', PrincipalType => 'Group',
+PrincipalId => $a_delegates->PrincipalId, DelegatedBy => $user_a->PrincipalId, DelegatedFrom => $own_ticket_ace->Id);
+ok ($delegated_ace->Id, "Found the delegated ACE");
+
+ok( $a_delegates->DeleteMember($user_b->PrincipalId) ,"user a removes b from pg 'delegates'");
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+ok( $a_delegates->AddMember($user_b->PrincipalId) ,"user a adds user b to personal group 'delegates'");
+ok( $user_b->HasRight(Right => 'OwnTicket', Object=> $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+ok( $delegated_ace->Delete ,"user a revokes pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest'");
+ok( ! $user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
+ok( $val ,"user a delegates pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest' - $msg");
+
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $user_a->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest". $msg);
+
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+ ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $user_a->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, $msg);
+
+ ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a has the right to own tickets in queue 'DelegationTest'");
+
+ ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+# {{{ get back to a known clean state
+($val, $msg) = $user_a->PrincipalObj->RevokeRight( Object => $q, Right => 'OwnTicket');
+ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest -". $msg);
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"make sure that user a can't own tickets in queue 'DelegationTest'");
+# }}}
+
+
+# {{{ Set up some groups and membership
+my $del1 = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del1->CreateUserDefinedGroup(Name => 'Del1');
+ok( $val ,"create a group del1 - $msg");
+
+my $del2 = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2->CreateUserDefinedGroup(Name => 'Del2');
+ok( $val ,"create a group del2 - $msg");
+($val, $msg) = $del1->AddMember($del2->PrincipalId);
+ok( $val,"make del2 a member of del1 - $msg");
+
+my $del2a = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2a->CreateUserDefinedGroup(Name => 'Del2a');
+ok( $val ,"create a group del2a - $msg");
+($val, $msg) = $del2->AddMember($del2a->PrincipalId);
+ok($val ,"make del2a a member of del2 - $msg");
+
+my $del2b = RT::Group->new($RT::SystemUser);
+($val, $msg) = $del2b->CreateUserDefinedGroup(Name => 'Del2b');
+ok( $val ,"create a group del2b - $msg");
+($val, $msg) = $del2->AddMember($del2b->PrincipalId);
+ok($val ,"make del2b a member of del2 - $msg");
+
+($val, $msg) = $del2->AddMember($user_a->PrincipalId) ;
+ok($val,"make 'user a' a member of del2 - $msg");
+
+($val, $msg) = $del2b->AddMember($user_a->PrincipalId) ;
+ok($val,"make 'user a' a member of del2b - $msg");
+
+# }}}
+
+# {{{ Grant a right to a group and make sure that a submember can delegate the right and that it does not get yanked
+# when a user is removed as a submember, when they're a sumember through another path
+($val, $msg) = $del1->PrincipalObj->GrantRight( Object=> $q, Right => 'OwnTicket');
+ok( $val ,"grant del1 the right to 'OwnTicket' in queue 'DelegationTest' - $msg");
+
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"make sure that user a can own tickets in queue 'DelegationTest'");
+
+my $group_ace= RT::ACE->new($user_a);
+$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object => $q, RightName => 'OwnTicket');
+
+ok ($group_ace->Id, "Found the ACE we want to test with for now");
+
+($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
+
+ok( $val ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+
+($val, $msg) = $del2b->DeleteMember($user_a->PrincipalId);
+ok( $val ,"remove user a from group del2b - $msg");
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a has the right to own tickets in queue 'DelegationTest'");
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+# }}}
+
+# {{{ When a user is removed froom a group by the only path they're in there by, make sure the delegations go away
+($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
+ok( $val ,"remove user a from group del2 - $msg");
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a does not have the right to own tickets in queue 'DelegationTest' ");
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest' ");
+# }}}
+
+($val, $msg) = $del2->AddMember($user_a->PrincipalId);
+ok( $val ,"make user a a member of group del2 - $msg");
+
+($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+
+my $del2_right = RT::ACE->new($user_a);
+$del2_right->LoadByValues( PrincipalId => $del2->PrincipalId, PrincipalType => 'Group', Object => $q, RightName => 'OwnTicket');
+ok ($del2_right->Id, "Found the right");
+
+($val, $msg) = $del2_right->Delegate(PrincipalId => $a_delegates->PrincipalId);
+ok( $val ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' gotten via del2 to personal group 'delegates' - $msg");
+
+# They have it via del1 and del2
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+
+($val, $msg) = $del2->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "revoke the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a does has the right to own tickets in queue 'DelegationTest' via del1");
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
+ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
+
+
+$group_ace= RT::ACE->new($user_a);
+$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object=>$q, RightName => 'OwnTicket');
+
+ok ($group_ace->Id, "Found the ACE we want to test with for now");
+
+($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
+
+ok( $val ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
+
+ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
+
+($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
+ok( $val ,"remove user a from group del2 - $msg");
+
+ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q) ,"user a does not have the right to own tickets in queue 'DelegationTest'");
+
+ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q) ,"user b does not have the right to own tickets in queue 'DelegationTest'");
+
+
+
+=end testing
+
+=cut
+
+sub Delegate {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ @_ );
+
+ unless ( $self->Id ) {
+ return ( 0, $self->loc("Right not loaded.") );
+ }
+ my $princ_obj;
+ ( $princ_obj, $args{'PrincipalType'} ) =
+ $self->_CanonicalizePrincipal( $args{'PrincipalId'},
+ $args{'PrincipalType'} );
+
+ unless ( $princ_obj->id ) {
+ return ( 0,
+ $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} )
+ );
+ }
+
+ # }}}
+
+ # {{{ Check the ACL
+
+ # First, we check to se if the user is delegating rights and
+ # they have the permission to
+ unless ( $self->CurrentUser->HasRight(Right => 'DelegateRights', Object => $self->Object) ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ unless ( $self->PrincipalObj->IsGroup ) {
+ return ( 0, $self->loc("System Error") );
+ }
+ unless ( $self->PrincipalObj->Object->HasMemberRecursively(
+ $self->CurrentUser->PrincipalObj
+ )
+ ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ # }}}
+
+ my $concurrency_check = RT::ACE->new($RT::SystemUser);
+ $concurrency_check->Load( $self->Id );
+ unless ( $concurrency_check->Id ) {
+ $RT::Logger->crit(
+ "Trying to delegate a right which had already been deleted");
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ my $delegated_ace = RT::ACE->new( $self->CurrentUser );
+
+ # Make sure the right doesn't already exist.
+ $delegated_ace->LoadByCols( PrincipalId => $princ_obj->Id,
+ PrincipalType => 'Group',
+ RightName => $self->__Value('RightName'),
+ ObjectType => $self->__Value('ObjectType'),
+ ObjectId => $self->__Value('ObjectId'),
+ DelegatedBy => $self->CurrentUser->PrincipalId,
+ DelegatedFrom => $self->id );
+ if ( $delegated_ace->Id ) {
+ return ( 0, $self->loc('That principal already has that right') );
+ }
+ my $id = $delegated_ace->SUPER::Create(
+ PrincipalId => $princ_obj->Id,
+ PrincipalType => 'Group', # do we want to hardcode this?
+ RightName => $self->__Value('RightName'),
+ ObjectType => $self->__Value('ObjectType'),
+ ObjectId => $self->__Value('ObjectId'),
+ DelegatedBy => $self->CurrentUser->PrincipalId,
+ DelegatedFrom => $self->id );
+
+ #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();
+
+ if ( $id > 0 ) {
+ return ( $id, $self->loc('Right Delegated') );
+ }
+ else {
+ return ( 0, $self->loc('System error. Right not delegated.') );
+ }
+}
+
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete { InsideTransaction => undef}
+
+Delete this object. This method should ONLY ever be called from RT::User or RT::Group (or from itself)
+If this is being called from within a transaction, specify a true value for the parameter InsideTransaction.
+Really, DBIx::SearchBuilder should use and/or fake subtransactions
+
+This routine will also recurse and delete any delegations of this right
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ( $self->Id ) {
+ return ( 0, $self->loc('Right not loaded.') );
+ }
+
+ # A user can delete an ACE if the current user has the right to modify it and it's not a delegated ACE
+ # or if it's a delegated ACE and it was delegated by the current user
+ unless (
+ ( $self->CurrentUser->HasRight(Right => 'ModifyACL', Object => $self->Object)
+ && $self->__Value('DelegatedBy') == 0 )
+ || ( $self->__Value('DelegatedBy') == $self->CurrentUser->PrincipalId )
+ ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ $self->_Delete(@_);
+}
+
+# Helper for Delete with no ACL check
+sub _Delete {
+ my $self = shift;
+ my %args = ( InsideTransaction => undef,
+ @_ );
+
+ my $InsideTransaction = $args{'InsideTransaction'};
+
+ $RT::Handle->BeginTransaction() unless $InsideTransaction;
+
+ my $delegated_from_this = RT::ACL->new($RT::SystemUser);
+ $delegated_from_this->Limit( FIELD => 'DelegatedFrom',
+ OPERATOR => '=',
+ VALUE => $self->Id );
+
+ my $delete_succeeded = 1;
+ my $submsg;
+ while ( my $delegated_ace = $delegated_from_this->Next ) {
+ ( $delete_succeeded, $submsg ) =
+ $delegated_ace->_Delete( InsideTransaction => 1 );
+ last if ($delete_succeeded);
+ }
+
+ unless ($delete_succeeded) {
+ $RT::Handle->Rollback() unless $InsideTransaction;
+ return ( 0, $self->loc('Right could not be revoked') );
+ }
+
+ my ( $val, $msg ) = $self->SUPER::Delete(@_);
+
+ #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();
+
+ if ($val) {
+ $RT::Handle->Commit() unless $InsideTransaction;
+ return ( $val, $self->loc('Right revoked') );
+ }
+ else {
+ $RT::Handle->Rollback() unless $InsideTransaction;
+ return ( 0, $self->loc('Right could not be revoked') );
+ }
+}
+
+# }}}
+
+# {{{ sub _BootstrapCreate
+
+=head2 _BootstrapCreate
+
+Grant a right with no error checking and no ACL. this is _only_ for
+installation. If you use this routine without the author's explicit
+written approval, he will hunt you down and make you spend eternity
+translating mozilla's code into FORTRAN or intercal.
+
+If you think you need this routine, you've mistaken.
+
+=cut
+
+sub _BootstrapCreate {
+ my $self = shift;
+ my %args = (@_);
+
+ # When bootstrapping, make sure we get the _right_ users
+ if ( $args{'UserId'} ) {
+ my $user = RT::User->new( $self->CurrentUser );
+ $user->Load( $args{'UserId'} );
+ delete $args{'UserId'};
+ $args{'PrincipalId'} = $user->PrincipalId;
+ $args{'PrincipalType'} = 'User';
+ }
+
+ my $id = $self->SUPER::Create(%args);
+
+ if ( $id > 0 ) {
+ return ($id);
+ }
+ else {
+ $RT::Logger->err('System error. right not granted.');
+ return (undef);
+ }
+
+}
+
+# }}}
+
+# {{{ sub CanonicalizeRightName
+
+=head2 CanonicalizeRightName <RIGHT>
+
+Takes a queue or system right name in any case and returns it in
+the correct case. If it's not found, will return undef.
+
+=cut
+
+sub CanonicalizeRightName {
+ my $self = shift;
+ my $right = shift;
+ $right = lc $right;
+ if ( exists $LOWERCASERIGHTNAMES{"$right"} ) {
+ return ( $LOWERCASERIGHTNAMES{"$right"} );
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+
+# {{{ sub Object
+
+=head2 Object
+
+If the object this ACE applies to is a queue, returns the queue object.
+If the object this ACE applies to is a group, returns the group object.
+If it's the system object, returns undef.
+
+If the user has no rights, returns undef.
+
+=cut
+
+
+
+
+sub Object {
+ my $self = shift;
+
+ my $appliesto_obj;
+
+ if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) {
+ $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser);
+ unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) {
+ return undef;
+ }
+ $appliesto_obj->Load( $self->__Value('ObjectId') );
+ return ($appliesto_obj);
+ }
+ else {
+ $RT::Logger->warning( "$self -> Object called for an object "
+ . "of an unknown type:"
+ . $self->ObjectType );
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub PrincipalObj
+
+=head2 PrincipalObj
+
+Returns the RT::Principal object for this ACE.
+
+=cut
+
+sub PrincipalObj {
+ my $self = shift;
+
+ my $princ_obj = RT::Principal->new( $self->CurrentUser );
+ $princ_obj->Load( $self->__Value('PrincipalId') );
+
+ unless ( $princ_obj->Id ) {
+ $RT::Logger->err(
+ "ACE " . $self->Id . " couldn't load its principal object" );
+ }
+ return ($princ_obj);
+
+}
+
+# }}}
+
+# {{{ ACL related methods
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+ return ( 0, $self->loc("ACEs can only be created and deleted.") );
+}
+
+# }}}
+
+# {{{ sub _Value
+
+sub _Value {
+ my $self = shift;
+
+ if ( $self->__Value('DelegatedBy') eq $self->CurrentUser->PrincipalId ) {
+ return ( $self->__Value(@_) );
+ }
+ elsif ( $self->PrincipalObj->IsGroup
+ && $self->PrincipalObj->Object->HasMemberRecursively(
+ $self->CurrentUser->PrincipalObj
+ )
+ ) {
+ return ( $self->__Value(@_) );
+ }
+ elsif ( $self->CurrentUser->HasRight(Right => 'ShowACL', Object => $self->Object) ) {
+ return ( $self->__Value(@_) );
+ }
+ else {
+ return undef;
+ }
+}
+
+# }}}
+
+
+# }}}
+
+# {{{ _CanonicalizePrincipal
+
+=head2 _CanonicalizePrincipal (PrincipalId, PrincipalType)
+
+Takes a principal id and a principal type.
+
+If the principal is a user, resolves it to the proper acl equivalence group.
+Returns a tuple of (RT::Principal, PrincipalType) for the principal we really want to work with
+
+=cut
+
+sub _CanonicalizePrincipal {
+ my $self = shift;
+ my $princ_id = shift;
+ my $princ_type = shift;
+
+ my $princ_obj = RT::Principal->new($RT::SystemUser);
+ $princ_obj->Load($princ_id);
+
+ unless ( $princ_obj->Id ) {
+ use Carp;
+ $RT::Logger->crit(Carp::cluck);
+ $RT::Logger->crit("Can't load a principal for id $princ_id");
+ return ( $princ_obj, undef );
+ }
+
+ # Rights never get granted to users. they get granted to their
+ # ACL equivalence groups
+ if ( $princ_type eq 'User' ) {
+ my $equiv_group = RT::Group->new( $self->CurrentUser );
+ $equiv_group->LoadACLEquivalenceGroup($princ_obj);
+ unless ( $equiv_group->Id ) {
+ $RT::Logger->crit(
+ "No ACL equiv group for princ " . $self->__Value('ObjectId') );
+ return ( 0, $self->loc('System error. Right not granted.') );
+ }
+ $princ_obj = $equiv_group->PrincipalObj();
+ $princ_type = 'Group';
+
+ }
+ return ( $princ_obj, $princ_type );
+}
+
+# }}}
+1;
diff --git a/rt/lib/RT/ACL.pm b/rt/lib/RT/ACL.pm
new file mode 100755
index 0000000..81f59c6
--- /dev/null
+++ b/rt/lib/RT/ACL.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::ACL -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::ACL
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::ACL;
+
+use RT::SearchBuilder;
+use RT::ACE;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'ACL';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::ACE item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::ACE->new($self->CurrentUser));
+}
+
+ eval "require RT::ACL_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACL_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ACL_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACL_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ACL_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ACL_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ACL_Overlay, RT::ACL_Vendor, RT::ACL_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ACL_Overlay.pm b/rt/lib/RT/ACL_Overlay.pm
new file mode 100644
index 0000000..9775776
--- /dev/null
+++ b/rt/lib/RT/ACL_Overlay.pm
@@ -0,0 +1,295 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::ACL - collection of RT ACE objects
+
+=head1 SYNOPSIS
+
+ use RT::ACL;
+my $ACL = new RT::ACL($CurrentUser);
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::ACL);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+=head2 Next
+
+Hand out the next ACE that was found
+
+=cut
+
+
+# {{{ LimitToObject
+
+=head2 LimitToObject $object
+
+Limit the ACL to rights for the object $object. It needs to be an RT::Record class.
+
+=cut
+
+sub LimitToObject {
+ my $self = shift;
+ my $obj = shift;
+ unless (defined($obj) && ref($obj) && UNIVERSAL::can($obj, 'id')) {
+ return undef;
+ }
+ $self->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => ref($obj), ENTRYAGGREGATOR => 'OR');
+ $self->Limit(FIELD => 'ObjectId', OPERATOR=> '=', VALUE => $obj->id, ENTRYAGGREGATOR => 'OR', QUOTEVALUE => 0);
+
+}
+
+# }}}
+
+# {{{ LimitToPrincipal
+
+=head2 LimitToPrincipal { Type => undef, Id => undef, IncludeGroupMembership => undef }
+
+Limit the ACL to the principal with PrincipalId Id and PrincipalType Type
+
+Id is not optional.
+Type is.
+
+if IncludeGroupMembership => 1 is specified, ACEs which apply to the principal due to group membership will be included in the resultset.
+
+
+=cut
+
+sub LimitToPrincipal {
+ my $self = shift;
+ my %args = ( Type => undef,
+ Id => undef,
+ IncludeGroupMembership => undef,
+ @_ );
+ if ( $args{'IncludeGroupMembership'} ) {
+ my $cgm = $self->NewAlias('CachedGroupMembers');
+ $self->Join( ALIAS1 => 'main',
+ FIELD1 => 'PrincipalId',
+ ALIAS2 => $cgm,
+ FIELD2 => 'GroupId' );
+ $self->Limit( ALIAS => $cgm,
+ FIELD => 'MemberId',
+ OPERATOR => '=',
+ VALUE => $args{'Id'},
+ ENTRYAGGREGATOR => 'OR' );
+ }
+ else {
+ if ( defined $args{'Type'} ) {
+ $self->Limit( FIELD => 'PrincipalType',
+ OPERATOR => '=',
+ VALUE => $args{'Type'},
+ ENTRYAGGREGATOR => 'OR' );
+ }
+ # if the principal id points to a user, we really want to point
+ # to their ACL equivalence group. The machinations we're going through
+ # lead me to start to suspect that we really want users and groups
+ # to just be the same table. or _maybe_ that we want an object db.
+ my $princ = RT::Principal->new($RT::SystemUser);
+ $princ->Load($args{'PrincipalId'});
+ if ($princ->PrincipalType eq 'User') {
+ my $group = RT::Group->new($RT::SystemUser);
+ $group->LoadACLEquivalenceGroup($princ);
+ $args{'PrincipalId'} = $group->PrincipalId;
+ }
+ $self->Limit( FIELD => 'PrincipalId',
+ OPERATOR => '=',
+ VALUE => $args{'Id'},
+ ENTRYAGGREGATOR => 'OR' );
+ }
+}
+
+# }}}
+
+
+
+# {{{ ExcludeDelegatedRights
+
+=head2 ExcludeDelegatedRights
+
+Don't list rights which have been delegated.
+
+=cut
+
+sub ExcludeDelegatedRights {
+ my $self = shift;
+ $self->DelegatedBy(Id => 0);
+ $self->DelegatedFrom(Id => 0);
+}
+# }}}
+
+# {{{ DelegatedBy
+
+=head2 DelegatedBy { Id => undef }
+
+Limit the ACL to rights delegated by the principal whose Principal Id is
+B<Id>
+
+Id is not optional.
+
+=cut
+
+sub DelegatedBy {
+ my $self = shift;
+ my %args = (
+ Id => undef,
+ @_
+ );
+ $self->Limit(
+ FIELD => 'DelegatedBy',
+ OPERATOR => '=',
+ VALUE => $args{'Id'},
+ ENTRYAGGREGATOR => 'OR'
+ );
+
+}
+
+# }}}
+
+# {{{ DelegatedFrom
+
+=head2 DelegatedFrom { Id => undef }
+
+Limit the ACL to rights delegate from the ACE which has the Id specified
+by the Id parameter.
+
+Id is not optional.
+
+=cut
+
+sub DelegatedFrom {
+ my $self = shift;
+ my %args = (
+ Id => undef,
+ @_);
+ $self->Limit(FIELD => 'DelegatedFrom', OPERATOR=> '=', VALUE => $args{'Id'}, ENTRYAGGREGATOR => 'OR');
+
+}
+
+# }}}
+
+
+# {{{ sub Next
+sub Next {
+ my $self = shift;
+
+ my $ACE = $self->SUPER::Next();
+ if ( ( defined($ACE) ) and ( ref($ACE) ) ) {
+
+ if ( $self->CurrentUser->HasRight( Right => 'ShowACL',
+ Object => $ACE->Object )
+ or $self->CurrentUser->HasRight( Right => 'ModifyACL',
+ Object => $ACE->Object )
+ ) {
+ return ($ACE);
+ }
+
+ #If the user doesn't have the right to show this ACE
+ else {
+ return ( $self->Next() );
+ }
+ }
+
+ #if there never was any ACE
+ else {
+ return (undef);
+ }
+
+}
+
+# }}}
+
+
+
+#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\n");
+ $self->_BuildHash();
+ return ($return);
+}
+
+
+#Build a hash of this ACL's entries.
+sub _BuildHash {
+ my $self = shift;
+
+ while (my $entry = $self->Next) {
+ my $hashkey = $entry->ObjectType . "-" . $entry->ObjectId . "-" . $entry->RightName . "-" . $entry->PrincipalId . "-" . $entry->PrincipalType;
+
+ $self->{'as_hash'}->{"$hashkey"} =1;
+
+ }
+}
+
+
+# {{{ HasEntry
+
+=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);
+ }
+}
+
+# }}}
+1;
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm
new file mode 100644
index 0000000..7c8c2b8
--- /dev/null
+++ b/rt/lib/RT/Action/AutoOpen.pm
@@ -0,0 +1,86 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# This Action will open the BASE if a dependent is resolved.
+
+package RT::Action::AutoOpen;
+require RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA=qw(RT::Action::Generic);
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return (ref $self );
+}
+# }}}
+
+
+# {{{ sub Prepare
+sub Prepare {
+ my $self = shift;
+
+ # if the ticket is already open or the ticket is new and the message is more mail from the
+ # requestor, don't reopen it.
+
+ if ( ( $self->TicketObj->Status eq 'open' )
+ || ( ( $self->TicketObj->Status eq 'new' )
+ && $self->TransactionObj->IsInbound )
+ ) {
+
+ return undef;
+ }
+ else {
+ return (1);
+ }
+}
+# }}}
+
+sub Commit {
+ my $self = shift;
+ my $oldstatus = $self->TicketObj->Status();
+ $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
+ $self->TicketObj->_NewTransaction(
+ Type => 'Status',
+ Field => 'Status',
+ OldValue => $oldstatus,
+ NewValue => 'open',
+ Data => 'Ticket auto-opened on incoming correspondence'
+ );
+
+
+ return(1);
+}
+
+eval "require RT::Action::AutoOpen_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/AutoOpen_Vendor.pm});
+eval "require RT::Action::AutoOpen_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/AutoOpen_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm
new file mode 100755
index 0000000..f58b8f2
--- /dev/null
+++ b/rt/lib/RT/Action/Autoreply.pm
@@ -0,0 +1,104 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Action::Autoreply;
+require RT::Action::SendEmail;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::SendEmail);
+
+
+# {{{ sub SetRecipients
+
+=head2 SetRecipients
+
+Sets the recipients of this message to this ticket's Requestor.
+
+=cut
+
+
+sub SetRecipients {
+ my $self=shift;
+
+ push(@{$self->{'To'}}, $self->TicketObj->Requestors->MemberEmailAddresses);
+
+ return(1);
+}
+
+# }}}
+
+
+# {{{ sub SetReturnAddress
+
+=head2 SetReturnAddress
+
+Set this message\'s return address to the apropriate queue address
+
+=cut
+
+sub SetReturnAddress {
+ my $self = shift;
+ my %args = ( is_comment => 0,
+ @_
+ );
+
+ my $replyto;
+ if ($args{'is_comment'}) {
+ $replyto = $self->TicketObj->QueueObj->CommentAddress ||
+ $RT::CommentAddress;
+ }
+ else {
+ $replyto = $self->TicketObj->QueueObj->CorrespondAddress ||
+ $RT::CorrespondAddress;
+ }
+
+ unless ($self->TemplateObj->MIMEObj->head->get('From')) {
+ if ($RT::UseFriendlyFromLine) {
+ my $friendly_name = $self->TicketObj->QueueObj->Description ||
+ $self->TicketObj->QueueObj->Name;
+ $friendly_name =~ s/"/\\"/g;
+ $self->SetHeader( 'From',
+ sprintf($RT::FriendlyFromLineFormat,
+ $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
+ );
+ }
+ else {
+ $self->SetHeader( 'From', $replyto );
+ }
+ }
+
+ unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) {
+ $self->SetHeader('Reply-To', "$replyto");
+ }
+
+}
+
+# }}}
+
+eval "require RT::Action::Autoreply_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Vendor.pm});
+eval "require RT::Action::Autoreply_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
new file mode 100644
index 0000000..e1c6f4a
--- /dev/null
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -0,0 +1,566 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Action::CreateTickets;
+require RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::Generic);
+
+use MIME::Entity;
+
+=head1 NAME
+
+ RT::Action::CreateTickets
+
+Create one or more tickets according to an externally supplied template.
+
+
+=head1 SYNOPSIS
+
+ ===Create-Ticket: codereview
+ Subject: Code review for {$Tickets{'TOP'}->Subject}
+ Depended-On-By: TOP
+ Content: Someone has created a ticket. you should review and approve it,
+ so they can finish their work
+ ENDOFCONTENT
+
+=head1 DESCRIPTION
+
+
+Using the "CreateTickets" ScripAction and mandatory dependencies, RT now has
+the ability to model complex workflow. When a ticket is created in a queue
+that has a "CreateTickets" scripaction, that ScripAction parses its "Template"
+
+
+
+=head2 FORMAT
+
+CreateTickets uses the template as a template for an ordered set of tickets
+to create. The basic format is as follows:
+
+
+ ===Create-Ticket: identifier
+ Param: Value
+ Param2: Value
+ Param3: Value
+ Content: Blah
+ blah
+ blah
+ ENDOFCONTENT
+ ===Create-Ticket: id2
+ Param: Value
+ Content: Blah
+ ENDOFCONTENT
+
+
+Each ===Create-Ticket: section is evaluated as its own
+Text::Template object, which means that you can embed snippets
+of perl inside the Text::Template using {} delimiters, but that
+such sections absolutely can not span a ===Create-Ticket boundary.
+
+After each ticket is created, it's stuffed into a hash called %Tickets
+so as to be available during the creation of other tickets during the same
+ScripAction. The hash is prepopulated with the ticket which triggered the
+ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
+shorthand TOP.
+
+A simple example:
+
+ ===Create-Ticket: codereview
+ Subject: Code review for {$Tickets{'TOP'}->Subject}
+ Depended-On-By: TOP
+ Content: Someone has created a ticket. you should review and approve it,
+ so they can finish their work
+ ENDOFCONTENT
+
+
+
+A convoluted example
+
+ ===Create-Ticket: approval
+ { # Find out who the administrators of the group called "HR"
+ # of which the creator of this ticket is a member
+ my $name = "HR";
+
+ my $groups = RT::Groups->new($RT::SystemUser);
+ $groups->LimitToUserDefinedGroups();
+ $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name");
+ $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,
+ );
+
+ my @admins;
+ while (my $admin = $adminccs->Next) {
+ push (@admins, $admin->EmailAddress);
+ }
+ }
+ Queue: Approvals
+ Type: Approval
+ AdminCc: {join ("\nAdminCc: ",@admins) }
+ Depended-On-By: TOP
+ Refers-To: TOP
+ Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
+ Due: {time + 86400}
+ Content-Type: text/plain
+ Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject}
+ Blah
+ Blah
+ ENDOFCONTENT
+ ===Create-Ticket: two
+ Subject: Manager approval
+ Depended-On-By: TOP
+ Refers-On: {$Tickets{"approval"}->Id}
+ Queue: Approvals
+ Content-Type: text/plain
+ Content:
+ Your approval is requred for this ticket, too.
+ ENDOFCONTENT
+
+=head2 Acceptable fields
+
+A complete list of acceptable fields for this beastie:
+
+
+ * Queue => Name or id# of a queue
+ Subject => A text string
+ ! Status => A valid status. defaults to 'new'
+ Due => Dates can be specified in seconds since the epoch
+ to be handled literally or in a semi-free textual
+ format which RT will attempt to parse.
+
+
+
+ Starts =>
+ Started =>
+ Resolved =>
+ Owner => Username or id of an RT user who can and should own
+ this ticket
+ + Requestor => Email address
+ + Cc => Email address
+ + AdminCc => Email address
+ TimeWorked =>
+ TimeEstimated =>
+ TimeLeft =>
+ InitialPriority =>
+ FinalPriority =>
+ Type =>
+ +! DependsOn =>
+ +! DependedOnBy =>
+ +! RefersTo =>
+ +! ReferredToBy =>
+ +! Members =>
+ +! MemberOf =>
+ Content => content. Can extend to multiple lines. Everything
+ within a template after a Content: header is treated
+ as content until we hit a line containing only
+ ENDOFCONTENT
+ ContentType => the content-type of the Content field
+ CustomField-<id#> => custom field value
+
+Fields marked with an * are required.
+
+Fields marked with a + man have multiple values, simply
+by repeating the fieldname on a new line with an additional value.
+
+Fields marked with a ! are postponed to be processed after all
+tickets in the same actions are created. Except for 'Status', those
+field can also take a ticket name within the same action (i.e.
+the identifiers after ==Create-Ticket), instead of raw Ticket ID
+numbers.
+
+When parsed, field names are converted to lowercase and have -s stripped.
+Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
+be treated as the same thing.
+
+
+=begin testing
+
+ok (require RT::Action::CreateTickets);
+use_ok(RT::Scrip);
+use_ok(RT::Template);
+use_ok(RT::ScripAction);
+use_ok(RT::ScripCondition);
+use_ok(RT::Ticket);
+
+my $approvalsq = RT::Queue->new($RT::SystemUser);
+$approvalsq->Create(Name => 'Approvals');
+ok ($approvalsq->Id, "Created Approvals test queue");
+
+
+my $approvals =
+'===Create-Ticket: approval
+{ my $name = "HR";
+ my $groups = RT::Groups->new($RT::SystemUser);
+ $groups->LimitToUserDefinedGroups();
+ $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name");
+ $groups->WithMember($Transaction->CreatorObj->Id);
+
+ my $groupid = $groups->First->Id;
+
+ my $adminccs = RT::Users->new($RT::SystemUser);
+ $adminccs->WhoHaveRight(Right => "AdminGroup", IncludeSystemRights => undef, IncludeSuperusers => 0, IncludeSubgroupMembers => 0, Object => $groups->First);
+
+ my @admins;
+ while (my $admin = $adminccs->Next) {
+ push (@admins, $admin->EmailAddress);
+ }
+}
+Queue: Approvals
+Type: Approval
+AdminCc: {join ("\nAdminCc: ",@admins) }
+Depended-On-By: TOP
+Refers-To: TOP
+Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
+Due: {time + 86400}
+Content-Type: text/plain
+Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject}
+Blah
+Blah
+ENDOFCONTENT
+===Create-Ticket: two
+Subject: Manager approval.
+Depends-On: {$Tickets{"approval"}->Id}
+Queue: Approvals
+Content-Type: text/plain
+Content:
+Your minion approved this ticket. you ok with that?
+ENDOFCONTENT
+';
+
+ok ($approvals =~ /Content/, "Read in the approvals template");
+
+my $apptemp = RT::Template->new($RT::SystemUser);
+$apptemp->Create( Content => $approvals, Name => "Approvals", Queue => "0");
+
+ok ($apptemp->Id);
+
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Create(Name => 'WorkflowTest');
+ok ($q->Id, "Created workflow test queue");
+
+my $scrip = RT::Scrip->new($RT::SystemUser);
+my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Transaction',
+ ScripAction => 'Create Tickets',
+ Template => 'Approvals',
+ Queue => $q->Id);
+ok ($sval, $smsg);
+ok ($scrip->Id, "Created the scrip");
+ok ($scrip->TemplateObj->Id, "Created the scrip template");
+ok ($scrip->ConditionObj->Id, "Created the scrip condition");
+ok ($scrip->ActionObj->Id, "Created the scrip action");
+
+my $t = RT::Ticket->new($RT::SystemUser);
+$t->Create(Subject => "Sample workflow test",
+ Owner => "root",
+ Queue => $q->Id);
+
+
+=end testing
+
+
+=head1 AUTHOR
+
+Jesse Vincent <jesse@bestpractical.com>
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
+
+my %LINKTYPEMAP = (
+ MemberOf => { Type => 'MemberOf',
+ Mode => 'Target', },
+ Members => { 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', },
+
+);
+
+# {{{ Scrip methods (Commit, Prepare)
+
+# {{{ sub Commit
+#Do what we need to do and send it out.
+sub Commit {
+ my $self = shift;
+ my (@links, @postponed);
+
+ # XXX: cargo cult programming that works. i'll be back.
+ use bytes;
+
+ # Create all the tickets we care about
+ return(1) unless $self->TicketObj->Type eq 'ticket';
+
+ %T::Tickets = ();
+
+ foreach my $template_id ( @{ $self->{'template_order'} } ) {
+ $T::Tickets{'TOP'} = $T::TOP = $self->TicketObj;
+ $RT::Logger->debug("Workflow: processing $template_id of $T::TOP");
+
+ $T::ID = $template_id;
+ @T::AllID = @{ $self->{'template_order'} };
+
+ my $template = Text::Template->new(
+ TYPE => 'STRING',
+ SOURCE => $self->{'templates'}->{$template_id}
+ );
+
+ $RT::Logger->debug("Workflow: evaluating\n$self->{templates}{$template_id}");
+
+ my $err;
+ my $filled_in = $template->fill_in( PACKAGE => 'T', BROKEN => sub {
+ $err = { @_ }->{error};
+ } );
+
+ $RT::Logger->debug("Workflow: yielding\n$filled_in");
+
+ if ($err) {
+ $RT::Logger->error("Ticket creation failed for ".$self->TicketObj->Id." ".$err);
+ while (my ($k, $v) = each %T::X) {
+ $RT::Logger->debug("Eliminating $template_id from ${k}'s parents.");
+ delete $v->{$template_id};
+ }
+ next;
+ }
+
+ my %args;
+ my @lines = ( split ( /\n/, $filled_in ) );
+ while ( defined(my $line = shift @lines) ) {
+ if ( $line =~ /^(.*?):(?:\s+(.*))?$/ ) {
+ my $value = $2;
+ my $tag = lc ($1);
+ $tag =~ s/-//g;
+
+ if (ref($args{$tag})) { #If it's an array, we want to push the value
+ push @{$args{$tag}}, $value;
+ }
+ elsif (defined ($args{$tag})) { #if we're about to get a second value, make it an array
+ $args{$tag} = [$args{$tag}, $value];
+ }
+ else { #if there's nothing there, just set the value
+ $args{ $tag } = $value;
+ }
+
+ if ( $tag eq 'content' ) { #just build up the content
+ # convert it to an array
+ $args{$tag} = defined($value) ? [ $value."\n" ] : [];
+ while ( defined(my $l = shift @lines) ) {
+ last if ($l =~ /^ENDOFCONTENT\s*$/) ;
+ push @{$args{'content'}}, $l."\n";
+ }
+ }
+ }
+ }
+
+ foreach my $date qw(due starts started resolved) {
+ my $dateobj = RT::Date->new($RT::SystemUser);
+ next unless $args{$date};
+ if ($args{$date} =~ /^\d+$/) {
+ $dateobj->Set(Format => 'unix', Value => $args{$date});
+ } else {
+ $dateobj->Set(Format => 'unknown', Value => $args{$date});
+ }
+ $args{$date} = $dateobj->ISO;
+ }
+ my $mimeobj = MIME::Entity->new();
+ $mimeobj->build(Type => $args{'contenttype'},
+ Data => $args{'content'});
+ # Now we have a %args to work with.
+ # Make sure we have at least the minimum set of
+ # reasonable data and do our thang
+ $T::Tickets{$template_id} ||= RT::Ticket->new($RT::SystemUser);
+
+ # Deferred processing
+ push @links, (
+ $T::Tickets{$template_id}, {
+ DependsOn => $args{'dependson'},
+ DependedOnBy => $args{'dependedonby'},
+ RefersTo => $args{'refersto'},
+ ReferredToBy => $args{'referredtoby'},
+ Members => $args{'members'},
+ MemberOf => $args{'memberof'},
+ }
+ );
+
+ push @postponed, (
+ # Status is postponed so we don't violate dependencies
+ $T::Tickets{$template_id}, {
+ Status => $args{'status'},
+ }
+ );
+
+ $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses;
+
+ $args{'type'} ||= 'ticket';
+
+ my %ticketargs = ( Queue => $args{'queue'},
+ Subject=> $args{'subject'},
+ Status => 'new',
+ Due => $args{'due'},
+ Starts => $args{'starts'},
+ Started => $args{'started'},
+ Resolved => $args{'resolved'},
+ Owner => $args{'owner'},
+ Requestor => $args{'requestor'},
+ Cc => $args{'cc'},
+ AdminCc=> $args{'admincc'},
+ TimeWorked =>$args{'timeworked'},
+ TimeEstimated =>$args{'timeestimated'},
+ TimeLeft =>$args{'timeleft'},
+ InitialPriority => $args{'initialpriority'},
+ FinalPriority => $args{'finalpriority'},
+ Type => $args{'type'},
+ MIMEObj => $mimeobj);
+
+
+ foreach my $key (keys(%args)) {
+ $key =~ /^customfield(\d+)$/ or next;
+ $ticketargs{ "CustomField-" . $1 } = $args{$key};
+ }
+
+ my ($id, $transid, $msg) = $T::Tickets{$template_id}->Create(%ticketargs);
+ if (!$id) {
+ $RT::Logger->error(
+ "Couldn't create related ticket $template_id for ".
+ $self->TicketObj->Id." ".$msg
+ );
+ next;
+ }
+
+ $RT::Logger->debug("Assigned $template_id with $id");
+ $T::Tickets{$template_id}->SetOriginObj($self->TicketObj)
+ if $T::Tickets{$template_id}->can('SetOriginObj');
+ }
+
+ # postprocessing: add links
+
+ while (my $ticket = shift(@links)) {
+ $RT::Logger->debug("Handling links for " . $ticket->Id);
+ my %args = %{shift(@links)};
+
+ foreach my $type ( keys %LINKTYPEMAP ) {
+ next unless (defined $args{$type});
+ foreach my $link (
+ ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
+ {
+ if (!exists $T::Tickets{$link}) {
+ $RT::Logger->debug("Skipping $type link for $link (non-existent)");
+ next;
+ }
+ $RT::Logger->debug("Building $type link for $link: " . $T::Tickets{$link}->Id);
+ $link = $T::Tickets{$link}->Id;
+
+ my ( $wval, $wmsg ) = $ticket->AddLink(
+ Type => $LINKTYPEMAP{$type}->{'Type'},
+ $LINKTYPEMAP{$type}->{'Mode'} => $link,
+ Silent => 1
+ );
+
+ $RT::Logger->warning("AddLink thru $link failed: $wmsg") unless $wval;
+ # push @non_fatal_errors, $wmsg unless ($wval);
+ }
+
+ }
+ }
+
+ # postponed actions -- Status only, currently
+ while (my $ticket = shift(@postponed)) {
+ $RT::Logger->debug("Handling postponed actions for $ticket");
+ my %args = %{shift(@postponed)};
+
+ $ticket->SetStatus($args{Status}) if defined $args{Status};
+ }
+
+ return(1);
+}
+# }}}
+
+# {{{ sub Prepare
+
+sub Prepare {
+ my $self = shift;
+
+ unless ($self->TemplateObj) {
+ $RT::Logger->warning("No template object handed to $self\n");
+ }
+
+ unless ($self->TransactionObj) {
+ $RT::Logger->warning("No transaction object handed to $self\n");
+
+ }
+
+ unless ($self->TicketObj) {
+ $RT::Logger->warning("No ticket object handed to $self\n");
+
+ }
+
+
+
+
+my $template_id;
+foreach my $line (split(/\n/,$self->TemplateObj->Content)) {
+ if ($line =~ /^===Create-Ticket: (.*)$/) {
+ $template_id = $1;
+ push @{$self->{'template_order'}},$template_id;
+ } else {
+ $self->{'templates'}->{$template_id} .= $line."\n";
+ }
+
+
+}
+
+ return 1;
+
+}
+
+# }}}
+
+# }}}
+
+eval "require RT::Action::CreateTickets_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Vendor.pm});
+eval "require RT::Action::CreateTickets_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm
new file mode 100644
index 0000000..7ed63ae
--- /dev/null
+++ b/rt/lib/RT/Action/EscalatePriority.pm
@@ -0,0 +1,142 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Action::EscalatePriority
+
+=head1 DESCRIPTION
+
+EscalatePriority is a ScripAction which is NOT intended to be called per
+transaction. It's intended to be called by an RT escalation daemon.
+(The daemon is called escalator).
+
+EsclatePriority uses the following formula to change a ticket's priority:
+
+ Priority = Priority + (( FinalPriority - Priority ) / ( DueDate-Today))
+
+Unless the duedate is past, in which case priority gets bumped straight
+to final priority.
+
+In this way, priority is either increased or decreased toward the final priority
+as the ticket heads toward its due date.
+
+
+=cut
+
+
+package RT::Action::EscalatePriority;
+require RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA=qw(RT::Action::Generic);
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return (ref $self . " will move a ticket's priority toward its final priority.");
+}
+# }}}
+
+
+# {{{ sub Prepare
+sub Prepare {
+ my $self = shift;
+
+ if ($self->TicketObj->Priority() == $self->TicketObj->FinalPriority()) {
+ # 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;
+ }
+ }
+
+ # 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();
+ }
+
+ }
+ return 1;
+}
+# }}}
+
+sub Commit {
+ my $self = shift;
+ my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'});
+
+ unless ($val) {
+ $RT::Logger->debug($self . " $msg\n");
+ }
+}
+
+eval "require RT::Action::EscalatePriority_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/EscalatePriority_Vendor.pm});
+eval "require RT::Action::EscalatePriority_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/EscalatePriority_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm
new file mode 100755
index 0000000..007d299
--- /dev/null
+++ b/rt/lib/RT/Action/Generic.pm
@@ -0,0 +1,195 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Action::Generic - a generic baseclass for RT Actions
+
+=head1 SYNOPSIS
+
+ use RT::Action::Generic;
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=begin testing
+
+ok (require RT::Action::Generic);
+
+=end testing
+
+=cut
+
+package RT::Action::Generic;
+
+use strict;
+
+# {{{ sub new
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ $self->_Init(@_);
+ return $self;
+}
+# }}}
+
+# {{{ sub new
+sub loc {
+ my $self = shift;
+ return $self->{'ScripObj'}->loc(@_);
+}
+# }}}
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ my %args = ( TransactionObj => undef,
+ TicketObj => undef,
+ ScripObj => undef,
+ TemplateObj => undef,
+ Argument => undef,
+ Type => undef,
+ @_ );
+
+
+ $self->{'Argument'} = $args{'Argument'};
+ $self->{'ScripObj'} = $args{'ScripObj'};
+ $self->{'TicketObj'} = $args{'TicketObj'};
+ $self->{'TransactionObj'} = $args{'TransactionObj'};
+ $self->{'TemplateObj'} = $args{'TemplateObj'};
+ $self->{'Type'} = $args{'Type'};
+}
+# }}}
+
+# Access Scripwide data
+
+# {{{ sub Argument
+sub Argument {
+ my $self = shift;
+ return($self->{'Argument'});
+}
+# }}}
+
+# {{{ sub TicketObj
+sub TicketObj {
+ my $self = shift;
+ return($self->{'TicketObj'});
+}
+# }}}
+
+# {{{ sub TransactionObj
+sub TransactionObj {
+ my $self = shift;
+ return($self->{'TransactionObj'});
+}
+# }}}
+
+# {{{ sub TemplateObj
+sub TemplateObj {
+ my $self = shift;
+ return($self->{'TemplateObj'});
+}
+# }}}
+
+# {{{ sub ScripObj
+sub ScripObj {
+ my $self = shift;
+ return($self->{'ScripObj'});
+}
+# }}}
+
+# {{{ sub Type
+sub Type {
+ my $self = shift;
+ return($self->{'Type'});
+}
+# }}}
+
+
+# Scrip methods
+
+#Do what we need to do and send it out.
+
+# {{{ sub Commit
+sub Commit {
+ my $self = shift;
+ return(0, $self->loc("Commit Stubbed"));
+}
+# }}}
+
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return $self->loc("No description for [_1]", ref $self);
+}
+# }}}
+
+
+#Parse the templates, get things ready to go.
+
+# {{{ sub Prepare
+sub Prepare {
+ my $self = shift;
+ return (0, $self->loc("Prepare Stubbed"));
+}
+# }}}
+
+
+#If this rule applies to this transaction, return true.
+
+# {{{ sub IsApplicable
+sub IsApplicable {
+ my $self = shift;
+ return(undef);
+}
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+ my $self = shift;
+
+ # We need to clean up all the references that might maybe get
+ # oddly circular
+ $self->{'TemplateObj'} =undef
+ $self->{'TicketObj'} = undef;
+ $self->{'TransactionObj'} = undef;
+ $self->{'ScripObj'} = undef;
+
+
+
+}
+
+# }}}
+
+eval "require RT::Action::Generic_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Vendor.pm});
+eval "require RT::Action::Generic_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm
new file mode 100755
index 0000000..1e4e4c0
--- /dev/null
+++ b/rt/lib/RT/Action/Notify.pm
@@ -0,0 +1,132 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Action::Notify;
+require RT::Action::SendEmail;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::SendEmail);
+
+# {{{ sub SetRecipients
+
+=head2 SetRecipients
+
+Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All.
+Explicitly B<does not> notify the creator of the transaction by default
+
+=cut
+
+sub SetRecipients {
+ my $self = shift;
+
+ my $arg = $self->Argument;
+
+ $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/;
+
+ my ( @To, @PseudoTo, @Cc, @Bcc );
+
+
+ if ($arg =~ /\bOtherRecipients\b/) {
+ if ($self->TransactionObj->Attachments->First) {
+ push (@Cc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
+ push (@Bcc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
+ }
+ }
+
+ if ( $arg =~ /\bRequestor\b/ ) {
+ push ( @To, $self->TicketObj->Requestors->MemberEmailAddresses );
+ }
+
+
+
+ if ( $arg =~ /\bCc\b/ ) {
+
+ #If we have a To, make the Ccs, Ccs, otherwise, promote them to To
+ if (@To) {
+ push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
+ push ( @Cc, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses );
+ }
+ else {
+ push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
+ push ( @To, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses );
+ }
+ }
+
+ if ( ( $arg =~ /\bOwner\b/ )
+ && ( $self->TicketObj->OwnerObj->id != $RT::Nobody->id ) )
+ {
+
+ # If we're not sending to Ccs or requestors,
+ # then the Owner can be the To.
+ if (@To) {
+ push ( @Bcc, $self->TicketObj->OwnerObj->EmailAddress );
+ }
+ else {
+ push ( @To, $self->TicketObj->OwnerObj->EmailAddress );
+ }
+
+ }
+
+ if ( $arg =~ /\bAdminCc\b/ ) {
+ push ( @Bcc, $self->TicketObj->AdminCc->MemberEmailAddresses );
+ push ( @Bcc, $self->TicketObj->QueueObj->AdminCc->MemberEmailAddresses );
+ }
+
+ if ($RT::UseFriendlyToLine) {
+ unless (@To) {
+ push (
+ @PseudoTo,
+ sprintf($RT::FriendlyToLineFormat, $arg, $self->TicketObj->id),
+ );
+ }
+ }
+
+ my $creator = $self->TransactionObj->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
+ if ($RT::NotifyActor) {
+ @{ $self->{'To'} } = @To;
+ @{ $self->{'Cc'} } = @Cc;
+ @{ $self->{'Bcc'} } = @Bcc;
+ }
+ else {
+ @{ $self->{'To'} } = grep ( !/^$creator$/, @To );
+ @{ $self->{'Cc'} } = grep ( !/^$creator$/, @Cc );
+ @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc );
+ }
+ @{ $self->{'PseudoTo'} } = @PseudoTo;
+ return (1);
+
+}
+
+# }}}
+
+eval "require RT::Action::Notify_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Vendor.pm});
+eval "require RT::Action::Notify_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm
new file mode 100755
index 0000000..210e4ab
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyAsComment.pm
@@ -0,0 +1,55 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Action::NotifyAsComment;
+require RT::Action::Notify;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::Notify);
+
+
+=head2 SetReturnAddress
+
+Tell SendEmail that this message should come out as a comment.
+Calls SUPER::SetReturnAddress.
+
+=cut
+
+sub SetReturnAddress {
+ my $self = shift;
+
+ # Tell RT::Action::SendEmail that this should come
+ # from the relevant comment email address.
+ $self->{'comment'} = 1;
+
+ return($self->SUPER::SetReturnAddress(is_comment => 1));
+}
+
+eval "require RT::Action::NotifyAsComment_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyAsComment_Vendor.pm});
+eval "require RT::Action::NotifyAsComment_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyAsComment_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm
new file mode 100644
index 0000000..02ff3a5
--- /dev/null
+++ b/rt/lib/RT/Action/ResolveMembers.pm
@@ -0,0 +1,88 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# This Action will resolve all members of a resolved group ticket
+
+package RT::Action::ResolveMembers;
+require RT::Action::Generic;
+require RT::Links;
+
+use strict;
+use vars qw/@ISA/;
+@ISA=qw(RT::Action::Generic);
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return $self->loc("[_1] will resolve all members of a resolved group ticket.", ref $self);
+}
+# }}}
+
+
+# {{{ sub Prepare
+sub Prepare {
+ # nothing to prepare
+ return 1;
+}
+# }}}
+
+sub Commit {
+ my $self = shift;
+
+ my $Links=RT::Links->new($RT::SystemUser);
+ $Links->Limit(FIELD => 'Type', VALUE => 'MemberOf');
+ $Links->Limit(FIELD => 'Target', VALUE => $self->TicketObj->id);
+
+ while (my $Link=$Links->Next()) {
+ # Todo: Try to deal with remote URIs as well
+ next unless $Link->BaseURI->IsLocal;
+ my $base=RT::Ticket->new($self->TicketObj->CurrentUser);
+ # Todo: Only work if Base is a plain ticket num:
+ $base->Load($Link->Base);
+ # I'm afraid this might be a major bottleneck if ResolveGroupTicket is on.
+ $base->Resolve;
+ }
+}
+
+
+# Applicability checked in Commit.
+
+# {{{ sub IsApplicable
+sub IsApplicable {
+ my $self = shift;
+ 1;
+ return 1;
+}
+# }}}
+
+eval "require RT::Action::ResolveMembers_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/ResolveMembers_Vendor.pm});
+eval "require RT::Action::ResolveMembers_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/ResolveMembers_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
new file mode 100755
index 0000000..6592380
--- /dev/null
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -0,0 +1,685 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
+
+package RT::Action::SendEmail;
+require RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::Generic);
+
+use MIME::Words qw(encode_mimeword);
+
+use RT::EmailParser;
+
+=head1 NAME
+
+RT::Action::SendEmail - An Action which users can use to send mail
+or can subclassed for more specialized mail sending behavior.
+RT::Action::AutoReply is a good example subclass.
+
+=head1 SYNOPSIS
+
+ require RT::Action::SendEmail;
+ @ISA = qw(RT::Action::SendEmail);
+
+
+=head1 DESCRIPTION
+
+Basically, you create another module RT::Action::YourAction which ISA
+RT::Action::SendEmail.
+
+If you want to set the recipients of the mail to something other than
+the addresses mentioned in the To, Cc, Bcc and headers in
+the template, you should subclass RT::Action::SendEmail and override
+either the SetRecipients method or the SetTo, SetCc, etc methods (see
+the comments for the SetRecipients sub).
+
+
+=begin testing
+
+ok (require RT::Action::SendEmail);
+
+=end testing
+
+
+=head1 AUTHOR
+
+Jesse Vincent <jesse@bestpractical.com> and Tobias Brox <tobix@cpan.org>
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
+
+# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
+
+# {{{ sub _Init
+# We use _Init from RT::Action
+# }}}
+
+# {{{ sub Commit
+#Do what we need to do and send it out.
+sub Commit {
+ my $self = shift;
+
+ my $MIMEObj = $self->TemplateObj->MIMEObj;
+ my $msgid = $MIMEObj->head->get('Message-Id');
+ chomp $msgid;
+ $RT::Logger->info($msgid." #".$self->TicketObj->id."/".$self->TransactionObj->id." - Scrip ". $self->ScripObj->id ." ".$self->ScripObj->Description);
+ #send the email
+
+ # Weed out any RT addresses. We really don't want to talk to ourselves!
+ @{$self->{'To'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'To'}});
+ @{$self->{'Cc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Cc'}});
+ @{$self->{'Bcc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Bcc'}});
+ # If there are no recipients, don't try to send the message.
+ # If the transaction has content and has the header RT-Squelch-Replies-To
+
+ if ( defined $self->TransactionObj->Attachments->First() ) {
+
+ my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To');
+
+ if ($squelch) {
+ my @blacklist = split ( /,/, $squelch );
+
+ # Cycle through the people we're sending to and pull out anyone on the
+ # system blacklist
+
+ foreach my $person_to_yank (@blacklist) {
+ $person_to_yank =~ s/\s//g;
+ @{ $self->{'To'} } =
+ grep ( !/^$person_to_yank$/, @{ $self->{'To'} } );
+ @{ $self->{'Cc'} } =
+ grep ( !/^$person_to_yank$/, @{ $self->{'Cc'} } );
+ @{ $self->{'Bcc'} } =
+ grep ( !/^$person_to_yank$/, @{ $self->{'Bcc'} } );
+ }
+ }
+ }
+
+ # Go add all the Tos, Ccs and Bccs that we need to to the message to
+ # make it happy, but only if we actually have values in those arrays.
+
+ $self->SetHeader( 'To', join ( ',', @{ $self->{'To'} } ) )
+ if ( $self->{'To'} && @{ $self->{'To'} } );
+ $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) )
+ if ( $self->{'Cc'} && @{ $self->{'Cc'} } );
+ $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) )
+ if ( $self->{'Bcc'} && @{ $self->{'Bcc'} } );
+
+
+ $self->SetHeader('MIME-Version', '1.0');
+
+ # try to convert message body from utf-8 to $RT::EmailOutputEncoding
+ $self->SetHeader( 'Content-Type', 'text/plain; charset="utf-8"' );
+
+ RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
+ $self->SetHeader( 'Content-Type', 'text/plain; charset="' . $RT::EmailOutputEncoding . '"' );
+
+
+ # Build up a MIME::Entity that looks like the original message.
+
+ my $do_attach = $self->TemplateObj->MIMEObj->head->get('RT-Attach-Message');
+
+ if ($do_attach) {
+ $self->TemplateObj->MIMEObj->head->delete('RT-Attach-Message');
+
+ my $attachments = RT::Attachments->new($RT::SystemUser);
+ $attachments->Limit( FIELD => 'TransactionId',
+ VALUE => $self->TransactionObj->Id );
+ $attachments->OrderBy('id');
+
+ my $transaction_content_obj = $self->TransactionObj->ContentObj;
+
+ # attach any of this transaction's attachments
+ while ( my $attach = $attachments->Next ) {
+
+ # Don't attach anything blank
+ next unless ( $attach->ContentLength );
+
+ # We want to make sure that we don't include the attachment that's being sued as the "Content" of this message"
+ next
+ if ( $transaction_content_obj
+ && $transaction_content_obj->Id == $attach->Id
+ && $transaction_content_obj->ContentType =~ qr{text/plain}i
+ );
+ $MIMEObj->make_multipart('mixed');
+ $MIMEObj->attach( Type => $attach->ContentType,
+ Charset => $attach->OriginalEncoding,
+ Data => $attach->OriginalContent,
+ Filename => $self->MIMEEncodeString( $attach->Filename, $RT::EmailOutputEncoding ),
+ Encoding => '-SUGGEST');
+ }
+
+ }
+
+
+ my $retval = $self->SendMessage($MIMEObj);
+
+
+ return ($retval);
+}
+
+# }}}
+
+# {{{ sub Prepare
+
+sub Prepare {
+ my $self = shift;
+
+ # This actually populates the MIME::Entity fields in the Template Object
+
+ unless ( $self->TemplateObj ) {
+ $RT::Logger->warning("No template object handed to $self\n");
+ }
+
+ unless ( $self->TransactionObj ) {
+ $RT::Logger->warning("No transaction object handed to $self\n");
+
+ }
+
+ unless ( $self->TicketObj ) {
+ $RT::Logger->warning("No ticket object handed to $self\n");
+
+ }
+
+ my ( $result, $message ) = $self->TemplateObj->Parse(
+ Argument => $self->Argument,
+ TicketObj => $self->TicketObj,
+ TransactionObj => $self->TransactionObj
+ );
+ if ($result) {
+
+ # Header
+ $self->SetSubject();
+ $self->SetSubjectToken();
+ $self->SetRecipients();
+ $self->SetReturnAddress();
+ $self->SetRTSpecialHeaders();
+ if ($RT::EmailOutputEncoding) {
+
+ # l10n related header
+ $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding );
+ }
+ }
+
+ return $result;
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ SendMessage
+=head2 SendMessage MIMEObj
+
+sends the message using RT's preferred API.
+TODO: Break this out to a seperate module
+
+=cut
+
+sub SendMessage {
+ my $self = shift;
+ my $MIMEObj = shift;
+
+ my $msgid = $MIMEObj->head->get('Message-Id');
+
+
+ #If we don't have any recipients to send to, don't send a message;
+ unless ( $MIMEObj->head->get('To')
+ || $MIMEObj->head->get('Cc')
+ || $MIMEObj->head->get('Bcc') ) {
+ $RT::Logger->info($msgid. " No recipients found. Not sending.\n");
+ return (1);
+ }
+
+ # PseudoTo (fake to headers) shouldn't get matched for message recipients.
+ # If we don't have any 'To' header, drop in the pseudo-to header.
+
+ $self->SetHeader( 'To', join ( ',', @{ $self->{'PseudoTo'} } ) )
+ if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
+ and ( !$MIMEObj->head->get('To') ) );
+ if ( $RT::MailCommand eq 'sendmailpipe' ) {
+ eval {
+ open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!;
+ print MAIL $MIMEObj->as_string;
+ close(MAIL);
+ };
+ if ($@) {
+ $RT::Logger->crit($msgid. "Could not send mail. -".$@ );
+ }
+ }
+ else {
+ my @mailer_args = ($RT::MailCommand);
+ local $ENV{MAILADDRESS};
+
+ if ( $RT::MailCommand eq 'sendmail' ) {
+ push @mailer_args, $RT::SendmailArguments;
+ }
+ elsif ( $RT::MailCommand eq 'smtp' ) {
+ $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
+ push @mailer_args, (Server => $RT::SMTPServer);
+ push @mailer_args, (Debug => $RT::SMTPDebug);
+ }
+ else {
+ push @mailer_args, $RT::MailParams;
+ }
+
+ unless ( $MIMEObj->send( @mailer_args ) ) {
+ $RT::Logger->crit($msgid. "Could not send mail." );
+ return (0);
+ }
+ }
+
+
+ my $success = ($msgid. " sent To: ".$MIMEObj->head->get('To') . " Cc: ".$MIMEObj->head->get('Cc') . " Bcc: ".$MIMEObj->head->get('Bcc'));
+ $success =~ s/\n//gi;
+ $RT::Logger->info($success);
+
+ return (1);
+}
+
+# }}}
+
+# {{{ Deal with message headers (Set* subs, designed for easy overriding)
+
+# {{{ sub SetRTSpecialHeaders
+
+=head2 SetRTSpecialHeaders
+
+This routine adds all the random headers that RT wants in a mail message
+that don't matter much to anybody else.
+
+=cut
+
+sub SetRTSpecialHeaders {
+ my $self = shift;
+
+ $self->SetReferences();
+
+ $self->SetMessageID();
+
+ $self->SetPrecedence();
+
+ $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
+ $self->SetHeader( 'RT-Ticket',
+ $RT::rtname . " #" . $self->TicketObj->id() );
+ $self->SetHeader( 'Managed-by',
+ "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
+
+ $self->SetHeader( 'RT-Originator',
+ $self->TransactionObj->CreatorObj->EmailAddress );
+ return ();
+
+}
+
+# {{{ sub SetReferences
+
+=head2 SetReferences
+
+ # This routine will set the References: and In-Reply-To headers,
+# autopopulating it with all the correspondence on this ticket so
+# far. This should make RT responses threadable.
+
+=cut
+
+sub SetReferences {
+ my $self = shift;
+
+ # TODO: this one is broken. What is this email really a reply to?
+ # If it's a reply to an incoming message, we'll need to use the
+ # actual message-id from the appropriate Attachment object. For
+ # incoming mails, we would like to preserve the In-Reply-To and/or
+ # References.
+
+ $self->SetHeader( 'In-Reply-To',
+ "<rt-" . $self->TicketObj->id() . "\@" . $RT::rtname . ">" );
+
+ # TODO We should always add References headers for all message-ids
+ # of previous messages related to this ticket.
+}
+
+# }}}
+
+# {{{ sub SetMessageID
+
+=head2 SetMessageID
+
+Without this one, threading won't work very nice in email agents.
+Anyway, I'm not really sure it's that healthy if we need to send
+several separate/different emails about the same transaction.
+
+=cut
+
+sub SetMessageID {
+ my $self = shift;
+
+ # TODO this one might be sort of broken. If we have several scrips +++
+ # sending several emails to several different persons, we need to
+ # pull out different message-ids. I'd suggest message ids like
+ # "rt-ticket#-transaction#-scrip#-receipient#"
+
+ $self->SetHeader( 'Message-ID',
+ "<rt-"
+ . $RT::VERSION ."-"
+ . $self->TicketObj->id() . "-"
+ . $self->TransactionObj->id() . "."
+ . rand(20) . "\@"
+ . $RT::Organization . ">" )
+ unless $self->TemplateObj->MIMEObj->head->get('Message-ID');
+}
+
+# }}}
+
+# }}}
+
+# {{{ sub SetReturnAddress
+
+=head2 SetReturnAddress is_comment => BOOLEAN
+
+Calculate and set From and Reply-To headers based on the is_comment flag.
+
+=cut
+
+sub SetReturnAddress {
+
+ my $self = shift;
+ my %args = ( is_comment => 0,
+ @_ );
+
+ # From and Reply-To
+ # $args{is_comment} should be set if the comment address is to be used.
+ my $replyto;
+
+ if ( $args{'is_comment'} ) {
+ $replyto = $self->TicketObj->QueueObj->CommentAddress
+ || $RT::CommentAddress;
+ }
+ else {
+ $replyto = $self->TicketObj->QueueObj->CorrespondAddress
+ || $RT::CorrespondAddress;
+ }
+
+ unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
+ if ($RT::UseFriendlyFromLine) {
+ my $friendly_name = $self->TransactionObj->CreatorObj->RealName;
+ if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
+ $friendly_name = $1;
+ }
+
+ $friendly_name =~ s/"/\\"/g;
+ $self->SetHeader( 'From',
+ sprintf($RT::FriendlyFromLineFormat,
+ $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
+ );
+ }
+ else {
+ $self->SetHeader( 'From', $replyto );
+ }
+ }
+
+ unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
+ $self->SetHeader( 'Reply-To', "$replyto" );
+ }
+
+}
+
+# }}}
+
+# {{{ sub SetHeader
+
+=head2 SetHeader FIELD, VALUE
+
+Set the FIELD of the current MIME object into VALUE.
+
+=cut
+
+sub SetHeader {
+ my $self = shift;
+ my $field = shift;
+ my $val = shift;
+
+ chomp $val;
+ chomp $field;
+ $self->TemplateObj->MIMEObj->head->fold_length( $field, 10000 );
+ $self->TemplateObj->MIMEObj->head->replace( $field, $val );
+ return $self->TemplateObj->MIMEObj->head->get($field);
+}
+
+# }}}
+
+# {{{ sub SetRecipients
+
+=head2 SetRecipients
+
+Dummy method to be overriden by subclasses which want to set the recipients.
+
+=cut
+
+sub SetRecipients {
+ my $self = shift;
+ return ();
+}
+
+# }}}
+
+# {{{ sub SetTo
+
+=head2 SetTo
+
+Takes a string that is the addresses you want to send mail to
+
+=cut
+
+sub SetTo {
+ my $self = shift;
+ my $addresses = shift;
+ return $self->SetHeader( 'To', $addresses );
+}
+
+# }}}
+
+# {{{ sub SetCc
+
+=head2 SetCc
+
+Takes a string that is the addresses you want to Cc
+
+=cut
+
+sub SetCc {
+ my $self = shift;
+ my $addresses = shift;
+
+ return $self->SetHeader( 'Cc', $addresses );
+}
+
+# }}}
+
+# {{{ sub SetBcc
+
+=head2 SetBcc
+
+Takes a string that is the addresses you want to Bcc
+
+=cut
+
+sub SetBcc {
+ my $self = shift;
+ my $addresses = shift;
+
+ return $self->SetHeader( 'Bcc', $addresses );
+}
+
+# }}}
+
+# {{{ sub SetPrecedence
+
+sub SetPrecedence {
+ my $self = shift;
+
+ unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ) {
+ $self->SetHeader( 'Precedence', "bulk" );
+ }
+}
+
+# }}}
+
+# {{{ sub SetSubject
+
+=head2 SetSubject
+
+This routine sets the subject. it does not add the rt tag. that gets done elsewhere
+If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
+the transaction's subject.
+
+=cut
+
+sub SetSubject {
+ my $self = shift;
+ my $subject;
+
+ unless ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
+ my $message = $self->TransactionObj->Attachments;
+ my $ticket = $self->TicketObj->Id;
+
+ if ( $self->{'Subject'} ) {
+ $subject = $self->{'Subject'};
+ }
+ elsif ( ( $message->First() )
+ && ( $message->First->Headers ) ) {
+ my $header = $message->First->Headers();
+ $header =~ s/\n\s+/ /g;
+ if ( $header =~ /^Subject: (.*?)$/m ) {
+ $subject = $1;
+ }
+ else {
+ $subject = $self->TicketObj->Subject();
+ }
+
+ }
+ else {
+ $subject = $self->TicketObj->Subject();
+ }
+
+ $subject =~ s/(\r\n|\n|\s)/ /gi;
+
+ chomp $subject;
+ $self->SetHeader( 'Subject', $subject );
+
+ }
+ return ($subject);
+}
+
+# }}}
+
+# {{{ sub SetSubjectToken
+
+=head2 SetSubjectToken
+
+This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
+
+=cut
+
+sub SetSubjectToken {
+ my $self = shift;
+ my $tag = "[$RT::rtname #" . $self->TicketObj->id . "]";
+ my $sub = $self->TemplateObj->MIMEObj->head->get('Subject');
+ unless ( $sub =~ /\Q$tag\E/ ) {
+ $sub =~ s/(\r\n|\n|\s)/ /gi;
+ chomp $sub;
+ $self->TemplateObj->MIMEObj->head->replace( 'Subject', "$tag $sub" );
+ }
+}
+
+# }}}
+
+# }}}
+
+# {{{
+
+=head2 SetHeaderAsEncoding($field_name, $charset_encoding)
+
+This routine converts the field into specified charset encoding.
+
+=cut
+
+sub SetHeaderAsEncoding {
+ my $self = shift;
+ my ( $field, $enc ) = ( shift, shift );
+
+ if ($field eq 'From' and $RT::SMTPFrom) {
+ $self->TemplateObj->MIMEObj->head->replace( $field, $RT::SMTPFrom );
+ return;
+ }
+
+ my $value = $self->TemplateObj->MIMEObj->head->get($field);
+
+ # don't bother if it's us-ascii
+
+ # See RT::I18N, 'NOTES: Why Encode::_utf8_off before Encode::from_to'
+
+ $value = $self->MIMEEncodeString($value, $enc);
+
+ $self->TemplateObj->MIMEObj->head->replace( $field, $value );
+
+
+}
+# }}}
+
+# {{{ MIMENcodeString
+
+=head2 MIMEEncodeString STRING ENCODING
+
+Takes a string and a possible encoding and returns the string wrapped in MIME goo.
+
+=cut
+
+sub MIMEEncodeString {
+ my $self = shift;
+ my $value = shift;
+ my $enc = shift;
+
+ chomp $value;
+ return ($value) unless $value =~ /[^\x20-\x7e]/;
+
+ $value =~ s/\s*$//;
+ Encode::_utf8_off($value);
+ my $res = Encode::from_to( $value, "utf-8", $enc );
+ $value = encode_mimeword( $value, 'B', $enc );
+}
+
+# }}}
+
+eval "require RT::Action::SendEmail_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm});
+eval "require RT::Action::SendEmail_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm
new file mode 100644
index 0000000..515eeb5
--- /dev/null
+++ b/rt/lib/RT/Action/SetPriority.pm
@@ -0,0 +1,61 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Action::SetPriority;
+require RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA=qw(RT::Action::Generic);
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return (ref $self . " will set a ticket's priority to the argument provided.");
+}
+# }}}
+
+
+# {{{ sub Prepare
+sub Prepare {
+ # nothing to prepare
+ return 1;
+}
+# }}}
+
+sub Commit {
+ my $self = shift;
+ $self->TicketObj->SetPriority($self->Argument);
+
+}
+
+eval "require RT::Action::SetPriority_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SetPriority_Vendor.pm});
+eval "require RT::Action::SetPriority_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SetPriority_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm
new file mode 100644
index 0000000..e2e3d72
--- /dev/null
+++ b/rt/lib/RT/Action/UserDefined.pm
@@ -0,0 +1,71 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+package RT::Action::UserDefined;
+use RT::Action::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Action::Generic);
+
+=head2 Prepare
+
+This happens on every transaction. it's always applicable
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+ my $retval = eval $self->ScripObj->CustomPrepareCode;
+ if ($@) {
+ $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Prepare failed: ".$@);
+ return (undef);
+ }
+ return ($retval);
+}
+
+=head2 Commit
+
+This happens on every transaction. it's always applicable
+
+=cut
+
+sub Commit {
+ my $self = shift;
+ my $retval = eval $self->ScripObj->CustomCommitCode;
+ if ($@) {
+ $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Commit failed: ".$@);
+ return (undef);
+ }
+ return ($retval);
+}
+
+eval "require RT::Action::UserDefined_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/UserDefined_Vendor.pm});
+eval "require RT::Action::UserDefined_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/UserDefined_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm
new file mode 100755
index 0000000..2ed5201
--- /dev/null
+++ b/rt/lib/RT/Attachment.pm
@@ -0,0 +1,372 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Attachment
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Attachment;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Attachments');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'TransactionId'.
+ int(11) 'Parent'.
+ varchar(160) 'MessageId'.
+ varchar(255) 'Subject'.
+ varchar(255) 'Filename'.
+ varchar(80) 'ContentType'.
+ varchar(80) 'ContentEncoding'.
+ longtext 'Content'.
+ longtext 'Headers'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ TransactionId => '0',
+ Parent => '0',
+ MessageId => '',
+ Subject => '',
+ Filename => '',
+ ContentType => '',
+ ContentEncoding => '',
+ Content => '',
+ Headers => '',
+
+ @_);
+ $self->SUPER::Create(
+ TransactionId => $args{'TransactionId'},
+ Parent => $args{'Parent'},
+ MessageId => $args{'MessageId'},
+ Subject => $args{'Subject'},
+ Filename => $args{'Filename'},
+ ContentType => $args{'ContentType'},
+ ContentEncoding => $args{'ContentEncoding'},
+ Content => $args{'Content'},
+ Headers => $args{'Headers'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item TransactionId
+
+Returns the current value of TransactionId.
+(In the database, TransactionId is stored as int(11).)
+
+
+
+=item SetTransactionId VALUE
+
+
+Set TransactionId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TransactionId will be stored as a int(11).)
+
+
+=cut
+
+
+=item Parent
+
+Returns the current value of Parent.
+(In the database, Parent is stored as int(11).)
+
+
+
+=item SetParent VALUE
+
+
+Set Parent to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Parent will be stored as a int(11).)
+
+
+=cut
+
+
+=item MessageId
+
+Returns the current value of MessageId.
+(In the database, MessageId is stored as varchar(160).)
+
+
+
+=item SetMessageId VALUE
+
+
+Set MessageId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, MessageId will be stored as a varchar(160).)
+
+
+=cut
+
+
+=item Subject
+
+Returns the current value of Subject.
+(In the database, Subject is stored as varchar(255).)
+
+
+
+=item SetSubject VALUE
+
+
+Set Subject to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Subject will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Filename
+
+Returns the current value of Filename.
+(In the database, Filename is stored as varchar(255).)
+
+
+
+=item SetFilename VALUE
+
+
+Set Filename to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Filename will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item ContentType
+
+Returns the current value of ContentType.
+(In the database, ContentType is stored as varchar(80).)
+
+
+
+=item SetContentType VALUE
+
+
+Set ContentType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ContentType will be stored as a varchar(80).)
+
+
+=cut
+
+
+=item ContentEncoding
+
+Returns the current value of ContentEncoding.
+(In the database, ContentEncoding is stored as varchar(80).)
+
+
+
+=item SetContentEncoding VALUE
+
+
+Set ContentEncoding to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ContentEncoding will be stored as a varchar(80).)
+
+
+=cut
+
+
+=item Content
+
+Returns the current value of Content.
+(In the database, Content is stored as longtext.)
+
+
+
+=item SetContent VALUE
+
+
+Set Content to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Content will be stored as a longtext.)
+
+
+=cut
+
+
+=item Headers
+
+Returns the current value of Headers.
+(In the database, Headers is stored as longtext.)
+
+
+
+=item SetHeaders VALUE
+
+
+Set Headers to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Headers will be stored as a longtext.)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ TransactionId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Parent =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ MessageId =>
+ {read => 1, write => 1, type => 'varchar(160)', default => ''},
+ Subject =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Filename =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ ContentType =>
+ {read => 1, write => 1, type => 'varchar(80)', default => ''},
+ ContentEncoding =>
+ {read => 1, write => 1, type => 'varchar(80)', default => ''},
+ Content =>
+ {read => 1, write => 1, type => 'longtext', default => ''},
+ Headers =>
+ {read => 1, write => 1, type => 'longtext', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Attachment_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Attachment_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Attachment_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachment_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Attachment_Overlay, RT::Attachment_Vendor, RT::Attachment_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Attachment_Overlay.pm b/rt/lib/RT/Attachment_Overlay.pm
new file mode 100644
index 0000000..9086c52
--- /dev/null
+++ b/rt/lib/RT/Attachment_Overlay.pm
@@ -0,0 +1,592 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 SYNOPSIS
+
+ use RT::Attachment;
+
+
+=head1 DESCRIPTION
+
+This module should never be instantiated directly by client code. it's an internal
+module which should only be instantiated through exported APIs in Ticket, Queue and other
+similar objects.
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Attachment);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use MIME::Base64;
+use MIME::QuotedPrint;
+
+# {{{ sub _ClassAccessible
+sub _ClassAccessible {
+ {
+ TransactionId => { 'read'=>1, 'public'=>1, },
+ MessageId => { 'read'=>1, },
+ Parent => { 'read'=>1, },
+ ContentType => { 'read'=>1, },
+ Subject => { 'read'=>1, },
+ Content => { 'read'=>1, },
+ ContentEncoding => { 'read'=>1, },
+ Headers => { 'read'=>1, },
+ Filename => { 'read'=>1, },
+ Creator => { 'read'=>1, 'auto'=>1, },
+ Created => { 'read'=>1, 'auto'=>1, },
+ };
+}
+# }}}
+
+# {{{ sub TransactionObj
+
+=head2 TransactionObj
+
+Returns the transaction object asscoiated with this attachment.
+
+=cut
+
+sub TransactionObj {
+ require RT::Transaction;
+ my $self=shift;
+ unless (exists $self->{_TransactionObj}) {
+ $self->{_TransactionObj}=RT::Transaction->new($self->CurrentUser);
+ $self->{_TransactionObj}->Load($self->TransactionId);
+ }
+ return $self->{_TransactionObj};
+}
+
+# }}}
+
+# {{{ sub Create
+
+=head2 Create
+
+Create a new attachment. Takes a paramhash:
+
+ 'Attachment' Should be a single MIME body with optional subparts
+ 'Parent' is an optional Parent RT::Attachment object
+ 'TransactionId' is the mandatory id of the Transaction this attachment is associated with.;
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my ($id);
+ my %args = ( id => 0,
+ TransactionId => 0,
+ Parent => 0,
+ Attachment => undef,
+ @_ );
+
+ #For ease of reference
+ my $Attachment = $args{'Attachment'};
+
+ #if we didn't specify a ticket, we need to bail
+ if ( $args{'TransactionId'} == 0 ) {
+ $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction\n" );
+ return (0);
+
+ }
+
+ #If we possibly can, collapse it to a singlepart
+ $Attachment->make_singlepart;
+
+ #Get the subject
+ my $Subject = $Attachment->head->get( 'subject', 0 );
+ defined($Subject) or $Subject = '';
+ chomp($Subject);
+
+ #Get the filename
+ my $Filename = $Attachment->head->recommended_filename || eval {
+ ${ $Attachment->head->{mail_hdr_hash}{'Content-Disposition'}[0] }
+ =~ /^.*\bfilename="(.*)"$/ ? $1 : ''
+ };
+
+ # If a message has no bodyhandle, that means that it has subparts (or appears to)
+ # and we should act accordingly.
+ unless ( defined $Attachment->bodyhandle ) {
+ $id = $self->SUPER::Create(
+ TransactionId => $args{'TransactionId'},
+ Parent => 0,
+ ContentType => $Attachment->mime_type,
+ Headers => $Attachment->head->as_string,
+ Subject => $Subject);
+
+ foreach my $part ( $Attachment->parts ) {
+ my $SubAttachment = new RT::Attachment( $self->CurrentUser );
+ $SubAttachment->Create(
+ TransactionId => $args{'TransactionId'},
+ Parent => $id,
+ Attachment => $part,
+ ContentType => $Attachment->mime_type,
+ Headers => $Attachment->head->as_string(),
+
+ );
+ }
+ return ($id);
+ }
+
+ #If it's not multipart
+ else {
+
+ my $ContentEncoding = 'none';
+
+ my $Body = $Attachment->bodyhandle->as_string;
+
+ #get the max attachment length from RT
+ my $MaxSize = $RT::MaxAttachmentSize;
+
+ #if the current attachment contains nulls and the
+ #database doesn't support embedded nulls
+
+ if ( $RT::AlwaysUseBase64 or
+ ( !$RT::Handle->BinarySafeBLOBs ) && ( $Body =~ /\x00/ ) ) {
+
+ # set a flag telling us to mimencode the attachment
+ $ContentEncoding = 'base64';
+
+ #cut the max attchment size by 25% (for mime-encoding overhead.
+ $RT::Logger->debug("Max size is $MaxSize\n");
+ $MaxSize = $MaxSize * 3 / 4;
+ # Some databases (postgres) can't handle non-utf8 data
+ } elsif ( !$RT::Handle->BinarySafeBLOBs
+ && $Attachment->mime_type !~ /text\/plain/gi
+ && !Encode::is_utf8( $Body, 1 ) ) {
+ $ContentEncoding = 'quoted-printable';
+ }
+
+ #if the attachment is larger than the maximum size
+ if ( ($MaxSize) and ( $MaxSize < length($Body) ) ) {
+
+ # if we're supposed to truncate large attachments
+ if ($RT::TruncateLongAttachments) {
+
+ # truncate the attachment to that length.
+ $Body = substr( $Body, 0, $MaxSize );
+
+ }
+
+ # elsif we're supposed to drop large attachments on the floor,
+ elsif ($RT::DropLongAttachments) {
+
+ # drop the attachment on the floor
+ $RT::Logger->info( "$self: Dropped an attachment of size " . length($Body) . "\n" . "It started: " . substr( $Body, 0, 60 ) . "\n" );
+ return (undef);
+ }
+ }
+
+ # if we need to mimencode the attachment
+ if ( $ContentEncoding eq 'base64' ) {
+
+ # base64 encode the attachment
+ Encode::_utf8_off($Body);
+ $Body = MIME::Base64::encode_base64($Body);
+
+ } elsif ($ContentEncoding eq 'quoted-printable') {
+ Encode::_utf8_off($Body);
+ $Body = MIME::QuotedPrint::encode($Body);
+ }
+
+
+ my $id = $self->SUPER::Create( TransactionId => $args{'TransactionId'},
+ ContentType => $Attachment->mime_type,
+ ContentEncoding => $ContentEncoding,
+ Parent => $args{'Parent'},
+ Headers => $Attachment->head->as_string,
+ Subject => $Subject,
+ Content => $Body,
+ Filename => $Filename, );
+ return ($id);
+ }
+}
+
+# }}}
+
+
+=head2 Import
+
+Create an attachment exactly as specified in the named parameters.
+
+=cut
+
+
+sub Import {
+ my $self = shift;
+ my %args = ( ContentEncoding => 'none',
+
+ @_ );
+ return($self->SUPER::Create(@_));
+}
+
+# {{{ sub Content
+
+=head2 Content
+
+Returns the attachment's content. if it's base64 encoded, decode it
+before returning it.
+
+=cut
+
+sub Content {
+ my $self = shift;
+ my $decode_utf8 = (($self->ContentType eq 'text/plain') ? 1 : 0);
+
+ if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
+ return $self->_Value(
+ 'Content',
+ decode_utf8 => $decode_utf8,
+ );
+ } elsif ( $self->ContentEncoding eq 'base64' ) {
+ return ( $decode_utf8
+ ? Encode::decode_utf8(MIME::Base64::decode_base64($self->_Value('Content')))
+ : MIME::Base64::decode_base64($self->_Value('Content'))
+ );
+ } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
+ return ( $decode_utf8
+ ? Encode::decode_utf8(MIME::QuotedPrint::decode($self->_Value('Content')))
+ : MIME::QuotedPrint::decode($self->_Value('Content'))
+ );
+ } else {
+ return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
+ }
+}
+
+
+# }}}
+
+
+# {{{ sub OriginalContent
+
+=head2 OriginalContent
+
+Returns the attachment's content as octets before RT's mangling.
+Currently, this just means restoring text/plain content back to its
+original encoding.
+
+=cut
+
+sub OriginalContent {
+ my $self = shift;
+
+ return $self->Content unless $self->ContentType eq 'text/plain';
+ my $enc = $self->OriginalEncoding;
+
+ my $content;
+ if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
+ $content = $self->_Value('Content', decode_utf8 => 0);
+ } elsif ( $self->ContentEncoding eq 'base64' ) {
+ $content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0));
+ } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
+ return MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
+ } else {
+ return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
+ }
+
+ # Encode::_utf8_on($content);
+ if (!$enc || $enc eq '' || $enc eq 'utf8' || $enc eq 'utf-8') {
+ # If we somehow fail to do the decode, at least push out the raw bits
+ eval {return( Encode::decode_utf8($content))} || return ($content);
+ }
+
+ eval { Encode::from_to($content, 'utf8' => $enc);};
+ if ($@) {
+ $RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@);
+ }
+ return $content;
+}
+
+# }}}
+
+
+# {{{ sub OriginalEncoding
+
+=head2 OriginalEncoding
+
+Returns the attachment's original encoding.
+
+=cut
+
+sub OriginalEncoding {
+ my $self = shift;
+ return $self->GetHeader('X-RT-Original-Encoding');
+}
+
+# }}}
+
+# {{{ sub Children
+
+=head2 Children
+
+ Returns an RT::Attachments object which is preloaded with all Attachments objects with this Attachment\'s Id as their 'Parent'
+
+=cut
+
+sub Children {
+ my $self = shift;
+
+ my $kids = new RT::Attachments($self->CurrentUser);
+ $kids->ChildrenOf($self->Id);
+ return($kids);
+}
+
+# }}}
+
+# {{{ UTILITIES
+
+# {{{ sub Quote
+
+
+
+sub Quote {
+ my $self=shift;
+ my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system)
+ @_);
+
+ my ($quoted_content, $body, $headers);
+ my $max=0;
+
+ # TODO: Handle Multipart/Mixed (eventually fix the link in the
+ # ShowHistory web template?)
+ if ($self->ContentType =~ m{^(text/plain|message)}i) {
+ $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=new Text::Wrapper
+ (
+ columns => 70,
+ body_start => ($max > 70*3 ? ' ' : ''),
+ par_start => ''
+ );
+ $body=$wrapper->wrap($body);
+ }
+
+ $body =~ s/^/> /gm;
+
+ $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString()
+ . "]:\n\n"
+ . $body . "\n\n";
+
+ } else {
+ $body = "[Non-text message not quoted]\n\n";
+ }
+
+ $max=60 if $max<60;
+ $max=70 if $max>78;
+ $max+=2;
+
+ return (\$body, $max);
+}
+# }}}
+
+# {{{ sub NiceHeaders - pulls out only the most relevant headers
+
+=head2 NiceHeaders
+
+Returns the To, From, Cc, Date and Subject headers.
+
+It is a known issue that this breaks if any of these headers are not
+properly unfolded.
+
+=cut
+
+sub NiceHeaders {
+ my $self = shift;
+ my $hdrs = "";
+ my @hdrs = split(/\n/,$self->Headers);
+ while (my $str = shift @hdrs) {
+ next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Date|Subject): /i;
+ $hdrs .= $str . "\n";
+ $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/);
+ }
+ return $hdrs;
+}
+# }}}
+
+# {{{ sub Headers
+
+=head2 Headers
+
+Returns this object's headers as a string. This method specifically
+removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc.
+We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send
+out mail. (The mailing rules are seperated from the ticket update code by
+an abstraction barrier that makes it impossible to pass this data directly
+
+=cut
+
+sub Headers {
+ my $self = shift;
+ my $hdrs="";
+ for (split(/\n/,$self->SUPER::Headers)) {
+ $hdrs.="$_\n" unless /^(RT-Send-Bcc): /i
+ }
+ return $hdrs;
+}
+
+
+# }}}
+
+# {{{ sub GetHeader
+
+=head2 GetHeader ( 'Tag')
+
+Returns the value of the header Tag as a string. This bypasses the weeding out
+done in Headers() above.
+
+=cut
+
+sub GetHeader {
+ my $self = shift;
+ my $tag = shift;
+ foreach my $line (split(/\n/,$self->SUPER::Headers)) {
+ if ($line =~ /^\Q$tag\E:\s+(.*)$/i) { #if we find the header, return its value
+ return ($1);
+ }
+ }
+
+ # we found no header. return an empty string
+ return undef;
+}
+# }}}
+
+# {{{ sub SetHeader
+
+=head2 SetHeader ( 'Tag', 'Value' )
+
+Replace or add a Header to the attachment's headers.
+
+=cut
+
+sub SetHeader {
+ my $self = shift;
+ my $tag = shift;
+ my $newheader = '';
+
+ foreach my $line (split(/\n/,$self->SUPER::Headers)) {
+ if (defined $tag and $line =~ /^\Q$tag\E:\s+(.*)$/i) {
+ $newheader .= "$tag: $_[0]\n";
+ undef $tag;
+ }
+ else {
+ $newheader .= "$line\n";
+ }
+ }
+
+ $newheader .= "$tag: $_[0]\n" if defined $tag;
+ $self->__Set( Field => 'Headers', Value => $newheader);
+}
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+
+ #if the field is public, return it.
+ if ($self->_Accessible($field, 'public')) {
+ #$RT::Logger->debug("Skipping ACL check for $field\n");
+ return($self->__Value($field, @_));
+
+ }
+
+ #If it's a comment, we need to be extra special careful
+ elsif ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and
+ ($self->TransactionObj->Type eq 'Comment') ) or
+ ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) {
+ return($self->__Value($field, @_));
+ }
+ #if they ain't got rights to see, don't let em
+ else {
+ return(undef);
+ }
+
+
+}
+
+# }}}
+
+sub ContentLength {
+ my $self = shift;
+
+ unless ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and
+ ($self->TransactionObj->Type eq 'Comment') ) or
+ ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) {
+ return undef;
+ }
+
+ if (my $len = $self->GetHeader('Content-Length')) {
+ return $len;
+ }
+
+ {
+ use bytes;
+ my $len = length($self->Content);
+ $self->SetHeader('Content-Length' => $len);
+ return $len;
+ }
+}
+
+# }}}
+
+# Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
+sub _CacheConfig {
+ {
+ 'cache_p' => 1,
+ 'fast_update_p' => 1,
+ 'cache_for_sec' => 180,
+ }
+}
+
+1;
diff --git a/rt/lib/RT/Attachments.pm b/rt/lib/RT/Attachments.pm
new file mode 100755
index 0000000..177cdd0
--- /dev/null
+++ b/rt/lib/RT/Attachments.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Attachments -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Attachments
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Attachments;
+
+use RT::SearchBuilder;
+use RT::Attachment;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Attachments';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Attachment item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Attachment->new($self->CurrentUser));
+}
+
+ eval "require RT::Attachments_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachments_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Attachments_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachments_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Attachments_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Attachments_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Attachments_Overlay, RT::Attachments_Vendor, RT::Attachments_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Attachments_Overlay.pm b/rt/lib/RT/Attachments_Overlay.pm
new file mode 100644
index 0000000..ce94c9d
--- /dev/null
+++ b/rt/lib/RT/Attachments_Overlay.pm
@@ -0,0 +1,116 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Attachments - a collection of RT::Attachment objects
+
+=head1 SYNOPSIS
+
+ use RT::Attachments;
+
+=head1 DESCRIPTION
+
+This module should never be called directly by client code. it's an internal module which
+should only be accessed through exported APIs in Ticket, Queue and other similar objects.
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Attachments);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+
+ $self->{'table'} = "Attachments";
+ $self->{'primary_key'} = "id";
+ return ( $self->SUPER::_Init(@_));
+}
+# }}}
+
+
+# {{{ sub ContentType
+
+=head2 ContentType (VALUE => 'text/plain', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' )
+
+Limit result set to attachments of ContentType 'TYPE'...
+
+=cut
+
+
+sub ContentType {
+ my $self = shift;
+ my %args = ( VALUE => 'text/plain',
+ OPERATOR => '=',
+ ENTRYAGGREGATOR => 'OR',
+ @_);
+
+ $self->Limit ( FIELD => 'ContentType',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'});
+}
+# }}}
+
+# {{{ sub ChildrenOf
+
+=head2 ChildrenOf ID
+
+Limit result set to children of Attachment ID
+
+=cut
+
+
+sub ChildrenOf {
+ my $self = shift;
+ my $attachment = shift;
+ $self->Limit ( FIELD => 'Parent',
+ VALUE => $attachment);
+}
+# }}}
+
+# {{{ sub NewItem
+sub NewItem {
+ my $self = shift;
+
+ use RT::Attachment;
+ my $item = new RT::Attachment($self->CurrentUser);
+ return($item);
+}
+# }}}
+ 1;
+
+
+
+
diff --git a/rt/lib/RT/Base.pm b/rt/lib/RT/Base.pm
new file mode 100644
index 0000000..47742f8
--- /dev/null
+++ b/rt/lib/RT/Base.pm
@@ -0,0 +1,112 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Base;
+use Carp;
+use Scalar::Util;
+
+use strict;
+use vars qw(@EXPORT);
+
+@EXPORT=qw(loc CurrentUser);
+
+=head1 FUNCTIONS
+
+
+
+# {{{ sub CurrentUser
+
+=head2 CurrentUser
+
+If called with an argument, sets the current user to that user object.
+This will affect ACL decisions, etc.
+Returns the current user
+
+=cut
+
+sub CurrentUser {
+ my $self = shift;
+
+ if (@_) {
+ $self->{'original_user'} = $self->{'user'};
+ $self->{'user'} = shift;
+ Scalar::Util::weaken($self->{'user'}) if (ref($self->{'user'}) &&
+ $self->{'user'} == $self );
+ }
+
+ unless ( ref( $self->{'user'}) ) {
+ $RT::Logger->err( "$self was created without a CurrentUser\n" . Carp::cluck() );
+ return (0);
+ die;
+ }
+ return ( $self->{'user'} );
+}
+
+# }}}
+
+sub OriginalUser {
+ my $self = shift;
+
+ if (@_) {
+ $self->{'original_user'} = shift;
+ Scalar::Util::weaken($self->{'original_user'})
+ if (ref($self->{'original_user'}) && $self->{'original_user'} == $self );
+ }
+ return ( $self->{'original_user'} || $self->{'user'} );
+}
+
+
+=item loc LOC_STRING
+
+l is a method which takes a loc string
+to this object's CurrentUser->LanguageHandle for localization.
+
+you call it like this:
+
+ $self->loc("I have [quant,_1,concrete mixer].", 6);
+
+In english, this would return:
+ I have 6 concrete mixers.
+
+
+=cut
+
+sub loc {
+ my $self = shift;
+ if (my $user = $self->OriginalUser) {
+ return $user->loc(@_);
+ }
+ else {
+ use Carp;
+ Carp::confess("No currentuser");
+ return ("Critical error:$self has no CurrentUser", $self);
+ }
+}
+
+eval "require RT::Base_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Base_Vendor.pm});
+eval "require RT::Base_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Base_Local.pm});
+
+
+1;
diff --git a/rt/lib/RT/CachedGroupMember.pm b/rt/lib/RT/CachedGroupMember.pm
new file mode 100644
index 0000000..fc3e1bf
--- /dev/null
+++ b/rt/lib/RT/CachedGroupMember.pm
@@ -0,0 +1,258 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::CachedGroupMember
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::CachedGroupMember;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('CachedGroupMembers');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'GroupId'.
+ int(11) 'MemberId'.
+ int(11) 'Via'.
+ int(11) 'ImmediateParentId'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ GroupId => '',
+ MemberId => '',
+ Via => '',
+ ImmediateParentId => '',
+ Disabled => '0',
+
+ @_);
+ $self->SUPER::Create(
+ GroupId => $args{'GroupId'},
+ MemberId => $args{'MemberId'},
+ Via => $args{'Via'},
+ ImmediateParentId => $args{'ImmediateParentId'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item GroupId
+
+Returns the current value of GroupId.
+(In the database, GroupId is stored as int(11).)
+
+
+
+=item SetGroupId VALUE
+
+
+Set GroupId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, GroupId will be stored as a int(11).)
+
+
+=cut
+
+
+=item MemberId
+
+Returns the current value of MemberId.
+(In the database, MemberId is stored as int(11).)
+
+
+
+=item SetMemberId VALUE
+
+
+Set MemberId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, MemberId will be stored as a int(11).)
+
+
+=cut
+
+
+=item Via
+
+Returns the current value of Via.
+(In the database, Via is stored as int(11).)
+
+
+
+=item SetVia VALUE
+
+
+Set Via to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Via will be stored as a int(11).)
+
+
+=cut
+
+
+=item ImmediateParentId
+
+Returns the current value of ImmediateParentId.
+(In the database, ImmediateParentId is stored as int(11).)
+
+
+
+=item SetImmediateParentId VALUE
+
+
+Set ImmediateParentId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ImmediateParentId will be stored as a int(11).)
+
+
+=cut
+
+
+=item Disabled
+
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
+
+
+
+=item SetDisabled VALUE
+
+
+Set Disabled to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Disabled will be stored as a smallint(6).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ GroupId =>
+ {read => 1, write => 1, type => 'int(11)', default => ''},
+ MemberId =>
+ {read => 1, write => 1, type => 'int(11)', default => ''},
+ Via =>
+ {read => 1, write => 1, type => 'int(11)', default => ''},
+ ImmediateParentId =>
+ {read => 1, write => 1, type => 'int(11)', default => ''},
+ Disabled =>
+ {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::CachedGroupMember_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMember_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CachedGroupMember_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMember_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CachedGroupMember_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMember_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CachedGroupMember_Overlay, RT::CachedGroupMember_Vendor, RT::CachedGroupMember_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CachedGroupMember_Overlay.pm b/rt/lib/RT/CachedGroupMember_Overlay.pm
new file mode 100644
index 0000000..8ba7913
--- /dev/null
+++ b/rt/lib/RT/CachedGroupMember_Overlay.pm
@@ -0,0 +1,323 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+# {{{ Create
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ 'Group' is the "top level" group we're building the cache for. This is an
+ RT::Principal object
+
+ 'Member' is the RT::Principal of the user or group we're adding
+ to the cache.
+
+ 'ImmediateParent' is the RT::Principal of the group that this principal
+ belongs to to get here
+
+ int(11) 'Via' is an internal reference to CachedGroupMembers->Id of
+ the "parent" record of this cached group member. It should be empty if this
+ member is a "direct" member of this group. (In that case, it will be set to this
+ cached group member's id after creation)
+
+ This routine should _only_ be called by GroupMember->Create
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = ( Group => '',
+ Member => '',
+ ImmediateParent => '',
+ Via => '0',
+ Disabled => '0',
+ @_ );
+
+ unless ( $args{'Member'}
+ && UNIVERSAL::isa( $args{'Member'}, 'RT::Principal' )
+ && $args{'Member'}->Id ) {
+ $RT::Logger->debug("$self->Create: bogus Member argument");
+ }
+
+ unless ( $args{'Group'}
+ && UNIVERSAL::isa( $args{'Group'}, 'RT::Principal' )
+ && $args{'Group'}->Id ) {
+ $RT::Logger->debug("$self->Create: bogus Group argument");
+ }
+
+ unless ( $args{'ImmediateParent'}
+ && UNIVERSAL::isa( $args{'ImmediateParent'}, 'RT::Principal' )
+ && $args{'ImmediateParent'}->Id ) {
+ $RT::Logger->debug("$self->Create: bogus ImmediateParent argument");
+ }
+
+ # If the parent group for this group member is disabled, it's disabled too, along with all its children
+ if ( $args{'ImmediateParent'}->Disabled ) {
+ $args{'Disabled'} = $args{'ImmediateParent'}->Disabled;
+ }
+
+ my $id = $self->SUPER::Create(
+ GroupId => $args{'Group'}->Id,
+ MemberId => $args{'Member'}->Id,
+ ImmediateParentId => $args{'ImmediateParent'}->Id,
+ Disabled => $args{'Disabled'},
+ Via => $args{'Via'}, );
+
+ unless ($id) {
+ $RT::Logger->warning( "Couldn't create "
+ . $args{'Member'}
+ . " as a cached member of "
+ . $args{'Group'}->Id . " via "
+ . $args{'Via'} );
+ return (undef); #this will percolate up and bail out of the transaction
+ }
+ if ( $self->__Value('Via') == 0 ) {
+ my ( $vid, $vmsg ) = $self->__Set( Field => 'Via', Value => $id );
+ unless ($vid) {
+ $RT::Logger->warning( "Due to a via error, couldn't create "
+ . $args{'Member'}
+ . " as a cached member of "
+ . $args{'Group'}->Id . " via "
+ . $args{'Via'} );
+ return (undef)
+ ; #this will percolate up and bail out of the transaction
+ }
+ }
+
+ if ( $args{'Member'}->IsGroup() ) {
+ my $GroupMembers = $args{'Member'}->Object->MembersObj();
+ while ( my $member = $GroupMembers->Next() ) {
+ my $cached_member =
+ RT::CachedGroupMember->new( $self->CurrentUser );
+ my $c_id = $cached_member->Create(
+ Group => $args{'Group'},
+ Member => $member->MemberObj,
+ ImmediateParent => $args{'Member'},
+ Disabled => $args{'Disabled'},
+ Via => $id );
+ unless ($c_id) {
+ return (undef); #percolate the error upwards.
+ # the caller will log an error and abort the transaction
+ }
+
+ }
+ }
+
+ return ($id);
+
+}
+
+# }}}
+
+# {{{ Delete
+
+=head2 Delete
+
+Deletes the current CachedGroupMember from the group it's in and cascades
+the delete to all submembers. This routine could be completely excised if
+mysql supported foreign keys with cascading deletes.
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+
+ my $member = $self->MemberObj();
+ if ( $member->IsGroup ) {
+ my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ $deletable->Limit( FIELD => 'id',
+ OPERATOR => '!=',
+ VALUE => $self->id );
+ $deletable->Limit( FIELD => 'Via',
+ OPERATOR => '=',
+ VALUE => $self->id );
+
+ while ( my $kid = $deletable->Next ) {
+ my $kid_err = $kid->Delete();
+ unless ($kid_err) {
+ $RT::Logger->error(
+ "Couldn't delete CachedGroupMember " . $kid->Id );
+ return (undef);
+ }
+ }
+ }
+ my $err = $self->SUPER::Delete();
+ unless ($err) {
+ $RT::Logger->error( "Couldn't delete CachedGroupMember " . $self->Id );
+ return (undef);
+ }
+
+ # Unless $self->GroupObj still has the member recursively $self->MemberObj
+ # (Since we deleted the database row above, $self no longer counts)
+ unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) {
+
+
+ # Find all ACEs granted to $self->GroupId
+ my $acl = RT::ACL->new($RT::SystemUser);
+ $acl->LimitToPrincipal( Id => $self->GroupId );
+
+
+ while ( my $this_ace = $acl->Next() ) {
+ # Find all ACEs which $self-MemberObj has delegated from $this_ace
+ my $delegations = RT::ACL->new($RT::SystemUser);
+ $delegations->DelegatedFrom( Id => $this_ace->Id );
+ $delegations->DelegatedBy( Id => $self->MemberId );
+
+ # For each delegation
+ while ( my $delegation = $delegations->Next ) {
+ # WHACK IT
+ my $del_ret = $delegation->_Delete(InsideTransaction => 1);
+ unless ($del_ret) {
+ $RT::Logger->crit("Couldn't delete an ACL delegation that we know exists ". $delegation->Id);
+ return(undef);
+ }
+ }
+ }
+ }
+ return ($err);
+}
+
+# }}}
+
+# {{{ SetDisabled
+
+=head2 SetDisabled
+
+SetDisableds the current CachedGroupMember from the group it's in and cascades
+the SetDisabled to all submembers. This routine could be completely excised if
+mysql supported foreign keys with cascading SetDisableds.
+
+=cut
+
+sub SetDisabled {
+ my $self = shift;
+ my $val = shift;
+
+ my $err = $self->SUPER::SetDisabled($val);
+ unless ($err) {
+ $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $self->Id );
+ return (undef);
+ }
+
+ my $member = $self->MemberObj();
+ if ( $member->IsGroup ) {
+ my $deletable = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ $deletable->Limit( FIELD => 'Via', OPERATOR => '=', VALUE => $self->id );
+ $deletable->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->id );
+
+ while ( my $kid = $deletable->Next ) {
+ my $kid_err = $kid->SetDisabled($val );
+ unless ($kid_err) {
+ $RT::Logger->error( "Couldn't SetDisabled CachedGroupMember " . $kid->Id );
+ return (undef);
+ }
+ }
+ }
+
+ # Unless $self->GroupObj still has the member recursively $self->MemberObj
+ # (Since we SetDisabledd the database row above, $self no longer counts)
+ unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) {
+ # Find all ACEs granted to $self->GroupId
+ my $acl = RT::ACL->new($RT::SystemUser);
+ $acl->LimitToPrincipal( Id => $self->GroupId );
+
+ while ( my $this_ace = $acl->Next() ) {
+ # Find all ACEs which $self-MemberObj has delegated from $this_ace
+ my $delegations = RT::ACL->new($RT::SystemUser);
+ $delegations->DelegatedFrom( Id => $this_ace->Id );
+ $delegations->DelegatedBy( Id => $self->MemberId );
+
+ # For each delegation, blow away the delegation
+ while ( my $delegation = $delegations->Next ) {
+ # WHACK IT
+ my $del_ret = $delegation->_Delete(InsideTransaction => 1);
+ unless ($del_ret) {
+ $RT::Logger->crit("Couldn't delete an ACL delegation that we know exists ". $delegation->Id);
+ return(undef);
+ }
+ }
+ }
+ }
+ return ($err);
+}
+
+# }}}
+
+# {{{ GroupObj
+
+=head2 GroupObj
+
+Returns the RT::Principal object for this group Group
+
+=cut
+
+sub GroupObj {
+ my $self = shift;
+ my $principal = RT::Principal->new( $self->CurrentUser );
+ $principal->Load( $self->GroupId );
+ return ($principal);
+}
+
+# }}}
+
+# {{{ ImmediateParentObj
+
+=head2 ImmediateParentObj
+
+Returns the RT::Principal object for this group ImmediateParent
+
+=cut
+
+sub ImmediateParentObj {
+ my $self = shift;
+ my $principal = RT::Principal->new( $self->CurrentUser );
+ $principal->Load( $self->ImmediateParentId );
+ return ($principal);
+}
+
+# }}}
+
+# {{{ MemberObj
+
+=head2 MemberObj
+
+Returns the RT::Principal object for this group member
+
+=cut
+
+sub MemberObj {
+ my $self = shift;
+ my $principal = RT::Principal->new( $self->CurrentUser );
+ $principal->Load( $self->MemberId );
+ return ($principal);
+}
+
+# }}}
+1;
diff --git a/rt/lib/RT/CachedGroupMembers.pm b/rt/lib/RT/CachedGroupMembers.pm
new file mode 100644
index 0000000..b520f7b
--- /dev/null
+++ b/rt/lib/RT/CachedGroupMembers.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::CachedGroupMembers -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::CachedGroupMembers
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::CachedGroupMembers;
+
+use RT::SearchBuilder;
+use RT::CachedGroupMember;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'CachedGroupMembers';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::CachedGroupMember item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::CachedGroupMember->new($self->CurrentUser));
+}
+
+ eval "require RT::CachedGroupMembers_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMembers_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CachedGroupMembers_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMembers_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CachedGroupMembers_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CachedGroupMembers_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CachedGroupMembers_Overlay, RT::CachedGroupMembers_Vendor, RT::CachedGroupMembers_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CachedGroupMembers_Overlay.pm b/rt/lib/RT/CachedGroupMembers_Overlay.pm
new file mode 100644
index 0000000..500a54f
--- /dev/null
+++ b/rt/lib/RT/CachedGroupMembers_Overlay.pm
@@ -0,0 +1,150 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::CachedGroupMembers - a collection of RT::GroupMember objects
+
+=head1 SYNOPSIS
+
+ use RT::CachedGroupMembers;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::CachedGroupMembers);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ LimitToUsers
+
+=head2 LimitToUsers
+
+Limits this search object to users who are members of this group
+This is really useful when you want to haave your UI seperate out
+groups from users for display purposes
+
+=cut
+
+sub LimitToUsers {
+ my $self = shift;
+
+ my $principals = $self->NewAlias('Principals');
+ $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId',
+ ALIAS2 => $principals, FIELD2 =>'id');
+
+ $self->Limit( ALIAS => $principals,
+ FIELD => 'PrincipalType',
+ VALUE => 'User',
+ ENTRYAGGREGATOR => 'OR',
+ );
+}
+
+# }}}
+
+
+# {{{ LimitToGroups
+
+=head2 LimitToGroups
+
+Limits this search object to Groups who are members of this group
+This is really useful when you want to haave your UI seperate out
+groups from users for display purposes
+
+=cut
+
+sub LimitToGroups {
+ my $self = shift;
+
+ my $principals = $self->NewAlias('Principals');
+ $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId',
+ ALIAS2 => $principals, FIELD2 =>'id');
+
+ $self->Limit( ALIAS => $principals,
+ FIELD => 'PrincipalType',
+ VALUE => 'Group',
+ ENTRYAGGREGATOR => 'OR',
+ );
+}
+
+# }}}
+
+# {{{ sub LimitToMembersOfGroup
+
+=head2 LimitToMembersOfGroup PRINCIPAL_ID
+
+Takes a Principal Id as its only argument.
+Limits the current search principals which are _directly_ members
+of the group which has PRINCIPAL_ID as its principal id.
+
+=cut
+
+sub LimitToMembersOfGroup {
+ my $self = shift;
+ my $group = shift;
+
+ return ($self->Limit(
+ VALUE => $group,
+ FIELD => 'GroupId',
+ ENTRYAGGREGATOR => 'OR',
+ ));
+
+}
+# }}}
+
+# {{{ sub LimitToGroupsWithMember
+
+=head2 LimitToGroupsWithMember PRINCIPAL_ID
+
+Takes a Principal Id as its only argument.
+Limits the current search to groups which contain PRINCIPAL_ID as a member or submember.
+This function gets used by GroupMember->Create to populate subgroups
+
+=cut
+
+sub LimitToGroupsWithMember {
+ my $self = shift;
+ my $member = shift;
+
+
+
+ return ($self->Limit(
+ VALUE => $member || '0',
+ FIELD => 'MemberId',
+ ENTRYAGGREGATOR => 'OR',
+ QUOTEVALUE => 0
+ ));
+
+}
+# }}}
+1;
diff --git a/rt/lib/RT/Condition/AnyTransaction.pm b/rt/lib/RT/Condition/AnyTransaction.pm
new file mode 100644
index 0000000..4519fcf
--- /dev/null
+++ b/rt/lib/RT/Condition/AnyTransaction.pm
@@ -0,0 +1,51 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+package RT::Condition::AnyTransaction;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+This happens on every transaction. it's always applicable
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ return(1);
+}
+
+eval "require RT::Condition::AnyTransaction_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/AnyTransaction_Vendor.pm});
+eval "require RT::Condition::AnyTransaction_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/AnyTransaction_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/BeforeDue.pm b/rt/lib/RT/Condition/BeforeDue.pm
new file mode 100644
index 0000000..7911fd8
--- /dev/null
+++ b/rt/lib/RT/Condition/BeforeDue.pm
@@ -0,0 +1,64 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Condition::BeforeDue;
+require RT::Condition::Generic;
+
+use RT::Date;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+sub IsApplicable {
+ my $self = shift;
+
+ # Parse date string. Format is "1d2h3m4s" for 1 day and 2 hours
+ # 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 $elapse = $e{'d'} * 24*60*60 + $e{'h'} * 60*60 + $e{'m'} * 60 + $e{'s'};
+
+ my $cur = new RT::Date( $RT::SystemUser );
+ $cur->SetToNow();
+ my $due = $self->TicketObj->DueObj;
+ return (undef) if $due->Unix <= 0;
+
+ my $diff = $due->Diff($cur);
+ if ( $diff >= 0 and $diff <= $elapse ) {
+ return(1);
+ } else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::BeforeDue_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/BeforeDue_Vendor.pm});
+eval "require RT::Condition::BeforeDue_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/BeforeDue_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Condition/Generic.pm b/rt/lib/RT/Condition/Generic.pm
new file mode 100755
index 0000000..bd26931
--- /dev/null
+++ b/rt/lib/RT/Condition/Generic.pm
@@ -0,0 +1,211 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Condition::Generic - ;
+
+=head1 SYNOPSIS
+
+ use RT::Condition::Generic;
+ my $foo = new RT::Condition::IsApplicable(
+ TransactionObj => $tr,
+ TicketObj => $ti,
+ ScripObj => $scr,
+ Argument => $arg,
+ Type => $type);
+
+ if ($foo->IsApplicable) {
+ # do something
+ }
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Condition::Generic);
+
+=end testing
+
+
+=cut
+
+package RT::Condition::Generic;
+
+use RT::Base;
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Base);
+
+# {{{ sub new
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ $self->_Init(@_);
+ return $self;
+}
+# }}}
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ my %args = ( TransactionObj => undef,
+ TicketObj => undef,
+ ScripObj => undef,
+ TemplateObj => undef,
+ Argument => undef,
+ ApplicableTransTypes => undef,
+ @_ );
+
+ $self->{'Argument'} = $args{'Argument'};
+ $self->{'ScripObj'} = $args{'ScripObj'};
+ $self->{'TicketObj'} = $args{'TicketObj'};
+ $self->{'TransactionObj'} = $args{'TransactionObj'};
+ $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'};
+}
+# }}}
+
+# Access Scripwide data
+
+# {{{ sub Argument
+
+=head2 Argument
+
+Return the optional argument associated with this ScripCondition
+
+=cut
+
+sub Argument {
+ my $self = shift;
+ return($self->{'Argument'});
+}
+# }}}
+
+# {{{ sub TicketObj
+
+=head2 TicketObj
+
+Return the ticket object we're talking about
+
+=cut
+
+sub TicketObj {
+ my $self = shift;
+ return($self->{'TicketObj'});
+}
+# }}}
+
+# {{{ sub ScripObj
+
+=head2 ScripObj
+
+Return the Scrip object we're talking about
+
+=cut
+
+sub ScripObj {
+ my $self = shift;
+ return($self->{'ScripObj'});
+}
+# }}}
+# {{{ sub TransactionObj
+
+=head2 TransactionObj
+
+Return the transaction object we're talking about
+
+=cut
+
+sub TransactionObj {
+ my $self = shift;
+ return($self->{'TransactionObj'});
+}
+# }}}
+
+# {{{ sub Type
+
+=head2 Type
+
+
+
+=cut
+
+sub ApplicableTransTypes {
+ my $self = shift;
+ return($self->{'ApplicableTransTypes'});
+}
+# }}}
+
+
+# Scrip methods
+
+
+#What does this type of Action does
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return ($self->loc("No description for [_1]", ref $self));
+}
+# }}}
+
+
+#Parse the templates, get things ready to go.
+
+#If this rule applies to this transaction, return true.
+
+# {{{ sub IsApplicable
+sub IsApplicable {
+ my $self = shift;
+ return(undef);
+}
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+ my $self = shift;
+
+ # We need to clean up all the references that might maybe get
+ # oddly circular
+ $self->{'TemplateObj'} =undef
+ $self->{'TicketObj'} = undef;
+ $self->{'TransactionObj'} = undef;
+ $self->{'ScripObj'} = undef;
+
+}
+
+# }}}
+
+eval "require RT::Condition::Generic_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Vendor.pm});
+eval "require RT::Condition::Generic_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Condition/Overdue.pm b/rt/lib/RT/Condition/Overdue.pm
new file mode 100644
index 0000000..3a4efe7
--- /dev/null
+++ b/rt/lib/RT/Condition/Overdue.pm
@@ -0,0 +1,68 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+
+=head1 NAME
+
+RT::Condition::Overdue
+
+=head1 DESCRIPTION
+
+Returns true if the ticket we're operating on is overdue
+
+=cut
+
+package RT::Condition::Overdue;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+If the due date is before "now" return true
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ if ($self->TicketObj->DueObj->Unix > 0 and
+ $self->TicketObj->DueObj->Unix < time()) {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::Overdue_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Overdue_Vendor.pm});
+eval "require RT::Condition::Overdue_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Overdue_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/OwnerChange.pm b/rt/lib/RT/Condition/OwnerChange.pm
new file mode 100644
index 0000000..e17f589
--- /dev/null
+++ b/rt/lib/RT/Condition/OwnerChange.pm
@@ -0,0 +1,102 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+
+package RT::Condition::OwnerChange;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+If we're changing the owner return true, otherwise return false
+
+=begin testing
+
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Create(Name =>'ownerChangeTest');
+
+ok($q->Id, "Created a scriptest queue");
+
+my $s1 = RT::Scrip->new($RT::SystemUser);
+my ($val, $msg) =$s1->Create( Queue => $q->Id,
+ ScripAction => 'User Defined',
+ ScripCondition => 'On Owner Change',
+ CustomIsApplicableCode => '',
+ CustomPrepareCode => 'return 1',
+ CustomCommitCode => '
+ $RT::Logger->crit("Before, prio is ".$self->TicketObj->Priority);
+ $self->TicketObj->SetPriority($self->TicketObj->Priority+1);
+ $RT::Logger->crit("After, prio is ".$self->TicketObj->Priority);
+ return(1);
+ ',
+ Template => 'Blank'
+ );
+ok($val,$msg);
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id,
+ Subject => "hair on fire",
+ InitialPriority => '20'
+ );
+ok($tv, $tm);
+ok($ticket->SetOwner('root'));
+is ($ticket->Priority , '21', "Ticket priority is set right");
+ok($ticket->Steal);
+is ($ticket->Priority , '22', "Ticket priority is set right");
+ok($ticket->Untake);
+is ($ticket->Priority , '23', "Ticket priority is set right");
+ok($ticket->Take);
+is ($ticket->Priority , '24', "Ticket priority is set right");
+
+
+
+
+
+=end testing
+
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ if ($self->TransactionObj->Field eq 'Owner') {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::OwnerChange_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/OwnerChange_Vendor.pm});
+eval "require RT::Condition::OwnerChange_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/OwnerChange_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/PriorityExceeds.pm b/rt/lib/RT/Condition/PriorityExceeds.pm
new file mode 100644
index 0000000..7737ca5
--- /dev/null
+++ b/rt/lib/RT/Condition/PriorityExceeds.pm
@@ -0,0 +1,57 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+
+package RT::Condition::PriorityExceeds;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+If the priority exceeds the argument value
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ if ($self->TicketObj->Priority > $self->Argument) {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::PriorityExceeds_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/PriorityExceeds_Vendor.pm});
+eval "require RT::Condition::PriorityExceeds_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/PriorityExceeds_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/QueueChange.pm b/rt/lib/RT/Condition/QueueChange.pm
new file mode 100644
index 0000000..f3e646a
--- /dev/null
+++ b/rt/lib/RT/Condition/QueueChange.pm
@@ -0,0 +1,57 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+
+package RT::Condition::QueueChange;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+If the queue has changed.
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ if ($self->TransactionObj->Field eq 'Queue') {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::QueueChange_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/QueueChange_Vendor.pm});
+eval "require RT::Condition::QueueChange_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/QueueChange_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/StatusChange.pm b/rt/lib/RT/Condition/StatusChange.pm
new file mode 100644
index 0000000..8afabcd
--- /dev/null
+++ b/rt/lib/RT/Condition/StatusChange.pm
@@ -0,0 +1,59 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+
+package RT::Condition::StatusChange;
+require RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+If the argument passed in is equivalent to the new value of
+the Status Obj
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ if (($self->TransactionObj->Field eq 'Status') and
+ ($self->Argument eq $self->TransactionObj->NewValue())) {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+eval "require RT::Condition::StatusChange_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/StatusChange_Vendor.pm});
+eval "require RT::Condition::StatusChange_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/StatusChange_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/Condition/UserDefined.pm b/rt/lib/RT/Condition/UserDefined.pm
new file mode 100644
index 0000000..eb829f0
--- /dev/null
+++ b/rt/lib/RT/Condition/UserDefined.pm
@@ -0,0 +1,57 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+
+package RT::Condition::UserDefined;
+
+use RT::Condition::Generic;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Condition::Generic);
+
+
+=head2 IsApplicable
+
+This happens on every transaction. it's always applicable
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ my $retval = eval $self->ScripObj->CustomIsApplicableCode;
+ if ($@) {
+ $RT::Logger->error("Scrip ".$self->ScripObj->Id. " IsApplicable failed: ".$@);
+ return (undef);
+ }
+ return ($retval);
+}
+
+eval "require RT::Condition::UserDefined_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UserDefined_Vendor.pm});
+eval "require RT::Condition::UserDefined_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UserDefined_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/CurrentUser.pm b/rt/lib/RT/CurrentUser.pm
new file mode 100755
index 0000000..7fcc65c
--- /dev/null
+++ b/rt/lib/RT/CurrentUser.pm
@@ -0,0 +1,394 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::CurrentUser - an RT object representing the current user
+
+=head1 SYNOPSIS
+
+ use RT::CurrentUser
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::CurrentUser);
+
+=end testing
+
+=cut
+
+
+package RT::CurrentUser;
+
+use RT::Record;
+use RT::I18N;
+
+use strict;
+use vars qw/@ISA/;
+@ISA= qw(RT::Record);
+
+# {{{ sub _Init
+
+#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
+
+sub _Init {
+ my $self = shift;
+ my $Name = shift;
+
+ $self->{'table'} = "Users";
+
+ if (defined($Name)) {
+ $self->Load($Name);
+ }
+
+ # $self->CurrentUser($self);
+
+}
+# }}}
+
+# {{{ sub Create
+
+sub Create {
+ my $self = shift;
+ return (0, $self->loc('Permission Denied'));
+}
+
+# }}}
+
+# {{{ sub Delete
+
+sub Delete {
+ my $self = shift;
+ return (0, $self->loc('Permission Denied'));
+}
+
+# }}}
+
+# {{{ sub UserObj
+
+=head2 UserObj
+
+ Returns the RT::User object associated with this CurrentUser object.
+
+=cut
+
+sub UserObj {
+ my $self = shift;
+
+ use RT::User;
+ my $user = RT::User->new($self);
+
+ unless ($user->Load($self->Id)) {
+ $RT::Logger->err($self->loc("Couldn't load [_1] from the users database.\n", $self->Id));
+ }
+ return ($user);
+}
+# }}}
+
+# {{{ sub PrincipalObj
+
+=head2 PrincipalObj
+
+ Returns this user's principal object. this is just a helper routine for
+ $self->UserObj->PrincipalObj
+
+=cut
+
+sub PrincipalObj {
+ my $self = shift;
+ return($self->UserObj->PrincipalObj);
+}
+
+
+# }}}
+
+
+# {{{ sub PrincipalId
+
+=head2 PrincipalId
+
+ Returns this user's principal Id. this is just a helper routine for
+ $self->UserObj->PrincipalId
+
+=cut
+
+sub PrincipalId {
+ my $self = shift;
+ return($self->UserObj->PrincipalId);
+}
+
+
+# }}}
+
+
+# {{{ sub _Accessible
+sub _Accessible {
+ my $self = shift;
+ my %Cols = (
+ Name => 'read',
+ Gecos => 'read',
+ RealName => 'read',
+ Password => 'neither',
+ Lang => 'read',
+ EmailAddress => 'read',
+ Privileged => 'read',
+ IsAdministrator => 'read'
+ );
+ return($self->SUPER::_Accessible(@_, %Cols));
+}
+# }}}
+
+# {{{ sub LoadByEmail
+
+=head2 LoadByEmail
+
+Loads a User into this CurrentUser object.
+Takes the email address of the user to load.
+
+=cut
+
+sub LoadByEmail {
+ my $self = shift;
+ my $identifier = shift;
+
+ $identifier = RT::User::CanonicalizeEmailAddress(undef, $identifier);
+
+ $self->LoadByCol("EmailAddress",$identifier);
+
+}
+# }}}
+
+# {{{ sub LoadByGecos
+
+=head2 LoadByGecos
+
+Loads a User into this CurrentUser object.
+Takes a unix username as its only argument.
+
+=cut
+
+sub LoadByGecos {
+ my $self = shift;
+ my $identifier = shift;
+
+ $self->LoadByCol("Gecos",$identifier);
+
+}
+# }}}
+
+# {{{ sub LoadByName
+
+=head2 LoadByName
+
+Loads a User into this CurrentUser object.
+Takes a Name.
+=cut
+
+sub LoadByName {
+ my $self = shift;
+ my $identifier = shift;
+ $self->LoadByCol("Name",$identifier);
+
+}
+# }}}
+
+# {{{ sub Load
+
+=head2 Load
+
+Loads a User into this CurrentUser object.
+Takes either an integer (users id column reference) or a Name
+The latter is deprecated. Instead, you should use LoadByName.
+Formerly, this routine also took email addresses.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift;
+
+ #if it's an int, load by id. otherwise, load by name.
+ if ($identifier !~ /\D/) {
+ $self->SUPER::LoadById($identifier);
+ }
+
+ elsif (UNIVERSAL::isa($identifier,"RT::User")) {
+ # DWIM if they pass a user in
+ $self->SUPER::LoadById($identifier->Id);
+ }
+ else {
+ # This is a bit dangerous, we might get false authen if somebody
+ # uses ambigous userids or real names:
+ $self->LoadByCol("Name",$identifier);
+ }
+}
+
+# }}}
+
+# {{{ sub IsPassword
+
+=head2 IsPassword
+
+Takes a password as a string. Passes it off to IsPassword in this
+user's UserObj. If it is the user's password and the user isn't
+disabled, returns 1.
+
+Otherwise, returns undef.
+
+=cut
+
+sub IsPassword {
+ my $self = shift;
+ my $value = shift;
+
+ return ($self->UserObj->IsPassword($value));
+}
+
+# }}}
+
+# {{{ sub Privileged
+
+=head2 Privileged
+
+Returns true if the current user can be granted rights and be
+a member of groups.
+
+=cut
+
+sub Privileged {
+ my $self = shift;
+ return ($self->UserObj->Privileged());
+}
+
+# }}}
+
+
+# {{{ sub HasRight
+
+=head2 HasRight
+
+calls $self->UserObj->HasRight with the arguments passed in
+
+=cut
+
+sub HasRight {
+ my $self = shift;
+ return ($self->UserObj->HasRight(@_));
+}
+
+# }}}
+
+# {{{ Localization
+
+=head2 LanguageHandle
+
+Returns this current user's langauge handle. Should take a language
+specification. but currently doesn't
+
+=begin testing
+
+ok (my $cu = RT::CurrentUser->new('root'));
+ok (my $lh = $cu->LanguageHandle);
+ok ($lh != undef);
+ok ($lh->isa('Locale::Maketext'));
+ok ($cu->loc('TEST_STRING') eq "Concrete Mixer", "Localized TEST_STRING into English");
+ok ($lh = $cu->LanguageHandle('fr'));
+ok ($cu->loc('Before') eq "Avant", "Localized TEST_STRING into Frenc");
+
+=end testing
+
+=cut
+
+sub LanguageHandle {
+ my $self = shift;
+ if ((!defined $self->{'LangHandle'}) ||
+ (!UNIVERSAL::can($self->{'LangHandle'}, 'maketext')) ||
+ (@_)) {
+ if ( $self->Lang) {
+ push @_, $self->Lang;
+ }
+ $self->{'LangHandle'} = RT::I18N->get_handle(@_);
+ }
+ # Fall back to english.
+ unless ($self->{'LangHandle'}) {
+ die "We couldn't get a dictionary. Nye mogu naidti slovar. No puedo encontrar dictionario.";
+ }
+ return ($self->{'LangHandle'});
+}
+
+sub loc {
+ my $self = shift;
+ return '' if $_[0] eq '';
+
+ my $handle = $self->LanguageHandle;
+
+ if (@_ == 1) {
+ # pre-scan the lexicon hashes to return _AUTO keys verbatim,
+ # to keep locstrings containing '[' and '~' from tripping over Maketext
+ return $_[0] unless grep { exists $_->{$_[0]} } @{ $handle->_lex_refs };
+ }
+
+ return $handle->maketext(@_);
+}
+
+sub loc_fuzzy {
+ my $self = shift;
+ return '' if $_[0] eq '';
+
+ # XXX: work around perl's deficiency when matching utf8 data
+ return $_[0] if Encode::is_utf8($_[0]);
+ my $result = $self->LanguageHandle->maketext_fuzzy(@_);
+
+ return($result);
+}
+# }}}
+
+
+=head2 CurrentUser
+
+Return the current currentuser object
+
+=cut
+
+sub CurrentUser {
+ my $self = shift;
+ return($self);
+
+}
+
+eval "require RT::CurrentUser_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Vendor.pm});
+eval "require RT::CurrentUser_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Local.pm});
+
+1;
+
diff --git a/rt/lib/RT/CustomField.pm b/rt/lib/RT/CustomField.pm
new file mode 100644
index 0000000..cd40a3a
--- /dev/null
+++ b/rt/lib/RT/CustomField.pm
@@ -0,0 +1,340 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::CustomField
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::CustomField;
+use RT::Record;
+use RT::Queue;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('CustomFields');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(200) 'Type'.
+ int(11) 'Queue'.
+ varchar(255) 'Description'.
+ int(11) 'SortOrder'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Type => '',
+ Queue => '0',
+ Description => '',
+ SortOrder => '0',
+ Disabled => '0',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Type => $args{'Type'},
+ Queue => $args{'Queue'},
+ Description => $args{'Description'},
+ SortOrder => $args{'SortOrder'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(200).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Queue
+
+Returns the current value of Queue.
+(In the database, Queue is stored as int(11).)
+
+
+
+=item SetQueue VALUE
+
+
+Set Queue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Queue will be stored as a int(11).)
+
+
+=cut
+
+
+=item QueueObj
+
+Returns the Queue Object which has the id returned by Queue
+
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+ my $Queue = RT::Queue->new($self->CurrentUser);
+ $Queue->Load($self->__Value('Queue'));
+ return($Queue);
+}
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item SortOrder
+
+Returns the current value of SortOrder.
+(In the database, SortOrder is stored as int(11).)
+
+
+
+=item SetSortOrder VALUE
+
+
+Set SortOrder to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, SortOrder will be stored as a int(11).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+=item Disabled
+
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
+
+
+
+=item SetDisabled VALUE
+
+
+Set Disabled to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Disabled will be stored as a smallint(6).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Queue =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ SortOrder =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ Disabled =>
+ {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::CustomField_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomField_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomField_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomField_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomField_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomField_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CustomField_Overlay, RT::CustomField_Vendor, RT::CustomField_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CustomFieldValue.pm b/rt/lib/RT/CustomFieldValue.pm
new file mode 100644
index 0000000..f434b44
--- /dev/null
+++ b/rt/lib/RT/CustomFieldValue.pm
@@ -0,0 +1,294 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::CustomFieldValue
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::CustomFieldValue;
+use RT::Record;
+use RT::CustomField;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('CustomFieldValues');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'CustomField'.
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ int(11) 'SortOrder'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ CustomField => '0',
+ Name => '',
+ Description => '',
+ SortOrder => '0',
+
+ @_);
+ $self->SUPER::Create(
+ CustomField => $args{'CustomField'},
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ SortOrder => $args{'SortOrder'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item CustomField
+
+Returns the current value of CustomField.
+(In the database, CustomField is stored as int(11).)
+
+
+
+=item SetCustomField VALUE
+
+
+Set CustomField to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomField will be stored as a int(11).)
+
+
+=cut
+
+
+=item CustomFieldObj
+
+Returns the CustomField Object which has the id returned by CustomField
+
+
+=cut
+
+sub CustomFieldObj {
+ my $self = shift;
+ my $CustomField = RT::CustomField->new($self->CurrentUser);
+ $CustomField->Load($self->__Value('CustomField'));
+ return($CustomField);
+}
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item SortOrder
+
+Returns the current value of SortOrder.
+(In the database, SortOrder is stored as int(11).)
+
+
+
+=item SetSortOrder VALUE
+
+
+Set SortOrder to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, SortOrder will be stored as a int(11).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ CustomField =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ SortOrder =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::CustomFieldValue_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValue_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFieldValue_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValue_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFieldValue_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValue_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CustomFieldValue_Overlay, RT::CustomFieldValue_Vendor, RT::CustomFieldValue_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CustomFieldValues.pm b/rt/lib/RT/CustomFieldValues.pm
new file mode 100644
index 0000000..0e792b1
--- /dev/null
+++ b/rt/lib/RT/CustomFieldValues.pm
@@ -0,0 +1,121 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::CustomFieldValues -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::CustomFieldValues
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::CustomFieldValues;
+
+use RT::SearchBuilder;
+use RT::CustomFieldValue;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'CustomFieldValues';
+ $self->{'primary_key'} = 'id';
+
+
+
+ # By default, order by name
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'SortOrder',
+ ORDER => 'ASC');
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::CustomFieldValue item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::CustomFieldValue->new($self->CurrentUser));
+}
+
+ eval "require RT::CustomFieldValues_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFieldValues_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFieldValues_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CustomFieldValues_Overlay, RT::CustomFieldValues_Vendor, RT::CustomFieldValues_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CustomFieldValues_Overlay.pm b/rt/lib/RT/CustomFieldValues_Overlay.pm
new file mode 100644
index 0000000..0384db9
--- /dev/null
+++ b/rt/lib/RT/CustomFieldValues_Overlay.pm
@@ -0,0 +1,47 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub LimitToCustomField
+
+=head2 LimitToCustomField FIELD
+
+Limits the returned set to values for the custom field with Id FIELD
+
+=cut
+
+sub LimitToCustomField {
+ my $self = shift;
+ my $cf = shift;
+ return ($self->Limit( FIELD => 'CustomField',
+ VALUE => $cf,
+ OPERATOR => '='));
+
+}
+
+# }}}
+
+1;
+
diff --git a/rt/lib/RT/CustomField_Overlay.pm b/rt/lib/RT/CustomField_Overlay.pm
new file mode 100644
index 0000000..84902a0
--- /dev/null
+++ b/rt/lib/RT/CustomField_Overlay.pm
@@ -0,0 +1,566 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+use vars qw(@TYPES %TYPES);
+
+use RT::CustomFieldValues;
+use RT::TicketCustomFieldValues;
+
+# Enumerate all valid types for this custom field
+@TYPES = (
+ 'SelectSingle', # loc
+ 'SelectMultiple', # loc
+ 'FreeformSingle', # loc
+ 'FreeformMultiple', # loc
+);
+
+# Populate a hash of types of easier validation
+for (@TYPES) { $TYPES{$_} = 1};
+
+
+
+
+=head1 NAME
+
+ RT::CustomField_Overlay
+
+=head1 DESCRIPTION
+
+=head1 'CORE' METHODS
+
+=cut
+
+
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(200) 'Type'.
+ int(11) 'Queue'.
+ varchar(255) 'Description'.
+ int(11) 'SortOrder'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Type => '',
+ Queue => '0',
+ Description => '',
+ SortOrder => '0',
+ Disabled => '0',
+
+ @_);
+
+
+
+ if ( ! $args{'Queue'} ) {
+ unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AdminCustomFields') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ else {
+ my $queue = RT::Queue->new($self->CurrentUser);
+ $queue->Load($args{'Queue'});
+ unless ($queue->Id) {
+ return (0, $self->loc("Queue not found"));
+ }
+ unless ( $queue->CurrentUserHasRight('AdminCustomFields') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Type => $args{'Type'},
+ Queue => $args{'Queue'},
+ Description => $args{'Description'},
+ SortOrder => $args{'SortOrder'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+# {{{ sub LoadByNameAndQueue
+
+=head2 LoadByNameAndQueue (Queue => QUEUEID, Name => NAME)
+
+Loads the Custom field named NAME for Queue QUEUE. If QUEUE is 0,
+loads a global custom field
+
+=cut
+
+# Compatibility for API change after 3.0 beta 1
+*LoadNameAndQueue = \&LoadByNameAndQueue;
+
+sub LoadByNameAndQueue {
+ my $self = shift;
+ my %args = (
+ Queue => undef,
+ Name => undef,
+ @_,
+ );
+
+ if ($args{'Queue'} =~ /\D/) {
+ my $QueueObj = RT::Queue->new($self->CurrentUser);
+ $QueueObj->Load($args{'Queue'});
+ $args{'Queue'} = $QueueObj->Id;
+ }
+
+ return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
+
+}
+
+# }}}
+
+# {{{ Dealing with custom field values
+
+=begin testing
+use_ok(RT::CustomField);
+ok(my $cf = RT::CustomField->new($RT::SystemUser));
+ok(my ($id, $msg)= $cf->Create( Name => 'TestingCF',
+ Queue => '0',
+ SortOrder => '1',
+ Description => 'A Testing custom field',
+ Type=> 'SelectSingle'), 'Created a global CustomField');
+ok($id != 0, 'Global custom field correctly created');
+ok ($cf->SingleValue);
+ok($cf->Type eq 'SelectSingle');
+
+ok($cf->SetType('SelectMultiple'));
+ok($cf->Type eq 'SelectMultiple');
+ok(!$cf->SingleValue );
+ok(my ($bogus_val, $bogus_msg) = $cf->SetType('BogusType') , "Trying to set a custom field's type to a bogus type");
+ok($bogus_val == 0, "Unable to set a custom field's type to a bogus type");
+
+ok(my $bad_cf = RT::CustomField->new($RT::SystemUser));
+ok(my ($bad_id, $bad_msg)= $cf->Create( Name => 'TestingCF-bad',
+ Queue => '0',
+ SortOrder => '1',
+ Description => 'A Testing custom field with a bogus Type',
+ Type=> 'SelectSingleton'), 'Created a global CustomField with a bogus type');
+ok($bad_id == 0, 'Global custom field correctly decided to not create a cf with a bogus type ');
+
+=end testing
+
+=cut
+
+# {{{ AddValue
+
+=head2 AddValue HASH
+
+Create a new value for this CustomField. Takes a paramhash containing the elements Name, Description and SortOrder
+
+=begin testing
+
+ok(my $cf = RT::CustomField->new($RT::SystemUser));
+$cf->Load(1);
+ok($cf->Id == 1);
+ok(my ($val,$msg) = $cf->AddValue(Name => 'foo' , Description => 'TestCFValue', SortOrder => '6'));
+ok($val != 0);
+ok (my ($delval, $delmsg) = $cf->DeleteValue($val));
+ok ($delval != 0);
+
+=end testing
+
+=cut
+
+sub AddValue {
+ my $self = shift;
+ my %args = ( Name => undef,
+ Description => undef,
+ SortOrder => undef,
+ @_ );
+
+ unless ($self->CurrentUserHasRight('AdminCustomFields')) {
+ return (0, $self->loc('Permission Denied'));
+ }
+
+ unless ($args{'Name'}) {
+ return(0, $self->loc("Can't add a custom field value without a name"));
+ }
+ my $newval = RT::CustomFieldValue->new($self->CurrentUser);
+ return($newval->Create(
+ CustomField => $self->Id,
+ Name =>$args{'Name'},
+ Description => ($args{'Description'} || ''),
+ SortOrder => ($args{'SortOrder'} || '0')
+ ));
+}
+
+
+# }}}
+
+# {{{ DeleteValue
+
+=head2 DeleteValue ID
+
+Deletes a value from this custom field by id.
+
+Does not remove this value for any article which has had it selected
+
+=cut
+
+sub DeleteValue {
+ my $self = shift;
+ my $id = shift;
+ unless ($self->CurrentUserHasRight('AdminCustomFields')) {
+ return (0, $self->loc('Permission Denied'));
+ }
+
+ my $val_to_del = RT::CustomFieldValue->new($self->CurrentUser);
+ $val_to_del->Load($id);
+ unless ($val_to_del->Id) {
+ return (0, $self->loc("Couldn't find that value"));
+ }
+ unless ($val_to_del->CustomField == $self->Id) {
+ return (0, $self->loc("That is not a value for this custom field"));
+ }
+
+ my $retval = $val_to_del->Delete();
+ if ($retval) {
+ return ($retval, $self->loc("Custom field value deleted"));
+ } else {
+ return(0, $self->loc("Custom field value could not be deleted"));
+ }
+}
+
+# }}}
+
+# {{{ Values
+
+=head2 Values FIELD
+
+Return a CustomFieldeValues object of all acceptable values for this Custom Field.
+
+
+=cut
+
+sub Values {
+ my $self = shift;
+
+ my $cf_values = RT::CustomFieldValues->new($self->CurrentUser);
+ if ( $self->__Value('Queue') == 0 || $self->CurrentUserHasRight( 'SeeQueue') ) {
+ $cf_values->LimitToCustomField($self->Id);
+ }
+ return ($cf_values);
+}
+
+# }}}
+
+# }}}
+
+# {{{ Ticket related routines
+
+# {{{ ValuesForTicket
+
+=head2 ValuesForTicket TICKET
+
+Returns a RT::TicketCustomFieldValues object of this Field's values for TICKET.
+TICKET is a ticket id.
+
+
+=cut
+
+sub ValuesForTicket {
+ my $self = shift;
+ my $ticket_id = shift;
+
+ my $values = new RT::TicketCustomFieldValues($self->CurrentUser);
+ $values->LimitToCustomField($self->Id);
+ $values->LimitToTicket($ticket_id);
+
+ return ($values);
+}
+
+# }}}
+
+# {{{ AddValueForTicket
+
+=head2 AddValueForTicket HASH
+
+Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
+
+=cut
+
+sub AddValueForTicket {
+ my $self = shift;
+ my %args = ( Ticket => undef,
+ Content => undef,
+ @_ );
+
+ my $newval = RT::TicketCustomFieldValue->new($self->CurrentUser);
+ my $val = $newval->Create(Ticket => $args{'Ticket'},
+ Content => $args{'Content'},
+ CustomField => $self->Id);
+
+ return($val);
+
+}
+
+
+# }}}
+
+# {{{ DeleteValueForTicket
+
+=head2 DeleteValueForTicket HASH
+
+Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
+
+=cut
+
+sub DeleteValueForTicket {
+ my $self = shift;
+ my %args = ( Ticket => undef,
+ Content => undef,
+ @_ );
+
+ my $oldval = RT::TicketCustomFieldValue->new($self->CurrentUser);
+ $oldval->LoadByTicketContentAndCustomField (Ticket => $args{'Ticket'},
+ Content => $args{'Content'},
+ CustomField => $self->Id );
+ # check ot make sure we found it
+ unless ($oldval->Id) {
+ return(0, $self->loc("Custom field value [_1] could not be found for custom field [_2]", $args{'Content'}, $self->Name));
+ }
+ # delete it
+
+ my $ret = $oldval->Delete();
+ unless ($ret) {
+ return(0, $self->loc("Custom field value could not be found"));
+ }
+ return(1, $self->loc("Custom field value deleted"));
+}
+
+
+# }}}
+# }}}
+
+
+=head2 ValidateQueue Queue
+
+Make sure that the queue specified is a valid queue name
+
+=cut
+
+sub ValidateQueue {
+ my $self = shift;
+ my $id = shift;
+
+ if ($id eq '0') { # 0 means "Global" null would _not_ be ok.
+ return (1);
+ }
+
+ my $q = RT::Queue->new($RT::SystemUser);
+ $q->Load($id);
+ unless ($q->id) {
+ return undef;
+ }
+ return (1);
+
+
+}
+
+
+# {{{ Types
+
+=head2 Types
+
+Retuns an array of the types of CustomField that are supported
+
+=cut
+
+sub Types {
+ return (@TYPES);
+}
+
+# }}}
+
+
+=head2 FriendlyType [TYPE]
+
+Returns a localized human-readable version of the custom field type.
+If a custom field type is specified as the parameter, the friendly type for that type will be returned
+
+=cut
+
+sub FriendlyType {
+ my $self = shift;
+
+ my $type = shift || $self->Type;
+
+ if ( $type eq 'SelectSingle' ) {
+ return ( $self->loc('Select one value') );
+ }
+ elsif ( $type eq 'SelectMultiple' ) {
+ return ( $self->loc('Select multiple values') );
+ }
+ elsif ( $type eq 'FreeformSingle' ) {
+ return ( $self->loc('Enter one value') );
+ }
+ elsif ( $type eq 'FreeformMultiple' ) {
+ return ( $self->loc('Enter multiple values') );
+ }
+ else {
+ return ( $self->loc( $self->Type ) );
+ }
+}
+
+
+=head2 ValidateType TYPE
+
+Takes a single string. returns true if that string is a value
+type of custom field
+
+=begin testing
+
+ok(my $cf = RT::CustomField->new($RT::SystemUser));
+ok($cf->ValidateType('SelectSingle'));
+ok($cf->ValidateType('SelectMultiple'));
+ok(!$cf->ValidateType('SelectFooMultiple'));
+
+=end testing
+
+=cut
+
+sub ValidateType {
+ my $self = shift;
+ my $type = shift;
+
+ if( $TYPES{$type}) {
+ return(1);
+ }
+ else {
+ return undef;
+ }
+}
+
+# {{{ SingleValue
+
+=head2 SingleValue
+
+Returns true if this CustomField only accepts a single value.
+Returns false if it accepts multiple values
+
+=cut
+
+sub SingleValue {
+ my $self = shift;
+ if ($self->Type =~ /Single$/) {
+ return 1;
+ }
+ else {
+ return undef;
+ }
+}
+
+# }}}
+
+# {{{ sub CurrentUserHasRight
+
+=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;
+ # if there's no queue, we want to know about a global right
+ if ( ( !defined $self->__Value('Queue') ) || ( $self->__Value('Queue') == 0 ) ) {
+ return $self->CurrentUser->HasRight( Object => $RT::System, Right => $right);
+ } else {
+ return ( $self->QueueObj->CurrentUserHasRight($right) );
+ }
+}
+
+# }}}
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('AdminCustomFields') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ return ( $self->SUPER::_Set(@_) );
+
+}
+
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+ # We need to expose the queue so that we can do things like ACL checks
+ if ( $field eq 'Queue') {
+ return ( $self->SUPER::_Value($field) );
+ }
+
+
+ #Anybody can see global custom fields, otherwise we need to do the rights check
+ unless ( $self->__Value('Queue') == 0 || $self->CurrentUserHasRight( 'SeeQueue') ) {
+ return (undef);
+ }
+ return ( $self->__Value($field) );
+
+}
+
+# }}}
+# {{{ sub SetDisabled
+
+=head2 SetDisabled
+
+Takes a boolean.
+1 will cause this custom field to no longer be avaialble for tickets.
+0 will re-enable this queue
+
+=cut
+
+# }}}
+
+1;
diff --git a/rt/lib/RT/CustomFields.pm b/rt/lib/RT/CustomFields.pm
new file mode 100644
index 0000000..3e47765
--- /dev/null
+++ b/rt/lib/RT/CustomFields.pm
@@ -0,0 +1,121 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::CustomFields -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::CustomFields
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::CustomFields;
+
+use RT::SearchBuilder;
+use RT::CustomField;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'CustomFields';
+ $self->{'primary_key'} = 'id';
+
+
+
+ # By default, order by name
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'SortOrder',
+ ORDER => 'ASC');
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::CustomField item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::CustomField->new($self->CurrentUser));
+}
+
+ eval "require RT::CustomFields_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFields_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFields_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFields_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::CustomFields_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/CustomFields_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::CustomFields_Overlay, RT::CustomFields_Vendor, RT::CustomFields_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/CustomFields_Overlay.pm b/rt/lib/RT/CustomFields_Overlay.pm
new file mode 100644
index 0000000..97c7cb8
--- /dev/null
+++ b/rt/lib/RT/CustomFields_Overlay.pm
@@ -0,0 +1,135 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::CustomFields - a collection of RT CustomField objects
+
+=head1 SYNOPSIS
+
+ use RT::CustomFields;
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::CustomFields);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+# {{{ sub LimitToGlobalOrQueue
+
+=item LimitToGlobalOrQueue QUEUEID
+
+Limits the set of custom fields found to global custom fields or those tied to the queue with ID QUEUEID
+
+=cut
+
+sub LimitToGlobalOrQueue {
+ my $self = shift;
+ my $queue = shift;
+ $self->LimitToQueue($queue);
+ $self->LimitToGlobal();
+}
+
+# }}}
+
+# {{{ sub LimitToQueue
+
+=head2 LimitToQueue QUEUEID
+
+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
+
+=cut
+
+sub LimitToQueue {
+ my $self = shift;
+ my $queue = shift;
+
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Queue',
+ VALUE => "$queue")
+ if defined $queue;
+
+}
+# }}}
+
+# {{{ sub LimitToGlobal
+
+=head2 LimitToGlobal
+
+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
+
+=cut
+
+
+sub LimitToGlobal {
+ my $self = shift;
+
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Queue',
+ VALUE => 0);
+
+}
+# }}}
+
+
+# {{{ sub _DoSearch
+
+=head2 _DoSearch
+
+ A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that _Disabled ro
+ws never get seen unless
+we're explicitly trying to see them.
+
+=cut
+
+sub _DoSearch {
+ my $self = shift;
+
+ #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
+ unless($self->{'find_disabled_rows'}) {
+ $self->LimitToEnabled();
+ }
+
+ return($self->SUPER::_DoSearch(@_));
+
+}
+
+# }}}
+
+1;
+
diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm
new file mode 100644
index 0000000..355370a
--- /dev/null
+++ b/rt/lib/RT/Date.pm
@@ -0,0 +1,557 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Date - a simple Object Oriented date.
+
+=head1 SYNOPSIS
+
+ use RT::Date
+
+=head1 DESCRIPTION
+
+RT Date is a simple Date Object designed to be speedy and easy for RT to use
+
+The fact that it assumes that a time of 0 means "never" is probably a bug.
+
+=begin testing
+
+ok (require RT::Date);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+
+package RT::Date;
+
+use Time::Local;
+
+use RT::Base;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw/RT::Base/;
+
+use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR);
+
+$MINUTE = 60;
+$HOUR = 60 * $MINUTE;
+$DAY = 24 * $HOUR;
+$WEEK = 7 * $DAY;
+$MONTH = 4 * $WEEK;
+$YEAR = 365 * $DAY;
+
+# {{{ sub new
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ $self->CurrentUser(@_);
+ $self->Unix(0);
+ return $self;
+}
+
+# }}}
+
+# {{{ sub Set
+
+=head2 sub Set
+
+takes a param hash with the fields 'Format' and 'Value'
+
+if $args->{'Format'} is 'unix', takes the number of seconds since the epoch
+
+If $args->{'Format'} is ISO, tries to parse an ISO date.
+
+If $args->{'Format'} is 'unknown', require Time::ParseDate and make it figure
+things out. This is a heavyweight operation that should never be called from
+within RT's core. But it's really useful for something like the textbox date
+entry where we let the user do whatever they want.
+
+If $args->{'Value'} is 0, assumes you mean never.
+
+=begin testing
+
+use_ok(RT::Date);
+my $date = RT::Date->new($RT::SystemUser);
+$date->Set(Format => 'unix', Value => '0');
+ok ($date->ISO eq '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT");
+
+=end testing
+
+=cut
+
+sub Set {
+ my $self = shift;
+ my %args = ( Format => 'unix',
+ Value => time,
+ @_ );
+ if ( !$args{'Value'}
+ || ( ( $args{'Value'} =~ /^\d*$/ ) and ( $args{'Value'} == 0 ) ) ) {
+ $self->Unix(-1);
+ return ( $self->Unix() );
+ }
+
+ if ( $args{'Format'} =~ /^unix$/i ) {
+ $self->Unix( $args{'Value'} );
+ }
+
+ elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) {
+ $args{'Value'} =~ s!/!-!g;
+
+ if (( $args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
+ || ( $args{'Value'} =~
+ /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
+ || ( $args{'Value'} =~
+ /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
+ || ($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
+ ) {
+
+ my $year = $1;
+ my $mon = $2;
+ my $mday = $3;
+ my $hours = $4;
+ my $min = $5;
+ my $sec = $6;
+
+ #timegm expects month as 0->11
+ $mon--;
+
+ #now that we've parsed it, deal with the case where everything
+ #was 0
+ if ( $mon == -1 ) {
+ $self->Unix(-1);
+ }
+ else {
+
+ #Dateamnip strings aren't in GMT.
+ if ( $args{'Format'} =~ /^datemanip$/i ) {
+ $self->Unix(
+ timelocal( $sec, $min, $hours, $mday, $mon, $year ) );
+ }
+
+ #ISO and SQL dates are in GMT
+ else {
+ $self->Unix(
+ timegm( $sec, $min, $hours, $mday, $mon, $year ) );
+ }
+
+ $self->Unix(-1) unless $self->Unix;
+ }
+ }
+ else {
+ use Carp;
+ Carp::cluck;
+ $RT::Logger->debug(
+ "Couldn't parse date $args{'Value'} as a $args{'Format'}");
+
+ }
+ }
+ elsif ( $args{'Format'} =~ /^unknown$/i ) {
+ require Time::ParseDate;
+
+ #Convert it to an ISO format string
+
+ my $date = Time::ParseDate::parsedate($args{'Value'},
+ UK => $RT::DateDayBeforeMonth,
+ PREFER_PAST => $RT::AmbiguousDayInPast,
+ PREFER_FUTURE => !($RT::AmbiguousDayInPast));
+
+ #This date has now been set to a date in the _local_ timezone.
+ #since ISO dates are known to be in GMT (for RT's purposes);
+
+ $RT::Logger->debug( "RT::Date used date::parse to make "
+ . $args{'Value'}
+ . " $date\n" );
+
+ return ( $self->Set( Format => 'unix', Value => "$date" ) );
+ }
+ else {
+ die "Unknown Date format: " . $args{'Format'} . "\n";
+ }
+
+ return ( $self->Unix() );
+}
+
+# }}}
+
+# {{{ sub SetToMidnight
+
+=head2 SetToMidnight
+
+Sets the date to midnight (at the beginning of the day) GMT
+Returns the unixtime at midnight.
+
+=cut
+
+sub SetToMidnight {
+ my $self = shift;
+
+ use Time::Local;
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($self->Unix);
+ $self->Unix(timegm (0,0,0,$mday,$mon,$year,$wday,$yday));
+
+ return ($self->Unix);
+
+
+}
+
+
+# }}}
+
+# {{{ sub SetToNow
+sub SetToNow {
+ my $self = shift;
+ return($self->Set(Format => 'unix', Value => time))
+}
+# }}}
+
+# {{{ sub Diff
+
+=head2 Diff
+
+Takes either an RT::Date object or the date in unixtime format as a string
+
+Returns the differnce between $self and that time as a number of seconds
+
+=cut
+
+sub Diff {
+ my $self = shift;
+ my $other = shift;
+
+ if (ref($other) eq 'RT::Date') {
+ $other=$other->Unix;
+ }
+ return ($self->Unix - $other);
+}
+# }}}
+
+# {{{ sub DiffAsString
+
+=head2 sub DiffAsString
+
+Takes either an RT::Date object or the date in unixtime format as a string
+
+Returns the differnce between $self and that time as a number of seconds as
+as string fit for human consumption
+
+=cut
+
+sub DiffAsString {
+ my $self = shift;
+ my $other = shift;
+
+
+ if ($other < 1) {
+ return ("");
+ }
+ if ($self->Unix < 1) {
+ return("");
+ }
+ my $diff = $self->Diff($other);
+
+ return ($self->DurationAsString($diff));
+}
+# }}}
+
+# {{{ sub DurationAsString
+
+
+=head2 DurationAsString
+
+Takes a number of seconds. returns a string describing that duration
+
+=cut
+
+sub DurationAsString {
+
+ my $self = shift;
+ my $duration = shift;
+
+ my ( $negative, $s );
+
+ $negative = 1 if ( $duration < 0 );
+
+ $duration = abs($duration);
+
+ my $time_unit;
+ if ( $duration < $MINUTE ) {
+ $s = $duration;
+ $time_unit = $self->loc("sec");
+ }
+ elsif ( $duration < ( 2 * $HOUR ) ) {
+ $s = int( $duration / $MINUTE );
+ $time_unit = $self->loc("min");
+ }
+ elsif ( $duration < ( 2 * $DAY ) ) {
+ $s = int( $duration / $HOUR );
+ $time_unit = $self->loc("hours");
+ }
+ elsif ( $duration < ( 2 * $WEEK ) ) {
+ $s = int( $duration / $DAY );
+ $time_unit = $self->loc("days");
+ }
+ elsif ( $duration < ( 2 * $MONTH ) ) {
+ $s = int( $duration / $WEEK );
+ $time_unit = $self->loc("weeks");
+ }
+ elsif ( $duration < $YEAR ) {
+ $s = int( $duration / $MONTH );
+ $time_unit = $self->loc("months");
+ }
+ else {
+ $s = int( $duration / $YEAR );
+ $time_unit = $self->loc("years");
+ }
+ if (0) { # For now, never display the "AGO" # $negative) {
+ return $self->loc( "[_1] [_2] ago", $s, $time_unit );
+ }
+ else {
+ return $self->loc( "[_1] [_2]", $s, $time_unit );
+ }
+}
+
+# }}}
+
+# {{{ sub AgeAsString
+
+=head2 sub AgeAsString
+
+Takes nothing
+
+Returns a string that's the differnce between the time in the object and now
+
+=cut
+
+sub AgeAsString {
+ my $self = shift;
+ return ($self->DiffAsString(time));
+ }
+# }}}
+
+# {{{ sub AsString
+
+=head2 sub AsString
+
+Returns the object\'s time as a string with the current timezone.
+
+=cut
+
+sub AsString {
+ my $self = shift;
+ return ($self->loc("Not set")) if ($self->Unix <= 0);
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->Unix);
+
+ return $self->loc("[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]", $self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900));
+}
+# }}}
+
+# {{{ GetWeekday
+=head2 GetWeekday DAY
+
+Takes an integer day of week and returns a localized string for that day of week
+
+=cut
+
+sub GetWeekday {
+ my $self = shift;
+ my $dow = shift;
+
+ return $self->loc('Mon.') if ($dow == 1);
+ return $self->loc('Tue.') if ($dow == 2);
+ return $self->loc('Wed.') if ($dow == 3);
+ return $self->loc('Thu.') if ($dow == 4);
+ return $self->loc('Fri.') if ($dow == 5);
+ return $self->loc('Sat.') if ($dow == 6);
+ return $self->loc('Sun.') if ($dow == 0);
+}
+
+# }}}
+
+# {{{ GetMonth
+=head2 GetMonth DAY
+
+Takes an integer month and returns a localized string for that month
+
+=cut
+
+sub GetMonth {
+ my $self = shift;
+ my $mon = shift;
+
+ # We do this rather than an array so that we don't call localize 12x what we need to
+ return $self->loc('Jan.') if ($mon == 0);
+ return $self->loc('Feb.') if ($mon == 1);
+ return $self->loc('Mar.') if ($mon == 2);
+ return $self->loc('Apr.') if ($mon == 3);
+ return $self->loc('May.') if ($mon == 4);
+ return $self->loc('Jun.') if ($mon == 5);
+ return $self->loc('Jul.') if ($mon == 6);
+ return $self->loc('Aug.') if ($mon == 7);
+ return $self->loc('Sep.') if ($mon == 8);
+ return $self->loc('Oct.') if ($mon == 9);
+ return $self->loc('Nov.') if ($mon == 10);
+ return $self->loc('Dec.') if ($mon == 11);
+}
+
+# }}}
+
+# {{{ sub AddSeconds
+
+=head2 sub AddSeconds
+
+Takes a number of seconds as a string
+
+Returns the new time
+
+=cut
+
+sub AddSeconds {
+ my $self = shift;
+ my $delta = shift;
+
+ $self->Set(Format => 'unix', Value => ($self->Unix + $delta));
+
+ return ($self->Unix);
+
+
+}
+
+# }}}
+
+# {{{ sub AddDays
+
+=head2 AddDays $DAYS
+
+Adds 24 hours * $DAYS to the current time
+
+=cut
+
+sub AddDays {
+ my $self = shift;
+ my $days = shift;
+ $self->AddSeconds($days * $DAY);
+
+}
+
+# }}}
+
+# {{{ sub AddDay
+
+=head2 AddDay
+
+Adds 24 hours to the current time
+
+=cut
+
+sub AddDay {
+ my $self = shift;
+ $self->AddSeconds($DAY);
+
+}
+
+# }}}
+
+# {{{ sub Unix
+
+=head2 sub Unix [unixtime]
+
+Optionally takes a date in unix seconds since the epoch format.
+Returns the number of seconds since the epoch
+
+=cut
+
+sub Unix {
+ my $self = shift;
+
+ $self->{'time'} = shift if (@_);
+
+ return ($self->{'time'});
+}
+# }}}
+
+# {{{ sub ISO
+
+=head2 ISO
+
+Takes nothing
+
+Returns the object's date in ISO format
+
+=cut
+
+sub ISO {
+ my $self=shift;
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst, $date) ;
+
+ return ('1970-01-01 00:00:00') if ($self->Unix == -1);
+
+ # 0 1 2 3 4 5 6 7 8
+ ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($self->Unix);
+ #make the year YYYY
+ $year+=1900;
+
+ #the month needs incrementing, as gmtime returns 0-11
+ $mon++;
+
+ $date = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year,$mon,$mday, $hour,$min,$sec);
+
+ return ($date);
+}
+
+# }}}
+
+
+# {{{ sub LocalTimezone
+=head2 LocalTimezone
+
+ Returns the current timezone. For now, draws off a system timezone, RT::Timezone. Eventually, this may
+pull from a 'Timezone' attribute of the CurrentUser
+
+=cut
+
+sub LocalTimezone {
+ my $self = shift;
+
+ return $self->CurrentUser->Timezone
+ if $self->CurrentUser and $self->CurrentUser->can('Timezone');
+
+ return ($RT::Timezone);
+}
+
+# }}}
+
+eval "require RT::Date_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Vendor.pm});
+eval "require RT::Date_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Date_Local.pm});
+
+1;
diff --git a/rt/lib/RT/EmailParser.pm b/rt/lib/RT/EmailParser.pm
new file mode 100644
index 0000000..bba4d7e
--- /dev/null
+++ b/rt/lib/RT/EmailParser.pm
@@ -0,0 +1,820 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::EmailParser;
+
+
+use base qw/RT::Base/;
+
+use strict;
+use Mail::Address;
+use MIME::Entity;
+use MIME::Head;
+use MIME::Parser;
+use File::Temp qw/tempdir/;
+
+=head1 NAME
+
+ RT::Interface::CLI - helper functions for creating a commandline RT interface
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+
+=begin testing
+
+ok(require RT::EmailParser);
+
+=end testing
+
+
+=head1 METHODS
+
+=head2 new
+
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ return $self;
+}
+
+
+
+# {{{ sub debug
+
+sub debug {
+ my $val = shift;
+ my ($debug);
+ if ($val) {
+ $RT::Logger->debug( $val . "\n" );
+ if ($debug) {
+ print STDERR "$val\n";
+ }
+ }
+ if ($debug) {
+ return (1);
+ }
+}
+
+# }}}
+
+# {{{ sub CheckForLoops
+
+sub CheckForLoops {
+ my $self = shift;
+
+ my $head = $self->Head;
+
+ #If this instance of RT sent it our, we don't want to take it in
+ my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
+ chomp($RTLoop); #remove that newline
+ if ( $RTLoop =~ /^\Q$RT::rtname\E/o ) {
+ return (1);
+ }
+
+ # TODO: We might not trap the case where RT instance A sends a mail
+ # to RT instance B which sends a mail to ...
+ return (undef);
+}
+
+# }}}
+
+# {{{ sub CheckForSuspiciousSender
+
+sub CheckForSuspiciousSender {
+ my $self = shift;
+
+ #if it's from a postmaster or mailer daemon, it's likely a bounce.
+
+ #TODO: better algorithms needed here - there is no standards for
+ #bounces, so it's very difficult to separate them from anything
+ #else. At the other hand, the Return-To address is only ment to be
+ #used as an error channel, we might want to put up a separate
+ #Return-To address which is treated differently.
+
+ #TODO: search through the whole email and find the right Ticket ID.
+
+ my ( $From, $junk ) = $self->ParseSenderAddressFromHead();
+
+ if ( ( $From =~ /^mailer-daemon/i ) or ( $From =~ /^postmaster/i ) ) {
+ return (1);
+
+ }
+
+ return (undef);
+
+}
+
+# }}}
+
+# {{{ sub CheckForAutoGenerated
+sub CheckForAutoGenerated {
+ my $self = shift;
+ my $head = $self->Head;
+
+ my $Precedence = $head->get("Precedence") || "";
+ if ( $Precedence =~ /^(bulk|junk)/i ) {
+ return (1);
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub ParseMIMEEntityFromSTDIN
+
+sub ParseMIMEEntityFromSTDIN {
+ my $self = shift;
+ return $self->ParseMIMEEntityFromFileHandle(\*STDIN);
+}
+
+# }}}
+
+=head2 ParseMIMEEntityFromScalar $message
+
+Takes either a scalar or a reference to a scalr which contains a stringified MIME message.
+Parses it.
+
+Returns true if it wins.
+Returns false if it loses.
+
+
+=cut
+
+sub ParseMIMEEntityFromScalar {
+ my $self = shift;
+ my $message = shift;
+
+ $self->_DoParse('parse_data', $message);
+
+}
+
+# {{{ ParseMIMEEntityFromFilehandle *FH
+
+=head2 ParseMIMEEntityFromFilehandle *FH
+
+Parses a mime entity from a filehandle passed in as an argument
+
+=cut
+
+sub ParseMIMEEntityFromFileHandle {
+ my $self = shift;
+ my $filehandle = shift;
+
+ $self->_DoParse('parse', $filehandle);
+
+}
+
+# }}}
+
+# {{{ ParseMIMEEntityFromFile
+
+=head2 ParseMIMEEntityFromFile
+
+Parses a mime entity from a filename passed in as an argument
+
+=cut
+
+sub ParseMIMEEntityFromFile {
+ my $self = shift;
+
+ my $file = shift;
+ $self->_DoParse('parse_open', $file);
+}
+
+# }}}
+
+# {{{ _DoParse
+
+=head2 _DoParse PARSEMETHOD CONTENT
+
+
+A helper for the various parsers to turn around and do the dispatch to the actual parser
+
+=cut
+
+sub _DoParse {
+ my $self = shift;
+ my $method = shift;
+ my $file = shift;
+
+ # Create a new parser object:
+
+ my $parser = MIME::Parser->new();
+ $self->_SetupMIMEParser($parser);
+
+
+ # TODO: XXX 3.0 we really need to wrap this in an eval { }
+
+ unless ( $self->{'entity'} = $parser->$method($file) ) {
+
+ # Try again, this time without extracting nested messages
+ $parser->extract_nested_messages(0);
+ unless ( $self->{'entity'} = $parser->$method($file) ) {
+ $RT::Logger->crit("couldn't parse MIME stream");
+ return ( undef);
+ }
+ }
+ $self->_PostProcessNewEntity();
+ return (1);
+}
+
+# }}}
+
+
+# {{{ _PostProcessNewEntity
+
+=head2 _PostProcessNewEntity
+
+cleans up and postprocesses a newly parsed MIME Entity
+
+=cut
+
+sub _PostProcessNewEntity {
+ my $self = shift;
+
+ #Now we've got a parsed mime object.
+
+ # try to convert text parts into utf-8 charset
+ RT::I18N::SetMIMEEntityToEncoding($self->{'entity'}, 'utf-8');
+
+
+ # Unfold headers that are have embedded newlines
+ $self->Head->unfold;
+
+
+}
+
+# }}}
+
+# {{{ sub ParseTicketId
+
+sub ParseTicketId {
+ my $self = shift;
+
+ my $Subject = shift;
+
+ if ( $Subject =~ s/\[\Q$RT::rtname\E\s+\#(\d+)\s*\]//i ) {
+ my $id = $1;
+ $RT::Logger->debug("Found a ticket ID. It's $id");
+ return ($id);
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub MailError
+
+=head2 MailError { }
+
+
+# TODO this doesn't belong here.
+# TODO doc this
+
+
+=cut
+
+
+sub MailError {
+ my $self = shift;
+
+ my %args = (
+ To => $RT::OwnerEmail,
+ Bcc => undef,
+ From => $RT::CorrespondAddress,
+ Subject => 'There has been an error',
+ Explanation => 'Unexplained error',
+ MIMEObj => undef,
+ LogLevel => 'crit',
+ @_
+ );
+
+ $RT::Logger->log(
+ level => $args{'LogLevel'},
+ message => $args{'Explanation'}
+ );
+ my $entity = MIME::Entity->build(
+ Type => "multipart/mixed",
+ From => $args{'From'},
+ Bcc => $args{'Bcc'},
+ To => $args{'To'},
+ Subject => $args{'Subject'},
+ 'X-RT-Loop-Prevention' => $RT::rtname,
+ );
+
+ $entity->attach( Data => $args{'Explanation'} . "\n" );
+
+ my $mimeobj = $args{'MIMEObj'};
+ $mimeobj->sync_headers();
+ $entity->add_part($mimeobj);
+
+ if ( $RT::MailCommand eq 'sendmailpipe' ) {
+ open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || return (0);
+ print MAIL $entity->as_string;
+ close(MAIL);
+ }
+ else {
+ $entity->send( $RT::MailCommand, $RT::MailParams );
+ }
+}
+
+# }}}
+
+
+
+# {{{ sub GetCurrentUser
+
+sub GetCurrentUser {
+ my $self = shift;
+ my $ErrorsTo = shift;
+
+ my %UserInfo = ();
+
+ #Suck the address of the sender out of the header
+ my ( $Address, $Name ) = $self->ParseSenderAddressFromHead();
+
+ my $tempuser = RT::User->new($RT::SystemUser);
+
+ #This will apply local address canonicalization rules
+ $Address = $tempuser->CanonicalizeEmailAddress($Address);
+
+ #If desired, synchronize with an external database
+ my $UserFoundInExternalDatabase = 0;
+
+ # Username is the 'Name' attribute of the user that RT uses for things
+ # like authentication
+ my $Username = undef;
+ ( $UserFoundInExternalDatabase, %UserInfo ) =
+ $self->LookupExternalUserInfo( $Address, $Name );
+
+ $Address = $UserInfo{'EmailAddress'};
+ $Username = $UserInfo{'Name'};
+
+ #Get us a currentuser object to work with.
+ my $CurrentUser = RT::CurrentUser->new();
+
+ # First try looking up by a username, if we got one from the external
+ # db lookup. Next, try looking up by email address. Failing that,
+ # try looking up by users who have this user's email address as their
+ # username.
+
+ if ($Username) {
+ $CurrentUser->LoadByName($Username);
+ }
+
+ unless ( $CurrentUser->Id ) {
+ $CurrentUser->LoadByEmail($Address);
+ }
+
+ #If we can't get it by email address, try by name.
+ unless ( $CurrentUser->Id ) {
+ $CurrentUser->LoadByName($Address);
+ }
+
+ unless ( $CurrentUser->Id ) {
+
+ #If we couldn't load a user, determine whether to create a user
+
+ # {{{ If we require an incoming address to be found in the external
+ # user database, reject the incoming message appropriately
+ if ( $RT::SenderMustExistInExternalDatabase
+ && !$UserFoundInExternalDatabase ) {
+
+ my $Message =
+ "Sender's email address was not found in the user database.";
+
+ # {{{ This code useful only if you've defined an AutoRejectRequest template
+
+ require RT::Template;
+ my $template = new RT::Template($RT::Nobody);
+ $template->Load('AutoRejectRequest');
+ $Message = $template->Content || $Message;
+
+ # }}}
+
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Ticket Creation failed: user could not be created",
+ Explanation => $Message,
+ MIMEObj => $self->Entity,
+ LogLevel => 'notice' );
+
+ return ($CurrentUser);
+
+ }
+
+ # }}}
+
+ else {
+ my $NewUser = RT::User->new($RT::SystemUser);
+
+ my ( $Val, $Message ) = $NewUser->Create(
+ Name => ( $Username || $Address ),
+ EmailAddress => $Address,
+ RealName => "$Name",
+ Password => undef,
+ Privileged => 0,
+ Comments => 'Autocreated on ticket submission'
+ );
+
+ unless ($Val) {
+
+ # Deal with the race condition of two account creations at once
+ #
+ if ($Username) {
+ $NewUser->LoadByName($Username);
+ }
+
+ unless ( $NewUser->Id ) {
+ $NewUser->LoadByEmail($Address);
+ }
+
+ unless ( $NewUser->Id ) {
+ MailError(To => $ErrorsTo,
+ Subject => "User could not be created",
+ Explanation =>
+ "User creation failed in mailgateway: $Message",
+ MIMEObj => $self->Entity,
+ LogLevel => 'crit' );
+ }
+ }
+ }
+
+ #Load the new user object
+ $CurrentUser->LoadByEmail($Address);
+
+ unless ( $CurrentUser->id ) {
+ $RT::Logger->warning(
+ "Couldn't load user '$Address'." . "giving up" );
+ MailError(
+ To => $ErrorsTo,
+ Subject => "User could not be loaded",
+ Explanation =>
+ "User '$Address' could not be loaded in the mail gateway",
+ MIMEObj => $self->Entity,
+ LogLevel => 'crit' );
+
+ }
+ }
+
+ return ($CurrentUser);
+
+}
+
+# }}}
+
+
+# {{{ ParseCcAddressesFromHead
+
+=head2 ParseCcAddressesFromHead HASHREF
+
+Takes a hashref object containing QueueObj, Head and CurrentUser objects.
+Returns a list of all email addresses in the To and Cc
+headers b<except> the current Queue\'s email addresses, the CurrentUser\'s
+email address and anything that the $RTAddressRegexp matches.
+
+=cut
+
+sub ParseCcAddressesFromHead {
+
+ my $self = shift;
+
+ my %args = (
+ QueueObj => undef,
+ CurrentUser => undef,
+ @_
+ );
+
+ my (@Addresses);
+
+ my @ToObjs = Mail::Address->parse( $self->Head->get('To') );
+ my @CcObjs = Mail::Address->parse( $self->Head->get('Cc') );
+
+ foreach my $AddrObj ( @ToObjs, @CcObjs ) {
+ my $Address = $AddrObj->address;
+ my $user = RT::User->new($RT::SystemUser);
+ $Address = $user->CanonicalizeEmailAddress($Address);
+ next if ( $args{'CurrentUser'}->EmailAddress =~ /^$Address$/i );
+ next if ( $args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i );
+ next if ( $args{'QueueObj'}->CommentAddress =~ /^$Address$/i );
+ next if ( IsRTAddress($Address) );
+
+ push ( @Addresses, $Address );
+ }
+ return (@Addresses);
+}
+
+# }}}
+
+# {{{ ParseSenderAdddressFromHead
+
+=head2 ParseSenderAddressFromHead
+
+Takes a MIME::Header object. Returns a tuple: (user@host, friendly name)
+of the From (evaluated in order of Reply-To:, From:, Sender)
+
+=cut
+
+sub ParseSenderAddressFromHead {
+ my $self = shift;
+
+ #Figure out who's sending this message.
+ my $From = $self->Head->get('Reply-To')
+ || $self->Head->get('From')
+ || $self->Head->get('Sender');
+ return ( $self->ParseAddressFromHeader($From) );
+}
+
+# }}}
+
+# {{{ ParseErrorsToAdddressFromHead
+
+=head2 ParseErrorsToAddressFromHead
+
+Takes a MIME::Header object. Return a single value : user@host
+of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender)
+
+=cut
+
+sub ParseErrorsToAddressFromHead {
+ my $self = shift;
+
+ #Figure out who's sending this message.
+
+ foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
+
+ # If there's a header of that name
+ my $headerobj = $self->Head->get($header);
+ if ($headerobj) {
+ my ( $addr, $name ) = $self->ParseAddressFromHeader($headerobj);
+
+ # If it's got actual useful content...
+ return ($addr) if ($addr);
+ }
+ }
+}
+
+# }}}
+
+# {{{ ParseAddressFromHeader
+
+=head2 ParseAddressFromHeader ADDRESS
+
+Takes an address from $self->Head->get('Line') and returns a tuple: user@host, friendly name
+
+=cut
+
+sub ParseAddressFromHeader {
+ my $self = shift;
+ my $Addr = shift;
+
+ my @Addresses = Mail::Address->parse($Addr);
+
+ my $AddrObj = $Addresses[0];
+
+ unless ( ref($AddrObj) ) {
+ return ( undef, undef );
+ }
+
+ my $Name = ( $AddrObj->phrase || $AddrObj->comment || $AddrObj->address );
+
+ #Lets take the from and load a user object.
+ my $Address = $AddrObj->address;
+
+ return ( $Address, $Name );
+}
+
+# }}}
+
+# {{{ IsRTAddress
+
+=item IsRTaddress ADDRESS
+
+Takes a single parameter, an email address.
+Returns true if that address matches the $RTAddressRegexp.
+Returns false, otherwise.
+
+=begin testing
+
+is(RT::EmailParser::IsRTAddress("","rt\@example.com"),1, "Regexp matched rt address" );
+is(RT::EmailParser::IsRTAddress("","frt\@example.com"),undef, "Regexp didn't match non-rt address" );
+
+=end testing
+
+=cut
+
+sub IsRTAddress {
+ my $self = shift;
+ my $address = shift;
+
+ # Example: the following rule would tell RT not to Cc
+ # "tickets@noc.example.com"
+ if ( defined($RT::RTAddressRegexp) &&
+ $address =~ /$RT::RTAddressRegexp/ ) {
+ return(1);
+ } else {
+ return (undef);
+ }
+}
+
+# }}}
+
+
+# {{{ CullRTAddresses
+
+=item CullRTAddresses ARRAY
+
+Takes a single argument, an array of email addresses.
+Returns the same array with any IsRTAddress()es weeded out.
+
+=begin testing
+
+@before = ("rt\@example.com", "frt\@example.com");
+@after = ("frt\@example.com");
+ok(eq_array(RT::EmailParser::CullRTAddresses("",@before),@after), "CullRTAddresses only culls RT addresses");
+
+=end testing
+
+=cut
+
+sub CullRTAddresses {
+ my $self = shift;
+ my @addresses= (@_);
+ my @addrlist;
+
+ foreach my $addr( @addresses ) {
+ push (@addrlist, $addr) unless IsRTAddress("", $addr);
+ }
+ return (@addrlist);
+}
+
+# }}}
+
+
+# {{{ LookupExternalUserInfo
+
+
+# LookupExternalUserInfo is a site-definable method for synchronizing
+# incoming users with an external data source.
+#
+# This routine takes a tuple of EmailAddress and FriendlyName
+# EmailAddress is the user's email address, ususally taken from
+# an email message's From: header.
+# FriendlyName is a freeform string, ususally taken from the "comment"
+# portion of an email message's From: header.
+#
+# If you define an AutoRejectRequest template, RT will use this
+# template for the rejection message.
+
+
+=item LookupExternalUserInfo
+
+ LookupExternalUserInfo is a site-definable method for synchronizing
+ incoming users with an external data source.
+
+ This routine takes a tuple of EmailAddress and FriendlyName
+ EmailAddress is the user's email address, ususally taken from
+ an email message's From: header.
+ FriendlyName is a freeform string, ususally taken from the "comment"
+ portion of an email message's From: header.
+
+ It returns (FoundInExternalDatabase, ParamHash);
+
+ FoundInExternalDatabase must be set to 1 before return if the user was
+ found in the external database.
+
+ ParamHash is a Perl parameter hash which can contain at least the following
+ fields. These fields are used to populate RT's users database when the user
+ is created
+
+ EmailAddress is the email address that RT should use for this user.
+ Name is the 'Name' attribute RT should use for this user.
+ 'Name' is used for things like access control and user lookups.
+ RealName is what RT should display as the user's name when displaying
+ 'friendly' names
+
+=cut
+
+sub LookupExternalUserInfo {
+ my $self = shift;
+ my $EmailAddress = shift;
+ my $RealName = shift;
+
+ my $FoundInExternalDatabase = 1;
+ my %params;
+
+ #Name is the RT username you want to use for this user.
+ $params{'Name'} = $EmailAddress;
+ $params{'EmailAddress'} = $EmailAddress;
+ $params{'RealName'} = $RealName;
+
+ # See RT's contributed code for examples.
+ # http://www.fsck.com/pub/rt/contrib/
+ return ($FoundInExternalDatabase, %params);
+}
+
+# }}}
+
+# {{{ Accessor methods for parsed email messages
+
+=head2 Head
+
+Return the parsed head from this message
+
+=cut
+
+sub Head {
+ my $self = shift;
+ return $self->Entity->head;
+}
+
+=head2 Entity
+
+Return the parsed Entity from this message
+
+=cut
+
+sub Entity {
+ my $self = shift;
+ return $self->{'entity'};
+}
+
+# }}}
+# {{{ _SetupMIMEParser
+
+=head2 _SetupMIMEParser $parser
+
+A private instance method which sets up a mime parser to do its job
+
+=cut
+
+
+ ## TODO: Does it make sense storing to disk at all? After all, we
+ ## need to put each msg as an in-core scalar before saving it to
+ ## the database, don't we?
+
+ ## At the same time, we should make sure that we nuke attachments
+ ## Over max size and return them
+
+sub _SetupMIMEParser {
+ my $self = shift;
+ my $parser = shift;
+ my $AttachmentDir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 );
+
+ # Set up output directory for files:
+ $parser->output_dir("$AttachmentDir");
+ $parser->filer->ignore_filename(1);
+
+
+ #If someone includes a message, extract it
+ $parser->extract_nested_messages(1);
+
+ $parser->extract_uuencode(1); ### default is false
+
+ # Set up the prefix for files with auto-generated names:
+ $parser->output_prefix("part");
+
+ # do _not_ store each msg as in-core scalar;
+
+ $parser->output_to_core(0);
+}
+# }}}
+
+eval "require RT::EmailParser_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/EmailParser_Vendor.pm});
+eval "require RT::EmailParser_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/EmailParser_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Group.pm b/rt/lib/RT/Group.pm
new file mode 100755
index 0000000..4dcef3f
--- /dev/null
+++ b/rt/lib/RT/Group.pm
@@ -0,0 +1,258 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Group
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Group;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Groups');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ varchar(64) 'Domain'.
+ varchar(64) 'Type'.
+ varchar(64) 'Instance'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Description => '',
+ Domain => '',
+ Type => '',
+ Instance => '',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ Domain => $args{'Domain'},
+ Type => $args{'Type'},
+ Instance => $args{'Instance'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Domain
+
+Returns the current value of Domain.
+(In the database, Domain is stored as varchar(64).)
+
+
+
+=item SetDomain VALUE
+
+
+Set Domain to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Domain will be stored as a varchar(64).)
+
+
+=cut
+
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(64).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(64).)
+
+
+=cut
+
+
+=item Instance
+
+Returns the current value of Instance.
+(In the database, Instance is stored as varchar(64).)
+
+
+
+=item SetInstance VALUE
+
+
+Set Instance to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Instance will be stored as a varchar(64).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Domain =>
+ {read => 1, write => 1, type => 'varchar(64)', default => ''},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(64)', default => ''},
+ Instance =>
+ {read => 1, write => 1, type => 'varchar(64)', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Group_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Group_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Group_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Group_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Group_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Group_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Group_Overlay, RT::Group_Vendor, RT::Group_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/GroupMember.pm b/rt/lib/RT/GroupMember.pm
new file mode 100755
index 0000000..8de1a73
--- /dev/null
+++ b/rt/lib/RT/GroupMember.pm
@@ -0,0 +1,189 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::GroupMember
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::GroupMember;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('GroupMembers');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'GroupId'.
+ int(11) 'MemberId'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ GroupId => '0',
+ MemberId => '0',
+
+ @_);
+ $self->SUPER::Create(
+ GroupId => $args{'GroupId'},
+ MemberId => $args{'MemberId'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item GroupId
+
+Returns the current value of GroupId.
+(In the database, GroupId is stored as int(11).)
+
+
+
+=item SetGroupId VALUE
+
+
+Set GroupId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, GroupId will be stored as a int(11).)
+
+
+=cut
+
+
+=item MemberId
+
+Returns the current value of MemberId.
+(In the database, MemberId is stored as int(11).)
+
+
+
+=item SetMemberId VALUE
+
+
+Set MemberId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, MemberId will be stored as a int(11).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ GroupId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ MemberId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::GroupMember_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::GroupMember_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::GroupMember_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMember_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::GroupMember_Overlay, RT::GroupMember_Vendor, RT::GroupMember_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/GroupMember_Overlay.pm b/rt/lib/RT/GroupMember_Overlay.pm
new file mode 100644
index 0000000..9e5bf21
--- /dev/null
+++ b/rt/lib/RT/GroupMember_Overlay.pm
@@ -0,0 +1,351 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::GroupMember - a member of an RT Group
+
+=head1 SYNOPSIS
+
+RT::GroupMember should never be called directly. It should ONLY
+only be accessed through the helper functions in RT::Group;
+
+If you're operating on an RT::GroupMember object yourself, you B<ARE>
+doing something wrong.
+
+=head1 DESCRIPTION
+
+
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::GroupMember);
+
+=end testing
+
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+use RT::CachedGroupMembers;
+
+# {{{ sub Create
+
+=head2 Create { Group => undef, Member => undef }
+
+Add a Principal to the group Group.
+if the Principal is a group, automatically inserts all
+members of the principal into the cached members table recursively down.
+
+Both Group and Member are expected to be RT::Principal objects
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Group => undef,
+ Member => undef,
+ InsideTransaction => undef,
+ @_
+ );
+
+ unless ($args{'Group'} &&
+ UNIVERSAL::isa($args{'Group'}, 'RT::Principal') &&
+ $args{'Group'}->Id ) {
+
+ $RT::Logger->warning("GroupMember::Create called with a bogus Group arg");
+ return (undef);
+ }
+
+ unless($args{'Group'}->IsGroup) {
+ $RT::Logger->warning("Someone tried to add a member to a user instead of a group");
+ return (undef);
+ }
+
+ unless ($args{'Member'} &&
+ UNIVERSAL::isa($args{'Member'}, 'RT::Principal') &&
+ $args{'Member'}->Id) {
+ $RT::Logger->warning("GroupMember::Create called with a bogus Principal arg");
+ return (undef);
+ }
+
+
+ #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::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
+
+ # We really need to make sure we don't add any members to this group
+ # that contain the group itself. that would, um, suck.
+ # (and recurse infinitely) Later, we can add code to check this in the
+ # cache and bail so we can support cycling directed graphs
+
+ if ($args{'Member'}->IsGroup) {
+ my $member_object = $args{'Member'}->Object;
+ if ($member_object->HasMemberRecursively($args{'Group'})) {
+ $RT::Logger->debug("Adding that group would create a loop");
+ return(undef);
+ }
+ elsif ( $args{'Member'}->Id == $args{'Group'}->Id) {
+ $RT::Logger->debug("Can't add a group to itself");
+ return(undef);
+ }
+ }
+
+
+ my $id = $self->SUPER::Create(
+ GroupId => $args{'Group'}->Id,
+ MemberId => $args{'Member'}->Id
+ );
+
+ unless ($id) {
+ $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
+ return (undef);
+ }
+
+ my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser );
+ my $cached_id = $cached_member->Create(
+ Member => $args{'Member'},
+ Group => $args{'Group'},
+ ImmediateParent => $args{'Group'},
+ Via => '0'
+ );
+
+
+ #When adding a member to a group, we need to go back
+ #and popuplate the CachedGroupMembers of all the groups that group is part of .
+
+ my $cgm = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ # find things which have the current group as a member.
+ # $group is an RT::Principal for the group.
+ $cgm->LimitToGroupsWithMember( $args{'Group'}->Id );
+
+ while ( my $parent_member = $cgm->Next ) {
+ my $parent_id = $parent_member->MemberId;
+ my $via = $parent_member->Id;
+ my $group_id = $parent_member->GroupId;
+
+ my $other_cached_member =
+ RT::CachedGroupMember->new( $self->CurrentUser );
+ my $other_cached_id = $other_cached_member->Create(
+ Member => $args{'Member'},
+ Group => $parent_member->GroupObj,
+ ImmediateParent => $parent_member->MemberObj,
+ Via => $parent_member->Id
+ );
+ unless ($other_cached_id) {
+ $RT::Logger->err( "Couldn't add " . $args{'Member'}
+ . " as a submember of a supergroup" );
+ $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
+ return (undef);
+ }
+ }
+
+ unless ($cached_id) {
+ $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
+ return (undef);
+ }
+
+ $RT::Handle->Commit() unless ($args{'InsideTransaction'});
+
+ return ($id);
+}
+
+# }}}
+
+# {{{ sub _StashUser
+
+=head2 _StashUser PRINCIPAL
+
+Create { Group => undef, Member => undef }
+
+Creates an entry in the groupmembers table, which lists a user
+as a member of himself. This makes ACL checks a whole bunch easier.
+This happens once on user create and never ever gets yanked out.
+
+PRINCIPAL is expected to be an RT::Principal object for a user
+
+This routine expects to be called inside a transaction by RT::User->Create
+
+=cut
+
+sub _StashUser {
+ my $self = shift;
+ my %args = (
+ Group => undef,
+ Member => undef,
+ @_
+ );
+
+ #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();
+
+
+ # We really need to make sure we don't add any members to this group
+ # that contain the group itself. that would, um, suck.
+ # (and recurse infinitely) Later, we can add code to check this in the
+ # cache and bail so we can support cycling directed graphs
+
+ my $id = $self->SUPER::Create(
+ GroupId => $args{'Group'}->Id,
+ MemberId => $args{'Member'}->Id,
+ );
+
+ unless ($id) {
+ return (undef);
+ }
+
+ my $cached_member = RT::CachedGroupMember->new( $self->CurrentUser );
+ my $cached_id = $cached_member->Create(
+ Member => $args{'Member'},
+ Group => $args{'Group'},
+ ImmediateParent => $args{'Group'},
+ Via => '0'
+ );
+
+ unless ($cached_id) {
+ return (undef);
+ }
+
+ return ($id);
+}
+
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete
+
+Takes no arguments. deletes the currently loaded member from the
+group in question.
+
+Expects to be called _outside_ a transaction
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+
+ $RT::Handle->BeginTransaction();
+
+ # Find all occurrences of this member as a member of this group
+ # in the cache and nuke them, recursively.
+
+ # The following code will delete all Cached Group members
+ # where this member's group is _not_ the primary group
+ # (Ie if we're deleting C as a member of B, and B happens to be
+ # a member of A, will delete C as a member of A without touching
+ # C as a member of B
+
+ my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ $cached_submembers->Limit(
+ FIELD => 'MemberId',
+ OPERATOR => '=',
+ VALUE => $self->MemberObj->Id
+ );
+
+ $cached_submembers->Limit(
+ FIELD => 'ImmediateParentId',
+ OPERATOR => '=',
+ VALUE => $self->GroupObj->Id
+ );
+
+ #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();
+
+
+
+
+ while ( my $item_to_del = $cached_submembers->Next() ) {
+ my $del_err = $item_to_del->Delete();
+ unless ($del_err) {
+ $RT::Handle->Rollback();
+ $RT::Logger->warning("Couldn't delete cached group submember ".$item_to_del->Id);
+ return (undef);
+ }
+ }
+
+ my $err = $self->SUPER::Delete();
+ unless ($err) {
+ $RT::Logger->warning("Couldn't delete cached group submember ".$self->Id);
+ $RT::Handle->Rollback();
+ return (undef);
+ }
+ $RT::Handle->Commit();
+ return ($err);
+
+}
+
+# }}}
+
+# {{{ sub MemberObj
+
+=head2 MemberObj
+
+Returns an RT::Principal object for the Principal specified by $self->PrincipalId
+
+=cut
+
+sub MemberObj {
+ my $self = shift;
+ unless ( defined( $self->{'Member_obj'} ) ) {
+ $self->{'Member_obj'} = RT::Principal->new( $self->CurrentUser );
+ $self->{'Member_obj'}->Load( $self->MemberId ) if ($self->MemberId);
+ }
+ return ( $self->{'Member_obj'} );
+}
+
+# }}}
+
+# {{{ sub GroupObj
+
+=head2 GroupObj
+
+Returns an RT::Principal object for the Group specified in $self->GroupId
+
+=cut
+
+sub GroupObj {
+ my $self = shift;
+ unless ( defined( $self->{'Group_obj'} ) ) {
+ $self->{'Group_obj'} = RT::Principal->new( $self->CurrentUser );
+ $self->{'Group_obj'}->Load( $self->GroupId );
+ }
+ return ( $self->{'Group_obj'} );
+}
+
+# }}}
+
+1;
diff --git a/rt/lib/RT/GroupMembers.pm b/rt/lib/RT/GroupMembers.pm
new file mode 100755
index 0000000..31cb953
--- /dev/null
+++ b/rt/lib/RT/GroupMembers.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::GroupMembers -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::GroupMembers
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::GroupMembers;
+
+use RT::SearchBuilder;
+use RT::GroupMember;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'GroupMembers';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::GroupMember item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::GroupMember->new($self->CurrentUser));
+}
+
+ eval "require RT::GroupMembers_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMembers_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::GroupMembers_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMembers_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::GroupMembers_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/GroupMembers_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::GroupMembers_Overlay, RT::GroupMembers_Vendor, RT::GroupMembers_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/GroupMembers_Overlay.pm b/rt/lib/RT/GroupMembers_Overlay.pm
new file mode 100644
index 0000000..1259fd6
--- /dev/null
+++ b/rt/lib/RT/GroupMembers_Overlay.pm
@@ -0,0 +1,126 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::GroupMembers - a collection of RT::GroupMember objects
+
+=head1 SYNOPSIS
+
+ use RT::GroupMembers;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::GroupMembers);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ LimitToUsers
+
+=head2 LimitToUsers
+
+Limits this search object to users who are members of this group.
+This is really useful when you want to haave your UI seperate out
+groups from users for display purposes
+
+=cut
+
+sub LimitToUsers {
+ my $self = shift;
+
+ my $principals = $self->NewAlias('Principals');
+ $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId',
+ ALIAS2 => $principals, FIELD2 =>'id');
+
+ $self->Limit( ALIAS => $principals,
+ FIELD => 'PrincipalType',
+ VALUE => 'User',
+ ENTRYAGGREGATOR => 'OR',
+ );
+}
+
+# }}}
+
+
+# {{{ LimitToGroups
+
+=head2 LimitToGroups
+
+Limits this search object to Groups who are members of this group.
+This is really useful when you want to haave your UI seperate out
+groups from users for display purposes
+
+=cut
+
+sub LimitToGroups {
+ my $self = shift;
+
+ my $principals = $self->NewAlias('Principals');
+ $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId',
+ ALIAS2 => $principals, FIELD2 =>'id');
+
+ $self->Limit( ALIAS => $principals,
+ FIELD => 'PrincipalType',
+ VALUE => 'Group',
+ ENTRYAGGREGATOR => 'OR',
+ );
+}
+
+# }}}
+
+# {{{ sub LimitToMembersOfGroup
+
+=head2 LimitToMembersOfGroup PRINCIPAL_ID
+
+Takes a Principal Id as its only argument.
+Limits the current search principals which are _directly_ members
+of the group which has PRINCIPAL_ID as its principal id.
+
+=cut
+
+sub LimitToMembersOfGroup {
+ my $self = shift;
+ my $group = shift;
+
+ return ($self->Limit(
+ VALUE => $group,
+ FIELD => 'GroupId',
+ ENTRYAGGREGATOR => 'OR',
+ QUOTEVALUE => 0
+ ));
+
+}
+# }}}
+
+1;
diff --git a/rt/lib/RT/Group_Overlay.pm b/rt/lib/RT/Group_Overlay.pm
new file mode 100644
index 0000000..f71fe7f
--- /dev/null
+++ b/rt/lib/RT/Group_Overlay.pm
@@ -0,0 +1,1255 @@
+
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Released under the terms of version 2 of the GNU Public License
+
+=head1 NAME
+
+ RT::Group - RT\'s group object
+
+=head1 SYNOPSIS
+
+ use RT::Group;
+my $group = new RT::Group($CurrentUser);
+
+=head1 DESCRIPTION
+
+An RT group object.
+
+=head1 AUTHOR
+
+Jesse Vincent, jesse@bestpractical.com
+
+=head1 SEE ALSO
+
+RT
+
+=head1 METHODS
+
+
+=begin testing
+
+# {{{ Tests
+ok (require RT::Group);
+
+ok (my $group = RT::Group->new($RT::SystemUser), "instantiated a group object");
+ok (my ($id, $msg) = $group->CreateUserDefinedGroup( Name => 'TestGroup', Description => 'A test group',
+ ), 'Created a new group');
+ok ($id != 0, "Group id is $id");
+ok ($group->Name eq 'TestGroup', "The group's name is 'TestGroup'");
+my $ng = RT::Group->new($RT::SystemUser);
+
+ok($ng->LoadUserDefinedGroup('TestGroup'), "Loaded testgroup");
+ok(($ng->id == $group->id), "Loaded the right group");
+
+
+ok (($id,$msg) = $ng->AddMember('1'), "Added a member to the group");
+ok($id, $msg);
+ok (($id,$msg) = $ng->AddMember('2' ), "Added a member to the group");
+ok($id, $msg);
+ok (($id,$msg) = $ng->AddMember('3' ), "Added a member to the group");
+ok($id, $msg);
+
+# Group 1 now has members 1, 2 ,3
+
+my $group_2 = RT::Group->new($RT::SystemUser);
+ok (my ($id_2, $msg_2) = $group_2->CreateUserDefinedGroup( Name => 'TestGroup2', Description => 'A second test group'), , 'Created a new group');
+ok ($id_2 != 0, "Created group 2 ok- $msg_2 ");
+ok (($id,$msg) = $group_2->AddMember($ng->PrincipalId), "Made TestGroup a member of testgroup2");
+ok($id, $msg);
+ok (($id,$msg) = $group_2->AddMember('1' ), "Added member RT_System to the group TestGroup2");
+ok($id, $msg);
+
+# Group 2 how has 1, g1->{1, 2,3}
+
+my $group_3 = RT::Group->new($RT::SystemUser);
+ok (($id_3, $msg) = $group_3->CreateUserDefinedGroup( Name => 'TestGroup3', Description => 'A second test group'), 'Created a new group');
+ok ($id_3 != 0, "Created group 3 ok - $msg");
+ok (($id,$msg) =$group_3->AddMember($group_2->PrincipalId), "Made TestGroup a member of testgroup2");
+ok($id, $msg);
+
+# g3 now has g2->{1, g1->{1,2,3}}
+
+my $principal_1 = RT::Principal->new($RT::SystemUser);
+$principal_1->Load('1');
+
+my $principal_2 = RT::Principal->new($RT::SystemUser);
+$principal_2->Load('2');
+
+ok (($id,$msg) = $group_3->AddMember('1' ), "Added member RT_System to the group TestGroup2");
+ok($id, $msg);
+
+# g3 now has 1, g2->{1, g1->{1,2,3}}
+
+ok($group_3->HasMember($principal_2) == undef, "group 3 doesn't have member 2");
+ok($group_3->HasMemberRecursively($principal_2), "group 3 has member 2 recursively");
+ok($ng->HasMember($principal_2) , "group ".$ng->Id." has member 2");
+my ($delid , $delmsg) =$ng->DeleteMember($principal_2->Id);
+ok ($delid !=0, "Sucessfully deleted it-".$delid."-".$delmsg);
+
+#Gotta reload the group objects, since we've been messing with various internals.
+# we shouldn't need to do this.
+#$ng->LoadUserDefinedGroup('TestGroup');
+#$group_2->LoadUserDefinedGroup('TestGroup2');
+#$group_3->LoadUserDefinedGroup('TestGroup');
+
+# G1 now has 1, 3
+# Group 2 how has 1, g1->{1, 3}
+# g3 now has 1, g2->{1, g1->{1, 3}}
+
+ok(!$ng->HasMember($principal_2) , "group ".$ng->Id." no longer has member 2");
+ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 doesn't have member 2");
+ok($group_2->HasMemberRecursively($principal_2) == undef, "group 2 doesn't have member 2");
+ok($ng->HasMember($principal_2) == undef, "group 1 doesn't have member 2");;
+ok($group_3->HasMemberRecursively($principal_2) == undef, "group 3 has member 2 recursively");
+
+# }}}
+
+=end testing
+
+
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use RT::Users;
+use RT::GroupMembers;
+use RT::Principals;
+use RT::ACL;
+
+use vars qw/$RIGHTS/;
+
+$RIGHTS = {
+ AdminGroup => 'Modify group metadata or delete group', # loc_pair
+ AdminGroupMembership =>
+ 'Modify membership roster for this group', # loc_pair
+ ModifyOwnMembership => 'Join or leave this group' # loc_pair
+};
+
+# Tell RT::ACE that this sort of object can get acls granted
+$RT::ACE::OBJECT_TYPES{'RT::Group'} = 1;
+
+
+#
+
+# TODO: This should be refactored out into an RT::ACLedObject or something
+# stuff the rights into a hash of rights that can exist.
+
+foreach my $right ( keys %{$RIGHTS} ) {
+ $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+}
+
+
+=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 the rights do
+
+=cut
+
+sub AvailableRights {
+ my $self = shift;
+ return($RIGHTS);
+}
+
+
+# {{{ sub SelfDescription
+
+=head2 SelfDescription
+
+Returns a user-readable description of what this group is for and what it's named.
+
+=cut
+
+sub SelfDescription {
+ my $self = shift;
+ if ($self->Domain eq 'ACLEquivalence') {
+ my $user = RT::Principal->new($self->CurrentUser);
+ $user->Load($self->Instance);
+ return $self->loc("user [_1]",$user->Object->Name);
+ }
+ elsif ($self->Domain eq 'UserDefined') {
+ return $self->loc("group '[_1]'",$self->Name);
+ }
+ elsif ($self->Domain eq 'Personal') {
+ my $user = RT::User->new($self->CurrentUser);
+ $user->Load($self->Instance);
+ return $self->loc("personal group '[_1]' for user '[_2]'",$self->Name, $user->Name);
+ }
+ elsif ($self->Domain eq 'RT::System-Role') {
+ return $self->loc("system [_1]",$self->Type);
+ }
+ elsif ($self->Domain eq 'RT::Queue-Role') {
+ my $queue = RT::Queue->new($self->CurrentUser);
+ $queue->Load($self->Instance);
+ return $self->loc("queue [_1] [_2]",$queue->Name, $self->Type);
+ }
+ elsif ($self->Domain eq 'RT::Ticket-Role') {
+ return $self->loc("ticket #[_1] [_2]",$self->Instance, $self->Type);
+ }
+ elsif ($self->Domain eq 'SystemInternal') {
+ return $self->loc("system group '[_1]'",$self->Type);
+ }
+ else {
+ return $self->loc("undescribed group [_1]",$self->Id);
+ }
+}
+
+# }}}
+
+# {{{ sub Load
+
+=head2 Load ID
+
+Load a group object from the database. Takes a single argument.
+If the argument is numerical, load by the column 'id'. Otherwise,
+complain and return.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift || return undef;
+
+ #if it's an int, load by id. otherwise, load by name.
+ if ( $identifier !~ /\D/ ) {
+ $self->SUPER::LoadById($identifier);
+ }
+ else {
+ $RT::Logger->crit("Group -> Load called with a bogus argument");
+ return undef;
+ }
+}
+
+# }}}
+
+# {{{ sub LoadUserDefinedGroup
+
+=head2 LoadUserDefinedGroup NAME
+
+Loads a system group from the database. The only argument is
+the group's name.
+
+
+=cut
+
+sub LoadUserDefinedGroup {
+ my $self = shift;
+ my $identifier = shift;
+
+ $self->LoadByCols( "Domain" => 'UserDefined',
+ "Name" => $identifier );
+}
+
+# }}}
+
+# {{{ sub LoadACLEquivalenceGroup
+
+=head2 LoadACLEquivalenceGroup PRINCIPAL
+
+Loads a user's acl equivalence group. Takes a principal object.
+ACL equivalnce groups are used to simplify the acl system. Each user
+has one group that only he is a member of. Rights granted to the user
+are actually granted to that group. This greatly simplifies ACL checks.
+While this results in a somewhat more complex setup when creating users
+and granting ACLs, it _greatly_ simplifies acl checks.
+
+
+
+=cut
+
+sub LoadACLEquivalenceGroup {
+ my $self = shift;
+ my $princ = shift;
+
+ $self->LoadByCols( "Domain" => 'ACLEquivalence',
+ "Type" => 'UserEquiv',
+ "Instance" => $princ->Id);
+}
+
+# }}}
+
+# {{{ sub LoadPersonalGroup
+
+=head2 LoadPersonalGroup {Name => NAME, User => USERID}
+
+Loads a personal group from the database.
+
+=cut
+
+sub LoadPersonalGroup {
+ my $self = shift;
+ my %args = ( Name => undef,
+ User => undef,
+ @_);
+
+ $self->LoadByCols( "Domain" => 'Personal',
+ "Instance" => $args{'User'},
+ "Type" => '',
+ "Name" => $args{'Name'} );
+}
+
+# }}}
+
+# {{{ sub LoadSystemInternalGroup
+
+=head2 LoadSystemInternalGroup NAME
+
+Loads a Pseudo group from the database. The only argument is
+the group's name.
+
+
+=cut
+
+sub LoadSystemInternalGroup {
+ my $self = shift;
+ my $identifier = shift;
+
+ $self->LoadByCols( "Domain" => 'SystemInternal',
+ "Type" => $identifier );
+}
+
+# }}}
+
+# {{{ sub LoadTicketRoleGroup
+
+=head2 LoadTicketRoleGroup { Ticket => TICKET_ID, Type => TYPE }
+
+Loads a ticket group from the database.
+
+Takes a param hash with 2 parameters:
+
+ Ticket is the TicketId we're curious about
+ Type is the type of Group we're trying to load:
+ Requestor, Cc, AdminCc, Owner
+
+=cut
+
+sub LoadTicketRoleGroup {
+ my $self = shift;
+ my %args = (Ticket => '0',
+ Type => undef,
+ @_);
+ $self->LoadByCols( Domain => 'RT::Ticket-Role',
+ Instance =>$args{'Ticket'},
+ Type => $args{'Type'}
+ );
+}
+
+# }}}
+
+# {{{ sub LoadQueueRoleGroup
+
+=head2 LoadQueueRoleGroup { Queue => Queue_ID, Type => TYPE }
+
+Loads a Queue group from the database.
+
+Takes a param hash with 2 parameters:
+
+ Queue is the QueueId we're curious about
+ Type is the type of Group we're trying to load:
+ Requestor, Cc, AdminCc, Owner
+
+=cut
+
+sub LoadQueueRoleGroup {
+ my $self = shift;
+ my %args = (Queue => undef,
+ Type => undef,
+ @_);
+ $self->LoadByCols( Domain => 'RT::Queue-Role',
+ Instance =>$args{'Queue'},
+ Type => $args{'Type'}
+ );
+}
+
+# }}}
+
+# {{{ sub LoadSystemRoleGroup
+
+=head2 LoadSystemRoleGroup Type
+
+Loads a System group from the database.
+
+Takes a single param: Type
+
+ Type is the type of Group we're trying to load:
+ Requestor, Cc, AdminCc, Owner
+
+=cut
+
+sub LoadSystemRoleGroup {
+ my $self = shift;
+ my $type = shift;
+ $self->LoadByCols( Domain => 'RT::System-Role',
+ Type => $type
+ );
+}
+
+# }}}
+
+# {{{ sub Create
+=head2 Create
+
+You need to specify what sort of group you're creating by calling one of the other
+Create_____ routines.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ $RT::Logger->crit("Someone called RT::Group->Create. this method does not exist. someone's being evil");
+ return(0,$self->loc('Permission Denied'));
+}
+
+# }}}
+
+# {{{ sub _Create
+
+=head2 _Create
+
+Takes a paramhash with named arguments: Name, Description.
+
+Returns a tuple of (Id, Message). If id is 0, the create failed
+
+=cut
+
+sub _Create {
+ my $self = shift;
+ my %args = (
+ Name => undef,
+ Description => undef,
+ Domain => undef,
+ Type => undef,
+ Instance => '0',
+ InsideTransaction => undef,
+ @_
+ );
+
+ $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
+ # Groups deal with principal ids, rather than user ids.
+ # When creating this group, set up a principal Id for it.
+ my $principal = RT::Principal->new( $self->CurrentUser );
+ my $principal_id = $principal->Create(
+ PrincipalType => 'Group',
+ ObjectId => '0'
+ );
+ $principal->__Set(Field => 'ObjectId', Value => $principal_id);
+
+
+ $self->SUPER::Create(
+ id => $principal_id,
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ Type => $args{'Type'},
+ Domain => $args{'Domain'},
+ Instance => ($args{'Instance'} || '0')
+ );
+ my $id = $self->Id;
+ unless ($id) {
+ return ( 0, $self->loc('Could not create group') );
+ }
+
+ # If we couldn't create a principal Id, get the fuck out.
+ unless ($principal_id) {
+ $RT::Handle->Rollback() unless ($args{'InsideTransaction'});
+ $self->crit( "Couldn't create a Principal on new user create. Strange things are afoot at the circle K" );
+ return ( 0, $self->loc('Could not create group') );
+ }
+
+ # Now we make the group a member of itself as a cached group member
+ # this needs to exist so that group ACL checks don't fall over.
+ # you're checking CachedGroupMembers to see if the principal in question
+ # is a member of the principal the rights have been granted too
+
+ # in the ordinary case, this would fail badly because it would recurse and add all the members of this group as
+ # cached members. thankfully, we're creating the group now...so it has no members.
+ my $cgm = RT::CachedGroupMember->new($self->CurrentUser);
+ $cgm->Create(Group =>$self->PrincipalObj, Member => $self->PrincipalObj, ImmediateParent => $self->PrincipalObj);
+
+
+
+ $RT::Handle->Commit() unless ($args{'InsideTransaction'});
+ return ( $id, $self->loc("Group created") );
+}
+
+# }}}
+
+# {{{ CreateUserDefinedGroup
+
+=head2 CreateUserDefinedGroup { Name => "name", Description => "Description"}
+
+A helper subroutine which creates a system group
+
+Returns a tuple of (Id, Message). If id is 0, the create failed
+
+=cut
+
+sub CreateUserDefinedGroup {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ $RT::Logger->warning( $self->CurrentUser->Name
+ . " Tried to create a group without permission." );
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ return($self->_Create( Domain => 'UserDefined', Type => '', Instance => '', @_));
+}
+
+# }}}
+
+# {{{ _CreateACLEquivalenceGroup
+
+=head2 _CreateACLEquivalenceGroup { Principal }
+
+A helper subroutine which creates a group containing only
+an individual user. This gets used by the ACL system to check rights.
+Yes, it denormalizes the data, but that's ok, as we totally win on performance.
+
+Returns a tuple of (Id, Message). If id is 0, the create failed
+
+=cut
+
+sub _CreateACLEquivalenceGroup {
+ my $self = shift;
+ my $princ = shift;
+
+ my $id = $self->_Create( Domain => 'ACLEquivalence',
+ Type => 'UserEquiv',
+ Name => 'User '. $princ->Object->Id,
+ Description => 'ACL equiv. for user '.$princ->Object->Id,
+ Instance => $princ->Id,
+ InsideTransaction => 1);
+ unless ($id) {
+ $RT::Logger->crit("Couldn't create ACL equivalence group");
+ return undef;
+ }
+
+ # We use stashuser so we don't get transactions inside transactions
+ # and so we bypass all sorts of cruft we don't need
+ my $aclstash = RT::GroupMember->new($self->CurrentUser);
+ my ($stash_id, $add_msg) = $aclstash->_StashUser(Group => $self->PrincipalObj,
+ Member => $princ);
+
+ unless ($stash_id) {
+ $RT::Logger->crit("Couldn't add the user to his own acl equivalence group:".$add_msg);
+ # We call super delete so we don't get acl checked.
+ $self->SUPER::Delete();
+ return(undef);
+ }
+ return ($id);
+}
+
+# }}}
+
+# {{{ CreatePersonalGroup
+
+=head2 CreatePersonalGroup { PrincipalId => PRINCIPAL_ID, Name => "name", Description => "Description"}
+
+A helper subroutine which creates a personal group. Generally,
+personal groups are used for ACL delegation and adding to ticket roles
+PrincipalId defaults to the current user's principal id.
+
+Returns a tuple of (Id, Message). If id is 0, the create failed
+
+=cut
+
+sub CreatePersonalGroup {
+ my $self = shift;
+ my %args = (
+ Name => undef,
+ Description => undef,
+ PrincipalId => $self->CurrentUser->PrincipalId,
+ @_
+ );
+
+ if ( $self->CurrentUser->PrincipalId == $args{'PrincipalId'} ) {
+
+ unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups') ) {
+ $RT::Logger->warning( $self->CurrentUser->Name
+ . " Tried to create a group without permission." );
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ }
+ else {
+ unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
+ $RT::Logger->warning( $self->CurrentUser->Name
+ . " Tried to create a group without permission." );
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ }
+
+ return (
+ $self->_Create(
+ Domain => 'Personal',
+ Type => '',
+ Instance => $args{'PrincipalId'},
+ Name => $args{'Name'},
+ Description => $args{'Description'}
+ )
+ );
+}
+
+# }}}
+
+# {{{ CreateRoleGroup
+
+=head2 CreateRoleGroup { Domain => DOMAIN, Type => TYPE, Instance => ID }
+
+A helper subroutine which creates a ticket group. (What RT 2.0 called Ticket watchers)
+Type is one of ( "Requestor" || "Cc" || "AdminCc" || "Owner")
+Domain is one of (RT::Ticket-Role || RT::Queue-Role || RT::System-Role)
+Instance is the id of the ticket or queue in question
+
+This routine expects to be called from {Ticket||Queue}->CreateTicketGroups _inside of a transaction_
+
+Returns a tuple of (Id, Message). If id is 0, the create failed
+
+=cut
+
+sub CreateRoleGroup {
+ my $self = shift;
+ my %args = ( Instance => undef,
+ Type => undef,
+ Domain => undef,
+ @_ );
+ unless ( $args{'Type'} =~ /^(?:Cc|AdminCc|Requestor|Owner)$/ ) {
+ return ( 0, $self->loc("Invalid Group Type") );
+ }
+
+
+ return ( $self->_Create( Domain => $args{'Domain'},
+ Instance => $args{'Instance'},
+ Type => $args{'Type'},
+ InsideTransaction => 1 ) );
+}
+
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete
+
+Delete this object
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return ( 0, 'Permission Denied' );
+ }
+
+ $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
+ # TODO XXX
+
+ # Remove the principal object
+ # Remove this group from anything it's a member of.
+ # Remove all cached members of this group
+ # Remove any rights granted to this group
+ # remove any rights delegated by way of this group
+
+ return ( $self->SUPER::Delete(@_) );
+}
+
+# }}}
+
+=head2 SetDisabled BOOL
+
+If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
+It will not appear in most group listings.
+
+This routine finds all the cached group members that are members of this group (recursively) and disables them.
+=cut
+
+ # }}}
+
+ sub SetDisabled {
+ my $self = shift;
+ my $val = shift;
+ if ($self->Domain eq 'Personal') {
+ if ($self->CurrentUser->PrincipalId == $self->Instance) {
+ unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+ else {
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return (0, $self->loc('Permission Denied'));
+ }
+ }
+ $RT::Handle->BeginTransaction();
+ $self->PrincipalObj->SetDisabled($val);
+
+
+
+
+ # Find all occurrences of this member as a member of this group
+ # in the cache and nuke them, recursively.
+
+ # The following code will delete all Cached Group members
+ # where this member's group is _not_ the primary group
+ # (Ie if we're deleting C as a member of B, and B happens to be
+ # a member of A, will delete C as a member of A without touching
+ # C as a member of B
+
+ my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
+
+ #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();
+
+
+
+ while ( my $item = $cached_submembers->Next() ) {
+ my $del_err = $item->SetDisabled($val);
+ unless ($del_err) {
+ $RT::Handle->Rollback();
+ $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
+ return (undef);
+ }
+ }
+
+ $RT::Handle->Commit();
+ return (1, $self->loc("Succeeded"));
+
+}
+
+# }}}
+
+
+
+sub Disabled {
+ my $self = shift;
+ $self->PrincipalObj->Disabled(@_);
+}
+
+
+# {{{ DeepMembersObj
+
+=head2 DeepMembersObj
+
+Returns an RT::CachedGroupMembers object of this group's members.
+
+=cut
+
+sub DeepMembersObj {
+ my $self = shift;
+ my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+ $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
+
+ return ( $members_obj );
+
+}
+
+# }}}
+
+# {{{ UserMembersObj
+
+=head2 UserMembersObj
+
+Returns an RT::Users object of this group's members, including
+all members of subgroups
+
+=cut
+
+sub UserMembersObj {
+ my $self = shift;
+
+ my $users = RT::Users->new($self->CurrentUser);
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+
+ my $cached_members = $users->NewAlias('CachedGroupMembers');
+ $users->Join(ALIAS1 => $cached_members, FIELD1 => 'MemberId',
+ ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id');
+ $users->Limit(ALIAS => $cached_members,
+ FIELD => 'GroupId',
+ OPERATOR => '=',
+ VALUE => $self->PrincipalId);
+
+ return ( $users);
+
+}
+
+# }}}
+
+# {{{ MembersObj
+
+=head2 MembersObj
+
+Returns an RT::CachedGroupMembers object of this group's members.
+
+=cut
+
+sub MembersObj {
+ my $self = shift;
+ my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+ $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
+
+ return ( $members_obj );
+
+}
+
+# }}}
+
+# {{{ MemberEmailAddresses
+
+=head2 MemberEmailAddresses
+
+Returns an array of the email addresses of all of this group's members
+
+
+=cut
+
+sub MemberEmailAddresses {
+ my $self = shift;
+
+ my %addresses;
+ my $members = $self->UserMembersObj();
+ while (my $member = $members->Next) {
+ $addresses{$member->EmailAddress} = 1;
+ }
+ return(sort keys %addresses);
+}
+
+# }}}
+
+# {{{ MemberEmailAddressesAsString
+
+=head2 MemberEmailAddressesAsString
+
+Returns a comma delimited string of the email addresses of all users
+who are members of this group.
+
+=cut
+
+
+sub MemberEmailAddressesAsString {
+ my $self = shift;
+ return (join(', ', $self->MemberEmailAddresses));
+}
+
+# }}}
+
+# {{{ AddMember
+
+=head2 AddMember PRINCIPAL_ID
+
+AddMember adds a principal to this group. It takes a single principal id.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub AddMember {
+ my $self = shift;
+ my $new_member = shift;
+
+
+
+ if ($self->Domain eq 'Personal') {
+ if ($self->CurrentUser->PrincipalId == $self->Instance) {
+ unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+
+ else {
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+ unless ( ($new_member == $self->CurrentUser->PrincipalId &&
+ $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
+ $self->CurrentUserHasRight('AdminGroupMembership') ) {
+ #User has no permission to be doing this
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ }
+ $self->_AddMember(PrincipalId => $new_member);
+}
+
+# A helper subroutine for AddMember that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue AddWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+# takes a paramhash of { PrincipalId => undef, InsideTransaction }
+
+sub _AddMember {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ InsideTransaction => undef,
+ @_);
+ my $new_member = $args{'PrincipalId'};
+
+ unless ($self->Id) {
+ $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
+ return(0, $self->loc("Group not found"));
+ }
+
+ unless ($new_member =~ /^\d+$/) {
+ $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
+ }
+
+
+ my $new_member_obj = RT::Principal->new( $self->CurrentUser );
+ $new_member_obj->Load($new_member);
+
+
+ unless ( $new_member_obj->Id ) {
+ $RT::Logger->debug("Couldn't find that principal");
+ return ( 0, $self->loc("Couldn't find that principal") );
+ }
+
+ if ( $self->HasMember( $new_member_obj ) ) {
+
+ #User is already a member of this group. no need to add it
+ return ( 0, $self->loc("Group already has member") );
+ }
+ if ( $new_member_obj->IsGroup &&
+ $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
+
+ #This group can't be made to be a member of itself
+ return ( 0, $self->loc("Groups can't be members of their members"));
+ }
+
+
+ my $member_object = RT::GroupMember->new( $self->CurrentUser );
+ my $id = $member_object->Create(
+ Member => $new_member_obj,
+ Group => $self->PrincipalObj,
+ InsideTransaction => $args{'InsideTransaction'}
+ );
+ if ($id) {
+ return ( 1, $self->loc("Member added") );
+ }
+ else {
+ return(0, $self->loc("Couldn't add member to group"));
+ }
+}
+# }}}
+
+# {{{ HasMember
+
+=head2 HasMember RT::Principal
+
+Takes an RT::Principal object returns a GroupMember Id if that user is a
+member of this group.
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub HasMember {
+ my $self = shift;
+ my $principal = shift;
+
+
+ unless (UNIVERSAL::isa($principal,'RT::Principal')) {
+ $RT::Logger->crit("Group::HasMember was called with an argument that".
+ "isn't an RT::Principal. It's $principal");
+ return(undef);
+ }
+
+ unless ($principal->Id) {
+ return(undef);
+ }
+
+ my $member_obj = RT::GroupMember->new( $self->CurrentUser );
+ $member_obj->LoadByCols( MemberId => $principal->id,
+ GroupId => $self->PrincipalId );
+
+ #If we have a member object
+ if ( defined $member_obj->id ) {
+ return ( $member_obj->id );
+ }
+
+ #If Load returns no objects, we have an undef id.
+ else {
+ #$RT::Logger->debug($self." does not contain principal ".$principal->id);
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ HasMemberRecursively
+
+=head2 HasMemberRecursively RT::Principal
+
+Takes an RT::Principal object and returns true if that user is a member of
+this group.
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub HasMemberRecursively {
+ my $self = shift;
+ my $principal = shift;
+
+ unless (UNIVERSAL::isa($principal,'RT::Principal')) {
+ $RT::Logger->crit("Group::HasMemberRecursively was called with an argument that".
+ "isn't an RT::Principal. It's $principal");
+ return(undef);
+ }
+ my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
+ $member_obj->LoadByCols( MemberId => $principal->Id,
+ GroupId => $self->PrincipalId ,
+ Disabled => 0
+ );
+
+ #If we have a member object
+ if ( defined $member_obj->id ) {
+ return ( 1);
+ }
+
+ #If Load returns no objects, we have an undef id.
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ DeleteMember
+
+=head2 DeleteMember PRINCIPAL_ID
+
+Takes the principal id of a current user or group.
+If the current user has apropriate rights,
+removes that GroupMember from this group.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub DeleteMember {
+ my $self = shift;
+ my $member_id = shift;
+
+
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+
+ if ($self->Domain eq 'Personal') {
+ if ($self->CurrentUser->PrincipalId == $self->Instance) {
+ unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+ else {
+ unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
+ $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
+ $self->CurrentUserHasRight('AdminGroupMembership') ) {
+ #User has no permission to be doing this
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+ $self->_DeleteMember($member_id);
+}
+
+# A helper subroutine for DeleteMember that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+sub _DeleteMember {
+ my $self = shift;
+ my $member_id = shift;
+
+ my $member_obj = RT::GroupMember->new( $self->CurrentUser );
+
+ $member_obj->LoadByCols( MemberId => $member_id,
+ GroupId => $self->PrincipalId);
+
+
+ #If we couldn't load it, return undef.
+ unless ( $member_obj->Id() ) {
+ $RT::Logger->debug("Group has no member with that id");
+ return ( 0,$self->loc( "Group has no such member" ));
+ }
+
+ #Now that we've checked ACLs and sanity, delete the groupmember
+ my $val = $member_obj->Delete();
+
+ if ($val) {
+ return ( $val, $self->loc("Member deleted") );
+ }
+ else {
+ $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
+ return ( 0, $self->loc("Member not deleted" ));
+ }
+}
+
+# }}}
+
+# {{{ ACL Related routines
+
+# {{{ sub _Set
+sub _Set {
+ my $self = shift;
+
+ if ($self->Domain eq 'Personal') {
+ if ($self->CurrentUser->PrincipalId == $self->Instance) {
+ unless ( $self->CurrentUserHasRight('AdminOwnPersonalGroups')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ unless ( $self->CurrentUserHasRight('AdminAllPersonalGroups') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+ else {
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ return ( $self->SUPER::_Set(@_) );
+}
+
+# }}}
+
+
+
+
+=item CurrentUserHasRight RIGHTNAME
+
+Returns true if the current user has the specified right for this group.
+
+
+ TODO: we don't deal with membership visibility yet
+
+=cut
+
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+
+
+
+ if ($self->Id &&
+ $self->CurrentUser->HasRight( Object => $self,
+ Right => $right )) {
+ return(1);
+ }
+ elsif ( $self->CurrentUser->HasRight(Object => $RT::System, Right => $right )) {
+ return (1);
+ } else {
+ return(undef);
+ }
+
+}
+
+# }}}
+
+
+
+
+# {{{ Principal related routines
+
+=head2 PrincipalObj
+
+Returns the principal object for this user. returns an empty RT::Principal
+if there's no principal object matching this user.
+The response is cached. PrincipalObj should never ever change.
+
+=begin testing
+
+ok(my $u = RT::Group->new($RT::SystemUser));
+ok($u->Load(4), "Loaded the first user");
+ok($u->PrincipalObj->ObjectId == 4, "user 4 is the fourth principal");
+ok($u->PrincipalObj->PrincipalType eq 'Group' , "Principal 4 is a group");
+
+=end testing
+
+=cut
+
+
+sub PrincipalObj {
+ my $self = shift;
+ unless ($self->{'PrincipalObj'} &&
+ ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
+ ($self->{'PrincipalObj'}->PrincipalType eq 'Group')) {
+
+ $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
+ $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
+ 'PrincipalType' => 'Group') ;
+ }
+ return($self->{'PrincipalObj'});
+}
+
+
+=head2 PrincipalId
+
+Returns this user's PrincipalId
+
+=cut
+
+sub PrincipalId {
+ my $self = shift;
+ return $self->Id;
+}
+
+# }}}
+1;
+
diff --git a/rt/lib/RT/Groups.pm b/rt/lib/RT/Groups.pm
new file mode 100755
index 0000000..29f12a5
--- /dev/null
+++ b/rt/lib/RT/Groups.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Groups -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Groups
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Groups;
+
+use RT::SearchBuilder;
+use RT::Group;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Groups';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Group item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Group->new($self->CurrentUser));
+}
+
+ eval "require RT::Groups_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Groups_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Groups_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Groups_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Groups_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Groups_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Groups_Overlay, RT::Groups_Vendor, RT::Groups_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Groups_Overlay.pm b/rt/lib/RT/Groups_Overlay.pm
new file mode 100644
index 0000000..a9ca44c
--- /dev/null
+++ b/rt/lib/RT/Groups_Overlay.pm
@@ -0,0 +1,360 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Groups - a collection of RT::Group objects
+
+=head1 SYNOPSIS
+
+ use RT::Groups;
+ my $groups = $RT::Groups->new($CurrentUser);
+ $groups->LimitToReal();
+ while (my $group = $groups->Next()) {
+ print $group->Id ." is a group id\n";
+ }
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Groups);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+# {{{ sub _Init
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "Groups";
+ $self->{'primary_key'} = "id";
+
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'Name',
+ ORDER => 'ASC');
+
+
+ return ( $self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ LimiToSystemInternalGroups
+
+=head2 LimitToSystemInternalGroups
+
+Return only SystemInternal Groups, such as "privileged" "unprivileged" and "everyone"
+
+=cut
+
+
+sub LimitToSystemInternalGroups {
+ my $self = shift;
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'SystemInternal');
+ # All system internal groups have the same instance. No reason to limit down further
+ #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '0');
+}
+
+
+# }}}
+
+# {{{ LimiToUserDefinedGroups
+
+=head2 LimitToUserDefined Groups
+
+Return only UserDefined Groups
+
+=cut
+
+
+sub LimitToUserDefinedGroups {
+ my $self = shift;
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined');
+ # All user-defined groups have the same instance. No reason to limit down further
+ #$self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '');
+}
+
+
+# }}}
+
+# {{{ LimiToPersonalGroups
+
+=head2 LimitToPersonalGroupsFor PRINCIPAL_ID
+
+Return only Personal Groups for the user whose principal id
+is PRINCIPAL_ID
+
+=cut
+
+
+sub LimitToPersonalGroupsFor {
+ my $self = shift;
+ my $princ = shift;
+
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'Personal');
+ $self->Limit( FIELD => 'Instance',
+ OPERATOR => '=',
+ VALUE => $princ,
+ ENTRY_AGGREGATOR => 'OR');
+}
+
+
+# }}}
+
+# {{{ LimitToRolesForQueue
+
+=item LimitToRolesForQueue QUEUE_ID
+
+Limits the set of groups found to role groups for queue QUEUE_ID
+
+=cut
+
+sub LimitToRolesForQueue {
+ my $self = shift;
+ my $queue = shift;
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::Queue-Role');
+ $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => $queue);
+}
+
+# }}}
+
+# {{{ LimitToRolesForTicket
+
+=item LimitToRolesForTicket Ticket_ID
+
+Limits the set of groups found to role groups for Ticket Ticket_ID
+
+=cut
+
+sub LimitToRolesForTicket {
+ my $self = shift;
+ my $Ticket = shift;
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::Ticket-Role');
+ $self->Limit(FIELD => 'Instance', OPERATOR => '=', VALUE => '$Ticket');
+}
+
+# }}}
+
+# {{{ LimitToRolesForSystem
+
+=item LimitToRolesForSystem System_ID
+
+Limits the set of groups found to role groups for System System_ID
+
+=cut
+
+sub LimitToRolesForSystem {
+ my $self = shift;
+ $self->Limit(FIELD => 'Domain', OPERATOR => '=', VALUE => 'RT::System-Role');
+}
+
+# }}}
+
+=head2 WithMember {PrincipalId => PRINCIPAL_ID, Recursively => undef}
+
+Limits the set of groups returned to groups which have
+Principal PRINCIPAL_ID as a member
+
+=begin testing
+
+my $u = RT::User->new($RT::SystemUser);
+$u->Create(Name => 'Membertests');
+my $g = RT::Group->new($RT::SystemUser);
+my ($id, $msg) = $g->CreateUserDefinedGroup(Name => 'Membertests');
+ok ($id,$msg);
+
+my ($aid, $amsg) =$g->AddMember($u->id);
+ok ($aid, $amsg);
+ok($g->HasMember($u->PrincipalObj),"G has member u");
+
+my $groups = RT::Groups->new($RT::SystemUser);
+$groups->LimitToUserDefinedGroups();
+$groups->WithMember(PrincipalId => $u->id);
+ok ($groups->Count == 1,"found the 1 group - " . $groups->Count);
+ok ($groups->First->Id == $g->Id, "it's the right one");
+
+
+
+
+=end testing
+
+
+=cut
+
+sub WithMember {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ Recursively => undef,
+ @_);
+ my $members;
+
+ if ($args{'Recursively'}) {
+ $members = $self->NewAlias('CachedGroupMembers');
+ } else {
+ $members = $self->NewAlias('GroupMembers');
+ }
+ $self->Join(ALIAS1 => 'main', FIELD1 => 'id',
+ ALIAS2 => $members, FIELD2 => 'GroupId');
+
+ $self->Limit(ALIAS => $members, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'});
+}
+
+
+sub WithRight {
+ my $self = shift;
+ my %args = ( Right => undef,
+ Object => => undef,
+ IncludeSystemRights => 1,
+ IncludeSuperusers => undef,
+ @_ );
+
+ my $groupprinc = $self->NewAlias('Principals');
+ my $acl = $self->NewAlias('ACL');
+
+ # {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser
+ $self->Limit( ALIAS => $acl,
+ FIELD => 'RightName',
+ OPERATOR => ($args{Right} ? '=' : 'IS NOT'),
+ VALUE => $args{Right} || 'NULL',
+ ENTRYAGGREGATOR => 'OR' );
+
+ if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
+ $self->Limit( ALIAS => $acl,
+ FIELD => 'RightName',
+ OPERATOR => '=',
+ VALUE => 'SuperUser',
+ ENTRYAGGREGATOR => 'OR' );
+ }
+ # }}}
+
+ my ($or_check_ticket_roles, $or_check_roles);
+ my $which_object = "$acl.ObjectType = 'RT::System'";
+
+ if ( defined $args{'Object'} ) {
+ if ( ref($args{'Object'}) eq 'RT::Ticket' ) {
+ $or_check_ticket_roles =
+ " OR ( main.Domain = 'RT::Ticket-Role' AND main.Instance = " . $args{'Object'}->Id . ") ";
+
+ # If we're looking at ticket rights, we also want to look at the associated queue rights.
+ # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
+ # we load the queue object and ask all the rest of our questions about the queue.
+ $args{'Object'} = $args{'Object'}->QueueObj;
+ }
+ # TODO XXX This really wants some refactoring
+ if ( ref($args{'Object'}) eq 'RT::Queue' ) {
+ $or_check_roles =
+ " OR ( ( (main.Domain = 'RT::Queue-Role' AND main.Instance = " .
+ $args{'Object'}->Id . ") $or_check_ticket_roles ) " .
+ " AND main.Type = $acl.PrincipalType AND main.id = $groupprinc.id) ";
+ }
+
+ if ( $args{'IncludeSystemRights'} ) {
+ $which_object .= ' OR ';
+ }
+ else {
+ $which_object = '';
+ }
+ $which_object .=
+ " ($acl.ObjectType = '" . ref($args{'Object'}) . "'" .
+ " AND $acl.ObjectId = " . $args{'Object'}->Id . ") ";
+ }
+
+ $self->_AddSubClause( "WhichObject", "($which_object)" );
+
+ $self->_AddSubClause( "WhichGroup",
+ qq{
+ ( ( $acl.PrincipalId = $groupprinc.id
+ AND $acl.PrincipalType = 'Group'
+ AND ( main.Domain = 'SystemInternal'
+ OR main.Domain = 'UserDefined'
+ OR main.Domain = 'ACLEquivalence')
+ AND main.id = $groupprinc.id)
+ $or_check_roles)
+ }
+ );
+}
+
+# {{{ sub LimitToEnabled
+
+=head2 LimitToEnabled
+
+Only find items that haven\'t been disabled
+
+=cut
+
+sub LimitToEnabled {
+ my $self = shift;
+
+ my $alias = $self->Join(
+ TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Principals',
+ FIELD2 => 'ObjectId'
+ );
+
+ $self->Limit( ALIAS => $alias,
+ FIELD => 'Disabled',
+ VALUE => '0',
+ OPERATOR => '=' );
+}
+# }}}
+
+# {{{ sub LimitToDisabled
+
+=head2 LimitToDeleted
+
+Only find items that have been deleted.
+
+=cut
+
+sub LimitToDeleted {
+ my $self = shift;
+
+ my $alias = $self->Join(
+ TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Principals',
+ FIELD2 => 'ObjectId'
+ );
+
+ $self->{'find_disabled_rows'} = 1;
+ $self->Limit( ALIAS => $alias,
+ FIELD => 'Disabled',
+ OPERATOR => '=',
+ VALUE => '1'
+ );
+}
+# }}}
+1;
+
diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm
new file mode 100644
index 0000000..9b611b9
--- /dev/null
+++ b/rt/lib/RT/Handle.pm
@@ -0,0 +1,106 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Handle - RT's database handle
+
+=head1 SYNOPSIS
+
+ use RT::Handle;
+
+=head1 DESCRIPTION
+
+=begin testing
+
+ok(require RT::Handle);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+package RT::Handle;
+
+use strict;
+use vars qw/@ISA/;
+
+eval "use DBIx::SearchBuilder::Handle::$RT::DatabaseType;
+\@ISA= qw(DBIx::SearchBuilder::Handle::$RT::DatabaseType);";
+#TODO check for errors here.
+
+=head2 Connect
+
+Connects to RT's database handle.
+Takes nothing. Calls SUPER::Connect with the needed args
+
+=cut
+
+sub Connect {
+my $self=shift;
+
+
+ if ($RT::DatabaseType eq 'Oracle') {
+ $ENV{'NLS_LANG'} = ".UTF8";
+ }
+
+ $self->SUPER::Connect(
+ User => $RT::DatabaseUser,
+ Password => $RT::DatabasePassword,
+ );
+
+}
+
+=item BuildDSN
+
+Build the DSN for the RT database. doesn't take any parameters, draws all that
+from the config file.
+
+=cut
+
+
+sub BuildDSN {
+ my $self = shift;
+# Unless the database port is a positive integer, we really don't want to pass it.
+$RT::DatabasePort = undef unless (defined $RT::DatabasePort && $RT::DatabasePort =~ /^(\d+)$/);
+$RT::DatabaseHost = undef unless (defined $RT::DatabaseHost && $RT::DatabaseHost ne '');
+
+
+ $self->SUPER::BuildDSN(Host => $RT::DatabaseHost,
+ Database => $RT::DatabaseName,
+ Port => $RT::DatabasePort,
+ Driver => $RT::DatabaseType,
+ RequireSSL => $RT::DatabaseRequireSSL,
+ DisconnectHandleOnDestroy => 1
+ );
+
+
+}
+
+eval "require RT::Handle_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Handle_Vendor.pm});
+eval "require RT::Handle_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Handle_Local.pm});
+
+1;
diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm
new file mode 100644
index 0000000..79c3e8a
--- /dev/null
+++ b/rt/lib/RT/I18N.pm
@@ -0,0 +1,436 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+RT::I18N - a base class for localization of RT
+
+=cut
+
+package RT::I18N;
+
+use strict;
+use Locale::Maketext 1.04;
+use Locale::Maketext::Lexicon 0.25;
+use base ('Locale::Maketext::Fuzzy');
+use vars qw( %Lexicon );
+
+#If we're running on 5.6, we desperately need Encode::compat. But if we're on 5.8, we don't really need it.
+BEGIN { if ($] < 5.007001) {
+require Encode::compat;
+} }
+use Encode;
+
+use MIME::Entity;
+use MIME::Head;
+
+# I decree that this project's first language is English.
+
+%Lexicon = (
+ 'TEST_STRING' => 'Concrete Mixer',
+
+ '__Content-Type' => 'text/plain; charset=utf-8',
+
+ '_AUTO' => 1,
+ # That means that lookup failures can't happen -- if we get as far
+ # as looking for something in this lexicon, and we don't find it,
+ # then automagically set $Lexicon{$key} = $key, before possibly
+ # compiling it.
+
+ # The exception is keys that start with "_" -- they aren't auto-makeable.
+
+);
+# End of lexicon.
+
+=head2 Init
+
+Initializes the lexicons used for localization.
+
+=begin testing
+
+use_ok (RT::I18N);
+ok(RT::I18N->Init);
+
+=end testing
+
+=cut
+
+sub Init {
+ # Load language-specific functions
+ foreach my $language ( glob(substr(__FILE__, 0, -3) . "/*.pm")) {
+ if ($language =~ /^([-\w.\/\\~:]+)$/) {
+ require $1;
+ }
+ else {
+ warn("$language is tainted. not loading");
+ }
+ }
+
+ my @lang = @RT::LexiconLanguages;
+ @lang = ('*') unless @lang;
+
+ # Acquire all .po files and iterate them into lexicons
+ Locale::Maketext::Lexicon->import({
+ _decode => 1, map {
+ $_ => [
+ Gettext => (substr(__FILE__, 0, -3) . "/$_.po"),
+ Gettext => "$RT::LocalLexiconPath/*/$_.po",
+ ],
+ } @lang
+ });
+
+ return 1;
+}
+
+=head2 encoding
+
+Returns the encoding of the current lexicon, as yanked out of __ContentType's "charset" field.
+If it can't find anything, it returns 'ISO-8859-1'
+
+=begin testing
+
+ok(my $chinese = RT::I18N->get_handle('zh_tw'));
+ok(UNIVERSAL::can($chinese, 'maketext'));
+ok($chinese->maketext('__Content-Type') =~ /utf-8/i, "Found the utf-8 charset for traditional chinese in the string ".$chinese->maketext('__Content-Type'));
+ok($chinese->encoding eq 'utf-8', "The encoding is 'utf-8' -".$chinese->encoding);
+
+ok(my $en = RT::I18N->get_handle('en'));
+ok(UNIVERSAL::can($en, 'maketext'));
+ok($en->encoding eq 'utf-8', "The encoding ".$en->encoding." is 'utf-8'");
+
+=end testing
+
+
+=cut
+
+
+sub encoding { 'utf-8' }
+
+# {{{ SetMIMEEntityToUTF8
+
+=head2 SetMIMEEntityToUTF8 $entity
+
+An utility method which will try to convert entity body into utf8.
+It's now a wrap-up of SetMIMEEntityToEncoding($entity, 'utf-8').
+
+=cut
+
+sub SetMIMEEntityToUTF8 {
+ RT::I18N::SetMIMEEntityToEncoding(shift, 'utf-8');
+}
+
+# }}}
+
+# {{{ SetMIMEEntityToEncoding
+
+=head2 SetMIMEEntityToEncoding $entity, $encoding
+
+An utility method which will try to convert entity body into specified
+charset encoding (encoded as octets, *not* unicode-strings). It will
+iterate all the entities in $entity, and try to convert each one into
+specified charset if whose Content-Type is 'text/plain'.
+
+This method doesn't return anything meaningful.
+
+=cut
+
+sub SetMIMEEntityToEncoding {
+ my ( $entity, $enc, $preserve_words ) = ( shift, shift, shift );
+
+ #if ( $entity->is_multipart ) {
+ #$RT::Logger->crit("This entity is a multipart " . $entity->head->as_string);
+ SetMIMEEntityToEncoding( $_, $enc, $preserve_words ) foreach $entity->parts;
+ #}
+
+ my $charset = _FindOrGuessCharset($entity) or return;
+ # one and only normalization
+ $charset = 'utf-8' if $charset =~ /^utf-?8$/i;
+ $enc = 'utf-8' if $enc =~ /^utf-?8$/i;
+
+ SetMIMEHeadToEncoding(
+ $entity->head,
+ _FindOrGuessCharset($entity, 1) => $enc,
+ $preserve_words
+ );
+
+ my $head = $entity->head;
+
+ # convert at least MIME word encoded attachment filename
+ foreach my $attr (qw(content-type.name content-disposition.filename)) {
+ if ( my $name = $head->mime_attr($attr) and !$preserve_words ) {
+ $head->mime_attr( $attr => DecodeMIMEWordsToUTF8($name) );
+ }
+ }
+
+ # If this is a textual entity, we'd need to preserve its original encoding
+ $head->add( "X-RT-Original-Encoding" => $charset )
+ if $head->mime_attr('content-type.charset') or $head->mime_type =~ /^text/;
+
+
+ return unless ( $head->mime_type =~ qr{^(text/plain|message/rfc822)$}i );
+
+
+ my $body = $entity->bodyhandle;
+
+ if ( $enc ne $charset && $body) {
+ my @lines = $body->as_lines or return;
+
+ # {{{ Convert the body
+ eval {
+ $RT::Logger->debug("Converting '$charset' to '$enc' for ". $head->mime_type . " - ". $head->get('subject'));
+
+ # NOTE:: see the comments at the end of the sub.
+ Encode::_utf8_off( $lines[$_] ) foreach ( 0 .. $#lines );
+ Encode::from_to( $lines[$_], $charset => $enc ) for ( 0 .. $#lines );
+ };
+
+ if ($@) {
+ $RT::Logger->error( "Encoding error: " . $@ . " defaulting to ISO-8859-1 -> UTF-8" );
+ eval {
+ Encode::from_to( $lines[$_], 'iso-8859-1' => $enc ) foreach ( 0 .. $#lines );
+ };
+ if ($@) {
+ $RT::Logger->crit( "Totally failed to convert to utf-8: " . $@ . " I give up" );
+ }
+ }
+ # }}}
+
+ my $new_body = MIME::Body::InCore->new( \@lines );
+
+ # set up the new entity
+ $head->mime_attr( "content-type" => 'text/plain' )
+ unless ( $head->mime_attr("content-type") );
+ $head->mime_attr( "content-type.charset" => $enc );
+ $entity->bodyhandle($new_body);
+ }
+}
+
+# NOTES: Why Encode::_utf8_off before Encode::from_to
+#
+# All the strings in RT are utf-8 now. Quotes from Encode POD:
+#
+# [$length =] from_to($octets, FROM_ENC, TO_ENC [, CHECK])
+# ... The data in $octets must be encoded as octets and not as
+# characters in Perl's internal format. ...
+#
+# Not turning off the UTF-8 flag in the string will prevent the string
+# from conversion.
+
+# }}}
+
+# {{{ DecodeMIMEWordsToUTF8
+
+=head2 DecodeMIMEWordsToUTF8 $raw
+
+An utility method which mimics MIME::Words::decode_mimewords, but only
+limited functionality. This function returns an utf-8 string.
+
+It returns the decoded string, or the original string if it's not
+encoded. Since the subroutine converts specified string into utf-8
+charset, it should not alter a subject written in English.
+
+Why not use MIME::Words directly? Because it fails in RT when I
+tried. Maybe it's ok now.
+
+=cut
+
+sub DecodeMIMEWordsToUTF8 {
+ my $str = shift;
+ DecodeMIMEWordsToEncoding($str, 'utf-8');
+}
+
+sub DecodeMIMEWordsToEncoding {
+ my $str = shift;
+ my $enc = shift;
+
+
+ @_ = $str =~ m/([^=]*)=\?([^?]+)\?([QqBb])\?([^?]+)\?=([^=]*)/g;
+
+ return ($str) unless (@_);
+
+ $str = "";
+ while (@_) {
+ my ($prefix, $charset, $encoding, $enc_str, $trailing) =
+ (shift, shift, shift, shift, shift);
+
+ $trailing =~ s/\s?\t?$//; # Observed from Outlook Express
+
+ if ($encoding eq 'Q' or $encoding eq 'q') {
+ use MIME::QuotedPrint;
+ $enc_str =~ tr/_/ /; # Observed from Outlook Express
+ $enc_str = decode_qp($enc_str);
+ } elsif ($encoding eq 'B' or $encoding eq 'b') {
+ use MIME::Base64;
+ $enc_str = decode_base64($enc_str);
+ } else {
+ $RT::Logger->warning("RT::I18N::DecodeMIMEWordsToCharset got a " .
+ "strange encoding: $encoding.");
+ }
+
+ # now we have got a decoded subject, try to convert into the encoding
+ unless ($charset eq $enc) {
+ eval { Encode::from_to($enc_str, $charset, $enc) };
+ if ($@) {
+ $charset = _GuessCharset( $enc_str );
+ Encode::from_to($enc_str, $charset, $enc);
+ }
+ }
+
+ $str .= $prefix . $enc_str . $trailing;
+ }
+
+ return ($str)
+}
+
+# }}}
+
+# {{{ _FindOrGuessCharset
+
+=head2 _FindOrGuessCharset MIME::Entity, $head_only
+
+When handed a MIME::Entity will first attempt to read what charset the message is encoded in. Failing that, will use Encode::Guess to try to figure it out
+
+If $head_only is true, only guesses charset for head parts. This is because header's encoding (e.g. filename="...") may be different from that of body's.
+
+=cut
+
+sub _FindOrGuessCharset {
+ my $entity = shift;
+ my $head_only = shift;
+ my $head = $entity->head;
+
+ if ($head->mime_attr("content-type.charset")) {
+ return $head->mime_attr("content-type.charset");
+ }
+
+ if ( !$head_only and $head->mime_type =~ m{^text/}) {
+ my $body = $entity->bodyhandle or return;
+ return _GuessCharset( $body->as_string );
+ }
+ else {
+ # potentially binary data -- don't guess the body
+ return _GuessCharset( $head->as_string );
+ }
+}
+
+# }}}
+
+
+# {{{ _GuessCharset
+
+=head2 _GuessCharset STRING
+
+use Encode::Guess to try to figure it out the string's encoding.
+
+=cut
+
+sub _GuessCharset {
+ my $fallback = 'iso-8859-1';
+ my $charset;
+
+ if ( @RT::EmailInputEncodings and eval { require Encode::Guess; 1 } ) {
+ Encode::Guess->set_suspects(@RT::EmailInputEncodings);
+ my $decoder = Encode::Guess->guess( $_[0] );
+
+ if ( ref $decoder ) {
+ $charset = $decoder->name;
+ $RT::Logger->debug("Guessed encoding: $charset");
+ return $charset;
+ }
+ elsif ($decoder =~ /(\S+ or .+)/) {
+ my %matched = map { $_ => 1 } split(/ or /, $1);
+ return 'utf-8' if $matched{'utf8'}; # one and only normalization
+
+ foreach my $suspect (@RT::EmailInputEncodings) {
+ next unless $matched{$suspect};
+ $RT::Logger->debug("Encode::Guess ambiguous ($decoder); using $suspect");
+ $charset = $suspect;
+ last;
+ }
+ }
+ else {
+ $RT::Logger->warning("Encode::Guess failed: $decoder; fallback to $fallback");
+ }
+ }
+ else {
+ $RT::Logger->warning("Cannot Encode::Guess; fallback to $fallback");
+ }
+
+ return($charset || $fallback);
+}
+
+# }}}
+
+# {{{ SetMIMEHeadToEncoding
+
+=head2 SetMIMEHeadToEncoding HEAD OLD_CHARSET NEW_CHARSET
+
+Converts a MIME Head from one encoding to another. This totally violates the RFC.
+We should never need this. But, Surprise!, MUAs are badly broken and do this kind of stuff
+all the time
+
+
+=cut
+
+sub SetMIMEHeadToEncoding {
+ my ( $head, $charset, $enc, $preserve_words ) = ( shift, shift, shift, shift );
+
+ $charset = 'utf-8' if $charset eq 'utf8';
+ $enc = 'utf-8' if $enc eq 'utf8';
+
+ return if $charset eq $enc and $preserve_words;
+
+ foreach my $tag ( $head->tags ) {
+ my @values = $head->get_all($tag);
+ $head->delete($tag);
+ foreach my $value (@values) {
+ if ( $charset ne $enc ) {
+
+ eval {
+ Encode::_utf8_off($value);
+ Encode::from_to( $value, $charset => $enc );
+ };
+ if ($@) {
+ $RT::Logger->error( "Encoding error: " . $@
+ . " defaulting to ISO-8859-1 -> UTF-8" );
+ eval { Encode::from_to( $value, 'iso-8859-1' => $enc ) };
+ if ($@) {
+ $RT::Logger->crit( "Totally failed to convert to utf-8: " . $@ . " I give up" );
+ }
+ }
+ }
+ $value = DecodeMIMEWordsToEncoding( $value, $enc ) unless $preserve_words;
+ $head->add( $tag, $value );
+ }
+ }
+
+}
+# }}}
+
+eval "require RT::I18N_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/I18N_Vendor.pm});
+eval "require RT::I18N_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/I18N_Local.pm});
+
+1; # End of module.
+
diff --git a/rt/lib/RT/I18N/cs.pm b/rt/lib/RT/I18N/cs.pm
new file mode 100644
index 0000000..132c3c2
--- /dev/null
+++ b/rt/lib/RT/I18N/cs.pm
@@ -0,0 +1,91 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::I18N::cs;
+
+# # CZECH TRANSLATORS COMMENTS see Locale::Maketext::TPJ13
+# Obecne parametry musi byt docela slozite (v pripade Slavistickych jazyku)
+# typu pocet, slovo, pad a rod
+#
+#pad 1., rod muzsky:
+#0 krecku
+#1 krecek
+#2..4 krecci
+#5.. krecku (nehodi se zde resit pravidlo mod 1,2,3,4 krom mod 11,12,13,14)
+#
+#0 kabatu
+#1 kabat
+#2..4 kabaty
+#5 kabatu
+#
+# => Vyplati se udelat quant s parametry typu pocet, slovo1, slovo2..4, slovo5 a slovo0
+#
+
+sub quant {
+ my($handle, $num, @forms) = @_;
+
+ return $num if @forms == 0; # what should this mean?
+ return $forms[3] if @forms > 3 and $num == 0; # special zeroth case
+
+ # Normal case:
+ # Note that the formatting of $num is preserved.
+ #return( $handle->numf($num) . ' ' . $handle->numerate($num, @forms) );
+ return( $handle->numerate($num, @forms) );
+ # Most human languages put the number phrase before the qualified phrase.
+}
+
+
+sub numerate {
+ # return this lexical item in a form appropriate to this number
+ my($handle, $num, @forms) = @_;
+ my $s = ($num == 1);
+
+ return '' unless @forms;
+ return
+ $s ? $forms[0] :
+ ( $num > 1 && $num < 5 ) ? $forms[1] :
+ $forms[2];
+}
+
+#--------------------------------------------------------------------------
+
+sub numf {
+ my($handle, $num) = @_[0,1];
+ if($num < 10_000_000_000 and $num > -10_000_000_000 and $num == int($num)) {
+ $num += 0; # Just use normal integer stringification.
+ # Specifically, don't let %G turn ten million into 1E+007
+ } else {
+ $num = CORE::sprintf("%G", $num);
+ # "CORE::" is there to avoid confusion with the above sub sprintf.
+ }
+ while( $num =~ s/^([-+]?\d+)(\d{3})/$1,$2/s ) {1} # right from perlfaq5
+ # The initial \d+ gobbles as many digits as it can, and then we
+ # backtrack so it un-eats the rightmost three, and then we
+ # insert the comma there.
+
+ $num =~ tr<.,><,.> if ref($handle) and $handle->{'numf_comma'};
+ # This is just a lame hack instead of using Number::Format
+ return $num;
+}
+
+1;
diff --git a/rt/lib/RT/I18N/cs.po b/rt/lib/RT/I18N/cs.po
new file mode 100644
index 0000000..75f3495
--- /dev/null
+++ b/rt/lib/RT/I18N/cs.po
@@ -0,0 +1,4496 @@
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 3.0.0\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2003-03-24 03:00+0800\n"
+"Last-Translator: Jan Okrouhly <okrouhly@civ.zcu.cz>\n"
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr "#%1"
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %3.%2.%7 %4:%5:%6"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 přidáno"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "- %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 změněno na %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 smazáno"
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 se vzorem %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 tento požadavek\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1. až %2. zobrazený"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - argument k předání %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Výstupní stav jde do STDOUT"
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Jaký akÄní modul chcete použít"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Jaký podmínkový modul chcete použít"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Jaký vyhledávací modul chcete použít"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "ScripAction %1 nahrána"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 přidáno jako hodnota pro %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 aliasy vyžadují k Äinnosti TicketId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 aliasy vyžadují k Äinnosti TicketId (odesílatel %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 vypadá jako lokální objekt, ale není v databázi"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 uživatelem %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 změněno z %2 na %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "%1 nemůže být nastaveno na %2."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 nemůže zaÄít transakci (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 nemůže nastavit stav na vyřešen. RT databáze může být nekonzistentní."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "%1 nejdůležitějších požadavků, které vlastním..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "%1 nejdůležitějších požadavků, které žádám..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 je nástroj zpracující požadavky z vnějšího plánovacího nástroje jako je cron"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 již není %2 této fronty."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 již není %2 tohoto požadavku."
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 již není hodnotou uživatelské položky %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 není platným identifikátorem fronty."
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 %quant(%1,minuta,minuty,minut,minut)"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 nezobrazeno"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "práva %1"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 provedeno\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "typ %1 neznámý pro $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "typ %1 neznámý pro %2"
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 vyÅ™eší vÅ¡echny Äleny skupiny vyÅ™eÅ¡eného požadavku."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 odloží [místní] BÃZI, je-li závislá [Äi Älenem] na spjatém požadavku."
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: neudána příloha"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1 B"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1 kB"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "%1 je neplatnou hodnotou pro stav"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "%1 je neznámá akce."
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Zatrhněte pro smazání scripu)"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Zatrhněte pro smazání)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Zadejte identifikátory Äi URL požadavku, oddÄ›lené mezerami)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(Pro prázdné pole se použije %1)"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(Žádné uživatelské položky)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Žádní Älenové)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "Žádné scripy"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Žádné vzory)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Zašle skrytou kopii této aktualizace mezerami oddělenému seznamu e-mail adres. <b>Neovlivňuje</b> příjemce budoucích aktualizací.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Zašle skrytou kopii této aktualizace mezerami oddělenému seznamu e-mail adres. <b>Neovlivňuje</b> příjemce budoucích aktualizací.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Zašle kopii této aktualizace mezerami oddělenému seznamu e-mail adres. Tito lidé <b>budou</b> dostávat budoucí aktualizace.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Zašle kopii této aktualizace mezerami oddělenému seznamu e-mail adres. <b>Nemění</b> příjemce budoucích aktualizací"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Zašle kopii této aktualizace mezerami oddělenému seznamu e-mail adres. <b>Neovlivňuje</b> příjemce budoucích aktualizací.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Zašle kopii této aktualizace mezerami oddělenému seznamu e-mail adres. Tito lidé <b>budou</b> dostávat budoucí aktualizace.)"
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(prázdná)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr "žádné jméno nebylo vypsáno"
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(bez předmětu)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(bez hodnoty)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(jen jeden požadavek)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(oÄekávájící schválení)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(jiné oÄekávající požadavky)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(povinné)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(nepojmenováno)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "25 mnou vlastněných nejdůležitějších požadavků..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "25 mnou žádaných nejdůležitějších požadavků..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Nový požadavek v\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Prázdný vzor"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE nenalezeno"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACE mohou být jen vytvářeny nebo rušeny."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Přerušeno k zamezení nežádoucích změn požadavku.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "O mnÄ›"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Řízení přístupu"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Akce"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Akce %1 nenalezena"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Akce provedena."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Akce připravena..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Přidat AdminCc"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Přidat Cc"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Přidat další soubory"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Přidat Žadatele"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Přidat nový globální scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Přidat scrip k této frontě"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Přidat scrip do všech front"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "PÅ™idat komentáře Äi odpovÄ›di k vybraným požadavkům"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "PÅ™idat Äleny"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Přidat nové pozorovatele"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "PřidatDalšíStav"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Uživatel přidán do této fronty jako %1"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Uživatel přidán k tomuto požadavku jako %1"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Adresa1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Adresa2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr "Administrativní komentář"
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr "Administrativní korespondence"
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Správa/Front"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Správa/Uživatelů"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Správa/Globální konfigurace"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Správa/Skupin"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Správa/Front/Základních údajů"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "Spravovat všechny osobní skupiny"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "AdminComment"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "AdminCorrespondence"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr "Spravovat uživatelem definované položky"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr "Spravovat skupinu"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr "Spravovat Älenství ve skupinách"
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr "Spravovat vlastní osobní skupiny"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr "Spravovat frontu"
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr "Spravovat uživatele"
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Administrativní Cc"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "PokroÄilé Vyhledávání"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Po"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Stáří"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Všechny uživatelské položky"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "VÅ¡echny Fronty"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Vždy posílá zprávu žadatelům nezávisle na odesílateli"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Schvalování"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Schválení #%1: $2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Schválení #$1: Poznámky neuloženy kvůli systémové chybě"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Schválení #%1: Poznámky uloženy"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Detaily schválení"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Schvalovací diagram"
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Schválit"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Poznámky schvalovatele: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "dub"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "VzestupnÄ›"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Přiložit"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Připojit soubor"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Připojený soubor"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Příloha '%1' nemůže být nahrána"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Příloha vytvořena"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Jméno souboru přílohy"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Přílohy"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "srp"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "AuthSystem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Automatická odpovÄ›Ä"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Automaticky odpověz žadatelům"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "Automatická odpovÄ›Ä Å¾adatelům"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Chybná PGP signatura: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Chybný identifikátor přílohy. Nelze nalézt přílohu'%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Chybná data v %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Chybné Äíslo transakce u přílohy. %1 má být %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Základní údaje"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Neopomeňte uložit vaše změny"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "Před"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "ZaÄátek schvalování"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Prázdný"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "Uložitelné URL pro toto hledání"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Zkrácené hlaviÄky"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Hromadná úprava požadavků"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Nelze měnit systémové uživatele"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Může tento uživatel vidět tuto frontu"
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Uživatelské položce nelze přidat hodnotu beze jména"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Požadavek nelze svázat se sebou samým"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Nelze slouÄit do slouÄeného požadavku. To by se vám nemÄ›lo nikdy stát."
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Nelze zadat zároveň zdroj i cíl"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Nelze vytvořit uživatele: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Změna hesla"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Zašrtnutím odstraníte"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Zatrhněte k odebrání práva"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Potomci"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Město"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "Vyřešeno"
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Uzavřené požadavky"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Neznámý příkaz!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Komentovat"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Adresa pro komentáře"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Komentář nezaznamenán"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Komentovat požadavky"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr "Komentovat požadavky"
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Poznámky"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Komentář (Neposílá se žadatelům)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Komentář (nepošle se žadatelům)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Poznámky o %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Poznámky o tomto uživateli"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Komentáře přidány"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "Commit v zárodku"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Omezení překladu"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Podmínka"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "Podmínky splněny..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Podmínka nenalezena"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Správa"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Potvrzení"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "Kontaktní informaÄní systém"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Datum kontaktu '%1' nemůže být rozpoznáno"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Obsah"
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr "Korespondence"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Adresa pro korespondenci"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Korespondence zaznamenána"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Korespondence nebyla zaznamenána"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Nelze přidat novou hodnotu uživatelské položky požadavku. "
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Nelze změnit vlastníka. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Nelze vytvořit Uživatelskou položku"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Nelze vytvořit skupinu"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Nelze vytvořit vzor: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Nelze vytvořit požadavek. Nenastavena fronta"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Nelze vytvořit uživatele"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Nelze nalézt požadavek s identifikátorem %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Nelze nalézt skupinu %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Tohoto uživatele nelze nalézt nebo vytvořit"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Nelze naléze tohoto uživatele"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Nelze nalézt uživatele %1."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Nelze naÄíst skupinu"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Tento uživatel nemůže být %1 této fronty"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Tento uživatel nemůže být %1 tohoto požadavku"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Tento uživatel nemůže být odstraněn jako %1 této fronty"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Tento uživatel nemůže být odstraněn jako %1 tohoto požadavku"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Do skupiny nelze pÅ™idat Älena"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Nelze vytvořit transakci: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Nelze zjistit co dělat s gpg odpovědí\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Skupinu nelze nalézt\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr "Nemohu nalézt sloupec"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Tohoto uživatele nelze nalézt"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Tuto hodnotu nelze nalézt"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Uživatele nelze nalézt\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Z uživatelské databáze nelze naÄíst %1.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "KonfiguraÄní soubor RT '%1'nelze naÄíst %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Scripy nelze naÄíst."
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Skupinu %1 nelze naÄíst"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Vazbu nelze naÄíst"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Frontu nelze naÄíst"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Frontu %1 nelze naÄíst"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Scrip nelze naÄíst"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Vzor nelze naÄíst"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Uživatele (%1) nelze naÄíst"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Požadavek '%1' nelze naÄíst"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "ZemÄ›"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Vytvořit"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Vytvořit požadavky"
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Vytvořit uživatelskou položku"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Vytvoření uživatelské položky pro frontu %1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Vytvoření uživatelské položky pro všechny front"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Vytvořit novou uživatelskou položku"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Vytvořit nový globální scrip"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Vytvořit novou skupinu"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Vytvořit novou vlastní skupinu"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Vytvořit novou frontu"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Vytvořit nový scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Vytvořit nový vzor"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Vytvoření nového požadavku"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Vytvořit nového uživatele"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Vytvořit frontu"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Vytvořit frontu nazvanou"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Vytvořit požadavek"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Vytvořit scrips pro frontu %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Vytvořit vzor"
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Vytvářet požadavky podle toho vzoru scripu"
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Vytvořit požadavek"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Vytvářet požadavky v této frontě"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Vytvářet, mazat a měnit uživatelen definované položky"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Vytvářet, mazat a měnit fronty"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Vytvářet, mazat a mÄ›nit Äleny uživatelských osobních skupin"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Vytvářet, mazat a mÄ›nit Äleny osobních skupin"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Vytvářen, mazat a měnit uživatele"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "Vytvořit požadavek"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Vytvořeno"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Uživatelská položka %1 vytvořena"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Vzor %1 vytvořen"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Aktuální relace"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Aktuální scripy"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Aktuální Älenové"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Aktuální práva"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Aktuální vyhledávací podmínky"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Aktuální pozorovatelé"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Uživatelská položka #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Uživatelské položky"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Čistící kód uživatelské akce"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Přípravný kód uživatelské akce"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Uživatelská podmínka"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Užitavelská položka %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Uživatelská položka %1 má hodnotu."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Uživatelská položka %1 nemá hodnotu."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Uživatelská položka %1 nenalezena"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr "Uživatelská položka smazána"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Uživatelská položka nenalezena"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Hodnota %1 nemůže být nalezena v uživatelské položce %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Hodnota uživatelské položky změněna z %1 na %2"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "Hodnota uživatelské položky nemůže být smazána"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "Hodnota uživatelské položky nemůže být nalezena"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Hodnota uživatelské položky smazána"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr "Uživatelská položka"
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Datumy"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "pro"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Implicitní vzor automatické odpovědi"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Implicitní vzor automatické odpovědi"
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Implicitní vzor administrativního komentáře"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr "Implicitní vzor administrativní korespondence"
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr "Implicitní korespondenÄní vzor"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Implicitní transakÄní vzor"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Defaulní: %1/%2 změněno z %3 na %4"
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Delegovat práva"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Delegovat specifická práva, která vám byla poskytnuta."
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr "Delegovat práva"
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Pověření"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Smazat"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Mazat požadavky"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr "Smazat požadavek"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Smazání tohoto objektu mohlo poruÅ¡it referenÄní integritu"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Smazání tohoto objektu by mohlo poruÅ¡it referenÄní integritu"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Smazání tohoto objektu by mohlo naruÅ¡it referenÄní integritu"
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Zamítnout"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Je rekvizitou pro"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Závistlosti: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Závisející na"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "SestupnÄ›"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Popište případ níže"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Popis"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Podrobnosti"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Zobrazit"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Zobrazit přístupová práva"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Zobrazovat scrips vzory pro tuto frontu"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Zobrazovat scripy pro tuto frontu"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Režim zobrazení"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Zobraz požadavek #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Dělat cokoli a všechno"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "NeobÄerstvovat tuto stránku."
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "Nezobrazit výsledky hledání"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Stáhnout"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Termín dokonÄení"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Datum termínu dokonÄení '%1' nemůže být rozpoznán"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "CHYBA: Nelze naÄíst požadavek '%1': %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Upravit"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Upravit uživatelské položky pro %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Upravit relace"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Upravit vzory pro frontu %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Upravit scprips"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Úprava systémových vzorů"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Upravit vzory pro %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Úprava konfigurace pro frontu %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Úprava konfigurace pro uživatele %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Úprava uživatelské položky %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Úprava Älenství ve skupinÄ› %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Úprava Älenství ve vlastní skupinÄ› %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Úprava vzoru %1"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Zdroj Äi cíl musí být zadány"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Email"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "Email adresa je použita"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "Email adresa"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "Kódování emailu"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Povolena (zrušením zatrhnutí zablokujete tuto uživatelskou položky)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Povolena (zrušením zatrhnutí zablokujete tuto skupinu)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Povoleno (zrušení zatrhnutí zablokuje tuto frontu)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Povolené uživatelské položky"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Povolené fronty"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Povolen stav %1"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr "Vyplnit více hodnot"
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr "Vyplnit jednu hodnotu"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Zadejte požadavky Äi URI se nimiž požadavky svázat. OddÄ›lte více položek mezerami."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Chyba"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Chyba v parametrech do Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Chyba v parametrech do Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Chyba v parametrech do Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Chyba v parametrech do Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Kdokoli"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Příklad:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "Identifikátor externí autentizace"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "Identifikátor externího kontaktu"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Doplňkové údaje"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Nepovedlo se nalézt uživatele 'Privilegované' pseudoskupiny."
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Nepovedlo se nalézt uživatele 'Neprivilegované' pseudoskupiny"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Nepovedlo se nahrát modul %1. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "úno"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Kon"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Koncová priorita"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr "Koncová priorita"
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Najít skupiny které"
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Najít nové/otevřené požadavky"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Najít ty, jejichž"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "Nalézt požadavky"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "ZávereÄné schválení"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "První"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "První stránka"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Vynutit změnu"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "Nalezen%quant(%1,,y,o) %numf(%1) %quant(%1,požadavek,požadavky,požadavků)"
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr "Nalezen objekt"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "Kontaktní údaje ve volné podobě"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "Volná forma vícenásobně"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr "Volná formu jedinkrát"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "pá"
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Celé hlaviÄky"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Aktuální uživatel se získává z PGP podpisu\\n"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Dán %1"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Globální"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Globální Scrips"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Globální vzor: %1"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Spusť!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Správný PGP podpis od %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Přejít na stránku"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Přejít na požadavek"
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Skupina"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Skupina %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Práva skupiny"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Skupina již má Älena"
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Skupina nemůže být založena: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Skupina vytvořena"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "Skupina nemá takového Älena"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Skupina nenalezena"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Skupina nenalezena.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Skupina neudána.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Skupiny"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Skupiny nemohou být svými Äleny"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Ahoj!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Ahoj, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Historie"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "Telefon domů"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Domovská stránka"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "Mám %quant(%1,míchaÄka,míchaÄky,míchaÄek)"
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "I have [quant,_1,concrete mixer]."
+
+#msgstr "Mám [quant,_1,MíchaÄku na beton,MíchaÄky na beton,MíchaÄek na beton]."
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Identifikátor"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identita"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Odmítni původce a zruš stávající schválení, bylo-li zamítnuto schválení"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Byl-li tento nástroj setgid, místní uživatel jej mohl použit k získaní administrativního přístupu k RT"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Pokud jste změnili cokoli nahoře, nezapomeňte"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr "Neplatná hodnota pro %1"
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr "Neměnitelná položka"
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Zahrnout do výpisu blokované uživatelské položky"
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Zahrnout blokované fronty do výpisu."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Zahrnout blokované uživatele do vyhledávání."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "PoÄáteÄní priorita"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr "PoÄáteÄní priorita"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Chyba na vstupu"
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "Vnitřní chyba"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Vnitřní chyba: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Neplatný typ skupiny"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr "Neplatné právo"
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr "Neplatná data"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Neplatný vlastník. Použije se 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Neplatná fronta"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Neplatné právo"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Neplatná hodnota pro %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Neplatná hodnota pro uživatelskou položku"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Neplatná hodnota pro stav"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Je velmi důležité, aby neprivilegovaní uživatelé nemohli spustit tento nástroj."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Pro spuÅ¡tÄ›ní tohoto nástroje se doporuÄuje založení neprivilegovaného UNIX uživatele se správným skupinovým Älenstvím a přístupem do RT."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Používá několik parametrů:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "VÄ›ci oÄekávající mé schválení"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "led"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "PÅ™idat se Äi odebrat z této skupiny"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Äec"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Maxi"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Äen"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "KlíÄové slovo"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Jazyk"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Poslední"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Poslední kontakt"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Naposled kontaktován"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "Naposled upozorněn"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Naposled aktualizován"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Zbývá"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Umožnit tomuto uživateli přístup k RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Umožnit dávat tomuto uživateli práva"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Vlastník omezen na %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Fronta omezena na %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "Vazba již existuje"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "Vazba nemůže být vytvořena"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Vazba vytvořena (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Vazba zrušena (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Vazba nenalezena"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Svázat požadavek #%1"
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Vazby"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Umístění"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Záznamový adresář %1 nenalezen nebo doň nemůže být zapisováno.\\ RT nemůže běžet."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Přihlášen jako %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Přihlásit"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Odhlásit"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Nastavit vlastníka"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Nastavit stav"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Nastavit datum termínu dokonÄení"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Nastavit datum vyřešení"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Nastavit datum, kdy zaÄal"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Nastavit datum, kdy zaÄne"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Nastavit datum posledního kontaktu"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Nastavit prioritu"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Nastavit frontu"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Nastavit předmět"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Správa skupin a Älenství v nich"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Správa vlastností a konfigurací platných ve všech frontách"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Správa front a jim příslušných vlastností"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Správa uživatelů a hesel"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "bře"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "kvÄ›"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Člen přidán"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Člen odebrán"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Člen neodebrán"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "ÄŒlen"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Členové"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "SlouÄení úspěšné"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "SlouÄení se nepodaÅ™ilo. Nelze nastavit EffectiveId"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "SlouÄit do"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Zpráva"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr "Chybí primární klíÄ?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Mobilní telefon"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "Mobilní telefon"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Upravovat seznam přístupových práv"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Upravit uživatelskou položku %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Úprava uživatelských položek pro všechny fronty"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Upravovat vzory scripů této fronty"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Upravovat scripů této fronty"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Upravovat vzor %1"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Upravovat uživatelskou položku pro frontu %1"
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Upravovat uživatelskou položku pro všechny fronty"
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Upravovat scrip pro frontu %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Upravovat scrip platný ve všech frontách"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Upravit datumy pro #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Úprav datumů pro požadavek # %1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Úprava globálních skupinových práv"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Úprava globálních skupinových práv."
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Úprava globálních scrips"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Úprava globálních uživatelských práv"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Úprava globálních uživatelských práv."
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Upravovat metadata skupiny nebo smazat skupinu"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Úprava skupinových práv pro %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Úprava skupinových práv pro frontu %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Upravovat seznam Älenů pro tuto skupinu"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Upravovat vlastní RT úÄet"
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Úprava uživatelů fronty %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Úprava uživatelů souvisejících s požadavkem #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Úprava scrips pro frontu %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Upravovat scripy platné ve všech frontách"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Úprava vzoru %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Upravit vzory pro všechny fronty"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Úprava skupiny %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Upravovat pozorovatele fronty"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Úprava uživatele %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Úprava požadavku # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Úprava požadavku #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Upravovat požadavky"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Úprava uživatelských práv pro skupinu %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Úprava skupinových práv pro frontu %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Úprava pozorovatelů fronty '%1'"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr "Upravovat seznam přístupových práv"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr "Upravovat Älenství ve skupinÄ›"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr "Upravovat pozorovale fronty"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr "Upravovat scripy"
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr "Upravovat sebe"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr "Upravovat vzor"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr "Upravovat požadavek"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "po"
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Více o %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr "Dát níže"
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr "Dát výše"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Vícenásobná"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "Nutno zadat atribut 'Jméno'"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Mnou schválené"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Mnou schválené"
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Jméno"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Jméno je použito"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Je třeba schválení systémového správce"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Nikdy"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Nové"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Nové heslo"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Nová probíhající schválení"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Nové relace"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Nové vyhledávání"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Vytvořit uživatelskou položku"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "Založit skupinu"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Nové heslo"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Oznámení nového hesla zasláno"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "Vytvoření fronty"
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Nový požadavek"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Nová práva"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Vytovření scripu"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nové vyhledání"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr "Vytvořit vzor"
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "Nový požadavek neexistuje"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Vytvořit uživatele"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Nový uživatel jména"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Nový pozorovatel"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Nové nastavení okna"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Další"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Další stránka"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Přezdívka"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Přezdívka"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Žádná uživatelská položka"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Nedefinována žádná skupina"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Nedefinována žádná fronta"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Žádný uživatel RT nenalezen. Prosím poraÄte se se správcem RT.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Žádný vzor"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Neudán požadavek. Přerušuje se požadavek "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Neudán požadavek. Přerušují se úpravy požadavku\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "bez akce"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr "Neudán sloupec"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Příkaz nenalezen\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Poznámky k tomuto uživateli neudány"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Žádná připojená korespondence"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Pro %1 není popis"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Neudána skupina"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "Heslo nenastaveno"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Nedostatek práv k vytváření front"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Nedostatek práv k vytváření požadavků ve frontě '%1'"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Nedostatek práv k vytváření uživatelů"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Nedostatek práv k zobrazení tohoto požadavku"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Nedostatek práv k zobrazení aktualizace požadavku"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Nezadán uživatel"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Nevybráni uživatelé."
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Nenalezeny žádné fronty odpovídající vyhledávací podmínce."
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "Práva nenalezena"
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Nepřidělena žádná práva."
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Bez vyhledání nelze pracovat."
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Neudán identifikátor požadavku"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Neudán typ transakce"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Nenalezeni uživatelé odpovídající vyhledávací podmínce"
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Nenalezen platný uživatel RT. OvladaÄ RT CVS uvolnÄ›n. Prosím poraÄte se se svým správcem RT.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr "Žádná z hodnot nanastavena na _Set!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "Nikdo"
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr "Neexistující položka"
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr "Nepřihlášen"
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Nepřihlášen."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Nenastaven"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Zatím neimplementováno."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Zatím neimplementováno..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Poznámky"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "Upozornění nemůže být zasláno"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Zaslat všem AdminCc"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Zaslat všem AdminCc jako komentář"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Zaslat ostatním příjemcům"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Zaslat ostatním příjemcům jako komentář"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Zaslat vlastníkovi"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Zaslat vlastníkovi jako komentář"
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Zaslat vlastníkům a vÅ¡em AdminCc nové případy oÄekávající jejich schválení"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Zaslat žadatelům"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Zaslat žadatelům a všem Cc"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Zaslat žadatelům a všem Cc jako komentář"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Zaslat žadatelům, všem Cc a všem AdminCc"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Zaslat žadatelům, vÄem Cc a vÄem AdminCc jako komentář"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "lis"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Objekt nemůže být vytvořen"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Objekt vytvořen"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "říj"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "Dne"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Při komentáři"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Při korespondenci"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Při založení"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Při změně vlastníka"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Při změně fronty"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Při vyřešení"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Při změně stavu"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Při transakci"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Zobrazit jen schvalování pro požadavky založené po %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Zobrazit jen schvalování pro požadavky založení před %quant(%1)"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Otevřené"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Otevřít"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Otevřené požadavky"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Otevřít požadavky (ze seznamu) v novém okně"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Otevřít požadavky (ze seznamu) v jiném okně"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Otevřít požadavky při korespondenci"
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Řazení a třídění"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organizace"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Původní požadavek: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Časem se priorita posouvá k"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Vlastnit požadavky"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr "Vlastnit požadavek"
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Vlastník"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Vlastník změněn z %1 na %2"
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Vlastník nuceně změněn z %1 na %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Vlastník"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Pager"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Číslo pageru"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "RodiÄe"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Heslo"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "PÅ™ipomínaÄ hesel"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Heslo příliš krátké"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Heslo: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Uživatelé"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Provedení uživatelem definované akce"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Přístup nepovolen"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Osobní skupiny"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Vlastní skupiny"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Vlastní skupiny:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Čísla telefonů"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Zábor místa"
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Nastavení"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Nastavení"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Prepare v zárodku"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Předchozí"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Předchozí stránka"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Uživatel %1 nenalezen."
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Priorita"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Priorita zaÄíná na"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilegovaný"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Privilegovaný stav: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Privilegovaní uživatelé"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudo skupina pro vnitřní použití"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Fronta"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Fronta %1 nenalezena"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Fronta '%1' nenalezena\\n"
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Název fronty"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Scripy fronty"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Fronta již existuje"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Fronta nemůže být vytvořena"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "Fronta nemůže být naÄtena."
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Fronta vytvořena"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "Není zadána fronta."
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Fronta nenalezena"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Fronty"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 pro %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 od <a href=\"http://bestpractical.com\">Best Practival Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "Správa RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "AutentizaÄní chyba RT."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT Bounce: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "KonfiguraÄní chyba RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "Kritická chyba RT. Zpráva nezaznamenána!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "Chyba RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT přijal poštu (%1) od sebe samého."
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Samoobsluha / Uzavřené požadavky"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT v celé své záři"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT vás nemůže autentizovat"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT nemůže nalézt žadatele přes hledání v externí databázi"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT nemůže nalézt frontu: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT nemůže ověřit tento PGP podpis. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT pro %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT pro %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT zpracoval vaše příkazy"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT je &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Šířeno pod <a href=\"http://www.gnu.org/copyleft/gpl.html\">verzí 2 GNU General Public License.</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT bere tuto zprávu jako bounce"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT zpracuje tuto zprávu tak, jako by byla nepodepsaná.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "Emailový příkazový režim RT vyžaduje PGP autentizaci. Nepodepsal jste vaši zprávu nebo váš podpis nemůže být ověřen."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "SkuteÄné jméno"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "SkuteÄné jméno"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Je odkazem z"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Odkazuje na"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Zjemnit"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Zjemnit vyhledání"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Obnovit tuto stránku %quant(%1,každou,každé,každých) %numf(%1) %quant(%1,minutu,minuty,minut)."
+
+#??? quant
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Vztahy"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Odstranit AdminCc"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Odstranit Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Odstranit žadatele"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Odpovědět"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Odpovědět na požadavky"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "Odpovídat na požadavky"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Žadatel"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Emailová adresa žadatele"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Žadatel(é)"
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Žadatelé"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Požadavky mají být vyřešeny do"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Vynulovat"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Bydliště"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Vyřešit"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Vyřešení požadavku #%1 (%2)"
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Vyřešen"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "OdpovÄ›Ä Å¾adatelům"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Výsledky"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Výsledků na stránku"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Zopakujte heslo"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Nenalezeno právo %1 pro %2 %3 v mezích %4 (%5)"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Právo delegováno"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Práva přidána"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Právo naÄteno"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Právo nemůže být odebráno"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Právo nenalezeno"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Právo nenaÄteno."
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Právo odebráno"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Práva"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "Práva pro %1 nemohou být přidělena"
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "Práva nemohou být %1 odebrána"
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Pravidla"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "Kořenový schvalovatel"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "so"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Uložit změny"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Nezapomeňte uložit změny - "
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "Scrip #%1"
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Scrip vytvořen"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Scrip smazán"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Scripy"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scripy fro %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Scripy platné ve všech frontách"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Vyhledávání"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Podmínky vyhledávání"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "Vyhledávání schvalování"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "ZabezpeÄní:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr "Vidět frontu"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Výběr skupiny"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Výběr fronty"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Výběr uživatele"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr "Vybrat uživatelskou položku"
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Vybrat skupinu"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr "Vybrat více hodnot"
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr "Vybrat jednu hodnotu"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Výběr fronty"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Výběr scripu"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr "Vybrat vzor"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Výběr uživatele"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "Výběr vícenásobný"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "VýbÄ›t jedineÄný"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Samoobsluha"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Zaslat e-mail všem pozorovatelům"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Zaslat e-mail všem pozorovatelům jako \"komentář\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Zaslat e-mail žadatelům a všem Cc"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Zaslat e-mail žadatelům a všem Ccs jako komentář"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Posílá zprávu všem žadatelům"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Posílá e-mail všem přesně vyjmenovaným Cc a Bcc"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Posílá e-mail všem administrativním Cc"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Posílá e-mail všem administrativním Cc jako komentář"
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Posílá e-mail vlastníkovi"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "zář"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Zobrazit výsledky"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Zobrazit schválené požadavky"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Zobrazit základní údaje"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Zobrazit odepřené požadavky"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Zobrazit podrobnosti"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Zobrazit trvající požadavky"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Zobrazit požadavky Äekající na jejich schválení"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Zobrazovat privátní komentáře požadavku"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Zobrazovat výsledky požadavku"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr "Zobrazovat seznam přístupových práv"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr "Zobrazit scripy"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr "Zobrazit vzor"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr "Zobrazit požadavek"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr "Zobrazit komentáře požadavku"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Být žadatelem Äi Cc požadavku Äi fronty"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Být AdminCc požadavku nebo fronty"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Podpis"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr "Příhlášen jako %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Jednoduchá"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr "PÅ™eskoÄit menu"
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Třídící klíÄ"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Třídit výsledky dle"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "Třídící pořadí"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Odložené"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Úvodní stránka"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "ZapoÄato"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Datum zapoÄetí '%1' nemůže být rozpoznáno"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "ZaÄíná"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "ZaÄíná"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Datum zaÄínání '%1' nemůže být rozpoznáno"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Stát"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Stav"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr "Změna Stavu"
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Stav změněn z %1 na %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "Změna stavu"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Vzít"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Vzato %1 "
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Předmět"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Předmět změněn na %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Odeslat"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Potvrdit model zpracování"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Úspěšné"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "ne"
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "Super uživatel"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "Systém"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Systémová chyba"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Systémová chyba. Právo nedelegováno."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Systémová chyba. Právo nepřiděleno."
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Systémové skupiny"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "Skupina systémovýh pravidel pro vnitřní použití"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "MíchaÄka na beton"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Vzít"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Vzatý"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Vzor"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Vzor #%!"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Vzor smazán"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Vzor nenalezen"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Vzor nenalezen\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Vzor rozpoznán"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Vzory"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Vzory pro %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Toto je již aktuální hodnota"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Toto není hodnota pro tuto uživatelskou položku"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Toto je shodná hodnota"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Tento uživatel je již v této frontě %1"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Tento uživatel je již u tohoto požadavku %1"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Tento uživatel není v této frontě %1"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Tento uživatel není u tohoto požadavku %1"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Tata fronta neexistuje"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Tento požadavek má nevyřešené závislosti"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Tento uživatel již má toto právo"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Tento uživatel již tento požadavek vlastní"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Tento uživatel neexistuje"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Tento uživatel je již privilegován"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Tento uživatel je již neprivilegován"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Uživatel je nyní privilegován"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Uživatel je nyní neprivilegován"
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "V této frontě nemůže tento uživatel vlastnit požadavky"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Toto není Äíselný identifikátor"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Základní údaje"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "Cc požadavku"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "Administrativní Cc požadavku"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "Komentář byl zaznamenán"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "Následující příkaz najde všechny aktivní požadavky ve frontě 'general' a nastaví jejich priority na 99, pokud nebyly tknuty poslední 4 hodiny:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Následující příkazy nebyly zpracovány\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "Nová hodnota nastavena."
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "Vlastník požadavku"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "Žadatel požadavku"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Tyto komentáře nejsou běžně viditelné uživateli"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Tento požadavek %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Tento nástroj umožňuje uživateli spustit libovolné perl moduly z RT."
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Tato transakce vypadá, že nemá obsah"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "nejdůležitější%quant(%1, požadavek,požadavky,ch požadavků) tohoto uživatele"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "25 nejdůležitějších požadavků tohoto uživatele"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Ät"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Požadavek # %1 %2"
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Maxi aktualizace požadavku #%1: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Požadavek #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Požadavek %1 vytvořen ve frontě '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Požadavek %1 naÄten\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Požadavek %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Historie požadavku # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Identifikátor požadavku"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr "Požadavek vyřešen"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Příloha požadavku"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Obsah požadavku"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Typ obsahu požadavku"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "Požadaven nemůže být vytvořen pro vnitřní chybu"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Požadavek vytvořen"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Nezdařilo se vytvoření požadavku"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Požadavek smazán"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Id požadavku nenalezeno"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Požadavek nenalezen"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "Stav požadavku změněn"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Pozorovatelé požadavku"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr "Požadavky"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Požadavky %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Požadavky %1 dle %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Požadavky z %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Požadavky, které záleží na tomto schválení:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Zbývající Äas"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Čas práce"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Zbývající Äas"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Čas k zobrazení"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Čas práce"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "Čas práce"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Vytvořit diff tohoto commitu:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Vytvořit diff tohoto commitu:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Poslední kontakt"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transakce"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transakce %1 vymazána"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transakce vytvořena"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Bez udání id požadavku nelze volat Transaction->Create"
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Transakce jsou neměnné"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Pokus o smazání práva: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "út"
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "typ"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Neimplementováno"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unixový login"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Unixové uživatelské jméno"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Neznámé kódování obsahu %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "NeomezenÄ›"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Neprivilegovaný"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Vrácen"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Aktualizace"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Identifikátor aktualizace"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Typ aktualizace"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Aktualizovat spoleÄnÄ› vÅ¡echny tyty požadavky"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Aktualizovat email"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Aktualizovat jméno"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Aktualizace nezaznamenána"
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Aktualizovat vybrané požadavky"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Aktualizace podpisu"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Aktualizace požadavku"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Aktualizace požadavku # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Aktualizace požadavku #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Aktualizace požadavku #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "Typ aktualizace nebyl ani korespondence ani komentář."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Aktualizováno"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Uživatel %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Heslo uživatele %1: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Uživatel '%1' nenalezen"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Uživatel '%1' nenalezen\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Uživatelem definované"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "Identifikátor uživatele"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Identifikátor uživatele"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Práva uživatele"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Uživatel nemůže být vytvořen: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Uživatel vytvořen"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Uživatelem definované skupiny"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Uživatel upozorněn"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Uživatelský pohled"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Uživatelské jméno"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Uživatelé"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Uživatelé odpovídající podmínce vyhledání"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "Hodnota fronty"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Hodnoty"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr "Být pozorovatelem"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr "Být AdminCc pozorovatelem"
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Pozorovatelé"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "Kódování WWW"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "st"
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Přidat korespondenci k původnímu požadavku, pokud byl požadavek schválen všemi"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Přidat korespondenci k původnímu požadavku, pokud byl požadavek kýmkoli schválen"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Když je požadavek vytvořen"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Upozornit vlastníka a vÅ¡echny AdminCc, jejichž schválení se oÄekává, pÅ™i vytvoÅ™ení schvalovaného požadavku"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Stane-li se cokoli"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Je-li vyřešen požadavek"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Změní-li se vlastník požadavku"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Změní-li se fronta požadavku"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Změní-li se stav požadavku"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Splní-li se uživatelská podmínka"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Přijde-li komentář"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Přijde-li korespondence"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Zaměstnání"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Telefon do zaměstnání"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Odpracováno"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Požadavek již vlastníte"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Nejste autorizovaný uživatel"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Můžete přidělit pouze požadavky, které jsou vaše nebo nejsou vlastněny"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Nemáte právo k zobrazení tohoto požadavku.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Nalezl jste %1 požadavků ve frontě %2"
+
+#??? quant
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Byl jste odhlášen od RT."
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "V této frontě nemáte práva vytvářet požadavky."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "V této frontě nemůžete vytvářet požadavky."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Jste vítáni k dalšímu přihlášení"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Vašich %1 požadavků"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Váš správce RT chybně nastavil poštovní aliasy, které volají RT"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Váš požadavek byl schválen uživatelem %1. Další schválení mohou být jeÅ¡tÄ› oÄekávána."
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Váš požadavek byl schválen."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "Váš požadavek byl odmítnut"
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Váš požadavek byl odmítnut."
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "VaÅ¡e uživatelské jméno Äi heslo je nesprávné"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "PSČ"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "jak je dovoleno %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "obsahuje"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "obsah"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "typ obsahu"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "korespondence (zřejmě) neposlána"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "korespondence poslána"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "dnů"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "smazat"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "smazán"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "neodpovídá"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "neobsahuje"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "je rovno"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "název souboru"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "větší než"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "skupina '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "hodin"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "Identifikátor"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "je"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "není"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "menší než"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "odpovídá"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minut"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "úpravy\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "měsíců"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "nový"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "znehodnotit"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "žádný"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "není rovno"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "otevřený"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "vlastní skupina '%1' pro uživatele '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "fronta %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "zamítnutý"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "vyřešený"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sek"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "odložený"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "systém %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "systémová skupina '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "volající komponenta neudala důvod"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "požadavek #%1 %2"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "nepopsaná skupina %1"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "nepopsaná skupina %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "uživatel %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "týdnů"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "se vzorem %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "roků"
+
diff --git a/rt/lib/RT/I18N/de.po b/rt/lib/RT/I18N/de.po
new file mode 100644
index 0000000..3ffc8fb
--- /dev/null
+++ b/rt/lib/RT/I18N/de.po
@@ -0,0 +1,4792 @@
+# German localization catalog for Request Tracker (RT)
+# FIRST AUTHOR: Florian Bischof <flo@fxb.de>, May 2002
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 2.1.54\n"
+"POT-Creation-Date: 2002-06-22 06:06+0200\n"
+"PO-Revision-Date: 2003-02-20 04:47+0200\n"
+"Last-Translator: Karsten Konrad <karsten.konrad@uni-graz.at>\n"
+"Language-Team: RT German <rt@fxb.de>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %3. %2 %7, %4:%5:%6"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 hinzugefügt"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "vor %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 geändert in %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 gelöscht"
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 mit der Vorlage %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 dieses Ticket\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1 - %2 angezeigt"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - ein Argument zur Ãœbergabe an %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr ""
+
+#msgstr "%1 - Schreibe Statusupdates nach STDOUT"
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Gebe an, welches Action-Modul benutzt werden soll"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Gebe an, welches Condition-Modul benutzt werden soll"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Gebe an, welches Search-Modul benutzt werden soll"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "ScripAction %1 geladen"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 als Wert für %2 hinzugefügt"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr ""
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 konnte nicht in der Datenbank gefunden werden obwohl es ein lokales Objekt zu sein scheint"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "am %1 von %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 von %2 in %3 geändert"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "%1 konnte nicht auf %2 gesetzt werden."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 konnte den Status nicht auf erledigt setzen. Die RT-Datenbank könnte inkonsistent sein."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "%1 mir zugewiesene Anfragen mit höchster Priorität..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "Die %1 von mir ausgelösten Anfragen mit höchster Priorität"
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 ist ein Werkzeug um Anfragen über externe Terminierungstools wie \"cron\" zu verarbeiten"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 ist kein %2 dieses Stapels mehr."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 ist nicht mehr %2 dieser Anfrage."
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 ist kein Wert des benutzerdefinierten Feldes %2 mehr"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 ist keine gültige Stapel-Id."
+
+#: RTFM
+msgid "%1 matches"
+msgstr "%1 enthält"
+
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 Min"
+
+#: html/RTFM/UpdatedArticles:19
+msgid "%1 most recently updated articles"
+msgstr "%1 zuletzt überarbeitete Artikel"
+
+#: RTFM
+msgid "%1 newest articles"
+msgstr "%1 neueste Artikel"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 Rechte"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr ""
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 wird alle Mitglieder eines erledigten Gruppentickets erledigen."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: kein Anhang angegeben"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' ist ein ungültiger Wert für Status"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Markieren um Scrip zu löschen)"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Markieren um zu löschen)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Gib Anfragenummern oder URLs getrennt durch Leerzeichen ein)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(Bei Freilassen %1"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(Keine benutzerdefinierten Felder)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Keine Mitglieder)"
+
+#: html/RTFM/NewestArticles:35
+msgid "(no name)"
+msgstr "(kein Name)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Keine Scrips)"
+
+#: html/RTFM/UpdatedArticles.html:36
+msgid "(no Summary)"
+msgstr "keine Zusammenfassung"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Keine Vorlagen)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Schickt eine Blindkopie dieser Aktualisierung an eine durch Komma getrennte Liste von E-Mail-Adressen. Ändert <b>nicht</b> wer künftig Aktualisierungen geschickt bekommt.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr ""
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Schickt eine Kopie dieser Aktualisierung an eine durch Komma getrennte Liste von administrativen E-Mail-Adressen. Diese <b>werden</b> künftig Aktualisierungen erhalten.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Schickt eine Kopie dieser Aktualisierung an eine durch Komma getrennte Liste von E-Mail-Adressen. Ändert <b>nicht</b> wer künftig Aktualisierungen geschickt bekommt.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr ""
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Schickt eine Kopie dieser Aktualisierung an eine durch Komma getrennte Liste von E-Mail-Adressen. Diese <b>werden</b> künftig Aktualisierungen erhalten.)"
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(leer)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr ""
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(kein Betreff)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(keine Angabe)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(nur eine Anfrage)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(wartet auf Freigabe)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(wartet auf andere Anfragen)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(notwendig)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(unbenannt)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr ""
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Neue Anfrage in\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Eine leere Vorlage"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE nicht gefunden"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEs können nur erstellt und gelöscht werden."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Breche ab um ungewünschte Veränderungen an der Anfrage zu verhindern.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "Ãœber mich"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Zugriffskontrolle"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Aktion"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Aktion %1 nicht gefunden"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Aktion durchgeführt."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Aktion vorbereitet..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "AdminCC hinzufügen"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "CC hinzufügen"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Mehr Dateien anhängen"
+msgstr "Weitere Dateien anhängen"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Klient hinzufügen"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Erstelle ein neues globales Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Erstelle ein Scrip für diesen Stapel"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Scrip erstellen, das auf alle Stapel angewendet wird"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Füge den ausgewählten Anfragen Kommentare oder Antworten hinzu"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Mitglieder hinzufügen"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Neue Beobachter hinzufügen"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Habe einen Hauptverantwortlichen als %1 für diesen Stapel hinzugefügt"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Habe einen Hauptverantwortlichen als %1 für diese Anfrage hinzugefügt"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Adresse 1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Adresse 2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Admin CC"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr "Admin Kommentar"
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr "Admin Korrespondenz"
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Admin Stapel"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr ""
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Admin/Globale Einstellungen"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Admin/Stapel/Basics"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr ""
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr ""
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Administrative CC"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Erweiterte Suche"
+
+#: RTFM
+msgid "Advanced Search Criteria"
+msgstr "Erweiterte Suchkriterien"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Nach dem"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Alter"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Alle benutzerdefinierten Felder"
+
+#: html/RTFM/Admin/Classes/index.html:57
+msgid "All Classes"
+msgstr "Alle Klassen"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Alle Stapel"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr ""
+
+#: RTFM
+msgid "and is not"
+msgstr "und ist nicht"
+
+#: RTFM
+msgid "and not"
+msgstr "und nicht"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Freigabe"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Freigabe #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Freigabe #%1: Notiz wurde aufgrund eines Systemfehlers nicht vermerkt"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Freigabe #%1: Notiz vermerkt"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Freigeben"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Notizen des Freigebenden: %1"
+
+#: html/RTFM/Admin/CustomFields/UserRights.html:117
+msgid "No Class defined"
+msgstr "Keine Klasse definiert"
+
+#: html/RTFM/Admin/CustomFields/Basics.html:69
+msgid "No CustomField"
+msgstr "Kein benutzerdef. Feld"
+
+#: html/RTFM/Admin/CustomFields/GroupRights.html:73
+msgid "No CustomField defined"
+msgstr "Kein benutzerdef. Feld definiert"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Apr"
+
+#: RTFM
+msgid "Are you sure you want to delete this article?"
+msgstr "Sind Sie sicher, dass sie diesen Artikel löschen wollen?"
+
+#: html/RTFM/Article/delete.html:69
+msgid "Article #%1 deleted"
+msgstr "Artikel #%1 gelöscht"
+
+#: html/RTFM/Article/Display.html:46
+msgid "Article #%1: %2"
+msgstr "Artikel #%1: %2"
+
+#: html/RTFM/Article/Display.html:35
+msgid "Article not found"
+msgstr "Artikel wurde nicht gefunden"
+
+#: RTFM
+msgid "Articles"
+msgstr "Artikel"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "aufsteigend"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Anhängen"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Datei anhängen"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Dateianhang"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Anhang '%1' konnte nicht geladen werden"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Anhang erstellt"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Dateiname des Anhangs"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Anhänge"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Aug"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "AuthSystem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Autoreply"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Autoreply an Klienten"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Fehlerhafte PGP-Signatur: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Fehlerhafte Anhangs-Id. Konnte Anhang '%1' nicht finden\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Fehlerhafte Daten in %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Fehlerhafte Transaktionsnummer für den Anhang. %1 solle %2 sein\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Grundlagen"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "BCC"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Denke daran, Deine Änderungen zu speichern"
+
+### wieder - Duzen???
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "vor dem"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr ""
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Leer"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "Speicherbare URL für diese Suche"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Kurze Kopfzeilen"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Massen Ticketupdate"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Kann Systembenutzer nicht ändern"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Kann dieser Hauptverantwortliche diesen Stapel sehen"
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Kann kein benutzerdefiniertes Feld ohne Namen hinzufügen"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Kann kein Ticket auf sich selbst verweisen lassen!"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Konnte das Ticket nicht in ein vereinigtes Ticket vereinigen. Diesen Fehler sollten Sie niemals sehen"
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Sie können Basis und Ziel nicht gleichzeitig angeben"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Kann Benutzer nicht anlegen: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "CC"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Zum Löschen ankreuzen"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Zum Entziehen einer Berechtigung ankreuzen"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Kinder"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Stadt"
+
+#: RTFM
+msgid "Class"
+msgstr "Klasse"
+
+#: RTFM
+msgid "Class is"
+msgstr "Klasse ist"
+
+#: RTFM
+msgid "Class Name"
+msgstr "Klassenname"
+
+#: RTFM
+msgid "Classes"
+msgstr "Klassen"
+
+#: share/html/SelfService/Closed.html:27
+msgid "closed"
+msgstr "geschlossenen"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "Geschlossen"
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed tickets"
+msgstr "Geschlossene Anfragen"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Kommentar"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Kommentaradresse"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Kommentiere Tickets"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Kommantare"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Kommentar (wird nicht an Klienten geschickt)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Kommentar (wird nicht an Klienten geschickt)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Kommentar über %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Kommentar zu diesen Benutzer"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Kommentar hinzugefügt"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "Ãœbergabe abgehakt"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Bedingung"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "Condition trifft zu..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Bedingung nicht gefunden"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Bestätigen"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr ""
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Inhalt"
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr "Korrespondenz"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Korrespondenzadresse"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Korrespondenz hinzugefügt"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Konnte dem Ticket kein neues benutzerdefiniertes Feld hinzufügen. "
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Konnte den Inhaber nicht ändern. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Konnte benutzerdefiniertes Feld nicht anlegen"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Konnte Gruppe nicht anlegen"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Konnte Vorlage nicht anlegen: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Konnte Ticket nicht anlegen. Stapel nicht bestimmt"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Konnte Benutzer nicht anlegen"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Konnte diesen Benutzer nicht finden oder anlegen"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Konnte diesen Hauptverantwortlichen nicht finden"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr ""
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Konnte die Gruppe nicht laden"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Konnte den Hauptverantwortlichen nicht zu einen %1 dieses Stapels machen"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Konnte diesen Hauptverantwortlichen nicht zu einem %1 dieses Tickets machen"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Konnte diesen Hauptverantwortlichen nicht als %1 dieses Stapels entfernen"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Konnte diesen Hauptverantwortlichen nicht als %1 dieses Tickets entfernen"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Konnte Mitglied nicht der Gruppe hinzufügen"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Konnte die Transaktion nicht anlegen: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr "Konne Zeile nicht finden"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Konnte diesen Hauptverantwortlichen nicht finden"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Konnte diesen Wert nicht finden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr ""
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Konnte %1 nicht aus der Benutzerdatenbank laden.\\n"
+
+#: html/RTFM/Admin/CustomFields/UserRights.html:121
+msgid "Couldn't load Class %1"
+msgstr "Konnte die Klasse %1 nicht laden"
+
+#: html/RTFM/Admin/CustomFields/GroupRights.html:77
+msgid "Couldn't load CustomField %1"
+msgstr "Konnte das benutzerdefinierte Feld %1 nicht laden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Konnte Gruppe %1 nicht laden"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Konnte den Verweis nicht laden"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Konnte den Stapel nicht laden"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Konnte den Stapel %1 nicht laden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Konnte diesen Benutzer nicht laden (%1)"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Konnte das Ticket '%1' nicht laden"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "Land"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Erstellen"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Erstelle Tickets"
+
+#: RTFM
+msgid "Create a Class"
+msgstr "Erstelle eine Klasse"
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Erstelle ein benutzerdefiniertes Feld"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Erstelle ein benutzerdef. Feld für Stapel %1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Erstelle ein benutzerdef. Feld für alle Stapel"
+
+#: html/RTFM/Article/Create.html:19
+msgid "Create a new article"
+msgstr "Erstelle einen neuen Artikel"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Erstelle eine neue Gruppe"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Erstelle eine neue persönliche Gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr ""
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Erstelle ein neues Ticket"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Erstelle einen neuen Benutzer"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Erstelle einen Stapel"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr ""
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Erstelle ein Ticket"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Erstelle ein Scrip für den Stapel %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Erstelle eine Vorlage"
+
+#: html/SelfService/Create.html:24
+msgid "Create a ticket"
+msgstr "Neue Anfrage"
+
+#: RTFM
+msgid "Create an article"
+msgstr "Erstelle einen Artikel"
+
+#: RTFM
+msgid "Create an article in class..."
+msgstr "Erstelle einen Artikel in der Klasse..."
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Erstelle neue Tickets basierend auf der Vorlage dieses Scrips"
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Ãœbermitteln"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Erstelle Tickets in diesem Stapel"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Erstellen, löschen und modifizieren von benutzerdef. Felder"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Erstelle, lösche und modifiziere Stapel"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Erstellen, löschen und modifizieren von Mitgliedern persönlicher Gruppen"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Erstellen, löschen und modifizieren von Benutzern"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr ""
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Angelegt"
+
+#: RTFM
+msgid "Created by"
+msgstr "Angelegt von"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Erstelle ein benutzerdefiniertes Feld %1"
+
+#: RTFM
+msgid "Created during"
+msgstr "Erstellt zwischen"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Momentane Beziehungen"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Aktuelle Scrips"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Aktuelle Mitglieder"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Aktuelle Rechte"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Aktuelle Suchkriterien"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Aktuelle Beobachter"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Benutzerdef. Feld #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Benutzerdef. Felder"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Benutzerdefinierter Action-Cleanup-Code"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Benutzerdefinierter Aktions-Vorbereitungs-Code"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Benutzerdefinierte Bedingung"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Benutzerdefiniertes Feld %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Benutzerdefiniertes Feld %1 hat einen Wert."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Benutzerdefiniertes Feld %1 hat keinen Wert."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Benutzerdefiniertes Feld %1 nicht gefunden"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr "Benutzerdefiniertes Feld wurde gelöscht"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Benutzerdefiniertes Feld nicht gefunden"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Wert %1 des benutzerdefinierten Feldes %2 konnte nicht gefunden werden"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "Wert des benutzerdefinierten Felds konnte nicht gelöscht werden"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "Wert des benutzerdefinierten Feldes konnte nicht gefunden werden"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Wert des benutzerdefinierten Feldes gelöscht"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Datumsangaben"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Dez"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr ""
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Standard Autoresponse-Vorlage"
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Standard Admin-Kommentar-Vorlage"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr "Standard Admin-Korrespondenz-Vorlage"
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr "Standard Korrespondenz-Vorlage"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Standard Transaktions-Vorlage"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Standard: %1/%2 von \"%3\" in \"%4\" geändert."
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Rechte weitergeben"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Ihnen gewährte Rechte weitergeben"
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr ""
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Rechteweitergabe"
+
+#: RTFM
+msgid "Delete"
+msgstr "Löschen"
+
+#: html/RTFM/Article/delete.html:73
+msgid "Delete article #%1"
+msgstr "Lösche Artikel #%1"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Lösche Tickets"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Löschen dieses Objektes kann die referenzielle Integrität gefährden"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Löschen dieses Objektes würde die referenzielle Integrität gefährden"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Löschen dieses Objektes würde die referenzielle Integrität verletzen"
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Ablehnen"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Abhängig gemacht von"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr ""
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Abhängig von"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "absteigend"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Beschreibe hier das Problem"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Beschreibung"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Details"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Anzeigen"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Zeige Zugriffskontrollliste an"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Zeige Scrip-Vorlagen für diesen Stapel"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Zeige Scrips für diesen Stapel"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Anzeigemodus"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Zeige Ticket #%1 an"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Mache irgend etwas und alles"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Seite nicht aktualisieren."
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "Suchergebnisse nicht anzeigen"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Download"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Fällig"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr ""
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "FEHLER: Konnte Ticket '%1' nicht laden: %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr ""
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Bearbeite benutzerdefinierte Felder für %1"
+
+#: html/RTFM/Admin/Classes/CustomFields.html:73
+msgid "Edit Custom Fields for Class %1"
+msgstr "Bearbeite benutzerdefinierte Felder für Klasse %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Bearbeite Beziehungen"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Bearbeite Vorlagen für Stapel %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr ""
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Bearbeite Systemvorlagen"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr ""
+
+#: html/RTFM/Admin/Classes/Modify:79
+msgid "Editing Configuration for Class %1"
+msgstr "Bearbeite Konfiguration für die Klasse %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Bearbeite Konfiguration für den Stapel %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Bearbeite Konfiguration für Benutzer %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Bearbeite benutzerdefiniertes Feld %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Bearbeite Mitgliedschaft für die Gruppe %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Bearbeite Mitgliedschaft der persönlichen Gruppe %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Es muß entweder eine Basis oder ein Ziel angegeben werden"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "E-Mail"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "E-Mail-Adresse bereits in Gebrauch"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "E-Mail-Adresse"
+
+### muss das überhaupt übersetzt werden???
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "E-Mail-Kodierung"
+
+#: RTFM
+msgid "Enabled (Unchecking this box disables this Class)"
+msgstr "Aktiviert (Abwählen deaktiviert diese Klasse)"
+
+### muss das überhaupt übersetzt werden???
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Aktiviert (Abwählen deaktiviert dieses benutzerdef. Feld)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Aktiviert (Abwählen deaktiviert diese Gruppe)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Aktiviert (Abwählen deaktiviert diesen Stapel)"
+
+#: RTFM
+msgid "Enabled Classes"
+msgstr "Aktivierte Klassen"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Aktivierte benutzerdefinierte Felder"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Aktivierte Stapel"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Status %1 aktiviert"
+
+#: RTFM
+msgid "Enter Articles or URIs to link Articles to. Seperate multiple entries with spaces."
+msgstr "Artikel oder URIs getrennt durch Leerzeichen eingeben."
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr "Mehrere Werte eingeben"
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr "Einen Wert eingeben"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Ticketnummern oder URIs getrennt durch Leerzeichen eingeben."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Fehler"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Fehler in den Parameter für Queue-AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Fehler in den Paramter für Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Fehler in den Parameter für Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Fehler in den Parameter für Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Everyone"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Beispiel:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Zusatzinformationen"
+
+#: html/RTFM/Article/ExtractIntoClass.html:19
+msgid "Extract article from ticket #%1"
+msgstr "Extrahiere Artikel aus Anfrage #%1"
+
+#: html/RTFM/Article/ExtractFromTicket.html:19
+msgid "Extract article from ticket #%1 into class %2"
+msgstr "Extrahiere Artikel aus Anfrage #%1 in die Klasse %2"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Konnte die Pseudogruppe 'Privileged' nicht finden."
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Failed to find 'Unprivileged' users pseudogroup"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Konnte Modul %1 nicht laden. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Feb"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr ""
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Endpriorität"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr ""
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Finde Gruppe wessen"
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Finde neue/offene Tickets"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Finde Leute deren"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "Anfragen suchen"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Erste"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Erste Seite"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "erzwinge Änderung"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "%quant(%1,ticket) gefunden"
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr "Objekt gefunden"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "FreeformContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "FreieMehrfachauswahl"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr "FreieEinzelauswahl"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Fr"
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Alle Kopfzeilen"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "An %1 gegeben"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Global"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr ""
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Globale Vorlage: %1"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Los!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr ""
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Gehe zu Seite"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Zeige Anfrage"
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Gruppenrechte"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Gruppe hat bereits Mitglieder"
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Gruppe konnte nicht erstellt werden: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Gruppe angelegt"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "Gruppe hat kein solches Mitglied"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Gruppe nicht gefunden"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr ""
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Gruppen"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Gruppen können nicht Mitglied eines ihrer Mitglieder sein"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Hallo!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hallo %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Historie"
+
+#: html/RTFM/Article/History.html:22
+msgid "History for article #%1"
+msgstr "Historie für Artikel #%1"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "TelefonZuhause"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Start"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Nr."
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identität"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Wenn eine Freigabe abgewiesen wird, weise das Original ab und lösche wartende Freigaben"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Wenn dieses Werkzeug 'setgid' wäre könnte ein feindlicher lokaler Benutzer dadurch administrativen Zugriff auf RT erlangen."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Wenn Sie irgend etwas aktualisiert haben, denken Sie daran hier zu klicken"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr "Unerlaubter Wert für %1"
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr "Unveränderbares Feld"
+
+#: RTFM
+msgid "in class %1"
+msgstr "%1"
+
+#: RTFM
+msgid "Include disabled classes in listing."
+msgstr "Zeige auch deaktivierte Klassen an."
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Zeige auch deaktivierte benutzerdefinierte Felder an."
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Zeige auch deaktivierte Stapel an."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Zeige deaktivierte Benutzer auch in der Suche an."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Anfängliche Priorität"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Eingabefehler"
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "Interner Fehler"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Internet Fehler: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Ungültige Gruppenart"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr "Ungültiges Recht"
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr "Ungültige Daten"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Ungültiger Inhaber. Zurücksetzung auf 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Ungültiger Stapel"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Ungültiges Recht"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Ungültiger Wert für %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Ungültiger Wert für das benutzerdefinierte Feld"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Ungültiger Statuswert"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Es ist sehr wichtig dass nichtprivilegierte Benutzer dieses Werkzeug nicht aufrufen können."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Es wird empfohlen einen nichtprivilegierten Unix-User mit korrekter Gruppenzugehörigkeit zum Zugriff auf RT anzulegen um dieses Werkzeug aufzurufen."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Es verarbeitet verschiedene Parameter:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr ""
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Jan"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "Betrete oder verlasse diese Gruppe"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Alles"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Sprache"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "letzter Kontakt"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Letzter Kontakt"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Zuletzt Kontaktiert"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "Letzte Änderung"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Zuletzt Aktualisiert"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Diesen Benutzer RT-Zugriff gewähren"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Diesen Benutzer mehr Rechte gewähren"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "Beziehung existiert bereits"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "Beziehung konnte nicht erstellt werden"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Beziehung erstellt (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Beziehung gelöscht (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Beziehung nicht gefunden"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Verweise auf Ticket #%1"
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Beziehungen"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Adresse"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Log-Verzeichnis %1 nicht gefunden oder kein Schreibzugriff.\\n RT kann nicht starten."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Angemeldet als %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Anmelden"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Abmelden"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Mach Inhaber"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Mach Status"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Mach Fälligkeitsdatum"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Mach Erledigungsdatum"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Mach Datum gestartet"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Mach Startdatum"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Mach Eingangsdatum"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Mach Priorität"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Mach Stapel"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Betreff setzen"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Gruppen und Gruppenmitglieder verwalten"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Eigenschaften und Einstellungen für alle Stapel verwalten"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Stapel und stapelspezifische Einstellungen verwalten"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Benutzer und Passworte verwalten"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mär"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Mai."
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Mitglied hinzugefügt"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Mitglied gelöscht"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Mitglied nicht gelöscht"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Mitglied von"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Mitglieder"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "Zusammenführung erfolgreich"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Zusammenführung fehlgeschlagen. Konnte EffectiveId nicht setztn"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Zusammenführen in"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Nachricht"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr "%1: Fehlt ein Primärschlüssel?"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Mobil"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "Mobiltelefon"
+
+#: RTFM
+msgid "Modified"
+msgstr "Geändert"
+
+#: RTFM
+msgid "Modify"
+msgstr "Ändern"
+
+#: RTFM
+msgid "Modify article #%1"
+msgstr "Ändere Artikel #%1"
+
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Ändere Zugriffskontrollliste"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr ""
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Ändere benutzdefinierte Felder für diesen Stapel"
+
+#: html/RTFM/Admin/CustomFields/GroupRights.html:21
+msgid "Modify group rights for custom field %1"
+msgstr "Ändere Gruppenrechte für das benutzerdefinierte Feld %1"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Ändere Scrip-Vorlagen für diesen Stapel"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Ändere Scrips für diesen Stapel"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr ""
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Ändere ein benutzerdefiniertes Feld für Stapel %1"
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Ändere ein globales benutzerdefiniertes Feld"
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Ändere ein Scrip für den Stapel %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Ändere ein globales benutzerdefiniertes Feld"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Ändere Datumsangaben für #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Ändere Datumsangaben für Ticket #%1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Ändere globale Gruppenrechte"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Ändere globale Gruppenrechte."
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Ändere globale Benutzerrechte"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Ändere globale Benutzerrechte."
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Ändere Gruppen-Metadaten oder lösche die Gruppe"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Ändere die Gruppenrechte der Gruppe %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Ändere Gruppenrechte für Stapel %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Ändere Mitgliedsverzeichnis dieser Gruppe"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Ändere jemandens eigenen RT-Zugang"
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Ändere Leute bezogen auf Stapel %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Ändere Personen des Tickets #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Ändere Scrips für den Stapel %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Ändere auf alle Stapel angewandte Scrips"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Ändere Vorlage %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Ändere globale Templates"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Ändere Gruppe %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Ändere die Stapelbeobachter"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Ändere Benutzer %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Ändere Ticket #%1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Ändere Ticket #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Ändere Tickets"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Ändere Benutzerrechte für die Gruppe %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Ändere Benutzerrechte für Stapel %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr ""
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Mo"
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Mehr über %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr "Runter verschieben"
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr "Hoch verschieben"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Mehrere"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "Sie müssen eine Angabe bei 'Name' machen"
+
+#: share/html/SelfService/Elements/MyRequests:32
+msgid "My %1 tickets"
+msgstr "Meine %1 Anfragen"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr ""
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Meine Freigaben"
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Name"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Benutzername ist bereits in Gebrauch"
+
+#: RTFM
+msgid "Name matches"
+msgstr "Name enthält"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Niemals"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Neu"
+
+#: RTFM
+msgid "New Article"
+msgstr "Neuer Artikel"
+
+#: RTFM
+msgid "New class"
+msgstr "Neue Klasse"
+
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Neues Passwort"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Neue wartende Freigaben"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Neue Beziehungen"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Neue Suche"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Neues benutzerdef. Feld"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "Neue Gruppe"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Neues Passwort"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Neue Passworterinnerung wurde verschickt"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "Neuer Stapel"
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New ticket"
+msgstr "Neue Anfrage"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Neue Rechte"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Neues Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr ""
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr "Neue Vorlage"
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "Neues Ticket existiert nicht"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Neuer Benutzer"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Neues Benutzer aufgerufen"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Neue Beobachter"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Speichere Fenstereinstellungen"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Nächste"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Nächste Seite"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Spitzname"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Spitzname"
+
+#: RTFM
+msgid "No"
+msgstr "Nein"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Kein benutzerdefiniertes Feld"
+
+#: html/RTFM/Admin/CustomFields/GrroupRights.html:73
+msgid "No CustomField defined"
+msgstr "Kein benutzerdefiniertes Feld definiert"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Keine Gruppe definiert"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Kein Stapel vorhanden"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Kein RT-Benutzer gefunden. Bitte kontaktiere Deinen RT-Administrator.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Keine Vorlage"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Kein Ticket angegeben. Bereche Ticket ab "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "Keine Aktion"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr "Keine Spalte angegeben"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr ""
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Kein Kommentar über diesen Benutzer angegeben"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Keine Korrespondenz aufgezeichnet"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Keine Beschreibung für %1 vorhanden"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Keine Gruppe angegeben"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "Kein Passwort gesetzt"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Kein Recht Stapel anzulegen"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Kein Recht um Tickets im Stapel '%1' anzulegen"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Kein Recht Benutzer anzulegen"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Kein Recht dieses Ticket anzuzeigen"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Kein Recht dieses Ticket zu aktualisieren"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Kein Hauptverantwortlicher angegeben"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Keine Hauptverantwortliche ausgewählt."
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Keine den Suchkriterien entsprechenden Stapel gefunden"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "Keine Rechte gefunden"
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Keine Rechte gewährt."
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Keine Suchliste zum bearbeiten."
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Kein Transaktionstyp angegeben"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Keine auf die Suchkriterien passende Benutzer gefunden"
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Kein gültiger RT-Benutzer gefunden. RT CVS-Handler weggefallen. Bitte kontaktiere Deinen RT-Administrator.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr "Kein Wert an _Set geschickt!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "Niemand"
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr "Nichtexistierendes Feld?"
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr "Nicht angemeldet"
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Nicht angemeldet."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Nicht angegeben"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Noch nicht implementiert."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Noch nicht implementiert..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Bemerkungen"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "Benachrichtigung konnte nicht verschickt werden"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Benachrichtige AdminCCs"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Benachrichtige AdminCCs als Kommentar"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Benachrichtige andere Empfänger"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Benachrichtige andere Empfänger als Kommentar"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Benachrichte Inhaber"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Benachrichtige Inhaber als Kommentar"
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Benachrichtige Inhaber und AdminCCs neuer auf Freigabe wartende Anfragen"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Benachrichtige die Klienten"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Benachrichtige die Klienten und CCs"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Benachrichtige die Klienten und CCs als Kommentar"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Benachrichtige die Klienten, CCs und AdminCCs"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Benachrichtige die Klienten, CCs und AdminCCs als Kommentar"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Objekt konnte nicht erstellt werden"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Objekt erstellt"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Okt"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "am"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Bei Kommentar"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Bei Korrespondenz"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Bei Erstellen"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Bei Eigentümerwechsel"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Bei Stapelwechsel"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Beim Erledigen"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Bei Statuswechsel"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Bei einer Transaktion"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Zeige nur Freigaben für nach dem %1 erstelle Anfragen"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Zeige nur Freigaben für vor dem %1 erstellte Anfragen"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Offen"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Öffnen"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open tickets"
+msgstr "Offene Anfragen"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Öffne Anfragen (aus der Liste) in neuem Fenster"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Öffne Anfragen (aus der Liste) in ein anderes Fenster"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Öffne Anfragen bei Korrespondenz"
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Sortierung und Reihenfolge"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organisation"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Ursprüngliche Anfrage: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Mit der Zeit steigt die Priorität auf"
+
+#: html/RTFM/index.html:19
+msgid "Overview"
+msgstr "Ãœbersicht"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Eigene Anfrage"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr ""
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Inhaber"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Inhaberwechsel von %1 zu %2"
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Inhaber mit Gewalt von %1 in %2 geändert"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Inhaber ist"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Pager"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "PagerTelefon"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Eltern"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Passwort"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Passworterinnerung"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Passwort ist zu kurz"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Passwort: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Personen"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Führe eine benutzerdefinierte Aktion aus"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Zugriff verweigert"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Persönliche Gruppen"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Persönliche Gruppen"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Persönliche Gruppen:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Telefonnummern"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Platzhalter"
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Voreinstellungen"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr ""
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Vorbereitung abgehakt"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Vorherige"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Vorherige Seite"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Hauptverantwortlichen %1 nicht gefunden."
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Priorität"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Priorität beginnt bei"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilegiert"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Privilegierungsstatus: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Privilegierte Benutzer"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudogruppe für internen Gebrauch"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Stapel"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Stapel %2 nicht gefunden"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Name des Stapels"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Stapel existiert bereits"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Stapel konne nicht angelegt werden"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "Stapel konnte nicht geladen werden"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Stapel angelegt"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr ""
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Stapel nicht gefunden"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Stapel"
+
+#: RTFM
+msgid "Quick search"
+msgstr "Schnellsuche"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 für %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 von <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "RT Administration"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr ""
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "RT Fehler"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr ""
+
+#: html/RTFM/Error:36
+msgid "RTFM Error"
+msgstr "RTFM Fehler"
+
+#: html/SelfService/Closed.html:25
+msgid "Closed Tickets"
+msgstr "Geschlossene Anfragen"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT auf einen Blick"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr ""
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT für %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT hat Ihre Befehle verarbeitet"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Vertrieben unter der <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+
+#: share/html/SelfService/Elements/Tabs:35
+msgid "RT Self Service"
+msgstr "RT Selbstbedienung"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr ""
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Realer Name"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "RealerName"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Bezogen von"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Bezieht sich auf"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Suche Verfeinen"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Seite alle %1 Minuten aktualisieren."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Beziehungen"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Entferne AdminCC"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Entferne CC"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Entferne Klient"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Antworten"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Antworte auf Anfragen"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr ""
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Klient"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "email-Adresse des Klienten"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr ""
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Klienten"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Anfragen sollten erlegt werden in"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Zuhause"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Erledigen"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Erledige Anfrage Nr. %1 (%2)"
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Erledigt"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Antwort an alle Klienten"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Ergebnisse"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Ergebnisse pro Seite"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Passwort wiederholen"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Recht delegiert"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Recht erteilt"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Recht geladen"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Recht konnte nicht zurückgezogen werden"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Recht nicht gefunden"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Recht nicht gefunden."
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Recht zurückgezogen"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Rechte"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "Rechte konnten für %1 nicht gewährt werden"
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "Rechte konnten nicht für %1 entzogen werden"
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Rollen"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr ""
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Sa"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Änderungen Sichern"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Änderungen Sichern"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Scrip angelegt"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Scrip gelöscht"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Scrips"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrips für %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Auf alle Stapel angewande Scrips"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Suchen"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "Suche nach Freigaben"
+
+#: html/RTFM/Article/Search.html:19
+msgid "Search for articles"
+msgstr "Artikel suchen"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "Sicherheit:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr ""
+
+#: RTFM
+msgid "Select a Class"
+msgstr "Wähle eine Klasse aus"
+
+#: RTFM
+msgid "Select a Custom Fields"
+msgstr "Wähle ein benutzerdefiniertes Feld aus"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Wähle eine Gruppe aus"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr ""
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Wähle einen Benutzer aus"
+
+#: RTFM
+msgid "Select class"
+msgstr "Wähle eine Klasse"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr "Wähle ein benutzerdef. Feld"
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Wähle eine Gruppe"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr "Wähle mehrere Werte"
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr "Wähle einen Wert"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Wähle einen Stapel"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Wähle ein Scrip"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr "Wähle eine Vorlage"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Wähle einen Benutzer"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "Mehrfachauswahlfeld"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "Einzelauswahlfeld"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Selbstbedienung"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Schicke eine Mail an alle Beobachter"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Schicke eine Mail an alle Beobachter als \"Kommentar\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Schicke eine Mail an die Klienten und CCs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Schicke eine Mail an die Klienten und CCs als Kommentar"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Schicke eine Mail an die Klienten"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Schicke eine Mail an die direkt angegebenen CCs und BCCs"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Schicke eine Mail an die administrativen CCs"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Schicke eine Mail an die administrativen CCs als Kommentar"
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Schicke eine Mail an den Inhaber"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep"
+
+#: RTFM
+msgid "Seperate multiple URLs with spaces"
+msgstr "Mehrere URLs getrennt durch Leerzeichen eingeben"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr ""
+
+#: RTFM
+msgid "Show advanced search options..."
+msgstr "Zeige erweiterte Suchoptionen an..."
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Zeige freigegebene Anfragen"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Zeige Grundlagen"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Zeige abgelehnte Anfragen"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Zeige Details"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Zeige schwebende Anfragen"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Zeige auf andere Freigaben wartende Anfragen"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Zeige private Kommentare des Anfragen"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Zeige Kurzfassungen der Anfragen"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Als Klient einer Anfrage oder Anfrage- bzw. Stapel-CC eintragen"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Als Anfrage- oder Stapel-AdminCC eintragen"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "E-Mail-Signatur"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr "Angemeldet als %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Einzel"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr "Überspringe Menü"
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Sortierschlüssel"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Sortiere Ergebnisse nach"
+
+#: RTFM
+msgid "Sort Order"
+msgstr "Sortierreihenfolge"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "Sortierreihenfolge"
+
+#: RTFM
+msgid "Summary"
+msgstr "Zusammenfassung"
+
+#: RTFM
+msgid "Summary matches"
+msgstr "Zusammenfassung enthält"
+
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr ""
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Begonnen"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr ""
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Beginnt"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Staat"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Status"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr "Ändere Status"
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Status von %1 auf %2 geändert"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Stehlen"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Gestohlen von %1 "
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Betreff"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Betreff wurde auf %1 geändert"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Ãœbermitteln"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Query"
+msgstr "Suchen"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Geglückt"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "So"
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "System"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Systemfehler"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Systemfehler. Recht nicht delegiert."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Systemfehler. Recht nicht gewährt."
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Systemgruppen"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRolegroup für internen Gebrauch"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Ãœbernehmen"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Ãœbernommen"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Vorlage"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Vorlage #%1"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Vorlage gelöscht"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Vorlage nicht gefunden"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr ""
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Vorlagen eingelesen"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Vorlagen"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Vorlagen für %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Das ist bereits der aktuelle Wert"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Dies ist kein gültiger Wert für dieses benutzerdefinierte Feld"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Das ist der gleiche Wert"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Dieser Hauptverantwortliche ist bereits ein %1 dieses Stapels"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Dieser Hauptverantwortliche ist bereits ein %1 dieser Anfrage"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Dieser Hauptverantwortliche ist nicht ein %1 dieses Stapels"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Dieser Hauptverantwortliche ist nicht ein %1 dieser Anfrage"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Dieser Stapel existiert nicht"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Diese Anfrage hat ungelöste Abhängigkeiten"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Dieser Benutzer hat dieses Recht bereits"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Diesem Benutzer gehört diese Anfrage bereits"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Dieser Benutzer existiert nicht"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Dieser Benutzer ist bereits privilegiert"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Dieser Benutzer ist bereits ungeprivilegiert"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Dieser Benutzer ist jetzt privilegiert"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Dieser Benutzer ist jetzt unprivelegiert"
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Diesem Benutzer dürfen keine Anfragen aus diesen Stapel gehören"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Dies ist keine numerische Id"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Grundlagen"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "Der CC einer Anfrage"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "Der administrative CC einer Anfrage"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "Der Kommentar wurde aufgezeichnet"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "Das folgende Kommando wird alle aktiven Anfragen des Stapels 'general' finden und ihre Priorität auf 99 setzen, wenn sie innerhalb der letzten 4 Stunden nicht angefasst wurden:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Die folgenden Kommandos wurden nicht verarbeitet:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "Der neue Wert wurde gesetzt."
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "Der Inhaber einer Anfrage"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "Der Klient einer Anfrage"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Diese Kommentare sind generell nicht für den Benutzer sichtbar"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr ""
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Dieses Werkzeug erlaubt es Benutzern beliebige Perl-Module von RT aus aufzurufen."
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Diese Transaktion scheint keinen Inhalt zu haben"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "Die %1 höchstpriorisiertesten Anfragen dieses Benutzers"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr ""
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Do"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Anfrage Nr. %1 Alles aktualisieren: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Anfrage Nr. %1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Anfrage %1 wurde in Anfrage '%2' angelegt"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Anfrage %1 geladen\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Anfrage %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Verlauf von Anfrage Nr. %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Anfrage Nr."
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr "Anfrage erledigt"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Anhang der Anfrage"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Inhalt der Anfrage"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Art des Inhalts der Anfrage"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "Anfrage konnte aufgrund eines internen Fehlers nicht angelegt werden"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Anfrage angelegt"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Anfrage gelöscht"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Anfragenummer nicht gefunden"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Anfrage nicht gefunden"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "Status der Anfrage geändert"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Beobachter der Anfrage"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr "Anfragen"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Anfragen %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Anfragen %2 von %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Anfragen von %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Anfragen, die von dieser Freigabe abhängen:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Ãœbrige Zeit"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Arbeitszeit"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Ãœbrige Zeit"
+
+#: RTFM
+msgid "till"
+msgstr "und"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Benötigte Zeit"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Arbeitszeit"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "Gearbeitete Zeit"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Um ein 'diff' dieser Ãœbergabe zu erstellen:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Um ein 'diff' dieser Ãœbergabe zu erstellen:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Eingegangen"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transaktion"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transaktion %1 durchgeprügelt"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transaktion erstellt"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create konnte nicht ausgeführt werden da keine Ticketnummer angegeben wurde"
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Transaktionen sind unveränderbar"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Di"
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Typ"
+
+#: html/RTFM/Article/delete.html:59
+msgid "Unable to load article"
+msgstr "Artikel kann nicht geladen werden"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Nicht implementiert"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unix Login"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "UnixBenutzername"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Unbekannte Inhalts-Kodierung %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "unbegrenzt"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Unprivilegiert"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Zurückgegeben"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Aktualisieren"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Aktualisierungs-ID"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Aktualisierungtyp"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Aktualisiere alle diese Anfragen auf einmal"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Aktualisiere E-Mail"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Aktualisiere Name"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Aktualisierung nicht aufgezeichnet."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Aktualisiere ausgewählte Anfragen"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Aktualisiere Unterschrift"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Aktualisiere Anfrage"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Aktualisiere Anfrage Nr. %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Aktualisiere Anfrage Nr. %1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Aktualisiere Anfrage Nr. %1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "Aktualisierungstyp war weder Korrespondenz noch Kommentar."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Aktualisiert"
+
+#: html/RTFM/Article/ExtractFromTicket.html:26
+msgid "Use the dropdown menus to select which transactions you want to extract into a new RTFM article"
+msgstr "Ãœber die Auswahllisten kann bestimmt werden welche Transaktionen in den neuen RTFM Artikel extrahiert werden"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr ""
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Benutzerdefiniert"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "Benutzer-ID"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Benutzername"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Benutzerrechte"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Benutzer konnte nicht angelegt werden: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Benutzer angelegt"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Benutzerdefinierte Gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Benutzeransicht"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Benutzername"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Benutzer"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Auf diese Kriterien zutreffenede Benutzer"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "ValueOfQueue"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Werte"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Beobachter"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "Webkodierung"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Mi"
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Füge Korrespondenz zum Originalticket, wenn eine Anfrage von allen Freigebenden freigegeben wurde"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Füge Korrespondenz zum Originalticket wenn eine Anfrage von einem Freigebenden freigegeben wurde"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Wenn eine Afrage erstellt wird"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Benachrichtige Inhaber und AdminCCs der auf Freigabe wartende Anfrage wenn ein Freigabeticket erstellt wurde"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Wenn irgendetwas passiert"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Immer wenn eine Anfrage erledigt wird"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Immer wenn der Eigentümer einer Anfrage wechselt"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Immer wenn eine Anfrage den Stapel wechselt"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Immer wenn sich der Status einer Anfrage ändert"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Immer wenn eine benutzerdefinierte Bedingung auftritt"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Immer wenn ein neuer Kommentar eingeht"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Immer wenn neue Korrespondenz eingeht"
+
+#: html/RTFM/Article/Elements/ShowSearchCriteria:64
+msgid "Which refer to"
+msgstr "Beziehen sich auf"
+
+#: html/RTFM/Article/Elements/ShowSearchCriteria:64
+msgid "Which are referred to by "
+msgstr "Bezogen von"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Arbeit"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Arbeitstelefon"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Arbeitszeit"
+
+#: RTFM
+msgid "Yes"
+msgstr "Ja"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Sie besitzen diese Anfrage bereits"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Sie sind kein authorisierter Benutzer"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Sie können nur Anfragen ohne Inhaber zuweisen"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Sie haben %1 Anfragen in Stapel %2 gefunden"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Sie wurden von RT abgemeldet."
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "Sie haben kein Recht, Anfragen in diesen Stapel anzulegen."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "Sie dürfen in diesem Stapel keine Anfragen erstellen"
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Sie können sich gerne wieder anmelden"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Meine %1 Anfragen"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr ""
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Ihre Anfrage wurde von %1 freigegeben. Andere Freigaben können noch ausstehen."
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Ihre Anfrage wurde freigegeben."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr ""
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Ihre Anfrage wurde abgewiesen"
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Ihr Benutzername oder Passwort ist falsch"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "PLZ"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "wie an %1 gewährt"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "enthält"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "Inhalt"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "content-type"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "Korrepsondenz (möglicherweise) nicht verschickt"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "Korrespondenz verschickt"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "Tage"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "löschen"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "gelöscht"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "entspricht nicht"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "enthält nicht"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "entspricht"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "Dateiname"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "größer als"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "Gruppe '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "Stunden"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr ""
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "ist"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "ist nicht"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "kleiner als"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "entspricht"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "Min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "Minuten"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "Änderungen\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "Monate"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "neu"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "kein Wert"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "keine"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "entspricht nicht"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "offen"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "persönliche Gruppe '%1' für Benutzer '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "Stapel %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "abgewiesen"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "erledigt"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "Sek"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "zurückgestellt"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "System %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "Systemgruppe '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "die aufrufende Komponente gab nicht an warum"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "Ticket #%1 %2"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "unbeschriebene Gruppe %1"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "Benutzer %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "Wochen"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr ""
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "Jahre"
+
diff --git a/rt/lib/RT/I18N/en.po b/rt/lib/RT/I18N/en.po
new file mode 100644
index 0000000..ffdc5cc
--- /dev/null
+++ b/rt/lib/RT/I18N/en.po
@@ -0,0 +1,88 @@
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Apr"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Aug"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Dec"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Feb"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Fri"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Home"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Jan"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mar"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "May"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Mon"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Oct"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Open"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Home"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Sat"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Sun"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Thu"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Tue"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Wed"
+
diff --git a/rt/lib/RT/I18N/es.po b/rt/lib/RT/I18N/es.po
new file mode 100644
index 0000000..05006b1
--- /dev/null
+++ b/rt/lib/RT/I18N/es.po
@@ -0,0 +1,4749 @@
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 2.1.x\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2003-03-23 12:38\n"
+"Last-Translator: Tomàs Núñez Lirola <tomasnl@dsl.upc.es>\n"
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr "#%1"
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "Añadido %1 %2"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "Hace %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 ha cambiado a %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 borrado"
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 con la plantilla %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 este caso\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1 - %2 mostrados"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Un parametro para pasar a %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - El estado de la salida actualiza STDOUT"
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Especifica el modulo de accion que quieres usar"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Especifica el modulo de condicion que quieres usar"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Especifica el modulo de busqueda que quieres usar"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1 ScripAction cargado"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "$1 añadido como un valor de %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 alias requieren un TicketId en el que trabajar"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1 alias requieren un TicketId en el que trabajar "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 alias requieren un TicketId en el que trabajar (de %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 parece ser un objeto local, pero no se encuentra en la base de datos"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 por %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 ha cambiado de %2 a %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "%1 no se ha podido fijar a %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 no pudo iniciar una transacción (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 no pudo fijar el estado a resuelto. La base de datos de RT podría ser inconsistente."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "Los %1 tickets de mayor prioridad que poseo... "
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "Los %1 tickets de mayor prioridad que he pedido"
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "$1 es una herramienta para actuar sobre los tickets con una herramienta de planificacion externa, como crom"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 ha dejado de ser un %2 para esta cola."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 ha dejado de ser un %2 para este ticket."
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 ha dejado de ser un valor para campo personalizable %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 no es un identificador de Cola válido."
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 no mostrado"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 privilegios"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 exitoso\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1 tipo desconocido para $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1 tipo desconocido para %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 se creó sin CurrentUser\\n"
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 resolverá todos los miembros de un grupo de tickets resueltos."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 pondrá como pendiente una BASE [local] si es dependiente [o miembro] de una solicitud ligada."
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: ningún archivo adjunto especificado"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' es un valor inválido para el estado"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' no es una acción reconocida. "
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(Marque la caja para borrar al miembro del grupo)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Marque la caja para borrar el scrip)"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Marque la caja para borrar)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(Marque las cajas para borrar)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Introduzca los identificadores de ticket o URLs, separados por espacios)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(Si se deja vacio, pasara por defecto a %1"
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(Sin Valor)"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(No hay campos custom)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Sin miembros)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Sin scrips)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Sin plantillas)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envia una copia oculta de esta actualizacion a una lista delimitada por comas de direcciones de email. <b>NO</b> cambia quien recibirá futuras actualizaciones)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Envía una copia oculta de esta actualización a una lista de direcciones de correo delimitada por comas. <b>No</b> cambia a quien recibirá futuras actualizaciones.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envia una copia oculta de esta actualización a una lista delimitada por comas de direcciones de email administrativas. Estas personas <b>recibirán</b> las futuras actualizaciones.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envia una copia oculta de esta actualización a una lista delimitada por comas de direcciones de email.<b>NO</b> cambia quien recibirá futuras actualizaciones."
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Manda una copia de esta actualización a una lista de direcciones de correo delimitada por comas. <b>No</b> cambia a quien recibirá futuras actualizaciones.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envia una copia de esta actualización a una lista de direcciones de correo delimitada por comas. Estas personas <b>recibirán</b> actualizaciones futuras."
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(vacío)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr "(no hay nombres listados)"
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(sin asunto)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(sin valor)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(solo un ticket)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(pendiente de aprobacion)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(pendiente de otros tickets)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(requerido)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(sin titulo)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "Los 25 tickets de mayor prioridad que poseo..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "Los 25 tickets de mayor prioridad que he solicitado..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Nuevo ticket en\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Una plantilla en blanco"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE Borrado"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE Cargado"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "ACE no se pudo borrar"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "ACE no se encontró"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE no encontrado"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEs solo pueden ser creadas o borradas."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Abortando para prevenir modificaciones no intencionadas al ticket\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "Sobre mi"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Control de acceso"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Acción"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Acción %1 no encontrada"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Action committed."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Acción preparada..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Añadir AdminCc"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Añadir Cc"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Añadir más archivos"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Añadir solicitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "Añadir una seleccion de palabra clave a esta cola"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Añadir un nuevo scrip global"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Añadir un scrip a esta cola"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Añadir un scrip que se aplicará a todas las colas"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Añadir comentarios o respuestas a los tickets seleccionados"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Añadir miembro"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Añadir nuevos observadores"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "AddNextState"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Principal ha sido añadido como %1 para esta cola"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Principal ha sido añadido como %1 para este ticket"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Dirección 1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Dirección 2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr "Admin Comment"
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr "Admin Correspondence"
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Administración de colas"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Administración de usuarios"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Adminsitración de la configuración global"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Administración de Grupos"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Administración de una cola"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "AdminAllPersonalGroups"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "AdminComment"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "AdminCorrespondence"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr "AdminCustomFields"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr "AdminGroup"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr "AdminGroupMembership"
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr "AdminOwnPersonalGroups"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr "AdminQueue"
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr "AdminUsers"
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Cc Administrativa"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Búsqueda avanzada"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Después"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Edad"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Todos los campos custom"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Todas las colas"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Siempre envia un mensaje a los solicitantes independientemente del remitente del mensaje"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Aprobacion"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Aprobacion #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Aprobación #%1: No se han guardado las notas debido a un error del sistema"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Aprobacion #%1: Notas guardadas"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Detalles de la aprobación"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Diagrama de la aprobación"
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Aprobar"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Notas del aprobador: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Abr."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "Abril"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Ascendente"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Adjunto"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Adjuntar archivo"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Archivo adjunto"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Archivo adjunto '%1' no pudo ser cargado"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Archivo adjunto creado"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Nombre del archivo adjunto"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Archivos adjuntos"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Ago."
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "Agosto"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "Sistema de autenticación"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Autorespuesta"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Autorespuesta a los solicitantes"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "AutoreplyToRequestors"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Firma PGP incorrecta: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Identificador de archivo adjunto erróneo. No se puede encontrar el archivo '%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Datos incorrectos en %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Número de transacción incorrecta para el archivo adjunto. %1 debe ser %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Basicos"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Asegúrese de salvar sus cambios"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "Antes"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "Begin Approval"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Vacio"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "URL para guardar esta búsqueda en sus marcadores"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Encabezados breves"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Actualización de varios tickets a la vez"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "No se pueden modificar los usuarios del sistema"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Can this principal see this queue"
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "No se puede agregar un campo personalizable si no tiene un nombre"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "No se puede ligar un ticket a sí mismo"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "No se puede fusionar dentro de un caso ya fusionado. Nunca deberia recibir este error"
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "No se puede especificar origen y destino al mismo tiempo"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "No se puede crear el usuario: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Cambiar contraseña"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Check box to delete"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Seleccione la caja para quitar el permiso"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Hijo"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Ciudad"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "Cerrado"
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Solicitudes cerradas"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "No se entendió el comando!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Comentario"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Dirección de comentario"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Comentario no grabado"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Comentario sobre los tickets"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr "CommentOnTicket"
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Comentarios"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Comentarios (no se envían a los solicitantes)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Comentarios (no se envían a los solicitantes)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Comentarios acerca de %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Comentarios acerca de este usuario"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Comentarios añadidos"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "Acción realizada"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Compilar restricciones"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Condición"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "La condicion coincide..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Condición no encontrada"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Configuración"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Confirmar"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "Información de contacto"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Fecha de contacto '%1' no pudo ser leida"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Contenido"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "No se pudo crear grupo"
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr "Correspondencia"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Dirección de corresponencia"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Correspondencia agregada"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Correspondencia no guardada"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "No se pudo añadir un nuevo valor de campo personalizable para el ticket. "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "No se pudo añadir un nuevo valor de campo personalizable para el ticket. %1 "
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "No se pudo cambiar el propietario. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "No se puede crear un CampoPersonalizable"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "No se pudo crear el grupo"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "No se pudo crear la plantilla: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "No se pudo crear el ticket. Cola no seleccionada"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "No se pudo crear el usuario"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "No se pudo crear un observador para el solicitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "No se pudo encontrar un ticket con identificador $1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "No se pudo encontrar el grupo %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "No se pudo encontrar o crear el usuario"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "No se pudo encontrar ese principal"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "No se pudo encontrar el usuario %1."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "No se puede cargar el grupo"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "No se pudo hacer ese principal un %1 para esta cola"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "No se pudo hacer ese principal un %1 para este ticket"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "No se pudo quitar ese principal como un %1 para esta cola"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "No se pudo quitar ese principal como un %1 para este ticket"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "No se pudo agregar el miembro al grupo"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "No se pudo crear la transacción: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "No se pudo averiguar que hacer a partir de la firma gpg de la respuesta"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "No se pudo encontrar el grupo\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr "No se pudo encontrar la fila"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "No pudo enconcontrar ese principal"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "No se pudo encontrar ese valor"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "No se pudo encontrar ese observador"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "No se pudo encontrar el usuario\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "No se pudo cargar %1 desde la base de datos de usuarios.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "No se pudo cargar KeywordSelects"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "No se pudo cargar el archivo de configuración de RT '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "No se pudieron cargar los Scrips."
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "No se pudo cargar el grupo %1"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "No se puedo cargar el enlace"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "No se pudo cargar la cola"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "No se pudo cargar la cola %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "No se pudo cargar el scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "No se pudo cargar la plantilla"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "No se pudo cargar ese usuario (%1)"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "No se pudo cargar el ticket '%1'"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "País"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Crear"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Crear Tickets"
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Crear CampoPersonalizable"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Crear un campo personalizables para la cola %1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Crear un campo personalizable que se aplique a todas las colas"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Crear un nuevo campo personalizable"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Crear un nuevo scrip global"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Creat un nuevo grupo"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Crear un nuevo grupo personal"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Crear una nueva cola"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Crear un nuevo scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Crear una nueva plantilla"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Crear un nuevo ticket"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Crear un nuevo usuario"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Crear una cola"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Crear una cola llamada "
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Crear una solicitud"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Crear un scrip para la cola %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Crear una plantilla"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Creación fallida: %1 / %2 / %3 "
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "Creación fallida: %1 / %2 / %3 "
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Crear nuevos tickets basados en esta plantilla de scrip"
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Crear ticket"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Crear tickets en esta cola"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Crear, borrar y modifical campos personalizables"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Crear, borrar y modificar colas"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Crear, borrar y modificar los miembros de cualquier grupo personal de usuario"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Crear, borrar y modificar los miembros de los grupos personales"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Crear, borrar y modificar usuarios"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "CreateTicket"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Creado"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "CampoPersonalizable %1 creado"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Plantilla %1 creada"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Relaciones actuales"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Scrips actuales"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Miembros actuales"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Permisos actuales"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Criterio de busqueda actual"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Observadores actuales"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Campo personalizable #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Campos personalizables"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Codigo de limpieza de accion personalizable"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Codigo de preparacion de accion personalizable"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Condicion personalizable"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Campo personalizado %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Campo personalizado %1 tiene un valor."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Campo personalizado %1 no tiene un valor."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Campo personalizado %1 no encontrado"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr "Campo personalizable borrado"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Campo personalizado no encontrado"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "El valor del campo %1 no pudo ser encontrado para el campo %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Valor del campo cambiado de %1 a %2"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "El valor del campo no pudo ser borrado"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "El valor del campo no pudo se encontrado"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Valor del campo borrado"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr "CustomField"
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "Error de datos"
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Fechas"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Dic."
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "Diciembre"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Plantilla de autorespuesta por defecto"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Plantilla de autorespuesta por defect"
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Plantilla de comentario de admin por defecto"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr "Plantilla de correspondencia de admin por defecto"
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr "Plantilla de correspondencia por defecto"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Plantilla de trasacciones por defecto"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Por defecto: %1/%2 ha cambiado de %3 a %4"
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Delegar derechos"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Delegar derechos especificos que te han sido concedidos"
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr "DelegateRights"
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Delegar"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Borrar"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Borrar tickets"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr "DeleteTicket"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Al borrar este objeto, se puede romper la integridad referencial"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Al borrar este objeto, se romperá la integridad referencial"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Al borrar este objeto, se violará la integridad referencial"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Al borrar este objeto, se violará la integridad referencial."
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "Al borrar este objeto, se violará la integridad referencial. Eso es malo."
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Denegar"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Dependen de este ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Dependencias: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Depende de"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "DependsOn"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Descendiente"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Describa el problema debajo"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Descripción"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Detalles"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Despliegue"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Mostrar Lista de Control de Acceso"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Mostrar plantillas de scrip para esta cola"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Mostrar scrips para esta cola"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Modo de despliegue"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Despliega ticket #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Hacer cualquier cosa y todo"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "No recargar esta página"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "No mostrar los resultados de la busqueda"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Descargar"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Retraso"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "La fecha de retraso '%1' no pudo ser leida"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ERROR: No se pudo cargar el ticket '%1': %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Editar"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Editar campos personalizados para %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Editar relaciones"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Editar plantillas para la cola %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "Editar palabras clave"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Editar acciones"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Editar plantillas del sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Editar plantillas para %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Editando configuración para la cola %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Editando configuración para el usuario %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Editando campo %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Editando los miembros del grupo %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Editando los miembros para el grupo personal %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Editando plantilla %1"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "La base o el destinatario deben ser especificados"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Correo"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "La dirección de correo ya está en uso"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "Correo Electrónico"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "Codificación para el correo"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Habilitado (Desmarcar esta caja deshabilita este campo personalizable)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Habilitado (Desmarcar esta caja deshabilita este campo personalizable)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Habilitado (Desmarcar esta caja, deshabilita esta cola)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Campos Personalizables Habilitados"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Colas habilitadas"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Estado %1 habilitado"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr "Introducir multiples valores"
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr "Introducir un valor"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Ingrese los números de ticket o las URL que llevan hacia el ticket. Separe multiples entradas con espacios"
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Error"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "Error añadiendo observador"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Error en los parámetros para Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Error en los parámetros para Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Error en los parámetros para Queue->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Error en los parámetros para Queue->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Todos"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Ejemplo"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Información extra"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Problema para encontrar el pseudogrupo de usuarios 'Privileged'"
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Problema para encontrar el pseudogrupo de usuarios 'Unprivileged'"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Error al cargar el modulo %1. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Feb."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "Febrero"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Fin"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Prioridad Final"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr "FinalPriority"
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Encontrar grupo que"
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Encontrar tickets nuevos/abiertos"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Encontrar gente que"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "Encontrar tickets"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "Aprobacion final"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Primero"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Primera página"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Forzar cambio"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "Encontrado %quant(%1,ticket)"
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr "Objeto encontrado"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "FreeformContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "FreeformMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr "FreeformSingle"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Vie."
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Encabezados completos"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Obteniendo el usuario de la firma pgp"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Given to %1"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Global"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "Seleccion de palabras clave globales"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Acciones Globales"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Plantilla global"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr " Ir "
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Firma pgp correcta de %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Ir a página"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Ir a ticket"
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Grupo"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Grupo %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Derechos del grupo"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "El grupo ya tiene miembros"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "El grupo no se pudo crear"
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "El grupo no se pudo crear: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Grupo creado"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "El grupo no tiene este miembro"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Grupo no encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Grupo no entontrado\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Grupo no especificado\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Grupos"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Los grupos no pueden ser miembros de sus propios miembros"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Hola!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hola, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Historial"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "Tel Casa"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Inicio"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "Tengo %quant(%1,concrete mixer)."
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "Tengo [quant,_1,concrete mixer]."
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Id"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identidad"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Si una aprobacion es rechazada, rechazar la original y borrar las aprobaciones pendientes"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Si esta herramienta estaba setgid, un usuario hostil local podria usar esta herramienta para conseguir acceso administrativo a RT."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Si ha actualizado algo más arriba, no olvide"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr "Valor ilegal para %1"
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr "Campo inmutable"
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Incluir campos personalizables deshabilitados en el listado."
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Incluir colas deshabilitadas en el listado"
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Incluir usuarios deshabilitados en la búsqueda"
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Prioridad inicial"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr "InitialPriority"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Error de entrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "Interest noted"
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "Error interno"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Error interno: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Tipo de grupo inválido"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr "Derechos inválidos"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "Tipo inválido"
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr "Datos no válidos"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Propietario inválido. Estableciéndolo a 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Ãrea inválida"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Permiso inválido"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Valor inválido para %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Valor inválido para el campo personalizable"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Valor inválido para el estado"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Es increiblemente importante que los usuarios sin privilegios no puedan ejecutar esta herramienta"
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Es recomendable crear un usuario unix sin privilegios que pertenezca al grupo correcto y que tenga aceso a ejecutar esta herramienta"
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Tiene varios parametros:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Items pendientes de mi aprobación"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Ene."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "Enero"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "Unirse o abandonar este grupo"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "Julio"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Todo"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "Junio"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Palabras clave"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Leng"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Último"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Último contacto"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Último contactado"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "Se le notifico por ultima vez"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Actualizado por ultima vez"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "LastUpdated"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Izquierda"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Permitir a este usuario acceder al RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Permitir que este usuario tenga privilegios adicionales"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Limitando propietario a %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Limitando cola a %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "El vínculo ya existe"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "El vínculo no pudo ser creado"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Vínculo creado (%2)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Vínculo borrado (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Vínculo no encontrado"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Vincular caso #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "Enlazar ticket %1"
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Enlaces"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Direccion"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "El directorio del log %1 no pudo ser encontrado o no se pudo escribir en él.\\n RT no puede ejecutarse."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Autenticado como %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Entrar"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Salir"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Hacer propietario a"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Establecer estatus"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Establecer fecha de plazo"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Establecer fecha de resolución"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Establecer fecha de inicio"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Establecer fecha de inicio"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Establecer fecha de último cambio"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Establecer prioridad"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Establecer cola"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Establecer título"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Administrar grupos y miembros"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Administrar propiedades y configuracion que se aplique a todas las colas"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Administrar colas y propiedades especificas"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Administrar usuarios y contraseñas"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mar."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "Marzo"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "Mayo"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "May."
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Miembro añadido"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Miembro borrado"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Miembro no borrado"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Miembro de"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "MemberOf"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Miembros"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "Fusión exitosa"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Fusión fallida. No se pudo establecer el EffectiveId"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Fusionar dentro de"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Mensaje"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr "Falta una clave primaria?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Movil"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "Telefono Movil"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Modificar lista de control de acceso"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Modificar el campo personalizable %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Modificar los campos personalizables que se apliquen a todas las colas"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Modificar plantillas Sript para esta cola"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Modificar Scrips para esta cola"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "Modificar ACLs de sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Modificar plantilla %1"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Modificar un campo personalizable para la cola %1"
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Modificar un campo personalizable que se aplique a todas las colas"
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Modificar un scrip para la cola %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Modificar un scrip que se aplique a todas las colas"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "Modificar fechas para # %1"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Modificar fechas para #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Modificar fechas para ticket # %1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Modificar privilegios globales de grupo"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Modificar privilegios globales de grupo."
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "Modificar privilegios globales para grupos"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "Modificar privilegios globales para usuarios"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Modificar acciones globales"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Modificar derechos globales de usuario"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Modificar privilegios globales de usuario"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Modificar metadatos del grupo o borrar grupo"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Modificar privilegios de grupo para %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Modificar privilegios de grupo para la cola %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Modificar miembros de este grupo"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Modificar la propia cuenta RT"
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Modificar personas relacionadas al cola %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Modificar personas relacionadas al ticket #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Modificar acciones para la cola %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Modificar scrips que se aplican a todas las colas"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Modificar plantilla %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Modificar plantillas que se aplican a todas las colas"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Modificar el grupo %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Modificar los observadores de la cola"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Modificar el usuario %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Modificar el ticket # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Modificar el ticket #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Modificar tickets"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Modificar privilegios de usuario para el grupo %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Modificar derechos de usuario para la cola %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Modificar observadores para la cola '%1'"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr "ModifyACL"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr "ModifyOwnMembership"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr "ModifyQueueWatchers"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr "ModifyScrips"
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr "ModifySelf"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr "ModifyTemplate"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr "ModifyTicket"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Lun."
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Más acerca de %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr "Mover hacia abajo"
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr "Move hacia arriba"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Múltiple"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "Se debe especificar un nombre"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Mis aprobaciones"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Mis aprobaciones"
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Nombre"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Nombre en uso"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Se necesita aprobacion del administrador del sistema"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Nunca"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Nuevo"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Nueva contraseñaa"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Nueva pendiente de aprobación"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Nuevas relaciones"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Nueva búsqueda"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Nuevo campo personalizable"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "Nuevo grupo"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Nueva contraseñaa"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Notificación de nueva contraseña enviada"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "Nueva cola"
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Nueva solicitud"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Nuevos privilegios"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Nuevo scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nueva búsqueda"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr "Nueva plantilla"
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "El ticket nuevo no existe"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Nuevo usuario"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Nuevo usuario llamado"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Nuevo observador"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Establecer nueva ventana "
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Siguiente"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Siguiente página"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Alias"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Alias"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "No hay campo personalizable"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "No hay grupo definido"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "No hay cola definida"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "No se encontró el usuario. Por favor consulte al administrador.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "No hay plantilla"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "No se especificó el ticket. Abortada la transacción"
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "No se especificó ticket. Abortando las modificaciones al ticket\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "No action"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr "No se ha especificado ninguna columna"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Comando no encontrado\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "No hay comentarios sobre este usuario"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "No hay ningún archivo adjunto"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "No hay descripción para %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "No hay grupo especificado"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "No hay contraseña definida"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "No tiene privilegios para crear colas"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "No tiene privilegios para crear tickets en la cola '%1'"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "No tiene privilegios para crear usuarios"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "No tiene privilegios para mostrar el ticket"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Sin permiso para ver la actualización del ticket"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "No hay un principal especificado"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "No hay principales seleccionados"
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "No hay colas que concuerden con los criterios de búsqueda"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "No se encontraron derechos"
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Sin privilegios concedidos"
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "No hay búsqueda sobre la que operar"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "No se especificó el identificador del ticket"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "No se especificó el tipo de transacción"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "No se especificó email o usuario"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "No se encontraron usuarios que concuerden con los criterios de búsqueda"
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Usuario no encontrado. El manejador cvs está deshabilitado. Por favor consulte a su administrador.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr "No se envió ningun valor a _Set!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "Nadie"
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr "Campo no existente?"
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr "No autenticado"
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "No autenticado."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "No establecido"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "No se ha implementado."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "No está implementado..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Notas"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "La notificación no se pudo enviar"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Notificar AdminCcs"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Notificar AdminCcs como comentario"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Notificar otros destinatarios"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Notificar otros destinatarios como comentario"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Notificar al propietario"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Notificar al propietario como comentario"
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Notificar propietarios y AdminCcs de nuevos items pendientes de aprobación"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Notificar solicitantes"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Notificar solicitantes y Ccs"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Notificar solicitantes y Ccs como comentario"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Notificar solicitantes, Ccs y AdminCcs"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Notificar solicitantes, Ccs y AdminCcs como comentario"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "Noviembre"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "No se pudo crear el objeto"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Objeto creado"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Oct."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "Octubre"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "en "
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Al comentar"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "On Correspond"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Al crear"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Al cambiar de propietario"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Al cambiar de cola"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Al resolver"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Al cambiar de status"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Al hacer transaccion"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Solo muestra aprobaciones para solicitudes creadas despues de %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Solo muestra aprobaciones para solicitudes creadas antes de %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Abierto"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Abrirlo"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Solicitudes abiertas"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Tickets abiertos (del listado) en una nueva ventana"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Tickets abiertos (del listado) en otra ventana"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Open tickets on correspondence"
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Ordenación y clasificación"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organización"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Ticket originario: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Pasada la fecha de gracia, la prioridad se mueve a"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Tickets poseidos"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr "OwnTicket"
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Propietario"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Propietario cambiado de %1 a %2"
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Propietario cambiado forzosamente de %1 a %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "El propietario es"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Buscapersonas"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Buscapersonas Tel."
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Padres"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Contraseñaa"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Recordatorio de contraseña"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Contraseña demasiado corta"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Contraseña: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Personas"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Realizar una acion definida por el usuario"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Permiso denegado"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Grupos personales"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Grupos personales"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Grupos personales:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Números de teléfono"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Placeholder"
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Prefs"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Preparación cortada"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Prev"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Página anterior"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "No se encontró el principal %1"
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Prioridad"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "La prioridad empieza en"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilegiado"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Estado privilegiado: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Usuarios privilegiados:"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudogrupo para uso interno"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Cola"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Cola %1 no encontrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Cola '%1' no encontrada\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "Selecciones de palabras clave de la cola"
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Nombre de la cola"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Acciones de la cola"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "La cola ya existe"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "La cola no se pudo crear"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "La cola no se pudo cargar"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Cola creada"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "No se especifico ninguna cola"
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Cola no encontrada"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Colas"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 para %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 de <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Derechos reservados 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "Administración del RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "Error de autenticación en RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "Rechazo del RT: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "Error de configuración del RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "Error crítico en RT. El mensaje no fue grabado!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "Error del RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT recibió correo (%1) de sí mismo."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT recibió correo (%1) de sí mismo."
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT AutoServicio / Tickets cerrados"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT en un vistazo"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT no te pudo autenticar."
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT no pudo encontrar el solicitante a través de una busqueda a la base de datos externa"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT no pudo encontrar la cola: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT no pudo validar esta firma PGP. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT para %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT para %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT ha procesado tus comandos"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT es &copy; Copyright 1996-%1 de Jesse Vincent &lt;jesse@bestpractical.com&gt;. Es distrbuido bajo <a href=\"http://www.gnu.org/copyleft/gpl.html\">la version 2 de la licencia GNU GPL (General Public License)</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT cree que este mensaje puede ser un mensaje rebotado"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT procesará este mensaje como si fuera uno no firmado\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "El modo de comandos por correo de RT requiere autenticación PGP. Ya sea que no haya firmado su mensaje, o que su firma no pueda ser verificada."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Nombre real"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "Nombre real"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Referenciado por"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Hace referencia a"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "RefersTo"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Refinar"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Refinar la búsqueda"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Refrescar esta página cada %1 minutos"
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Relaciones"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Quitar AdminCc"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Quitar Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Quitar solicitante"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Responder"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Responder a los tickets"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "ReplyToTicket"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Solicitante"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Dirección de correo del solicitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Solicitante(s)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "RequestorAddresses"
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Solicitantes"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Las solicitudes entran en vencimiento en"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Borrar"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Residencia"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Resolver"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Resolver ticket #%1 (%2)"
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Resuelto"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Responder a los solicitantes"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Resultados"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Resultados por página"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Confirmar contraseña"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Privilegio %1 no encontrado para %2 %3 referente a %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Privilegio delegado"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Privilegio otorgado"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Privilegio cargado"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Privilegio no pudo ser revocado"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Privilegio no encontrado"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Privilegio no cargado"
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Privilegio revocado"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Privilegios"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "No se pudieron conceder los privilegios a %1"
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "No se pudieron revocar los privilegios de %1"
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Roles"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "RootApproval"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Sab."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Guardar Cambios"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Guardar cambios"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "Scrip #%1"
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Acción creada"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Acción borrada"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Acciones"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Acciones para %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Acciones que se aplican a todas las colas"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Búsqueda"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Criterios de búsqueda"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "Buscar aprobaciones"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "Seguridad:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr "Ver cola"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Seleccione un grupo"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Seleccione una cola"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Seleccione un usuario"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr "Seleccionar un campo personalizable"
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Seleccionar grupo"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr "Seleccionar valores múltiples"
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr "Seleccionar un valor"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Seleccionar cola"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Seleccionar accion"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr "Selecionar plantilla"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Seleccionar usuario"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "SelectMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "SelectSingle"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Autoservicio"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Enviar mail a todos los observadores"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Enviar mail a todos los observadores como comentario"
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Enviar mail a los solicitantes y Ccs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Enviar mail a los solicitantes y Ccs como comentario"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Envia un mesaje a los solicitantes"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Enviar mail a los Ccs y Bccs listados explicitamente"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Envia mail a los Ccs administrativos"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Envia mail a los Ccs administrativos como comentario"
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Enviar mail al propietario"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep."
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "Septiembre"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Mostrar resultados"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Mostrar peticiones aprobadas"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Mostrar lo básico"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Mostrar solicitudes denegadas"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Mostrar detalles"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Mostrar solicitudes pendientes"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Mostrar solicitudes esperando otras aprobaciones"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Mostrar ticket en un comentario privado"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Mostrar resumen del ticket"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr "ShowACL"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr "ShowScrips"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr "ShowTemplate"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr "ShowTicket"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr "ShowTicketComments"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Validarse como solicitante de ticket o ticket o cola Cc"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Validarse como ticket o cola AdminCc"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Firma"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr "Validado como %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Sencillo"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr "Saltar Menu"
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Clave de ordenación"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Ordenar resultados por"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "Ordenamiento"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Pendiente"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Página de inicio"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Empezado"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "La fecha de inicio '%1' no se pudo leer"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Empieza"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Empezado por"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "La fecha de inicio '%1' no se pudo ser leer"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Estado"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Estado"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr "Cambio de status"
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Estado cambiado de %1 a %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "StatusChange"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Robar"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Robado de %1"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Asunto"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Asunto cambiado a %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Enviar"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Submit Workflow"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Completado"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Dom."
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "Superusuario"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "Sistema"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Error del sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "Error de sistema. Derecho no concedido"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "Error de sistema. Derecho no concedido"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Error del sistema. Privilegio no delegado."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Error del sistema. Privilegio no otorgado"
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Error de sistema. Incapaz de conceder permisos"
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Grupos del sistema"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRolegroup for internal use"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Coger"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Cogido"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Plantilla"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Plantilla #%1"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Plantilla borrada"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Plantilla no encontrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Plantilla no encontrada\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Plantilla procesada"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Plantillas"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Plantillas de %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Ese es el valor actual"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Ese no es un valor para este campo personalizable"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Este es el mismo valor"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Ese principal ya es un %1 para esta cola"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Ese principal ya es un %1 para este ticket"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Ese principal no es un %1 para esta cola"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Ese principal no es un %1 para este ticket"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Esa cola no existe"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Ese ticket tiene dependencias sin resolver"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Ese usuario ya tiene ese privilegio"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Ese usuario ya posee ese ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Ese usuario no existe"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Ese usuario ya tiene privilegios"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Ese usuario ya está sin privilegios"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Ese usuario ahora tiene privilegios"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Ese usuario ya no tiene privilegios"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "Este usuario ya no tiene privilegios"
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Ese usuario puede no poseer tickets en esa cola"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Ese no es un identificador numérico"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Lo básico"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "El CC de un ticket"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "El CC administrativo de un ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "El comentario ha sido grabado"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "El siguiente comando encontrará todos los tickets activos en la cola 'general' y pondra su prioridad a 99 si no han sido tocados en 4 horas:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Los siguientes comandos no han sido procesados:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "Ha sido establecido el nuevo valor"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "El propietario de un ticket"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "El solicitante de un ticket"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Estos comentarios generalmente no están visibles para el usuario"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Este ticket %1 %2 (%3)"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Esta herramiento permite al usuario ejectutar modulos perl arbitrarios desde dentro de RT"
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Parece que esta transacción no tiene contenido"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "Los %1 tickets de mayor prioridad de este usuario"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "Los 25 casos de mayor prioridad de este usuario"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Jue."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Ticket # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "Actualizacion Jumbo para el ticket # %1: %2"
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Actualización Jumbo para el ticket #%1: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Ticket #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Ticket %1 creado en la cola '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Ticket %1 cargado\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Ticket %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Historial del ticket # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Id del ticket:"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr "Ticket resuelto"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Archivos adjuntos del ticket"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Contenido del ticket"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Tipo de contenido del ticket"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "No se pudo crear el ticket debido a un error interno"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Ticket creado"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Creación del ticket fallida"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Ticket borrado"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Id de ticket no encontrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "Ticket matado"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Ticket no encontrado"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "Estado del ticket cambiado"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Observadores del ticket"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr "Tickets"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Tickets %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Tickets %1 por %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Tickets de %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Tickets que dependen de esta aprobación:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Tiempo Restante"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Tiempo Trabajado"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Tiempo restante"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Tiempo para mostrar"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Tiempo trabajado"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "TimeLeft"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "TimeWorked"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Para generar una comparación de este cometido:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Para generar una comparación de este cometido:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Última actualización"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transacción"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transacción %1 limpiada"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transacción creada"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create no pudo, ya no no especificó un ID de ticket"
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Las transacciones son inmutables"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Intentando borrar el privilegio: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Mar."
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Tipo"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "No implementado"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Usuario en Unix"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Usuario en Unix"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Codificación de contenido desconocida: %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Ilimitado"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "No privilegiado"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "No cogido"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Actualizar"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Id de actualización"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Tipo de actualización"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Actualizar todos estos casos al mismo tiempo"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Actualizar correo"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Actualizar nombre"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Actualización no grabada."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Actualizar tickets seleccionados"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Actualizar firma"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Actualizar ticket"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Actualización de ticket # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Actualizar ticket #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Actualizar ticket #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "El tipo de actualización no fue ni respuesta ni comentario"
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Actualizado"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Usuario %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Usuario %1 Contraseña: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Usuario '%1' no encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Usuario '%1' no encontrado\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Definido por el usuario"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "ID de usuario"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Id de usuario"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Privilegios de usuario"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "El usuario no pudo ser creado: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Usuario creado"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Grupos definidos por el usuario"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Usuario notificado"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Vista de usuario"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Nombre de usuario"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Usuarios"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Usuarios que concuerdan con los criterios de búsqueda"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "Valor de la cola"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Valores"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr "Observar"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr "WatchAsAdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "Observador cargado"
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Observadores"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "Codificación de Web"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Mie."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Cuando un ticket ha sido aprobado por todos los aprobadores, añadir correspondencia al ticket original"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Cuando un ticket ha sido aprobado por cualquier aprobador, añadir correspondencia al ticket original"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Cuando un ticket se crea"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Cuando una aprobacion de ticket se crea, notifica al propietario y AdminCC del item que espera su aprobación"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Cuando pasa cualquier cosa"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Siempre que un ticket este sin resolver"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Siempre que el propietario de un ticket cambie"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Siempre que la cola de un ticket cambie"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Siempre que el estado de un ticket cambie"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Siempre que ocurra una condicion definida por el usuario"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Siempre que venga algun comentario"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Siempre que venga correspondencia"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Trabajo"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Tel Trabajo"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Trabajado"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Usted ya es propietario de este caso"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Usted no es un usuario autorizado"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Usted solo puede reasignar casos que posee o que no posee nadie³"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "No tiene permiso para ver ese ticket.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Usted encontró %1 casos en la cola %2"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Se ha desconectado del sistema RT"
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "No tiene permiso para crear tickets en esa cola."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "No puede crear solicitudes en esa cola."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Es bienvenido a regresar en cualquier momento."
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Sus solicitudes %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Su administrador del RT ha desconfigurado el alias de correo que invoca el RT"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Su petición ha sido aprobada por %1. Otras aprobaciones pueden estar pendientes todavia"
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Su peticion ha sido aprobada."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "Su petición ha sido rechazada"
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Su petición ha sido rechazada"
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Nombre o contraseña de usuario incorrectos"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "Zip"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[sin asunto]"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "como priviligiado para %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "contiene"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "contenido"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "content-type"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "Respuesta (probablemente) no enviada"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "Correspondencia enviada"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "días"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "muerto"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "borrar"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "borrado"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "no coincide"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "no contiene"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "igual a"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "falso"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "nombre de archivo"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "mayor que"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "grupo '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "horas"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "id"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "es"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "no es"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "menor que"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "contiene"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minutos"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "modificaciones\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "meses"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "nuevo"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "sin valor"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "ninguno"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "no igual a"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "notlike"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "abierto"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "grupo personal '%1' para usuario '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "Cola %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "rechazado"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "resuelto"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sec"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "pendiente"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "sistema %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "grupo del sistema '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "el componente que llama no especifica por qué"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "ticket #%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "verdadero"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "grupo sin descripción %1"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "grupo sin descripción %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "usuario %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "semanas"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "con plantilla %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "años"
+
diff --git a/rt/lib/RT/I18N/fi.po b/rt/lib/RT/I18N/fi.po
new file mode 100644
index 0000000..ee1ad71
--- /dev/null
+++ b/rt/lib/RT/I18N/fi.po
@@ -0,0 +1,4750 @@
+# Finnish localization catalog for Request Tracker (RT)
+# First Author: Janne Pirkkanen <jp@oppipoika.net>, Jul 2002
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 2.1.x\n"
+"POT-Creation-Date: 2002-07-08 17:41+0200\n"
+"PO-Revision-Date: 2002-07-09 18:09+0200\n"
+"Last-Translator: Janne Pirkkanen <jp@oppipoika.net>\n"
+"Language-Team: Finnish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr ""
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr ""
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %3.%2 %7 %4:%5:%6"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr ""
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1: %2 muutettu arvoon %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 tässä työpyynnössä\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr ""
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr ""
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr ""
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "ScriptAction %1 ladattu"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 lisätty arvoksi %2lle"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 aliakset vaativat työpyynnön id:n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1 aliakset vaativat työpyynnön id:n "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 aliakset vaativat työpyynnön id:n (osoite %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 - %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 muutettu arvosta %2 arvoon %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 ei voinut suorittaa toimintoa (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 ei voinut asettaa tilan arvoa päätetyksi. RT:n tietokanta saattaa olla vioittunut."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr ""
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr ""
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 ei ole enää %2 tälle työjonolle"
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 ei ole enää %2 tälle työpyynnölle"
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 ei ole enää kentän %2 arvo"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 ei ole kelvollinen työjonon id"
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 ei näy"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 onnistui\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1 tyyppi tuntematon viestille $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1 tyyppi tuntematon viestille %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr ""
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 päättää kaikki päätetyt työpyynnöt -ryhmän työpyynnöt."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 jäädyttää [paikallisen] BASE jos se riippuu linkitetystä työpyynnöstä [tai on sen jäsen]."
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: liitetiedostoa ei ole määritelty"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' ei kelpaa tilan arvoksi"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' ei ole tunnettu tapahtuma."
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Rastita laatikko poistaaksesi skriptin)"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Rastita laatikko poistaaksesi)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr ""
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Syötä työpyynnön numerot tai www-osoitteet, välilyönneillä erotettuina)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Ei jäseniä)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Ei skriptejä)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr ""
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Lähettää piilokopion tästä päivityksestä pilkulla erotettuihin sähköpostiosoitteisiin. <b>Ei muuta</b> jatkossa tehtävien lähetysten kohteita.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Lähettää kopion tästä päivityksestä pilkulla erotettuihin sähköpostiosoitteisiin. <b>Ei muuta</b> jatkossa tehtävien lähetysten kohteita.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(tyhjä)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr ""
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(ei otsikkoa)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(ei arvoa)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(vain yksi työpyyntö)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr ""
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(pakollinen)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "25 omistamaani korkeimpien prioriteettien työpyyntöä..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "25 tilaamaani korkeimman prioriteetin työpyyntöä..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr ""
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Uusi työpyyntö\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE ei löytynyt"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACE:ja voi vain luoda ja poistaa."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Poistuminen ei-tarkoitettujen työpyyntömuutosten välttämiseksi.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Pääsynvalvonta"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Tapahtuma"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Tapahtumaa %1 ei löydetty"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr ""
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr ""
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Lisää kopio ylläpidolle"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Lisää kopio"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr ""
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Lisää tilaaja"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Lisää uusi globaali lappu"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Lisää lappu tälle työjonolle"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Lisää kaikille työjonoille yhteinen lappu"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Lisää kommentteja tai vastauksia valituille työpyynnöille"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Lisää jäsenä"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Lisää uusia tarkkailijoita"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Lisätty toimeksiantaja %1:ksi tähän työjonoon"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Lisätty toimeksiantaja %1:ksi tälle työpyynnölle"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Osoite1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Osoite2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Kopio ylläpidolle"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr ""
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr ""
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Työjonojen ylläpito"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Käyttäjien ylläpito"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Ylläpito/Globaalit asetukset"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Ylläpito/Ryhmät"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Ylläpito/Työjono/Perustiedot"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr ""
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "Kopio ylläpidolle"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr ""
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Kopio ylläpidolle"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Tarkennettu haku"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Jälkeen"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Kaikki työjonot"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr ""
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr ""
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr ""
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr ""
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr ""
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Huhti"
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Nouseva"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Liitä"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Liitä tiedosto"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr ""
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Liitteen '%1' lataaminen ei onnistunut"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Liitetiedosto luotu"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Liitetiedoston nimi"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Liitetiedostot"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Elo"
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr ""
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr ""
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Virheellinen PGP allekirjoitus: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Virheellinen liitteen numero. Liitetiedostoa '%1' ei löytynyt\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Virheellistä dataa kentässä %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Virheellinen toiminnon numero liitetiedostolle. %1 pitäisi olla %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Perustiedot"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Piilokopio"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Muista tallentaa muutokset"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "Ennen"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr ""
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr ""
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "Osoite tähän kyselyyn (selaimen kirjanmerkkeihin)"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Lyhyet otsikot"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Työpyyntöjen ryhmäpäivitys"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Systeemikäyttäjien muokkaus ei ole sallittua"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Kentän lisääminen ilman nimeä ei onnistu"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Työpyyntöä ei voi linkittää itseensä"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Et voi yhdistää jo yhdistettyyn työpyyntöön. Sinun ei pitäisi saada tätä virhettä koskaan."
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Sekä basen ja kohteen määritteleminen samalla ei ole mahdollista"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Käyttäjää ei voitu luoda: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Kopio"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Muuta salasana"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Valitse laatikko poistaaksesi oikeuden"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Lapsi"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Kaupunki"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Suljetut työpyynnöt"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Komentoa ei ymmärretty!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Kommentoi"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Kommenttien osoite"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Kommenttia ei tallennettu"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Kommentit"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Kommentit (Ei lähetetä tilaajille)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Kommentit (Ei lähetetä tilaajille)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Kommentit kohteesta %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Kommentit tästä käyttäjästä"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Kommentit lisätty"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "Suorita tumppi"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Kokoa rajoitukset"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Ehto"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Ehtoa ei löydetty"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Ylläpito"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Varmista"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "Kontaktitietojärjestelmä"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Järjestelmä ei ymmärrä päivää '%1'"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Sisältö"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr ""
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Kirjeenvaihdon osoite"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Kirjeenvaihto lisätty"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Vastausta ei tallennettu"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Uuden tiedon lisääminen kenttään ei onnistunut"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Omistajaa ei voitu vaihtaa."
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Uuden kentän lisääminen ei onnistunut"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Ryhmän luominen ei onnistunut"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Ei onnistuttu luomaan pohjaa: "
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Työpyynön luominen ei onnistunut. Työjonoa ei ole asetettu"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Käyttäjän luominen ei onnistunut"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Työpyyntöä numero '%1' ei löytynyt."
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Ryhmää '%1' ei löytynyt."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Käyttäjää ei löydetty eikä pystytty luomaan"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Tätä toimeksiantajaa ei löytynyt"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Käyttäjää '%1' ei löytynyt."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Ryhmän lataaminen ei onnistunut"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Ei voinut tehdä toimeksiantajaa %1:ksi tälle työjonolle"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Ei voinut tehdä toimeksiantajaa tälle työpyynnölle: %1"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Ei voinut poistaa toimeksiantajaa tältä työjonolta: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Ei voinut poistaa toimeksiantajaa tältä työpyynnöltä: %1"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Jäsenen lisääminen ryhmään ei onnistunut"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Toiminnon luominen ei onnistunut: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Järjestelmä ei ymmärtänyt mitä tehdä pgp:n vastauksella\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Ryhmää ei löytynyt\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Tätä toimeksiantajaa ei löytynyt"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Tätä arvoa ei löytynyt"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Käyttäjää ei löytynyt\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Ei onnistuttu lataamaan käytäjää %1 tietokannasta.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "RT asetustiedoston lataaminen ei onnistunut:'%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Lappujen lataaminen ei onnistunut."
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Ryhmän %1 lataaminen ei onnistunut"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Linkin lataaminen ei onnistunut"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Työjonon lataaminen ei onnistunut"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Työjonon %1 lataaminen ei onnistunut"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Lapun lataaminen ei onnistunut"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Pohjan lataaminen ei onnistunut"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Tämän käyttäjän lataaminen ei onnistunut (%1)"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Työpyynnön '%1' lataaminen ei onnistunut"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "Maa"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Luo"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Luo kenttä"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Luo uusi kenttä"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Luo uusi globaali lappu"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Luo uusi ryhmä"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Luo uusi personaali ryhmä"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Luo uusi työjono"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Luo uusi pohja"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Luo uusi työpyyntö"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Luo uusi käyttäjä"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Luo uusi työjono"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Luo työjono nimeltään"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Luo työpyyntö"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Luo pohja"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr ""
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr ""
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Luo työpyyntö"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr ""
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Luotu"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Luotu kenttä %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Luotu pohja %1"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Tämänhetkiset suhteet"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Tämänhetkiset jäsenet"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Tämänhetkiset oikeudet"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr ""
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Tämänhetkiset tarkkailijat"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Kentät"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Kenttä %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Kentällä %1 on arvo"
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Kentällä %1 ei ole arvoa"
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Kenttää %1 ei löytynyt"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Kenttää ei löytynyt"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Kenttän arvoa %1 ei löytynyt kentälle %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Kenttän arvo muutettu arvosta %1 arvoon"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "Kenttän arvoa ei pystytty poistamaan"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "Kenttän arvoa ei löydetty"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Kenttän arvo poistettu"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr ""
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Päivät"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Joulu"
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr ""
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr ""
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr ""
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr ""
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr ""
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr ""
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Delegoi oikeuksia"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr ""
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr ""
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Poista"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Tämän objektin poistaminen saattaa rikkoa tietokannan viitteet"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Tämän objektin poistaminen rikkoo tietokannan viitteet"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Tämän objektin poistaminen rikkoo tietokannan viitteet"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr ""
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr ""
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Tästä pyynnöstä riippuu"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Riippuvuudet: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Riippuu pyynnöstä"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Laskeva"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Työtilauksen kuvaus"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Kuvaus"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Yksityiskohdat"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Näytä"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr ""
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Näkymä"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Näytä työpyyntö #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr ""
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Älä päivitä tätä sivua"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Lataa"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Mennessä"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Mennessä -päivää '%1' ei onnistuttu kääntämään järjestelmälle."
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "VIRHE: Työpyynnön '%1' lataaminen ei onnistunut: %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Muokkaa"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Muokkaa kenttiä: työjono %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Muokkaa suhteita"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Muokkaa lappuja"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Muokkaa systeemipohjia"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Muokkaa pohjia: työjono %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Asetusten muokkaus: työjono %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Asetusten muokkaus: käyttäjä %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Kentän %1 muokkaus"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Ryhmän %1 jäsenten muokkaus"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Henkilökohtaisen ryhmän %1 jäsenten muokkaus"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Pohjan %1 muokkaus"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Joko juuri tai kohde täytyy olla määritelty"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Sähköposti"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "Sähköpostiosoite on jo käytössä"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "Sähköpostiosoite"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "Sähköpostin koodaus"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Aktiivinen (Rastin poistaminen asettaa työjonon ei-aktiiviseksi)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Aktiiviset työjonot"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Aktivoitu tila %1"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Lisää työpyyntöjen numerot tai www-linkit. Käytä välilyöntiä erottimena syöttäessäsi useampaa numeroa tai linkkiä."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Virhe"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Virhe parametreissa: Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Virhe parametreissa: Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Virhe parametreissa: Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Virhe parametreissa: Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr ""
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "Ulkoinen autentikointitunnus"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "Ulkoinen yhteystietotunnus"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Lisatieto"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "'Etuoikeutettu' -pseudoryhmää ei löytynyt"
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Ei-etuoikeutettu' -pseudoryhmää ei löytynyt"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr ""
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Helmi"
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Fin"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Loppuprioriteetti"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr ""
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr ""
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Etsi uudet/avoimet työpyynnöt"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Etsi käyttäjät joiden"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Ensimmäinen"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Viimeinen sivu"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr ""
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Pakota muutos"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "Vapaamuotoiset yhteystiedot"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr ""
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Pe"
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Kokonaiset otsikot"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr ""
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Globaali"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Globaalit laput"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Ok!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Hyvä PGP allekirjoitus käyttäjältä %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Siirry sivulle"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Siirry työpyyntöön"
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Ryhmä %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Ryhmän oikeudet"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Ryhmässä on jo jäsen"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Ryhmä luotu"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Ryhmää ei löydetty"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Ryhmää ei löydetty.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Ryhmää ei määritelty.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Ryhmät"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Ryhmät eivät voi olla jäsentensä jäseniä"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Hei!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hei, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Historia"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "Kotipuhelin"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Kotisivu"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Numero"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identiteetti"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr ""
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr ""
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Jos olet muuttanut tietoja, muista tallentaa"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr ""
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Sisällytä listaukseen myös ei-aktiiviset työjonot."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Sisällytä listaukseen myös ei-aktiiviset käyttäjät."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Alkuprioriteetti"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Virhe syötteessä"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr ""
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Ryhmän tyyppi ei kelpaa"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Omistaja ei kelpaa. Asetetaan oletusasetusten mukaan 'eikukaan'"
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Kelpaamaton työjono"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Kelpaamaton oikeus"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Kelpaamaton arvo kohteelle %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Kelpaamaton arvo kentälle"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Kelpaamaton arvo tilalle"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr ""
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Tammi"
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr ""
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Heinä"
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Jumbo"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Kesä"
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Avainsana"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Keili"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Viimeinen"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Viimeinen yhteydenotto"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Viimeksi otettu yhteyttä"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr ""
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Viimeksi päivitetty"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Vasen"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Päästä tämä käyttäjä sisään RT:een"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Tälle käyttäjälle voidaan antaa oikeuksia"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Rajoitetaan omistajaa %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Rajoitetaan työjonoa %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "Linkki on jo olemassa"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "Linkkiä ei voitu luoda"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Linkki luotu (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Linkki poistettu (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Linkkiä ei löydetty"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Linkitä työpyyntö #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Linkit"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Sijainti"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Lokihakemistoa %1 ei löytynyt tai kirkoittaminen ei onnistunut.\\n RT ei voi toimia."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Kirjautunut sisään tunnuksella %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Kirjaudu sisään"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Kirjaudu ulos"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Aseta omistaja"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Aseta tila"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Aseta mennessä -aika"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Aseta päätetty -aika"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Aseta aloitettu -aika"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Aseta alkaa -aika"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Aseta oltu yhteydessä -aika"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Aseta prioriteetti"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Aseta työjono"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Aseta otsikko"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr ""
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr ""
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr ""
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr ""
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Maasis"
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr ""
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Touko"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Jäsen lisätty"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Jäsen poistettu"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Jäsentä ei poistettu"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Jäsen:"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Jäsenet"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "Yhdistäminen onnistui"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Yhdistäminen epäonnistui. EffectiveId:n arvoa ei pystytty asettamaan"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Yhdistä"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Käsipuhelin"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "Käsipuhelin"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Muokkaa kenttää %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr ""
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr ""
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Muokkaa työpyynnön #%1 päiviä"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Muokkaa työpyynnön #%1 päiviä"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Muokkaa ryhmien globaaleja oikeuksia"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Muokkaa ryhmien globaaleja oikeuksia."
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Muokkaa globaaleja lappuja"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Muokkaa käyttäjien globaaleja oikeuksia."
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Muokkaa ryhmän %1 oikeuksia."
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Muokkaa ryhmän oikeuksia työjonossa %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr ""
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Muokkaa työjonoon %1 liittyviä käyttäjiä"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Muokkaa työpyyntöön %1 liittyviä käyttäjiä"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Muokkaa työjonoon %1 liittyviä lappuja"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr ""
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Muokkaa pohjaa %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Muokkaa työjonoa %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Muokkaa käyttäjää %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Muokkaa työpyyntöä #%1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Muokkaa työpyyntöä #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr ""
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Muokkaa ryhmän %1 käyttäjien oikeuksia"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Muokkaa työjonoon %1 liittyviä käyttäjien oikeuksia"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Muokkaa työpyynnön %1 katselijoita"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr ""
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Ma"
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Lisätietoa: %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Monta"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "'Nimi' täytyy määritellä"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr ""
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr ""
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Nimi"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Nimi on käytössä"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr ""
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Uusi"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Uusi salasana"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Uusi linkki"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr ""
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Uusi salasana"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Uusi salasana"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Uusi työpyyntö"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Uudet oikeudet"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Uusi haku"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "Uutta työpyyntöä ei löydy"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr ""
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Uusi käyttäjä pyydetty"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Uusi tarkkailija"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Uusi ikkunan asetus"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Seuraava"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Seuraava sivu"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Lempinimi"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Lempinimi"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Ei kenttiä"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Ryhmää ei ole määritelty"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Työjonoa ei ole määritelty"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Käyttäjää ei löydy. Ole hyvä ja ota yhteyttä RT:n ylläpitäjään.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Ei pohjaa"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Työpyyntöä ei määritelty. Poistutaan työpyynnöstä"
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Työpyyntöä ei määritelty. Poistutaan työpyynnön muokkauksesta\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Komentoa ei löytynyt\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Tälle käyttäjälle ei ole annettu kommentteja"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Ei kirjeenvaihtoa liitettynä"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Ei kuvausta kohteelle %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Ryhmää ei ole määritelty"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "Salasanaa ei ole asetettu"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Ei oikeutta luoda kyselyitä"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Ei oikeutta luoda käyttäjiä"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Ei oikeutta tarkastella tätä työpyyntöä"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Ei oikeutta päivittää tätä työpyyntöä"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Toimeksiantajaa ei ole määritelty"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Johtajia ei ole valittu."
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Yhtään hakukriteerit täyttävää työpyyntöä ei löytynyt."
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Ei oikeuksia"
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Ei hakua jonka kanssa työskennellä"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Työpyynnön numeroa ei ole määritelty"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Toiminnon tyyppiä ei ole määritelty"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr ""
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Yhtään hakukriteerit täyttävää käyttäjää ei löytynyt."
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Ei kelpaa RT käyttäjäksi. RT cvs käsittelijä irrottautuu. Ole hyvä ja ota yhteyttä RT:n ylläpitäjään.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr ""
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr ""
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr ""
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Et ole kirjautunut järjestelmään"
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Ei asetettu"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Ei vielä implementoitu."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Ei vielä implementoitu..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "Ilmoitusta ei pystytty lähettämään"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr ""
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr ""
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr ""
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr ""
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr ""
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr ""
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr ""
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr ""
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr ""
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr ""
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr ""
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Marras"
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr ""
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr ""
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr ""
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Loka"
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr ""
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "-"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr ""
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr ""
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr ""
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr ""
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr ""
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr ""
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr ""
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr ""
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Avoin"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Avaa"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Avoimet työpyynnöt"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Avoimet työpyynnöt (listasta) uudessa ikkunassa"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Avoimet työpyynnöt (listasta) toisessa ikkunassa"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Järjestäminen"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organisaatio"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Ajan kuluessa prioriteetti muuttuu kohti"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr ""
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Omistaja"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Omistaja pakottamalla muutettu arvosta %1 arvoon %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Omistaja on"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Hakulaite"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Hakulaite puhelin"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Isät"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Salasana"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Salasanan muistuttaja"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Salasana liian lyhyt"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Salasana: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Käyttäjät"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Pääsy kielletty"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr ""
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Omat ryhmät"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Omat ryhmät:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Puhelinnumerot"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Paikanpitäjä"
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Asetukset"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Asetukset"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Valmistele tumppi"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Edellinen"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Edellinen sivu"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Prioriteetti"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Prioriteetti alkaa arvosta"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Etuoikeutuksen tila: &1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Etuoikeutetut käyttäjät"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr ""
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Työjono"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Työjonoa '%1' ei löytynyt"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Työjonon nimi"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Työjonon laput"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Työjono on jo olemassa"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Työjonoa ei voitu luoda"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "Työjonoa ei voitu ladata."
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Työjono luotu"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr ""
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Työjonoa ei löytynyt"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Työjonot"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 - %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1, tekijä <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "RT Ylläpito"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RT Virhe tunnistamisessa"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT palautus: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT Konfiguraatiovirhe"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RT Kriittinen virhe. Viestiä ei tallennettu!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "RT Virhe"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT Sai sähköpostin (%1) itseltään."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr ""
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Itsepalvelu / Suljetut työpyynnöt"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT Ei pystynyt tunnistamaan sinua"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT ei löytänyt tilaajaa ulkopuolisesta tietokannasta"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT ei löytänyt työjonoa: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT ei pystynyt tarkistamaan tätä PGP allekirjoitusta.\\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT on prosessoinut antamasi komennot"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT on tekijänoikeuslain alainen, &copy; 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Se on jakelussa seuraavalla lisenssillä: <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT luulee että tämä viesti on palautus"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT prosessoi tämän viestin kuten se olisi allekirjoittamaton."
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT:n sähköpostiohjaus moodi vaatii PGP tunnistamista. Et allekirjoittanut (PGP) viestiä tai allekirjoitustasi ei pystytty varmistamaan."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Oikea nimi"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "Oikea nimi"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Viitattu jostakin"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Viittaus johonkin"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Päivitä"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Päivitä haku"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Päivitä tämä sivu %1 minuutin välein"
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Linkit"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Poista kopio ylläpidolle"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Poista kopio"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Poista tilaaja"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Vastaa"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr ""
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Tilaaja"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Tilaajan sähköpostiosoite"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr ""
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Tilaajat"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Työpyyntö tulisi suorittaa mennessä"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Palauta"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Asuinpaikka"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Päätä"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr ""
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Päätetty"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Vastaus tilaajille"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Tulokset"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Tulosta sivulle"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Varmista salasana"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Oikeutta %1 ei löydetty %2 %3 laajuudessa %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Oikeus delegoitu"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Oikeus delegoitu"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Oikeus ladattu"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Oikeutta ei voitu peruuttaa"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Oikeutta ei löydetty"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Oikeutta ei ladattu"
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Oikeus peruutettu"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Oikeudet"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Roolit"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr ""
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "La"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Tallenna muutokset"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Tallenna muutokset"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Lappu luotu"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Laput"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Laput työjonolle %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr ""
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Hae"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Hakukriteerit"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr ""
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr ""
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Valitse ryhmä"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Valitse työjono"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Valitse käyttäjä"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr ""
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr ""
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Itsepalvelu"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr ""
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr ""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr ""
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr ""
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr ""
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr ""
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Syys"
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Näytä tulokset"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr ""
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Näytä perustiedot"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr ""
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Näytä yksityiskohdat"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Allekirjoitus"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Yksittäinen"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Järjestys"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Järjestä tulokset"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "Lajittelujärjestyt"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Jäädytetty"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Etusivu"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Aloitettu"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Aloitettu -aikaa '%1' ei pystytty tulkitsemaan"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Alkaa"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Alkaa mennessä"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Alkaa -aikaa '%1' ei pystytty tulkitsemaan"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Tila"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Tila"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Tila muutettu arvosta %1 arvoon %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Kaappaa"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Kaapattu käyttäjältä %1"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Otsikko"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr ""
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Lähetä"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr ""
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Su"
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Systeemivirhe"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Systeemivirhe. Oikeutta ei delegoitu."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Systeemivirhe. Oikeutta ei luovutettu."
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Systeemiryhmät"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr ""
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TESTI_STRINGI"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Ota itselle"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Otettu"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Pohja"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr ""
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Pohjaa ei löydetty"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Pohjaa ei löydetty\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Pohja tulkittu"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Pohjat"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Pohjat työjonolle %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Ei ole arvo tälle kentälle"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Tämä on sama arvo"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Tämä toimeksiantaja on jo %1 tälle työjonolle"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Tämä toimeksiantaja on jo %1 tälle työpyynnölle"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Tämä toimeksiantaja ei ole %1 tälle työjonolle"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Tämä toimeksiantaja ei ole %1 tälle työpyynnölle"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Tätä työjonoa ei ole olemassa"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Tämä työpyyntö sisältää ei-päätettyjä riippuvuuksia"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Käyttäjällä on jo tuo oikeus"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Käyttäjä omistaa jo tämän työpyynnön"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Käyttäjää ei ole olemassa"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Tämä käyttäjä on jo etuoikeutettu"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Tämä käyttäjä on jo ei-etuoikeutettu"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Tämä käyttäjä on nyt etuoikeutettu"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Tämä käyttäjä on nyt ei-etuoikeutettu"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Käyttäjä ei voi omistaa työpyyntöjä tuossa työjonossa"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Ei ole numero"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Perustiedot"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "Kommentti on tallennettu"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr ""
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Seuraavia komentoja ei suoritettu:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr ""
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Nämä kommentit eivät ole yleisesti näkyvillä käyttäjälle"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Tämä työpyyntö %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Tämä toiminto ei näytä sisältävän mitään"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "Tämän käyttäjän 25 korkeimman prioriteetin työpyyntöä"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "To"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Työpyyntö # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Työpyyntö #%1 Jumbo päivitys: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Työpyyntö %1 luotu työjonoon '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Työpyyntö %1 ladattu\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Työpyyntö %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Työpyynnön historia # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Työpyynnön numero"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Työpyynnön liite"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Työpyynnön sisältö"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Työpyynnön sisällön tyyppi"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Työpyyntö luotu"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Työpyynnön luonti epäonnistui"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Työpyyntö poistettu"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr ""
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr ""
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr ""
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Työpyynnön tarkkailijat"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr ""
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Työpyynnöt %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr ""
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Aikaa jäljellä"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Aikaa käytetty"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Aikaa jäljellä"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Aika"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Aikaa käytetty"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr ""
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Luodaksesi diffin tästä käskystä:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "To generate a diff of this commit:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Oltu yhteydessä"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Toiminto %1 puhdistettu"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Toiminto luotu"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Toiminnot ovat muuttumattomia"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Yritetään poistaa oikeus: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Ti"
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Tyyppi"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Toteuttamaton"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unix login"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Käyttäjän Unix-tunnus"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Tuntematon sisällön enkoodaus %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Rajoittamaton"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Ottamaton"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Päivitä"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Päivitä numero"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Päivitä tyyppi"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Päivitä kaikki nämä työpyynnöt kerralla"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Päivitä sähköposti"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Päivitä nimi"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Päivitystä ei tallennettu"
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Päivitä valitut työpyynnöt"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Päivitä allekirjoitus"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Päivitä työpyyntö"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Päivitä työpyyntö # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Päivitä työpyyntö #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "Päivityksen tyyppi ei ollut kirjeenvaihto eikä kommentti."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Päivitetty"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Käyttäjä %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Käyttäjä %1 Salasana: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Käyttäjää '%1' ei löydetty"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Käyttäjää '%1' ei löydetty\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "Käyttäjän tunnus"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Käyttäjän tunnus"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Käyttäjän oikeudet"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Käyttäjää ei voitu luoda: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Käyttäjä luotu"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Käyttäjän luomat ryhmät"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Käyttäjää informoitu"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Käyttäjän näkymä"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Käyttäjätunnus"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Käyttäjät"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Hakua vastaavat käyttäjät"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "Työpyynnon arvo"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Arvot"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Tarkkailijat"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "Web Enkoodaus"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Ke"
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr ""
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr ""
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr ""
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr ""
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr ""
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr ""
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr ""
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr ""
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr ""
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Työ"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Työpuhelin"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Tehty"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Omistat jo tämän työpyynnön"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Et ole autorisoitu käyttäjä"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Voit palauttaa vain työpyyntöjä jotka omistat itse tai jotka ovat ilman omistajaa"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Sinulla ei ole oikeutta tarkastella tätä työpyyntöä.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Löysit %1 työpyyntöä työjonosta %2"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Olet kirjautunut ulos RT:stä"
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "Sinulla ei ole oikeutta luoda työpyyntöjä tähän työjonoon."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "Et ehkä voi luoda työpyyntöjä tuohon työjonoon."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Tervetuloa kirjautumaan järjestelmään uudelleen"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Sinun %1 työpyyntöäsi"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "RT:n ylläpitäjä on konfiguroinut RT:n käynnisävät sähköpostialiakset väärin."
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr ""
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr ""
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr ""
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Käyttäjätunnuksesi tai salasanasi on väärä"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "Postinumero"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "sallittu käyttäjälle %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "sisältää"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "kirjeenvaihtoa (luultavasti) ei ole asetettu"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "kirjeenvaihto lähetetty"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "päivää"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr ""
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "poista"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "poistettu"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "ei täsmää"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "ei sisällä"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "on yhtäsuuri"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr ""
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "suurempi kuin"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "ryhmä %1"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "tunnit"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "numero"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "on"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "ei ole"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "vähemmän kuin"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "sisältää"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minuuttia"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "muokkaukset\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "kuukausia"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "uusi"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr ""
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "ei mitään"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "eri suuri kuin"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "avoin"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "oma ryhmä '%1' käyttäjälle '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "työjono %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "hylätty"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "päätetty"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sec"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "jäädytetty"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "systeemi %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "systeemiryhmä '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "kutsuva komponentti ei eritellyt syytä"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "työpyyntö #%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "kuvalematon ryhmä %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "käyttäjä %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "viikkoa"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "pohjalla %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "vuosia"
+
diff --git a/rt/lib/RT/I18N/fr.po b/rt/lib/RT/I18N/fr.po
new file mode 100644
index 0000000..4ef68fb
--- /dev/null
+++ b/rt/lib/RT/I18N/fr.po
@@ -0,0 +1,4959 @@
+# Copyright (c) 2002 Jesse Vincent <jesse@bestpractical.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 3.0.4pre1\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2003-07-03 02:00+0800\n"
+"Last-Translator: Blaise Thauvin <blaise@fdn.fr>\n"
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:27 html/Elements/MyTickets:27
+msgid "#"
+msgstr "n°"
+
+#: NOT FOUND IN SOURCE
+msgid "#%1"
+msgstr "n°%1"
+
+#: html/Approvals/Elements/Approve:26 html/Approvals/Elements/ShowDependency:49 html/SelfService/Display.html:24 html/Ticket/Display.html:25 html/Ticket/Display.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "n°%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3569 lib/RT/Transaction_Overlay.pm:557 lib/RT/Transaction_Overlay.pm:599
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 ajouté"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "il y a %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:3575 lib/RT/Transaction_Overlay.pm:564
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 changé en %3"
+
+#: lib/RT/Ticket_Overlay.pm:3572 lib/RT/Transaction_Overlay.pm:560 lib/RT/Transaction_Overlay.pm:605
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 supprimé"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 of group %3"
+msgstr "%1 %2 du groupe %3"
+
+#: html/Admin/Elements/EditScrips:43 html/Admin/Elements/ListGlobalScrips:27
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 avec modèle %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 ce ticket\\n"
+
+#: html/Search/Listing.html:56
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "Tickets %1 à %2"
+
+#: bin/rt-crontool:168 bin/rt-crontool:175 bin/rt-crontool:181
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Un paramètre à passer à %2"
+
+#: bin/rt-crontool:184
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Ecrit les mises à jour de statuts sur STDOUT"
+
+#: bin/rt-crontool:178
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Précisez l'action que vous voulez utiliser"
+
+#: bin/rt-crontool:172
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Précisez la condition que vous voulez utiliser"
+
+#: bin/rt-crontool:165
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Précisez la recherche que vous voulez utiliser"
+
+#: lib/RT/ScripAction_Overlay.pm:121
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1 ScripAction chargée"
+
+#: lib/RT/Ticket_Overlay.pm:3602
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 ajouté(e) comme valeur de %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "les alias %1 nécessitent un TicketId sur lequel travailler"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "les alias %1 nécessitent un TicketId sur lequel travailler "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "les alias %1 nécessitent un TicketId pour fonctionner avec (depuis %2) %3"
+
+#: lib/RT/Link_Overlay.pm:116 lib/RT/Link_Overlay.pm:123
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 semble être un objet local, mais est introuvable dans la base de données"
+
+#: html/Ticket/Elements/ShowDates:51 lib/RT/Transaction_Overlay.pm:481
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 par %2"
+
+#: lib/RT/Transaction_Overlay.pm:535 lib/RT/Transaction_Overlay.pm:675 lib/RT/Transaction_Overlay.pm:684 lib/RT/Transaction_Overlay.pm:687
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 changé(e) de %2 à %3"
+
+#: lib/RT/Interface/Web.pm:893
+msgid "%1 could not be set to %2."
+msgstr "%1 n'a pas pu être positionné à %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 n'a pas pu initialiser une transaction (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2867
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 ne peut pas mettre le statut à résolu. La base de données RT est peut être incohérente."
+
+#: html/Elements/MyTickets:24
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "Mes %1 tickets à traiter en priorité..."
+
+#: html/Elements/MyRequests:24
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "Mes %1 demandes les plus prioritaires..."
+
+#: bin/rt-crontool:160
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 est un outil agissant sur les tickets depuis un planificateur externe tel que cron"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 n'est plus un %2 pour cette queue."
+
+#: lib/RT/Ticket_Overlay.pm:1587
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 n'est plus un %2 pour ce ticket."
+
+#: lib/RT/Ticket_Overlay.pm:3658
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 n'est plus une valeur pour le champ personnalisé %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 n'est pas un identifiant de queue valide"
+
+#: html/Ticket/Elements/ShowBasics:35
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 non montré"
+
+#: html/User/Elements/DelegateRights:75
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "Droits de %1"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 réussi\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "Type %1 inconnu pour $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "type %1 inconnu pour %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 a été créé sans utilisateur courant\\n"
+
+#: lib/RT/Action/ResolveMembers.pm:41
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 résoudra tous les membres d'un ticket groupé résolu."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 va bloquer une base [locale] s'il dépend ou est membre d'une demande liée."
+
+#: lib/RT/Transaction_Overlay.pm:433
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: pas d'attachement spécifié"
+
+#: html/Ticket/Elements/ShowTransaction:88
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:85
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1176
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' est un statut invalide"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' n'est pas une action connue. "
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(Cocher la case pour supprimer un membre du groupe)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Cocher la case pour supprimer un scrip)"
+
+#: html/Admin/Elements/EditCustomFieldValues:24 html/Admin/Elements/EditQueueWatchers:28 html/Admin/Elements/EditScrips:34 html/Admin/Elements/EditTemplates:35 html/Admin/Groups/Members.html:51 html/Ticket/Elements/EditLinks:32 html/Ticket/Elements/EditPeople:45 html/User/Groups/Members.html:54
+msgid "(Check box to delete)"
+msgstr "(Cocher la case pour supprimer)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(Cocher la case pour supprimer)"
+
+#: html/Ticket/Create.html:177
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Entrer les numéros de tickets ou les URLs, séparés par des espaces)"
+
+#: html/Admin/Queues/Modify.html:53 html/Admin/Queues/Modify.html:59
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "Si laissé à blanc, valeur par défaut : %1"
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(Non renseigné)"
+
+#: html/Admin/Elements/EditCustomFields:32 html/Admin/Elements/ListGlobalCustomFields:31
+msgid "(No custom fields)"
+msgstr "Pas de champ personnalisé"
+
+#: html/Admin/Groups/Members.html:49 html/User/Groups/Members.html:52
+msgid "(No members)"
+msgstr "(Aucun membre)"
+
+#: html/Admin/Elements/EditScrips:31 html/Admin/Elements/ListGlobalScrips:31
+msgid "(No scrips)"
+msgstr "(Aucun Scrip)"
+
+#: html/Admin/Elements/EditTemplates:30
+msgid "(No templates)"
+msgstr "Aucun modèle"
+
+#: html/Ticket/Update.html:83
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envoie une copie cachée de cette mise à jour à une liste d'adresses email séparées par des virgules. Ceci <b>ne changera pas</b> les destinataires des mises à jour suivantes.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Envoie une copie cachée de cette mise à jour à une liste d'adresses email séparées par des virgules. Ceci <b>ne changera pas</b> les destinataires des mises à jour suivantes.)"
+
+#: html/Ticket/Create.html:78
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envoie une copie de cette mise à jour à une liste d'adresses email séparées par des virgules. Ces personnes <b>recevront</b> les mises à jour suivantes.)"
+
+#: html/Ticket/Update.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envoie une copie de cette mise à jour à une liste d'adresses email séparées par des virgules. Ceci <b>ne changera pas</b> les destinataires des mises à jour suivantes.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "Envoie une copie de cette mise à jour à une liste d'adresses email séparées par des virgules. Ceci <b>ne changera pas</b> les destinataires des mises à jour suivantes.)"
+
+#: html/Ticket/Create.html:68
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envoie une copie de cette mise à jour à une liste d'adresses email séparées par des virgules. Ces personnes <b>recevront</b> les mises à jour suivantes.)"
+
+#: html/Admin/Groups/index.html:32 html/User/Groups/index.html:32
+msgid "(empty)"
+msgstr "(vide)"
+
+#: html/Admin/Users/index.html:38
+msgid "(no name listed)"
+msgstr "(aucun nom)"
+
+#: html/Elements/MyRequests:42 html/Elements/MyTickets:44
+msgid "(no subject)"
+msgstr "(pas de sujet)"
+
+#: html/Admin/Elements/SelectRights:47 html/Elements/SelectCustomFieldValue:29 html/Ticket/Elements/EditCustomField:60 html/Ticket/Elements/ShowCustomFields:35 lib/RT/Transaction_Overlay.pm:534
+msgid "(no value)"
+msgstr "(non renseigné)"
+
+#: html/Ticket/Elements/BulkLinks:27 html/Ticket/Elements/EditLinks:115
+msgid "(only one ticket)"
+msgstr "(un seul ticket)"
+
+#: html/Elements/MyRequests:51 html/Elements/MyTickets:54
+msgid "(pending approval)"
+msgstr "(en attente d'approbation)"
+
+#: html/Elements/MyRequests:53 html/Elements/MyTickets:56
+msgid "(pending other tickets)"
+msgstr "(en attente d'autres tickets)"
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr "(groupe du demandeur)"
+
+#: html/Admin/Users/Modify.html:49
+msgid "(required)"
+msgstr "(exigé)"
+
+#: html/Ticket/Elements/ShowTransaction:91
+msgid "(untitled)"
+msgstr "(sans titre)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "Mes 25 tickets à traiter en priorité..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "Mes 25 demandes les plus prioritaires..."
+
+#: html/Ticket/Elements/ShowBasics:31
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Statut%>"
+
+#: html/Elements/SelectTicketTypes:26
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:25
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Créer un ticket dans\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Un modèle vide"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE Supprimé"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE Chargé"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "l'ACE n'a pu être supprimé"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "l'ACE n'a pu être trouvé"
+
+#: lib/RT/ACE_Overlay.pm:156 lib/RT/Principal_Overlay.pm:180
+msgid "ACE not found"
+msgstr "ACE non trouvé"
+
+#: lib/RT/ACE_Overlay.pm:830
+msgid "ACEs can only be created and deleted."
+msgstr "Les ACE peuvent seulement être créés et effacés."
+
+#: bin/rt-commit-handler:754
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Interruption pour éviter des modifications de ticket involontaires"
+
+#: html/User/Elements/Tabs:31
+msgid "About me"
+msgstr "A propos"
+
+#: html/Admin/Users/Modify.html:79
+msgid "Access control"
+msgstr "contrôle d'accès"
+
+#: html/Admin/Elements/EditScrip:56
+msgid "Action"
+msgstr "Action"
+
+#: lib/RT/Scrip_Overlay.pm:146
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Action %1 non trouvée"
+
+#: bin/rt-crontool:122
+msgid "Action committed."
+msgstr "Action validée"
+
+#: bin/rt-crontool:118
+msgid "Action prepared..."
+msgstr "Action préparée..."
+
+#: html/Search/Bulk.html:95
+msgid "Add AdminCc"
+msgstr "Ajouter AdminCC"
+
+#: html/Search/Bulk.html:91
+msgid "Add Cc"
+msgstr "Ajouter CC"
+
+#: html/Ticket/Create.html:113 html/Ticket/Update.html:98
+msgid "Add More Files"
+msgstr "Ajouter d'autres fichiers"
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr "Ajouter étape suivant"
+
+#: html/Search/Bulk.html:87
+msgid "Add Requestor"
+msgstr "Ajouter Demandeur"
+
+#: html/Admin/Elements/AddCustomFieldValue:24
+msgid "Add Value"
+msgstr "Ajouter une valeur"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "Ajouter une sélection de mots clé à cette queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Ajouter un nouveau scrip global"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Ajouter un scrip à cette queue"
+
+#: html/Admin/Global/Scrip.html:54
+msgid "Add a scrip which will apply to all queues"
+msgstr "Ajouter un scrip qui s'ajoute à toutes les queues"
+
+#: html/Search/Bulk.html:127
+msgid "Add comments or replies to selected tickets"
+msgstr "Ajouter des commentaires ou des réponses aux tickets sélectionnés"
+
+#: html/Admin/Groups/Members.html:41 html/User/Groups/Members.html:38
+msgid "Add members"
+msgstr "Ajouter des membres"
+
+#: html/Admin/Queues/People.html:65 html/Ticket/Elements/AddWatchers:27
+msgid "Add new watchers"
+msgstr "Ajouter de nouveaux observateurs"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "AjouterEtatSuivant"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Ajout groupe/utilisateur comme %1 pour cette queue"
+
+#: lib/RT/Ticket_Overlay.pm:1471
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Ajout groupe/utilisateur comme %1 pour ce ticket"
+
+#: html/Admin/Elements/ModifyUser:75 html/Admin/Users/Modify.html:121 html/User/Prefs.html:87
+msgid "Address1"
+msgstr "Adresse1"
+
+#: html/Admin/Elements/ModifyUser:77 html/Admin/Users/Modify.html:126 html/User/Prefs.html:89
+msgid "Address2"
+msgstr "Adresse2"
+
+#: html/Ticket/Create.html:73
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr "Commentaire Admin"
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr "Correspondance Admin "
+
+#: html/Admin/Queues/index.html:24 html/Admin/Queues/index.html:27
+msgid "Admin queues"
+msgstr "Administrateurs de queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Gérer les Utilisateurs"
+
+#: html/Admin/Global/index.html:25 html/Admin/Global/index.html:27
+msgid "Admin/Global configuration"
+msgstr "configuration Gestion/Globale"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Gestion/Groupes"
+
+#: html/Admin/Queues/Modify.html:24 html/Admin/Queues/Modify.html:28
+msgid "Admin/Queue/Basics"
+msgstr "Gestion/Queues/Essentiel"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "GérerTousGroupesPersonnels"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:38 html/Ticket/Update.html:49 lib/RT/ACE_Overlay.pm:88
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "CommentaireAdministrateur"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "CorrespondanceAdministrateur"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "AdminCustomFields"
+msgstr "GérerChampsPersonnalisés"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "AdminGroup"
+msgstr "GérerGroupes"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "AdminGroupMembership"
+msgstr "GérerAppartenanceGroupes"
+
+#: lib/RT/System.pm:58
+msgid "AdminOwnPersonalGroups"
+msgstr "GérerGroupesPersonnelsPropres"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "AdminQueue"
+msgstr "GérerQueues"
+
+#: lib/RT/System.pm:59
+msgid "AdminUsers"
+msgstr "GérerUtilisateurs"
+
+#: html/Admin/Queues/People.html:47 html/Ticket/Elements/EditPeople:53
+msgid "Administrative Cc"
+msgstr "Cc Administratif"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr "Administrateurs"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Recherche avancée"
+
+#: html/Elements/SelectDateRelation:35
+msgid "After"
+msgstr "Après"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Age"
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr "Alias pour"
+
+#: etc/initialdata:348
+msgid "All Approvals Passed"
+msgstr "Toutes les approbations obtenues"
+
+#: html/Admin/Elements/EditCustomFields:95
+msgid "All Custom Fields"
+msgstr "Tous les champs personnalisés"
+
+#: html/Admin/Queues/index.html:52
+msgid "All Queues"
+msgstr "Toutes les queues"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Envoie toujours un message au demandeur indépendamment de l'expéditeur"
+
+#: html/Elements/Tabs:55
+msgid "Approval"
+msgstr "Approbation"
+
+#: html/Approvals/Display.html:45 html/Approvals/Elements/ShowDependency:41 html/Approvals/index.html:64
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Approbation n°%1: %2"
+
+#: html/Approvals/index.html:53
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Approbation n°%1: Notes non enregistrées en raison d'une erreur système"
+
+#: html/Approvals/index.html:51
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Approbation n°%1: Notes non enregistrées"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Détails de l'approbation"
+
+#: etc/initialdata:336
+msgid "Approval Passed"
+msgstr "Approbations obtenues"
+
+#: etc/initialdata:359
+msgid "Approval Rejected"
+msgstr "Approbations refusées"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Diagramme d'approbation"
+
+#: html/Approvals/Elements/Approve:43
+msgid "Approve"
+msgstr "Approuver"
+
+#: etc/initialdata:486 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Notes de l'approbateur: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Avr."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "Avril"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Ascending"
+msgstr "Croissant"
+
+#: html/Search/Bulk.html:136 html/SelfService/Update.html:32 html/Ticket/ModifyAll.html:82 html/Ticket/Update.html:98
+msgid "Attach"
+msgstr "Attaché"
+
+#: html/SelfService/Create.html:64 html/Ticket/Create.html:109
+msgid "Attach file"
+msgstr "Attacher un ficher"
+
+#: html/Ticket/Create.html:97 html/Ticket/Update.html:87
+msgid "Attached file"
+msgstr "Fichier attaché"
+
+#: NOT FOUND IN SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr "Attachement '%1' ne peut pas être chargé"
+
+#: lib/RT/Transaction_Overlay.pm:441
+msgid "Attachment created"
+msgstr "Attachement créé"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Nom de fichier de l'attachement"
+
+#: html/Ticket/Elements/ShowAttachments:25
+msgid "Attachments"
+msgstr "Attachements"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Aoû."
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "Août"
+
+#: html/Admin/Elements/ModifyUser:65
+msgid "AuthSystem"
+msgstr "AuthSystem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "RéponseAuto"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Réponse automatique aux demandeurs"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "RéponseAutomtiqueAuxDemandeurs"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Signature PGP invalide: %1\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Id d'attachement erroné. Impossible de trouver l'attachement '%1'\\n"
+
+#: bin/rt-commit-handler:826
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Données incorrectes dans %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Numéro de transaction incorrect pour l'attachement. %1 doit être %2\\n"
+
+#: html/Admin/Elements/GroupTabs:38 html/Admin/Elements/QueueTabs:38 html/Admin/Elements/UserTabs:37 html/Ticket/Elements/Tabs:89 html/User/Elements/GroupTabs:37
+msgid "Basics"
+msgstr "Essentiel"
+
+#: html/Ticket/Update.html:81
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:87 html/Admin/Global/GroupRights.html:84 html/Admin/Global/Template.html:45 html/Admin/Global/UserRights.html:53 html/Admin/Groups/GroupRights.html:72 html/Admin/Groups/Members.html:80 html/Admin/Groups/Modify.html:55 html/Admin/Groups/UserRights.html:54 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:44 html/Admin/Queues/UserRights.html:53 html/User/Groups/Modify.html:55
+msgid "Be sure to save your changes"
+msgstr "Assurez-vous de sauvegarder vos modifications"
+
+#: html/Elements/SelectDateRelation:33 lib/RT/CurrentUser.pm:321
+msgid "Before"
+msgstr "Avant"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "Débuter l'approbation"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Vide"
+
+#: html/Search/Listing.html:78
+msgid "Bookmarkable URL for this search"
+msgstr "URL prédéfinie pour cette recherche"
+
+#: html/Ticket/Elements/ShowHistory:38 html/Ticket/Elements/ShowHistory:44
+msgid "Brief headers"
+msgstr "En-têtes courts"
+
+#: html/Search/Bulk.html:24 html/Search/Bulk.html:25
+msgid "Bulk ticket update"
+msgstr "modification de tickets en masse"
+
+#: lib/RT/User_Overlay.pm:1524
+msgid "Can not modify system users"
+msgstr "Les utilisateurs système ne peuvent être modifiés"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "Can this principal see this queue"
+msgstr "Le groupe/utilisateur peut-il voir cette queue"
+
+#: lib/RT/CustomField_Overlay.pm:205
+msgid "Can't add a custom field value without a name"
+msgstr "Impossible d'ajouter une valeur de champ personnalisé sans un nom"
+
+#: lib/RT/Link_Overlay.pm:131
+msgid "Can't link a ticket to itself"
+msgstr "Un ticket ne peut être lié à lui même"
+
+#: lib/RT/Ticket_Overlay.pm:2844
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Impossible de fusionner un ticket à un ticket fusionné. Vous ne devriez jamais obtenir cette erreur"
+
+#: lib/RT/Ticket_Overlay.pm:2646 lib/RT/Ticket_Overlay.pm:2725
+msgid "Can't specifiy both base and target"
+msgstr "Impossible de spécifier à la fois la base et la cible"
+
+#: html/autohandler:113
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Impossible de créer l'utilisateur: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:43 html/SelfService/Create.html:48 html/Ticket/Create.html:63 html/Ticket/Elements/EditPeople:50 html/Ticket/Elements/ShowPeople:34 html/Ticket/Update.html:44 html/Ticket/Update.html:76 lib/RT/ACE_Overlay.pm:87
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:30
+msgid "Change password"
+msgstr "Changer le mot de passe"
+
+#: html/Ticket/Create.html:100 html/Ticket/Update.html:90
+msgid "Check box to delete"
+msgstr "Cocher la case pour supprimer"
+
+#: html/Admin/Elements/SelectRights:30
+msgid "Check box to revoke right"
+msgstr "Cocher la case pour retirer le droit"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/BulkLinks:42 html/Ticket/Elements/EditLinks:130 html/Ticket/Elements/EditLinks:68 html/Ticket/Elements/ShowLinks:56
+msgid "Children"
+msgstr "Fils"
+
+#: html/Admin/Elements/ModifyUser:79 html/Admin/Users/Modify.html:131 html/User/Prefs.html:91
+msgid "City"
+msgstr "Ville"
+
+#: html/Ticket/Elements/ShowDates:46
+msgid "Closed"
+msgstr "Fermé"
+
+#: html/SelfService/Closed.html:24
+msgid "Closed Tickets"
+msgstr "Tickets fermés"
+
+#: NOT FOUND IN SOURCE
+msgid "Closed requests"
+msgstr "Demandes closes"
+
+#: html/SelfService/Elements/Tabs:44
+msgid "Closed tickets"
+msgstr "Tickets fermés"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Commande incomprise! \\n"
+
+#: html/Ticket/Elements/ShowTransaction:165 html/Ticket/Elements/Tabs:152
+msgid "Comment"
+msgstr "Commenter"
+
+#: html/Admin/Elements/ModifyQueue:44 html/Admin/Queues/Modify.html:57
+msgid "Comment Address"
+msgstr "Adresse de commentaire"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Commentaire non enregistré"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Comment on tickets"
+msgstr "Commentaire sur le ticket"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "CommentOnTicket"
+msgstr "CommenterTicket"
+
+#: html/Admin/Elements/ModifyUser:34
+msgid "Comments"
+msgstr "Commentaires"
+
+#: html/Ticket/ModifyAll.html:69 html/Ticket/Update.html:68
+msgid "Comments (Not sent to requestors)"
+msgstr "Commentaires (non envoyés aux demandeurs)"
+
+#: html/Search/Bulk.html:131
+msgid "Comments (not sent to requestors)"
+msgstr "Commentaires (non envoyés aux demandeurs)"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Comments about %1"
+msgstr "Commentaires sur %1"
+
+#: html/Admin/Users/Modify.html:184 html/Ticket/Elements/ShowRequestor:43
+msgid "Comments about this user"
+msgstr "Commentaires sur cet utilisateur"
+
+#: lib/RT/Transaction_Overlay.pm:543
+msgid "Comments added"
+msgstr "Commentaires ajoutés"
+
+#: lib/RT/Action/Generic.pm:139
+msgid "Commit Stubbed"
+msgstr "tr(Commit Stubbed)"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Restrictions de compilation"
+
+#: html/Admin/Elements/EditScrip:40
+msgid "Condition"
+msgstr "Condition"
+
+#: bin/rt-crontool:108
+msgid "Condition matches..."
+msgstr "La condition satisfait..."
+
+#: lib/RT/Scrip_Overlay.pm:159
+msgid "Condition not found"
+msgstr "Condition non trouvée"
+
+#: html/Elements/Tabs:49
+msgid "Configuration"
+msgstr "Configuration"
+
+#: html/SelfService/Prefs.html:32
+msgid "Confirm"
+msgstr "Confirmer"
+
+#: html/Admin/Elements/ModifyUser:59
+msgid "ContactInfoSystem"
+msgstr "ContactInfoSystem"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Date de contact ne peut pas être analysée"
+
+#: html/Admin/Elements/ModifyTemplate:43 html/Ticket/ModifyAll.html:86
+msgid "Content"
+msgstr "Contenu"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "Le groupe n'a pas pu être créé"
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr "Courrier"
+
+#: html/Admin/Elements/ModifyQueue:38 html/Admin/Queues/Modify.html:50
+msgid "Correspondence Address"
+msgstr "Adresse de correspondance"
+
+#: lib/RT/Transaction_Overlay.pm:539
+msgid "Correspondence added"
+msgstr "Courrier ajouté"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Courrier non enregistré"
+
+#: lib/RT/Ticket_Overlay.pm:3589
+msgid "Could not add new custom field value for ticket. "
+msgstr "Impossible d'ajouter une nouvelle valeur de champ personnalisé pour ce ticket. "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "La valeur de champ personnalisé n'a pas pu être ajoutée. %1"
+
+#: lib/RT/Ticket_Overlay.pm:3095 lib/RT/Ticket_Overlay.pm:3103 lib/RT/Ticket_Overlay.pm:3120
+msgid "Could not change owner. "
+msgstr "Impossible de changer l'intervenant. "
+
+#: html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditCustomFields:165
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Impossible de créer un champ personnalisé CustomField"
+
+#: html/User/Groups/Modify.html:76 lib/RT/Group_Overlay.pm:473 lib/RT/Group_Overlay.pm:480
+msgid "Could not create group"
+msgstr "Impossible de créer un groupe"
+
+#: html/Admin/Global/Template.html:74 html/Admin/Queues/Template.html:71
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Impossible de créer un modèle : %1"
+
+#: lib/RT/Ticket_Overlay.pm:1109 lib/RT/Ticket_Overlay.pm:352
+msgid "Could not create ticket. Queue not set"
+msgstr "Impossible de créer un ticket. Queue non indiquée"
+
+#: lib/RT/User_Overlay.pm:267 lib/RT/User_Overlay.pm:279 lib/RT/User_Overlay.pm:297 lib/RT/User_Overlay.pm:483
+msgid "Could not create user"
+msgstr "Impossible de créer un utilisateur"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "L'observateur n'a pas pu être crée pour le demandeur"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Impossible de trouver le ticket numéro %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Impossible de trouver le groupe %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1439
+msgid "Could not find or create that user"
+msgstr "Impossible de trouver ou créer cet utilisateur"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1518
+msgid "Could not find that principal"
+msgstr "Impossible de trouver ce groupe ou utilisateur"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Impossible de trouver l'utilisateur %1."
+
+#: html/Admin/Groups/Members.html:87 html/User/Groups/Members.html:89 html/User/Groups/Modify.html:81
+msgid "Could not load group"
+msgstr "Impossible de charger ce groupe"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Impossible de faire de ce groupe/utilisateur un %1 pour cette queue"
+
+#: lib/RT/Ticket_Overlay.pm:1460
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Impossible de faire de ce groupe/utilisateur un %1 pour ce ticket"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Impossible de supprimer ce groupe/utilisateur comme un %1 pour cette queue"
+
+#: lib/RT/Ticket_Overlay.pm:1576
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Impossible de supprimer ce groupe/utilisateur comme un %1 pour ce ticket"
+
+#: lib/RT/Group_Overlay.pm:984
+msgid "Couldn't add member to group"
+msgstr "Impossible d'ajouter un membre à ce groupe"
+
+#: lib/RT/Ticket_Overlay.pm:3599 lib/RT/Ticket_Overlay.pm:3655
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Impossible de créer une transaction : %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Impossible de comprendre ce qu'il faut faire avec cette réponse gpg\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Groupe introuvable\\n"
+
+#: lib/RT/Interface/Web.pm:902
+msgid "Couldn't find row"
+msgstr "Colonne introuvable"
+
+#: lib/RT/Group_Overlay.pm:958
+msgid "Couldn't find that principal"
+msgstr "groupe/utilisateur introuvable"
+
+#: lib/RT/CustomField_Overlay.pm:239
+msgid "Couldn't find that value"
+msgstr "Valeur introuvable"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "L'observateur n'a pas pu être trouvé"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Utilisateur introuvable\\n"
+
+#: lib/RT/CurrentUser.pm:111
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Impossible de charger %1 depuis la base de données des utilisateurs.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "KeywordSelects n'a pas pu être chargé"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Impossible de charger le fichier de configuration RT '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Les scrips n'ont pas pu être chargés"
+
+#: html/Admin/Groups/GroupRights.html:87 html/Admin/Groups/UserRights.html:74
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Impossible de charger le groupe %1"
+
+#: lib/RT/Link_Overlay.pm:174 lib/RT/Link_Overlay.pm:183 lib/RT/Link_Overlay.pm:210
+msgid "Couldn't load link"
+msgstr "Impossible de charger le lien"
+
+#: html/Admin/Elements/EditCustomFields:146 html/Admin/Queues/People.html:120
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Impossible de charger la queue"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:71
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Impossible de charger la queue %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Impossible de charger le Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Impossible de charger le modèle"
+
+#: html/Admin/Users/Prefs.html:78
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Impossible de charger cet utilisateur (%1)"
+
+#: html/SelfService/Display.html:108
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Impossible de charger le ticket '%1'"
+
+#: html/Admin/Elements/ModifyUser:85 html/Admin/Users/Modify.html:148 html/User/Prefs.html:97
+msgid "Country"
+msgstr "Pays"
+
+#: html/Admin/Elements/CreateUserCalled:25 html/Ticket/Create.html:134 html/Ticket/Create.html:194
+msgid "Create"
+msgstr "Ajouter"
+
+#: etc/initialdata:127
+msgid "Create Tickets"
+msgstr "Ajouter des tickets"
+
+#: html/Admin/Elements/EditCustomField:74
+msgid "Create a CustomField"
+msgstr "Ajouter un Champ Personnalisé"
+
+#: html/Admin/Queues/CustomField.html:47
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Ajouter un champ personnalisé à la queue %1"
+
+#: html/Admin/Global/CustomField.html:47
+msgid "Create a CustomField which applies to all queues"
+msgstr "Ajouter un champ personnalisé à toutes les queues"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Ajouter un nouveaux champ personnalisé"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Ajouter un nouveau scrip global"
+
+#: html/Admin/Groups/Modify.html:66 html/Admin/Groups/Modify.html:92
+msgid "Create a new group"
+msgstr "Ajouter un nouveau groupe"
+
+#: html/User/Groups/Modify.html:66 html/User/Groups/Modify.html:91
+msgid "Create a new personal group"
+msgstr "Ajouter un nouveau groupe personnel"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Ajouter une nouvelle queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Ajouter un nouveau scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Ajouter un nouveau modèle"
+
+#: html/Ticket/Create.html:24 html/Ticket/Create.html:27 html/Ticket/Create.html:35
+msgid "Create a new ticket"
+msgstr "Ajouter un nouveau ticket"
+
+#: html/Admin/Users/Modify.html:213 html/Admin/Users/Modify.html:240
+msgid "Create a new user"
+msgstr "Ajouter un nouvel utilisateur"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Ajouter une queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Ajouter une nouvelle queue appelée"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a request"
+msgstr "Ajouter une demande"
+
+#: html/Admin/Queues/Scrip.html:58
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Ajouter un scrip pour la queue %1"
+
+#: html/Admin/Global/Template.html:68 html/Admin/Queues/Template.html:64
+msgid "Create a template"
+msgstr "Ajouter un modèle"
+
+#: html/SelfService/Create.html:24
+msgid "Create a ticket"
+msgstr "Ajouter un ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Echec à la création de: %1 / %2 / %3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "Echec à la création de: %1/%2/%3"
+
+#: etc/initialdata:129
+msgid "Create new tickets based on this scrip's template"
+msgstr "Ajouter de nouveaux tickets basés sur le modèle de ce scrip"
+
+#: html/SelfService/Create.html:77
+msgid "Create ticket"
+msgstr "Ajouter un ticket"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Create tickets in this queue"
+msgstr "Ajouter des tickets dans cette queue"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Create, delete and modify custom fields"
+msgstr "Ajouter, supprimer et modifier des champs personnalisés"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Create, delete and modify queues"
+msgstr "Ajouter, supprimer et modifier les queues"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Ajouter, supprime et modifie les membres des groupe spersonnels de n'importe quel utilisateur"
+
+#: lib/RT/System.pm:58
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Ajouter, supprimer et modifier les membres d'un groupe personnel"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify users"
+msgstr "Ajouter, supprimer et modifier les utilisateurs"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "CreateTicket"
+msgstr "CréerTicket"
+
+#: html/Elements/SelectDateType:25 html/Ticket/Elements/ShowDates:26 lib/RT/Ticket_Overlay.pm:1203
+msgid "Created"
+msgstr "Créé"
+
+#: html/Admin/Elements/EditCustomField:87
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Champ Personnalisé %1 ajouté"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Modèle %1 créé"
+
+#: html/Ticket/Elements/EditLinks:27
+msgid "Current Relationships"
+msgstr "Relations actuelles"
+
+#: html/Admin/Elements/EditScrips:29
+msgid "Current Scrips"
+msgstr "Scrips actuels"
+
+#: html/Admin/Groups/Members.html:38 html/User/Groups/Members.html:41
+msgid "Current members"
+msgstr "Membres actuels"
+
+#: html/Admin/Elements/SelectRights:28
+msgid "Current rights"
+msgstr "Droits actuels"
+
+#: html/Search/Listing.html:70
+msgid "Current search criteria"
+msgstr "Critères de recherche courants"
+
+#: html/Admin/Queues/People.html:40 html/Ticket/Elements/EditPeople:44
+msgid "Current watchers"
+msgstr "Observateurs actuels"
+
+#: html/Admin/Global/CustomField.html:54
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Champ personnalisé n°%1"
+
+#: html/Admin/Elements/QueueTabs:52 html/Admin/Elements/SystemTabs:39 html/Admin/Global/index.html:49 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Champs Personnalisés"
+
+#: html/Admin/Elements/EditScrip:72
+msgid "Custom action cleanup code"
+msgstr "Programme de nettoyage d'action personnalisé"
+
+#: html/Admin/Elements/EditScrip:64
+msgid "Custom action preparation code"
+msgstr "Programme de préparation d'action personnalisé "
+
+#: html/Admin/Elements/EditScrip:48
+msgid "Custom condition"
+msgstr "Condition personnalisée"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Champs personnalisés %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Le champ personnalisé %1 a une valeur"
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Le champ personnalisé %1 n'a pas de valeur"
+
+#: lib/RT/Ticket_Overlay.pm:3491
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Le champ personnalisé %1 est introuvable"
+
+#: html/Admin/Elements/EditCustomFields:196
+msgid "Custom field deleted"
+msgstr "Champ personnalisé supprimé"
+
+#: lib/RT/Ticket_Overlay.pm:3641
+msgid "Custom field not found"
+msgstr "Le champ personnalisé est introuvable"
+
+#: lib/RT/CustomField_Overlay.pm:349
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "La valeur du champ personnalisé %1 ne peut pas être trouvée pour le champ personnalisé %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Valeur de champ personnalisé modifié de %1 à %2"
+
+#: lib/RT/CustomField_Overlay.pm:249
+msgid "Custom field value could not be deleted"
+msgstr "La valeur du champ personnalisé ne peut pas être effacée"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Custom field value could not be found"
+msgstr "La valeur du champ personnalisé ne peut par être trouvée"
+
+#: lib/RT/CustomField_Overlay.pm:247 lib/RT/CustomField_Overlay.pm:357
+msgid "Custom field value deleted"
+msgstr "La valeur du champ personnalisé est effacée"
+
+#: lib/RT/Transaction_Overlay.pm:548
+msgid "CustomField"
+msgstr "ChampPersonnalisé"
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "Erreur de données"
+
+#: html/SelfService/Display.html:38 html/Ticket/Create.html:160 html/Ticket/Elements/ShowSummary:54 html/Ticket/Elements/Tabs:92 html/Ticket/ModifyAll.html:43
+msgid "Dates"
+msgstr "Dates"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Déc."
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "Décembre"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Modèle de réponse automatique par défaut"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Modèle de réponse automatique par défaut"
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr "Modèle de commentaire administrateur par défaut"
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr "Modèle de courrier administrateur par défaut"
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr "Modèle de courrier par défaut"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Modèle de transaction par défaut"
+
+#: lib/RT/Transaction_Overlay.pm:694
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Par défaut: %1/%2 modifié de %3 à %4"
+
+#: html/User/Delegation.html:24 html/User/Delegation.html:27
+msgid "Delegate rights"
+msgstr "Déléguer les droits"
+
+#: lib/RT/System.pm:62
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Déléguer des droits spécifiques qui vous ont été accordés"
+
+#: lib/RT/System.pm:62
+msgid "DelegateRights"
+msgstr "DéléguerDroits"
+
+#: html/User/Elements/Tabs:37
+msgid "Delegation"
+msgstr "Délégation"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Supprimer"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Delete tickets"
+msgstr "Supprimer des tickets"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "DeleteTicket"
+msgstr "SupprimerTicket"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Effacer cet objet pourrait briser l'intégrité référentielle"
+
+#: lib/RT/Queue_Overlay.pm:293
+msgid "Deleting this object would break referential integrity"
+msgstr "Effacer cet objet briserait l'intégrité référentielle"
+
+#: lib/RT/User_Overlay.pm:499
+msgid "Deleting this object would violate referential integrity"
+msgstr "Effacer cet objet violerait l'intégrité référentielle"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Effacer cet objet violerait l'intégrité référentielle"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "Effacer cet objet violerait l'intégrité référentielle, c'est serait facheux!"
+
+#: html/Approvals/Elements/Approve:44
+msgid "Deny"
+msgstr "Refuser"
+
+#: html/Ticket/Create.html:180 html/Ticket/Elements/BulkLinks:34 html/Ticket/Elements/EditLinks:122 html/Ticket/Elements/EditLinks:46 html/Ticket/Elements/ShowDependencies:31 html/Ticket/Elements/ShowLinks:36
+msgid "Depended on by"
+msgstr "En dépend"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Dépendances : \\n"
+
+#: lib/RT/Transaction_Overlay.pm:626
+#. ($value)
+msgid "Dependency by %1 added"
+msgstr "Ajout de la dépendance par %1"
+
+#: lib/RT/Transaction_Overlay.pm:655
+#. ($value)
+msgid "Dependency by %1 deleted"
+msgstr "Suppression de la dépendance par %1"
+
+#: lib/RT/Transaction_Overlay.pm:624
+#. ($value)
+msgid "Dependency on %1 added"
+msgstr "Ajout de la dépendance de %1"
+
+#: lib/RT/Transaction_Overlay.pm:653
+#. ($value)
+msgid "Dependency on %1 deleted"
+msgstr "Suppression de la dépendance de %1"
+
+#: html/Elements/SelectLinkType:26 html/Ticket/Create.html:179 html/Ticket/Elements/BulkLinks:30 html/Ticket/Elements/EditLinks:118 html/Ticket/Elements/EditLinks:35 html/Ticket/Elements/ShowDependencies:24 html/Ticket/Elements/ShowLinks:26
+msgid "Depends on"
+msgstr "Dépend de"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "DépendDe"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Descending"
+msgstr "Décroissant"
+
+#: html/SelfService/Create.html:72 html/Ticket/Create.html:118
+msgid "Describe the issue below"
+msgstr "Décrivez la situation ci-dessous"
+
+#: html/Admin/Elements/AddCustomFieldValue:35 html/Admin/Elements/EditCustomField:38 html/Admin/Elements/EditScrip:33 html/Admin/Elements/ModifyQueue:35 html/Admin/Elements/ModifyTemplate:35 html/Admin/Groups/Modify.html:48 html/Admin/Queues/Modify.html:47 html/Elements/SelectGroups:26 html/User/Groups/Modify.html:48
+msgid "Description"
+msgstr "Description"
+
+#: NOT FOUND IN SOURCE
+msgid "Details"
+msgstr "Détails"
+
+#: html/Ticket/Elements/Tabs:84
+msgid "Display"
+msgstr "Afficher"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Display Access Control List"
+msgstr "Afficher la liste des droits"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "Display Scrip templates for this queue"
+msgstr "Afficher les modèles de Scrips pour cette queue"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "Display Scrips for this queue"
+msgstr "Afficher les Scrips pour cette queue"
+
+#: html/Ticket/Elements/ShowHistory:34
+msgid "Display mode"
+msgstr "Mode d'affichage"
+
+#: NOT FOUND IN SOURCE
+msgid "Display ticket #%1"
+msgstr "Afficher le ticket n°%1"
+
+#: lib/RT/System.pm:53
+msgid "Do anything and everything"
+msgstr "Faire tout et n'importe quoi"
+
+#: html/Elements/Refresh:29
+msgid "Don't refresh this page."
+msgstr "Ne pas rafraîchir cette page."
+
+#: html/Search/Elements/PickRestriction:113
+msgid "Don't show search results"
+msgstr "Ne pas afficher le résultat de la recherche"
+
+#: html/Ticket/Elements/ShowTransaction:91
+msgid "Download"
+msgstr "Télécharger"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:44 html/Ticket/Elements/ShowDates:42 lib/RT/Ticket_Overlay.pm:1207
+msgid "Due"
+msgstr "Echéance"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Date d'échéance '%1' n'est pas comprise"
+
+#: bin/rt-commit-handler:753
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ERREUR: impossible de charger le ticket '%1' : %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Modifier"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr "Modifier les conditions"
+
+#: html/Admin/Queues/CustomFields.html:44
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Editer les champs personnalisés pour %1"
+
+#: html/Search/Bulk.html:143 html/Ticket/ModifyLinks.html:35
+msgid "Edit Relationships"
+msgstr "Modifier les relations"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Modifier les modèles pour la queue %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "Modifier les mots clé"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Modifier les scrips"
+
+#: html/Admin/Global/index.html:45
+msgid "Edit system templates"
+msgstr "Modifier les modèles système"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Modifier les modèles pour %1"
+
+#: html/Admin/Elements/ModifyQueue:24 html/Admin/Queues/Modify.html:118
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Modifier la configuration de la queue %1"
+
+#: html/Admin/Elements/ModifyUser:24
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Modifier la configuration de l'utilisateur %1"
+
+#: html/Admin/Elements/EditCustomField:90
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Modifier le ChampPersonnalisé %1"
+
+#: html/Admin/Groups/Members.html:31
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Modifier les membres du groupe %1"
+
+#: html/User/Groups/Members.html:128
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Modifier les membres du groupe personnel %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Modifie le modèle %1"
+
+#: lib/RT/Ticket_Overlay.pm:2660 lib/RT/Ticket_Overlay.pm:2738
+msgid "Either base or target must be specified"
+msgstr "La base ou la cible doivent être spécifiées"
+
+#: html/Admin/Users/Modify.html:52 html/Admin/Users/Prefs.html:45 html/Elements/SelectUsers:26 html/Ticket/Elements/AddWatchers:55 html/User/Prefs.html:41
+msgid "Email"
+msgstr "Email"
+
+#: lib/RT/User_Overlay.pm:247
+msgid "Email address in use"
+msgstr "Adresse email utilisée"
+
+#: html/Admin/Elements/ModifyUser:41
+msgid "EmailAddress"
+msgstr "EmailAddress"
+
+#: html/Admin/Elements/ModifyUser:53
+msgid "EmailEncoding"
+msgstr "EmailEncoding"
+
+#: html/Admin/Elements/EditCustomField:50
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Activé (Décocher cette case désactive ce champ personnalisé)"
+
+#: html/Admin/Groups/Modify.html:52 html/User/Groups/Modify.html:52
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Activé (Décocher cette case désactive ce groupe)"
+
+#: html/Admin/Queues/Modify.html:83
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Activé (Décocher cette case désactive cette queue)"
+
+#: html/Admin/Elements/EditCustomFields:98
+msgid "Enabled Custom Fields"
+msgstr "Champs personnalisés actifs"
+
+#: html/Admin/Queues/index.html:55
+msgid "Enabled Queues"
+msgstr "Queues actives"
+
+#: html/Admin/Elements/EditCustomField:106 html/Admin/Groups/Modify.html:116 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:282 html/User/Groups/Modify.html:116
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Statut actif %1"
+
+#: lib/RT/CustomField_Overlay.pm:427
+msgid "Enter multiple values"
+msgstr "Entrer plusieurs valeurs"
+
+#: lib/RT/CustomField_Overlay.pm:424
+msgid "Enter one value"
+msgstr "Entrer une seule valeur"
+
+#: html/Search/Bulk.html:144 html/Ticket/Elements/EditLinks:111
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Saisir les tickets ou URIs pour y lier les tickets. Séparer les saisies par des espaces."
+
+#: html/Elements/Login:39 html/SelfService/Error.html:24 html/SelfService/Error.html:25
+msgid "Error"
+msgstr "Erreur"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "Erreur à l'ajout de l'observateur"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Erreur de paramètres pour Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Erreur de paramètres pour Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1392
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Erreur de paramètres pour Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1549
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Erreur de paramètres pour Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Tout le monde"
+
+#: bin/rt-crontool:193
+msgid "Example:"
+msgstr "Exemple:"
+
+#: html/Admin/Elements/ModifyUser:63
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:57
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:72
+msgid "Extra info"
+msgstr "Info supplémentaire"
+
+#: lib/RT/User_Overlay.pm:363
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Recherche du pseudo groupe d'utilisateurs 'Priviligiés' infructueuse"
+
+#: lib/RT/User_Overlay.pm:370
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Recherche du pseudo groupe d'utilisateurs 'non-privilégiés' infructueuse"
+
+#: bin/rt-crontool:137
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Echec de chargement du module %1. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Fév."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "Février"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Fin"
+
+#: html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:58 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Priorité finale"
+
+#: lib/RT/Ticket_Overlay.pm:1198
+msgid "FinalPriority"
+msgstr "PrioritéFinale"
+
+#: html/Admin/Queues/People.html:60 html/Ticket/Elements/EditPeople:33
+msgid "Find group whose"
+msgstr "Trouver un groupe dont"
+
+#: NOT FOUND IN SOURCE
+msgid "Find new/open tickets"
+msgstr "Accéder aux tickets en cours"
+
+#: html/Admin/Queues/People.html:56 html/Admin/Users/index.html:45 html/Ticket/Elements/EditPeople:29
+msgid "Find people whose"
+msgstr "Trouver les gens dont"
+
+#: html/Search/Listing.html:107
+msgid "Find tickets"
+msgstr "Rechercher des tickets"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "Terminer l'approbation"
+
+#: html/Ticket/Elements/Tabs:57
+msgid "First"
+msgstr "Premier"
+
+#: html/Search/Listing.html:40
+msgid "First page"
+msgstr "Première page"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:86
+msgid "Force change"
+msgstr "Forcer la modification"
+
+#: html/Search/Listing.html:105
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "%quant(%1,ticket) trouvés"
+
+#: lib/RT/Interface/Web.pm:904
+msgid "Found Object"
+msgstr "Objet trouvé"
+
+#: html/Admin/Elements/ModifyUser:43
+msgid "FreeformContactInfo"
+msgstr "SaisieLibreInfoContact"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformMultiple"
+msgstr "SaisieLibreMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "FreeformSingle"
+msgstr "SaisieLibreSimple"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Ven."
+
+#: html/Ticket/Elements/ShowHistory:40 html/Ticket/Elements/ShowHistory:50
+msgid "Full headers"
+msgstr "En-têtes complets"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Obtention de l'utilisateur courant depuis une signature pgp\\n"
+
+#: lib/RT/Transaction_Overlay.pm:593
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Donné à %1"
+
+#: html/Admin/Elements/Tabs:40 html/Admin/index.html:37
+msgid "Global"
+msgstr "Global"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "Mots clé globaux"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Scrips globaux"
+
+#: html/Admin/Elements/SelectTemplate:37
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Modèle global: %1"
+
+#: html/Admin/Elements/EditCustomFields:74 html/Admin/Queues/People.html:58 html/Admin/Queues/People.html:62 html/Admin/Queues/index.html:43 html/Admin/Users/index.html:48 html/Ticket/Elements/EditPeople:31 html/Ticket/Elements/EditPeople:35 html/index.html:40
+msgid "Go!"
+msgstr "Go!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Signature pgp valide pour %1\\n"
+
+#: html/Search/Listing.html:49
+msgid "Goto page"
+msgstr "Aller à la page"
+
+#: html/Elements/GotoTicket:24 html/SelfService/Elements/GotoTicket:24
+msgid "Goto ticket"
+msgstr "Aller au ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr "Accorder"
+
+#: html/Ticket/Elements/AddWatchers:45 html/User/Elements/DelegateRights:77
+msgid "Group"
+msgstr "Groupe"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Groupe %1 %2 : %3"
+
+#: html/Admin/Elements/GroupTabs:44 html/Admin/Elements/QueueTabs:56 html/Admin/Elements/SystemTabs:43 html/Admin/Global/index.html:54
+msgid "Group Rights"
+msgstr "Droits de groupe"
+
+#: lib/RT/Group_Overlay.pm:964
+msgid "Group already has member"
+msgstr "Le groupe a déjà un membre"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "Le groupe n'a pas pu être créé"
+
+#: html/Admin/Groups/Modify.html:76
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Le groupe %1 n'a pu être créé"
+
+#: lib/RT/Group_Overlay.pm:496
+msgid "Group created"
+msgstr "Groupe ajouté"
+
+#: lib/RT/Group_Overlay.pm:1132
+msgid "Group has no such member"
+msgstr "Un tel membre n'appartient pas au groupe"
+
+#: lib/RT/Group_Overlay.pm:944 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1446 lib/RT/Ticket_Overlay.pm:1524
+msgid "Group not found"
+msgstr "Groupe introuvable"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Groupe introuvable.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Groupe non spécifié.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:34 html/Admin/Elements/Tabs:34 html/Admin/Groups/Members.html:63 html/Admin/Queues/People.html:82 html/Admin/index.html:31 html/User/Groups/Members.html:66
+msgid "Groups"
+msgstr "Groupes"
+
+#: lib/RT/Group_Overlay.pm:970
+msgid "Groups can't be members of their members"
+msgstr "Les groupes ne peuvent pas être membres de leurs membres"
+
+#: lib/RT/Interface/CLI.pm:72 lib/RT/Interface/CLI.pm:72
+msgid "Hello!"
+msgstr "Bonjour!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Bonjour, %1"
+
+#: html/Ticket/Elements/ShowHistory:29 html/Ticket/Elements/Tabs:87
+msgid "History"
+msgstr "Historique"
+
+#: html/Admin/Elements/ModifyUser:67
+msgid "HomePhone"
+msgstr "Téléphone domicile"
+
+#: html/Elements/Tabs:43
+msgid "Homepage"
+msgstr "Page d'accueil"
+
+#: lib/RT/Base.pm:73
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "J'ai %quant (%1, toupie à béton)"
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "J'ai %quant (%1, toupie à béton)"
+
+#: html/Ticket/Elements/ShowBasics:26 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Identifiant"
+
+#: html/Admin/Users/Modify.html:43 html/User/Prefs.html:38
+msgid "Identity"
+msgstr "Identité"
+
+#: etc/initialdata:411 etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Si une approbation est refusée, rejette l'original et supprime les approbations en attente"
+
+#: bin/rt-crontool:189
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Si cet outil était setgid, un utilisateur local mal intentionné pourrait l'utiliser pour obtenir un access administrateur à RT"
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyPeople.html:37
+msgid "If you've updated anything above, be sure to"
+msgstr "Si vous avez fait une modification, assurez vous de"
+
+#: lib/RT/Interface/Web.pm:896
+msgid "Illegal value for %1"
+msgstr "Valeur incorrecte pour %1"
+
+#: lib/RT/Interface/Web.pm:899
+msgid "Immutable field"
+msgstr "Champ non modifiable"
+
+#: html/Admin/Elements/EditCustomFields:73
+msgid "Include disabled custom fields in listing."
+msgstr "Inclure les champs personnalisés désactivés dans la liste"
+
+#: html/Admin/Queues/index.html:42
+msgid "Include disabled queues in listing."
+msgstr "Afficher les queues inactives."
+
+#: html/Admin/Users/index.html:46
+msgid "Include disabled users in search."
+msgstr "Inclure les utilisateurs désactivés dans le résultat"
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Priorité initiale"
+
+#: lib/RT/Ticket_Overlay.pm:1197 lib/RT/Ticket_Overlay.pm:1199
+msgid "InitialPriority"
+msgstr "PrioritéInitiale"
+
+#: lib/RT/ScripAction_Overlay.pm:104
+msgid "Input error"
+msgstr "Erreur à l'entrée"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "Votre intéret est noté"
+
+#: lib/RT/Ticket_Overlay.pm:3866
+msgid "Internal Error"
+msgstr "Erreur interne"
+
+#: lib/RT/Record.pm:142
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Erreur interne: %1"
+
+#: lib/RT/Group_Overlay.pm:643
+msgid "Invalid Group Type"
+msgstr "Type de groupe invalide"
+
+#: lib/RT/Principal_Overlay.pm:127
+msgid "Invalid Right"
+msgstr "Droit invalide"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "Type invalide"
+
+#: lib/RT/Interface/Web.pm:901
+msgid "Invalid data"
+msgstr "Données invalides"
+
+#: lib/RT/Ticket_Overlay.pm:457
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Intervenant invalide, affectation à 'personne'"
+
+#: lib/RT/Scrip_Overlay.pm:133 lib/RT/Template_Overlay.pm:250
+msgid "Invalid queue"
+msgstr "Queue invalide"
+
+#: lib/RT/ACE_Overlay.pm:243 lib/RT/ACE_Overlay.pm:252 lib/RT/ACE_Overlay.pm:258 lib/RT/ACE_Overlay.pm:269 lib/RT/ACE_Overlay.pm:274
+msgid "Invalid right"
+msgstr "Droit invalide"
+
+#: lib/RT/Record.pm:117
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Queue invalide pour %1"
+
+#: lib/RT/Ticket_Overlay.pm:3498
+msgid "Invalid value for custom field"
+msgstr "Valeur incorrecte pour le champ personnalisé"
+
+#: lib/RT/Ticket_Overlay.pm:364
+msgid "Invalid value for status"
+msgstr "Valeur de statut invalide"
+
+#: bin/rt-crontool:190
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Il est extrêmement important que les utilisateurs non authorisés n'aient pas accès à cet outil"
+
+#: bin/rt-crontool:191
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Il est suggéré de créer un utilisateur unix non privilégié appartenant au bon groupe et ayant accès à RT pour utiliser cet outil"
+
+#: bin/rt-crontool:162
+msgid "It takes several arguments:"
+msgstr "Il faut plusieurs paramètres:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Eléments attendant mon approbation"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Jan."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "Janvier"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Join or leave this group"
+msgstr "Rejoignez ou quittez ce groupe"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "Juillet"
+
+#: html/Ticket/Elements/Tabs:98
+msgid "Jumbo"
+msgstr "Tout"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "Juin"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Mot Clé"
+
+#: html/Admin/Elements/ModifyUser:51
+msgid "Lang"
+msgstr "Lang"
+
+#: html/Ticket/Elements/Tabs:72
+msgid "Last"
+msgstr "Dernier"
+
+#: html/Ticket/Elements/EditDates:37 html/Ticket/Elements/ShowDates:38
+msgid "Last Contact"
+msgstr "Dernier contact"
+
+#: html/Elements/SelectDateType:28
+msgid "Last Contacted"
+msgstr "Date dernier contact"
+
+#: html/Search/Elements/TicketHeader:40
+msgid "Last Notified"
+msgstr "Dernière notification"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Updated"
+msgstr "Date dernière MAJ"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "DernièreMAJ"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Restant"
+
+#: html/Admin/Users/Modify.html:82
+msgid "Let this user access RT"
+msgstr "Donner accès à RT à cet utilisateur"
+
+#: html/Admin/Users/Modify.html:86
+msgid "Let this user be granted rights"
+msgstr "Autoriser cet utilisateur à recevoir des droits"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Limitation des intervenants à %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Limitation de la queue à %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2752
+msgid "Link already exists"
+msgstr "Le lien existe déja"
+
+#: lib/RT/Ticket_Overlay.pm:2764
+msgid "Link could not be created"
+msgstr "Le lien ne peut être ajouté"
+
+#: lib/RT/Ticket_Overlay.pm:2772 lib/RT/Ticket_Overlay.pm:2784
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Le lien est ajouté (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2685
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Le lien est effacé (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2691
+msgid "Link not found"
+msgstr "Lien introuvable"
+
+#: html/Ticket/ModifyLinks.html:24 html/Ticket/ModifyLinks.html:28
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Lier le ticket n°%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "Lier au ticket %1"
+
+#: html/Ticket/Elements/Tabs:96
+msgid "Links"
+msgstr "Relations"
+
+#: html/Admin/Users/Modify.html:113 html/User/Prefs.html:84
+msgid "Location"
+msgstr "Localisation"
+
+#: lib/RT.pm:162
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Le répertoire de log %1 est introuvable ou en lecture seule. \\n RT ne peut pas démarrer"
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Connecté en tant que %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:35 html/Elements/Login:44 html/Elements/Login:54
+msgid "Login"
+msgstr "Connexion"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Déconnexion"
+
+#: html/Search/Bulk.html:85
+msgid "Make Owner"
+msgstr "Attribuer"
+
+#: html/Search/Bulk.html:109
+msgid "Make Status"
+msgstr "Appliquer Statut"
+
+#: html/Search/Bulk.html:117
+msgid "Make date Due"
+msgstr "Appliquer date d'échéance"
+
+#: html/Search/Bulk.html:119
+msgid "Make date Resolved"
+msgstr "Appliquer date de résolution"
+
+#: html/Search/Bulk.html:113
+msgid "Make date Started"
+msgstr "Appliquer date de début"
+
+#: html/Search/Bulk.html:111
+msgid "Make date Starts"
+msgstr "Appliquer date d'ouverture"
+
+#: html/Search/Bulk.html:115
+msgid "Make date Told"
+msgstr "Appliquer Age"
+
+#: html/Search/Bulk.html:105
+msgid "Make priority"
+msgstr "Appliquer priorité"
+
+#: html/Search/Bulk.html:107
+msgid "Make queue"
+msgstr "Appliquer queue"
+
+#: html/Search/Bulk.html:103
+msgid "Make subject"
+msgstr "Changer le sujet"
+
+#: html/Admin/index.html:32
+msgid "Manage groups and group membership"
+msgstr "Gérer les groupes et leurs membres"
+
+#: html/Admin/index.html:38
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Gérer les propriétés et configurations générales des queues"
+
+#: html/Admin/index.html:35
+msgid "Manage queues and queue-specific properties"
+msgstr "Gérer les queues et leurs propriétés individuelles"
+
+#: html/Admin/index.html:29
+msgid "Manage users and passwords"
+msgstr "Gérer les utilisateurs et mots de passe"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mar."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "Mars"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "Mai"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Mai."
+
+#: lib/RT/Transaction_Overlay.pm:635
+#. ($value)
+msgid "Member %1 added"
+msgstr "Membre %1 ajouté"
+
+#: lib/RT/Transaction_Overlay.pm:664
+#. ($value)
+msgid "Member %1 deleted"
+msgstr "Membre %1 supprimé"
+
+#: lib/RT/Group_Overlay.pm:981
+msgid "Member added"
+msgstr "Membre ajouté"
+
+#: lib/RT/Group_Overlay.pm:1139
+msgid "Member deleted"
+msgstr "Membre supprimé"
+
+#: lib/RT/Group_Overlay.pm:1143
+msgid "Member not deleted"
+msgstr "Membre non supprimé"
+
+#: html/Elements/SelectLinkType:25
+msgid "Member of"
+msgstr "Membre de"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "MembreDe"
+
+#: html/Admin/Elements/GroupTabs:41 html/User/Elements/GroupTabs:41
+msgid "Members"
+msgstr "Membres"
+
+#: lib/RT/Transaction_Overlay.pm:633
+#. ($value)
+msgid "Membership in %1 added"
+msgstr "Appartenance à %1 ajoutée"
+
+#: lib/RT/Transaction_Overlay.pm:662
+#. ($value)
+msgid "Membership in %1 deleted"
+msgstr "Appartenance à %1 supprimée"
+
+#: lib/RT/Ticket_Overlay.pm:2941
+msgid "Merge Successful"
+msgstr "Fusion réussie"
+
+#: lib/RT/Ticket_Overlay.pm:2861
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Echec de fusion. Ne peut appliquer EffectiveId"
+
+#: html/Ticket/Elements/BulkLinks:26 html/Ticket/Elements/EditLinks:114
+msgid "Merge into"
+msgstr "Fusionner dans"
+
+#: html/Search/Bulk.html:137 html/Ticket/Update.html:100
+msgid "Message"
+msgstr "Message"
+
+#: lib/RT/Interface/Web.pm:903
+msgid "Missing a primary key?: %1"
+msgstr "Clé primaire manquante? : %1"
+
+#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:53
+msgid "Mobile"
+msgstr "Mobile"
+
+#: html/Admin/Elements/ModifyUser:71
+msgid "MobilePhone"
+msgstr "MobilePhone"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Modify Access Control List"
+msgstr "Modifier la liste de droits"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Modifier champ personnalisé %1"
+
+#: html/Admin/Global/CustomFields.html:43 html/Admin/Global/index.html:50
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Modifier les champs personnalisés globaux"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Modify Scrip templates for this queue"
+msgstr "Modifier les modèles de Scrips pour cette queue"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Modify Scrips for this queue"
+msgstr "Modifier les Scrips pour cette queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "Modifier ACLs système"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Modifier le modèle %1"
+
+#: html/Admin/Queues/CustomField.html:44
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Modifier un champ personnalisé pour la queue %1"
+
+#: html/Admin/Global/CustomField.html:52
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Modifier un champ personnalisé global"
+
+#: html/Admin/Queues/Scrip.html:53
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Modifier le scrip pour la queue %1"
+
+#: html/Admin/Global/Scrip.html:47
+msgid "Modify a scrip which applies to all queues"
+msgstr "Modiier le scrip qui s'applique à toutes les queues"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "Modifier les dates pur n°%1"
+
+#: html/Ticket/ModifyDates.html:24 html/Ticket/ModifyDates.html:28
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Modifier les dates pour n°%1"
+
+#: html/Ticket/ModifyDates.html:34
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Modifier les dates du ticket n°%1"
+
+#: html/Admin/Global/GroupRights.html:24 html/Admin/Global/GroupRights.html:27 html/Admin/Global/index.html:55
+msgid "Modify global group rights"
+msgstr "Modifier les droits de groupe globaux"
+
+#: html/Admin/Global/GroupRights.html:32
+msgid "Modify global group rights."
+msgstr "Modifier les droits de groupe globaux"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "Modifier les droits globaux des groupes"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "Modifier les droits globaux des utilisateurs"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Modifier les scrips globaux"
+
+#: html/Admin/Global/UserRights.html:24 html/Admin/Global/UserRights.html:27 html/Admin/Global/index.html:59
+msgid "Modify global user rights"
+msgstr "Modifier les droits utilisateurs globaux"
+
+#: html/Admin/Global/UserRights.html:32
+msgid "Modify global user rights."
+msgstr "Modifier les droits utilisateurs globaux"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "Modify group metadata or delete group"
+msgstr "Modifier les métadonnées ou supprimer le groupe"
+
+#: html/Admin/Groups/GroupRights.html:24 html/Admin/Groups/GroupRights.html:28 html/Admin/Groups/GroupRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Modifier les droits du groupe %1"
+
+#: html/Admin/Queues/GroupRights.html:24 html/Admin/Queues/GroupRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Modifier les droits de groupe pour la queue %1"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "Modify membership roster for this group"
+msgstr "Modifier le membership roster pour ce groupe"
+
+#: lib/RT/System.pm:60
+msgid "Modify one's own RT account"
+msgstr "Modifier son propre profile RT"
+
+#: html/Admin/Queues/People.html:24 html/Admin/Queues/People.html:28
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Modifier les utilisateurs de la queue %1"
+
+#: html/Ticket/ModifyPeople.html:24 html/Ticket/ModifyPeople.html:28 html/Ticket/ModifyPeople.html:34
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Modifier les utilisateurs du ticket n°%1"
+
+#: html/Admin/Queues/Scrips.html:45
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Modifier les scrips de la queue %1"
+
+#: html/Admin/Global/Scrips.html:43 html/Admin/Global/index.html:41
+msgid "Modify scrips which apply to all queues"
+msgstr "Modifier les scrips s'appliquant à toutes les queues"
+
+#: html/Admin/Global/Template.html:24 html/Admin/Global/Template.html:29 html/Admin/Global/Template.html:80 html/Admin/Queues/Template.html:77
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Modifier le modèle %1"
+
+#: html/Admin/Global/Templates.html:43
+msgid "Modify templates which apply to all queues"
+msgstr "Modifier les modèles globaux"
+
+#: html/Admin/Groups/Modify.html:86 html/User/Groups/Modify.html:85
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Modifier le groupe %1"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify the queue watchers"
+msgstr "Modifier les observateurs de la queue"
+
+#: html/Admin/Users/Modify.html:235
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Modifier l'utilisateur %1"
+
+#: html/Ticket/ModifyAll.html:36
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Modifier le ticket # %1"
+
+#: html/Ticket/Modify.html:24 html/Ticket/Modify.html:27 html/Ticket/Modify.html:33
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Modifier le ticket n°%1"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Modify tickets"
+msgstr "Modifier les tickets"
+
+#: html/Admin/Groups/UserRights.html:24 html/Admin/Groups/UserRights.html:28 html/Admin/Groups/UserRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Modifier les droits utilisateurs pour le groupe %1"
+
+#: html/Admin/Queues/UserRights.html:24 html/Admin/Queues/UserRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Modifier les droits utilisateurs pour la queue %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Modifier les observateurs dela queue '%1'"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ModifyACL"
+msgstr "ModifierACL"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "ModifyOwnMembership"
+msgstr "ModifierPropresAppartenances"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyQueueWatchers"
+msgstr "ModifierObservateurs"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ModifyScrips"
+msgstr "ModifierScrips"
+
+#: lib/RT/System.pm:60
+msgid "ModifySelf"
+msgstr "ModifierDonnéesPerso"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "ModifyTemplate"
+msgstr "ModifierModèle"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "ModifyTicket"
+msgstr "ModifierTicket"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Lun."
+
+#: html/Ticket/Elements/ShowRequestor:41
+#. ($name)
+msgid "More about %1"
+msgstr "Plus d'info sur %1"
+
+#: html/Admin/Elements/EditCustomFields:60
+msgid "Move down"
+msgstr "Aller en bas"
+
+#: html/Admin/Elements/EditCustomFields:52
+msgid "Move up"
+msgstr "Aller en haut"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Multiple"
+msgstr "Multiple"
+
+#: lib/RT/User_Overlay.pm:238
+msgid "Must specify 'Name' attribute"
+msgstr "Attribut 'Nom' obligatoire"
+
+#: html/SelfService/Elements/MyRequests:48
+#. ($friendly_status)
+msgid "My %1 tickets"
+msgstr "Mes %1 tickets"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Mes approbations"
+
+#: html/Approvals/index.html:24 html/Approvals/index.html:25
+msgid "My approvals"
+msgstr "Mes approbations"
+
+#: html/Admin/Elements/AddCustomFieldValue:31 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/ModifyTemplate:27 html/Admin/Elements/ModifyUser:29 html/Admin/Groups/Modify.html:43 html/Elements/SelectGroups:25 html/Elements/SelectUsers:27 html/User/Groups/Modify.html:43
+msgid "Name"
+msgstr "Nom"
+
+#: lib/RT/User_Overlay.pm:245
+msgid "Name in use"
+msgstr "Nom utilisé"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Approbation de l'administrateur système nécessaire"
+
+#: html/Ticket/Elements/ShowDates:51
+msgid "Never"
+msgstr "Jamais"
+
+#: html/Elements/Quicksearch:29
+msgid "New"
+msgstr "Nouveau"
+
+#: html/Admin/Elements/ModifyUser:31 html/Admin/Users/Modify.html:92 html/User/Prefs.html:64
+msgid "New Password"
+msgstr "Nouveau mot de passe"
+
+#: etc/initialdata:317 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Nouvelles approbations en attente"
+
+#: html/Ticket/Elements/EditLinks:110
+msgid "New Relationships"
+msgstr "Nouvelles relations"
+
+#: html/Ticket/Elements/Tabs:35
+msgid "New Search"
+msgstr "Nouvelle recherche"
+
+#: html/Admin/Global/CustomField.html:40 html/Admin/Global/CustomFields.html:38 html/Admin/Queues/CustomField.html:51 html/Admin/Queues/CustomFields.html:39
+msgid "New custom field"
+msgstr "Nouveau champ personnalisé"
+
+#: html/Admin/Elements/GroupTabs:53 html/User/Elements/GroupTabs:51
+msgid "New group"
+msgstr "Nouveau groupe"
+
+#: html/SelfService/Prefs.html:31
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: lib/RT/User_Overlay.pm:764
+msgid "New password notification sent"
+msgstr "Notification de nouveau mot de passe envoyée"
+
+#: html/Admin/Elements/QueueTabs:69
+msgid "New queue"
+msgstr "Nouvelle queue"
+
+#: NOT FOUND IN SOURCE
+msgid "New request"
+msgstr "Nouvelle demande"
+
+#: html/Admin/Elements/SelectRights:41
+msgid "New rights"
+msgstr "Nouveaux droits"
+
+#: html/Admin/Global/Scrip.html:39 html/Admin/Global/Scrips.html:38 html/Admin/Queues/Scrip.html:42 html/Admin/Queues/Scrips.html:54
+msgid "New scrip"
+msgstr "Nouveau scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nouvelle recherche"
+
+#: html/Admin/Global/Template.html:59 html/Admin/Global/Templates.html:38 html/Admin/Queues/Template.html:57 html/Admin/Queues/Templates.html:49
+msgid "New template"
+msgstr "Nouveau modèle"
+
+#: html/SelfService/Elements/Tabs:47
+msgid "New ticket"
+msgstr "Nouveau ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2828
+msgid "New ticket doesn't exist"
+msgstr "Nouveau ticket inconnu"
+
+#: html/Admin/Elements/UserTabs:51
+msgid "New user"
+msgstr "Nouvel utilisateur"
+
+#: html/Admin/Elements/CreateUserCalled:25
+msgid "New user called"
+msgstr "Nouvel utilisateur appelé"
+
+#: html/Admin/Queues/People.html:54 html/Ticket/Elements/EditPeople:28
+msgid "New watchers"
+msgstr "Nouveaux observateurs"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "New window setting"
+msgstr "Nouveaux paramètres d'affichage"
+
+#: html/Ticket/Elements/Tabs:68
+msgid "Next"
+msgstr "Suivant"
+
+#: html/Search/Listing.html:47
+msgid "Next page"
+msgstr "Page suivante"
+
+#: html/Admin/Elements/ModifyUser:49
+msgid "NickName"
+msgstr "Surnom"
+
+#: html/Admin/Users/Modify.html:62 html/User/Prefs.html:45
+msgid "Nickname"
+msgstr "Surnom"
+
+#: html/Admin/Elements/EditCustomField:89 html/Admin/Elements/EditCustomFields:104
+msgid "No CustomField"
+msgstr "Pas de CustomField"
+
+#: html/Admin/Groups/GroupRights.html:83 html/Admin/Groups/UserRights.html:70
+msgid "No Group defined"
+msgstr "Aucun groupe défini"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:67
+msgid "No Queue defined"
+msgstr "Aucune queue définie"
+
+#: bin/rt-crontool:55
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Aucun utilisateur RT trouvé. Merci de consulter votre administrateur RT"
+
+#: html/Admin/Global/Template.html:78 html/Admin/Queues/Template.html:75
+msgid "No Template"
+msgstr "Pas de modèle"
+
+#: bin/rt-commit-handler:763
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Aucun ticket spécifié. Annulation de ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Aucun ticket spécifié. Annulation des modifications de tickets\\n\\n"
+
+#: html/Approvals/Elements/Approve:45
+msgid "No action"
+msgstr "Pas d'action"
+
+#: lib/RT/Interface/Web.pm:898
+msgid "No column specified"
+msgstr "Aucune colonne spécifiée"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Commande introuvable\\n"
+
+#: html/Elements/ViewUser:35 html/Ticket/Elements/ShowRequestor:44
+msgid "No comment entered about this user"
+msgstr "Pas de commentaires concernant cet utilisateur"
+
+#: lib/RT/Ticket_Overlay.pm:2220 lib/RT/Ticket_Overlay.pm:2288
+msgid "No correspondence attached"
+msgstr "Pas de texte dans le courrier"
+
+#: lib/RT/Action/Generic.pm:149 lib/RT/Condition/Generic.pm:175 lib/RT/Search/ActiveTicketsInQueue.pm:55 lib/RT/Search/Generic.pm:112
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Aucune description disponible pour %1"
+
+#: lib/RT/Users_Overlay.pm:150
+msgid "No group specified"
+msgstr "Aucun groupe spécifié"
+
+#: lib/RT/User_Overlay.pm:982
+msgid "No password set"
+msgstr "Pas de mot de passe configuré"
+
+#: lib/RT/Queue_Overlay.pm:260
+msgid "No permission to create queues"
+msgstr "Permission refusée pour la création de queue"
+
+#: lib/RT/Ticket_Overlay.pm:360
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Vous n'êtes pas autorisé à créer un ticket dans cette queue '%1'"
+
+#: lib/RT/User_Overlay.pm:211
+msgid "No permission to create users"
+msgstr "Permission refusée pour la création d'utilisateurs"
+
+#: html/SelfService/Display.html:117
+msgid "No permission to display that ticket"
+msgstr "Pas de permission pour afficher ce ticket"
+
+#: html/SelfService/Update.html:51
+msgid "No permission to view update ticket"
+msgstr "Pas de permission pour afficher le ticket mis à jour"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1505
+msgid "No principal specified"
+msgstr "Aucun groupe/utilisateur spécifié"
+
+#: html/Admin/Queues/People.html:153 html/Admin/Queues/People.html:163
+msgid "No principals selected."
+msgstr "Aucun groupe/utilisateur sélectionné"
+
+#: html/Admin/Queues/index.html:34
+msgid "No queues matching search criteria found."
+msgstr "Pas de queue correspondant aux critères de recherche"
+
+#: html/Admin/Elements/SelectRights:80
+msgid "No rights found"
+msgstr "Aucun droit trouvé"
+
+#: html/Admin/Elements/SelectRights:32
+msgid "No rights granted."
+msgstr "Aucun droit accordé"
+
+#: html/Search/Bulk.html:160
+msgid "No search to operate on."
+msgstr "Pas de critère de recherche."
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Aucun numéro de ticket spécifié."
+
+#: lib/RT/Transaction_Overlay.pm:478 lib/RT/Transaction_Overlay.pm:516
+msgid "No transaction type specified"
+msgstr "Aucun type de transaction spécifié."
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "Aucun utilisateur ou adresse email spécifié"
+
+#: html/Admin/Users/index.html:35
+msgid "No users matching search criteria found."
+msgstr "Aucun utilisateur ne correspond aux critères de recherche."
+
+#: bin/rt-commit-handler:643
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Aucun utilisateur RT valide trouvé. Gestionnaire de cvs RT inaccessible. Merci de contacter votre administrateur RT.\\n"
+
+#: lib/RT/Interface/Web.pm:895
+msgid "No value sent to _Set!\\n"
+msgstr "Aucune valeur envoyée à _Set!\\n"
+
+#: html/Search/Elements/TicketRow:36
+msgid "Nobody"
+msgstr "Personne"
+
+#: lib/RT/Interface/Web.pm:900
+msgid "Nonexistant field?"
+msgstr "Champ inexistant?"
+
+#: NOT FOUND IN SOURCE
+msgid "Not logged in"
+msgstr "Non loggé"
+
+#: html/Elements/Header:59
+msgid "Not logged in."
+msgstr "Non connecté"
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Non renseigné"
+
+#: html/NoAuth/Reminder.html:26
+msgid "Not yet implemented."
+msgstr "Fonction pas encore disponible"
+
+#: NOT FOUND IN SOURCE
+msgid "Not yet implemented...."
+msgstr "Fonction pas encore disponible..."
+
+#: html/Approvals/Elements/Approve:48
+msgid "Notes"
+msgstr "Notes"
+
+#: lib/RT/User_Overlay.pm:767
+msgid "Notification could not be sent"
+msgstr "Impossible d'envoyer la notification"
+
+#: etc/initialdata:93
+msgid "Notify AdminCcs"
+msgstr "Avertir les AdminCCs"
+
+#: etc/initialdata:89
+msgid "Notify AdminCcs as Comment"
+msgstr "Avertir les AdminCCs par un commentaire"
+
+#: etc/initialdata:120
+msgid "Notify Other Recipients"
+msgstr "Avertir les autres destinataires"
+
+#: etc/initialdata:116
+msgid "Notify Other Recipients as Comment"
+msgstr "Avertir les autres destinataires par un commentaire"
+
+#: etc/initialdata:85
+msgid "Notify Owner"
+msgstr "Avertir l'intervenant"
+
+#: etc/initialdata:81
+msgid "Notify Owner as Comment"
+msgstr "Avertir l'intervenant par un commentaire"
+
+#: etc/initialdata:361
+msgid "Notify Owner of their rejected ticket"
+msgstr "Avertir l'Intervenant du rejet de son ticket"
+
+#: etc/initialdata:350
+msgid "Notify Owner of their ticket has been approved by all approvers"
+msgstr "Avertir l'Intervenant de l'approbation de son ticket par tous les approbateurs"
+
+#: etc/initialdata:338
+msgid "Notify Owner of their ticket has been approved by some approver"
+msgstr "Avertir l'Intervenant de l'approbation de son ticket par un des approbateurs"
+
+#: etc/initialdata:319 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Avertir les intervenants et les AdminCCs de nouveaux éléments attendant leur approbation"
+
+#: etc/initialdata:77
+msgid "Notify Requestors"
+msgstr "Avertir les demandeurs"
+
+#: etc/initialdata:103
+msgid "Notify Requestors and Ccs"
+msgstr "Avertir les demandeurs et les Ccs"
+
+#: etc/initialdata:98
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Avertir les demandeurs et les CC par un commentaire"
+
+#: etc/initialdata:112
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Avertir les demandeurs, CCs et AdminCCs"
+
+#: etc/initialdata:108
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Avertir les demandeurs, CCs et AdminCCs par un commentaire"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "Novembre"
+
+#: lib/RT/Record.pm:156
+msgid "Object could not be created"
+msgstr "L'objet n'a pas pu être ajouté"
+
+#: lib/RT/Record.pm:175
+msgid "Object created"
+msgstr "Objet ajouté"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Oct."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "Octobre"
+
+#: html/Elements/SelectDateRelation:34
+msgid "On"
+msgstr "Le"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Lors d'un commentaire"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Lors d'un courrier"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Lors d'une création"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Lors d'un changement d'intervenant"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Lors d'un changement de queue"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Lors de la résolution/clôture"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Lors d'un changement de statut"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Lors d'une transaction"
+
+#: html/Approvals/Elements/PendingMyApproval:49
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Ne montrer que les approbations pour les demandes créées après %1"
+
+#: html/Approvals/Elements/PendingMyApproval:47
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Ne montrer que les approbations pour les demandes créées avant %1"
+
+#: html/Elements/Quicksearch:30
+msgid "Open"
+msgstr "Ouvert"
+
+#: html/Ticket/Elements/Tabs:135
+msgid "Open it"
+msgstr "Ouvrir"
+
+#: NOT FOUND IN SOURCE
+msgid "Open requests"
+msgstr "Ouvrir les demandes"
+
+#: html/SelfService/Elements/Tabs:41
+msgid "Open tickets"
+msgstr "Ouvrir les tickets"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in a new window"
+msgstr "Ouvrir les tickets (depuis une liste) dans une nouvelle fenêtre."
+
+#: html/Admin/Users/Prefs.html:39
+msgid "Open tickets (from listing) in another window"
+msgstr "Ouvrir les tickets (depuis une liste) dans une autre fenêtre."
+
+#: etc/initialdata:132
+msgid "Open tickets on correspondence"
+msgstr "Ouvrir les tickets lors d'une correspondance"
+
+#: html/Search/Elements/PickRestriction:100
+msgid "Ordering and sorting"
+msgstr "Ranger et classer"
+
+#: html/Admin/Elements/ModifyUser:45 html/Admin/Users/Modify.html:116 html/Elements/SelectUsers:28 html/User/Prefs.html:85
+msgid "Organization"
+msgstr "Organisation"
+
+#: html/Approvals/Elements/Approve:32
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Ticket source: n°%1"
+
+#: html/Admin/Elements/ModifyQueue:54 html/Admin/Queues/Modify.html:68
+msgid "Over time, priority moves toward"
+msgstr "Temps dépassé, priorité déplacée"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Own tickets"
+msgstr "Tickets propres"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "OwnTicket"
+msgstr "PrendreTicket"
+
+#: etc/initialdata:38 html/Elements/MyRequests:31 html/SelfService/Elements/MyRequests:29 html/Ticket/Create.html:47 html/Ticket/Elements/EditPeople:42 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/ShowPeople:26 html/Ticket/Update.html:62 lib/RT/ACE_Overlay.pm:85 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Intervenant"
+
+#: NOT FOUND IN SOURCE
+msgid "Owner changed from %1 to %2"
+msgstr "Intervenant changé de %1 en %2"
+
+#: lib/RT/Transaction_Overlay.pm:582
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Intervenant forcé de %1 à %2"
+
+#: html/Search/Elements/PickRestriction:30
+msgid "Owner is"
+msgstr "L'intervenant est"
+
+#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:55
+msgid "Pager"
+msgstr "Bipeur"
+
+#: html/Admin/Elements/ModifyUser:73
+msgid "PagerPhone"
+msgstr "PagerPhone"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/BulkLinks:38 html/Ticket/Elements/EditLinks:126 html/Ticket/Elements/EditLinks:57 html/Ticket/Elements/ShowLinks:46
+msgid "Parents"
+msgstr "Parents"
+
+#: html/Elements/Login:52 html/User/Prefs.html:60
+msgid "Password"
+msgstr "Mot de passe"
+
+#: html/NoAuth/Reminder.html:24
+msgid "Password Reminder"
+msgstr "Pense-bête pour votre mot de passe"
+
+#: lib/RT/User_Overlay.pm:228 lib/RT/User_Overlay.pm:985
+msgid "Password too short"
+msgstr "Mot de passe trop court"
+
+#: html/Admin/Users/Modify.html:290 html/User/Prefs.html:171
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Mot de passe: %1"
+
+#: html/Admin/Users/Modify.html:292
+msgid "Passwords do not match."
+msgstr "Les mots de passes sont différents"
+
+#: html/User/Prefs.html:173
+msgid "Passwords do not match. Your password has not been changed"
+msgstr "Les mots de passe sont différents. Votre mot de passe n'a pas été modifié"
+
+#: html/Ticket/Elements/ShowSummary:44 html/Ticket/Elements/Tabs:95 html/Ticket/ModifyAll.html:50
+msgid "People"
+msgstr "Personnes"
+
+#: etc/initialdata:125
+msgid "Perform a user-defined action"
+msgstr "Réaliser une action définie par l'utilisateur"
+
+#: lib/RT/ACE_Overlay.pm:230 lib/RT/ACE_Overlay.pm:236 lib/RT/ACE_Overlay.pm:562 lib/RT/ACE_Overlay.pm:572 lib/RT/ACE_Overlay.pm:582 lib/RT/ACE_Overlay.pm:647 lib/RT/CurrentUser.pm:82 lib/RT/CurrentUser.pm:91 lib/RT/CustomField_Overlay.pm:100 lib/RT/CustomField_Overlay.pm:201 lib/RT/CustomField_Overlay.pm:233 lib/RT/CustomField_Overlay.pm:511 lib/RT/CustomField_Overlay.pm:90 lib/RT/Group_Overlay.pm:1094 lib/RT/Group_Overlay.pm:1098 lib/RT/Group_Overlay.pm:1107 lib/RT/Group_Overlay.pm:1158 lib/RT/Group_Overlay.pm:1162 lib/RT/Group_Overlay.pm:1168 lib/RT/Group_Overlay.pm:425 lib/RT/Group_Overlay.pm:517 lib/RT/Group_Overlay.pm:595 lib/RT/Group_Overlay.pm:603 lib/RT/Group_Overlay.pm:700 lib/RT/Group_Overlay.pm:704 lib/RT/Group_Overlay.pm:710 lib/RT/Group_Overlay.pm:903 lib/RT/Group_Overlay.pm:907 lib/RT/Group_Overlay.pm:920 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:125 lib/RT/Scrip_Overlay.pm:136 lib/RT/Scrip_Overlay.pm:196 lib/RT/Scrip_Overlay.pm:433 lib/RT/Template_Overlay.pm:283 lib/RT/Template_Overlay.pm:87 lib/RT/Template_Overlay.pm:93 lib/RT/Ticket_Overlay.pm:1377 lib/RT/Ticket_Overlay.pm:1387 lib/RT/Ticket_Overlay.pm:1401 lib/RT/Ticket_Overlay.pm:1535 lib/RT/Ticket_Overlay.pm:1544 lib/RT/Ticket_Overlay.pm:1557 lib/RT/Ticket_Overlay.pm:1906 lib/RT/Ticket_Overlay.pm:2044 lib/RT/Ticket_Overlay.pm:2208 lib/RT/Ticket_Overlay.pm:2275 lib/RT/Ticket_Overlay.pm:2634 lib/RT/Ticket_Overlay.pm:2715 lib/RT/Ticket_Overlay.pm:2819 lib/RT/Ticket_Overlay.pm:2834 lib/RT/Ticket_Overlay.pm:3033 lib/RT/Ticket_Overlay.pm:3043 lib/RT/Ticket_Overlay.pm:3048 lib/RT/Ticket_Overlay.pm:3270 lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3630 lib/RT/Ticket_Overlay.pm:3682 lib/RT/Ticket_Overlay.pm:3860 lib/RT/Transaction_Overlay.pm:466 lib/RT/Transaction_Overlay.pm:473 lib/RT/Transaction_Overlay.pm:502 lib/RT/Transaction_Overlay.pm:509 lib/RT/User_Overlay.pm:1079 lib/RT/User_Overlay.pm:1527 lib/RT/User_Overlay.pm:687 lib/RT/User_Overlay.pm:722 lib/RT/User_Overlay.pm:978
+msgid "Permission Denied"
+msgstr "Accès refusé"
+
+#: html/User/Elements/Tabs:34
+msgid "Personal Groups"
+msgstr "Groupes personnels"
+
+#: html/User/Groups/index.html:29 html/User/Groups/index.html:39
+msgid "Personal groups"
+msgstr "Groupes personnels"
+
+#: html/User/Elements/DelegateRights:36
+msgid "Personal groups:"
+msgstr "Groupes personnels:"
+
+#: html/Admin/Users/Modify.html:155 html/User/Prefs.html:48
+msgid "Phone numbers"
+msgstr "Numéros de téléphone"
+
+#: NOT FOUND IN SOURCE
+msgid "Placeholder"
+msgstr "Paramètre fictif"
+
+#: html/Elements/Header:51 html/Elements/Tabs:52 html/SelfService/Elements/Tabs:50 html/SelfService/Prefs.html:24 html/User/Prefs.html:24 html/User/Prefs.html:27
+msgid "Preferences"
+msgstr "Préférences"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Préférences"
+
+#: lib/RT/Action/Generic.pm:159
+msgid "Prepare Stubbed"
+msgstr "Préparation interrompue"
+
+#: html/Ticket/Elements/Tabs:60
+msgid "Prev"
+msgstr "Précédent"
+
+#: html/Search/Listing.html:43
+msgid "Previous page"
+msgstr "Page précédente"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri."
+
+#: lib/RT/ACE_Overlay.pm:132 lib/RT/ACE_Overlay.pm:207 lib/RT/ACE_Overlay.pm:551
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Principal %1 non trouvé"
+
+#: html/Search/Elements/PickRestriction:53 html/Ticket/Create.html:153 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:38 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Priorité"
+
+#: html/Admin/Elements/ModifyQueue:50 html/Admin/Queues/Modify.html:64
+msgid "Priority starts at"
+msgstr "La priorité débute à "
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilégié"
+
+#: html/Admin/Users/Modify.html:270 html/User/Prefs.html:162
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Statuts privilégiés : %1"
+
+#: html/Admin/Users/index.html:61
+msgid "Privileged users"
+msgstr "Utilisateurs privilégiés"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudo groupe pour usage interne"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Elements/Quicksearch:28 html/Search/Elements/PickRestriction:45 html/SelfService/Create.html:32 html/Ticket/Create.html:37 html/Ticket/Elements/EditBasics:63 html/Ticket/Elements/ShowBasics:42 html/User/Elements/DelegateRights:79 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Queue"
+
+#: html/Admin/Queues/CustomField.html:41 html/Admin/Queues/Scrip.html:49 html/Admin/Queues/Scrips.html:47 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Queue %1 non trouvée"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Queue '%1' inconnue\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "Sélection des mots clé de queue"
+
+#: html/Admin/Elements/ModifyQueue:30 html/Admin/Queues/Modify.html:42
+msgid "Queue Name"
+msgstr "Nom de la queue"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Scrips de queue"
+
+#: lib/RT/Queue_Overlay.pm:264
+msgid "Queue already exists"
+msgstr "Queue déjà créée"
+
+#: lib/RT/Queue_Overlay.pm:273 lib/RT/Queue_Overlay.pm:279
+msgid "Queue could not be created"
+msgstr "Impossible de créer la queue"
+
+#: html/Ticket/Create.html:204
+msgid "Queue could not be loaded."
+msgstr "Queue ne pouvant être chargée"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:283
+msgid "Queue created"
+msgstr "Queue créée"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "Queue non spécifié"
+
+#: html/SelfService/Display.html:70 lib/RT/CustomField_Overlay.pm:97
+msgid "Queue not found"
+msgstr "Queue inconnue"
+
+#: html/Admin/Elements/Tabs:37 html/Admin/index.html:34
+msgid "Queues"
+msgstr "Queues"
+
+#: html/Elements/Quicksearch:24
+msgid "Quick search"
+msgstr "Recherche rapide"
+
+#: html/Elements/Login:44
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 pour %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:24 html/Admin/index.html:25
+msgid "RT Administration"
+msgstr "Administration RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "Erreur d'authentification RT."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "Avis de rejet RT: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "Erreur de configuration RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "Erreur critique RT. Courrier non enregistré !"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:40
+msgid "RT Error"
+msgstr "Erreur RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT a reçu un e-mail (%1) de lui-même."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT a reçu du courrier (%1) de lui même"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Self Service / Tickets résolus"
+
+#: html/index.html:24 html/index.html:27
+msgid "RT at a glance"
+msgstr "RT en un coup d'oeil"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT n'a pas réussi à vous identifier"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT n'a pas pu trouver de demandeur par sa recherche dans une base externe"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT n'a pas trouvé la queue"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT n'a pas réussi à valider cette signature PGP. \\n"
+
+#: html/Elements/PageLayout:85
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT pour %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT pour %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT a exécuté vos commandes"
+
+#: html/Elements/Login:94
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT est &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Distribué sous <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 de la licence générale GNU.</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT est &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Distribué sous <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 de la licence générale GNU.</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT pense que ce courrier peut être un avis de non-distribution"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT va traiter ce courrier comme s'il n'était pas signé.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "L'interface d'utilisation de RT par email utilise une authentification PGP. Soit vous n'avez pas signé votre courrier, soit la signature est n'a pas pu être vérifiée"
+
+#: html/Admin/Users/Modify.html:57 html/Admin/Users/Prefs.html:51 html/User/Prefs.html:43
+msgid "Real Name"
+msgstr "Nom"
+
+#: html/Admin/Elements/ModifyUser:47
+msgid "RealName"
+msgstr "RealName"
+
+#: lib/RT/Transaction_Overlay.pm:631
+#. ($value)
+msgid "Reference by %1 added"
+msgstr "Ajout d'une référence par %1"
+
+#: lib/RT/Transaction_Overlay.pm:660
+#. ($value)
+msgid "Reference by %1 deleted"
+msgstr "Suppression de la référence par %1"
+
+#: lib/RT/Transaction_Overlay.pm:629
+#. ($value)
+msgid "Reference to %1 added"
+msgstr "Ajout d'une reference à %1"
+
+#: lib/RT/Transaction_Overlay.pm:658
+#. ($value)
+msgid "Reference to %1 deleted"
+msgstr "Suppression d'une reference à %1"
+
+#: html/Ticket/Create.html:184 html/Ticket/Elements/BulkLinks:50 html/Ticket/Elements/EditLinks:138 html/Ticket/Elements/EditLinks:93 html/Ticket/Elements/ShowLinks:70
+msgid "Referred to by"
+msgstr "Mentionné par"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:183 html/Ticket/Elements/BulkLinks:46 html/Ticket/Elements/EditLinks:134 html/Ticket/Elements/EditLinks:79 html/Ticket/Elements/ShowLinks:60
+msgid "Refers to"
+msgstr "Se rapporte à"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "SeRapporteA"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Affiner"
+
+#: html/Search/Elements/PickRestriction:26
+msgid "Refine search"
+msgstr "Affiner la recherche"
+
+#: html/Elements/Refresh:35
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Rafraîchir cette page toutes les %1 minutes."
+
+#: html/Ticket/Create.html:173 html/Ticket/Elements/ShowSummary:61 html/Ticket/ModifyAll.html:56
+msgid "Relationships"
+msgstr "Relations"
+
+#: html/Search/Bulk.html:97
+msgid "Remove AdminCc"
+msgstr "Enlever AdminCc "
+
+#: html/Search/Bulk.html:93
+msgid "Remove Cc"
+msgstr "Enlever Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Enlever Demandeur"
+
+#: html/Ticket/Elements/ShowTransaction:159 html/Ticket/Elements/Tabs:121
+msgid "Reply"
+msgstr "Répondre"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Reply to tickets"
+msgstr "Répondre aux tickets"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "ReplyToTicket"
+msgstr "RépondreTicket"
+
+#: etc/initialdata:44 html/Ticket/Update.html:39 lib/RT/ACE_Overlay.pm:86
+msgid "Requestor"
+msgstr "Demandeur"
+
+#: html/Search/Elements/PickRestriction:37
+msgid "Requestor email address"
+msgstr "Adresse email du demandeur"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Demandeur(s)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "AdresseDuDemandeur"
+
+#: html/SelfService/Create.html:40 html/Ticket/Create.html:55 html/Ticket/Elements/EditPeople:47 html/Ticket/Elements/ShowPeople:30
+msgid "Requestors"
+msgstr "Demandeurs"
+
+#: html/Admin/Elements/ModifyQueue:60 html/Admin/Queues/Modify.html:74
+msgid "Requests should be due in"
+msgstr "Le demande doit être résolue dans"
+
+#: html/Elements/Submit:61
+msgid "Reset"
+msgstr "Remise à zéro"
+
+#: html/Admin/Users/Modify.html:158 html/User/Prefs.html:49
+msgid "Residence"
+msgstr "Domicile"
+
+#: html/Ticket/Elements/Tabs:131
+msgid "Resolve"
+msgstr "Résoudre"
+
+#: html/Ticket/Update.html:137
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Résoudre ticket n°%1 (%2)"
+
+#: etc/initialdata:308 html/Elements/SelectDateType:27 lib/RT/Ticket_Overlay.pm:1206
+msgid "Resolved"
+msgstr "Résolu"
+
+#: html/Search/Bulk.html:132 html/Ticket/ModifyAll.html:72 html/Ticket/Update.html:71
+msgid "Response to requestors"
+msgstr "Réponse aux demandeurs"
+
+#: html/Elements/ListActions:25
+msgid "Results"
+msgstr "Résultats"
+
+#: html/Search/Elements/PickRestriction:104
+msgid "Results per page"
+msgstr "Nb tickets par page"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:99 html/User/Prefs.html:71
+msgid "Retype Password"
+msgstr "Saisissez à nouveau votre mot de passe"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Le droit %1 introuvable pour %2 %3 dans le périmètre %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:612
+msgid "Right Delegated"
+msgstr "Droit délégué"
+
+#: lib/RT/ACE_Overlay.pm:302
+msgid "Right Granted"
+msgstr "Droit accordé"
+
+#: lib/RT/ACE_Overlay.pm:160
+msgid "Right Loaded"
+msgstr "Droit activé"
+
+#: lib/RT/ACE_Overlay.pm:677 lib/RT/ACE_Overlay.pm:692
+msgid "Right could not be revoked"
+msgstr "Droit irrévocable"
+
+#: html/User/Delegation.html:63
+msgid "Right not found"
+msgstr "Droit inconnu"
+
+#: lib/RT/ACE_Overlay.pm:542 lib/RT/ACE_Overlay.pm:637
+msgid "Right not loaded."
+msgstr "Droit non activé"
+
+#: lib/RT/ACE_Overlay.pm:688
+msgid "Right revoked"
+msgstr "Droit révoqué"
+
+#: html/Admin/Elements/UserTabs:40
+msgid "Rights"
+msgstr "Droits"
+
+#: lib/RT/Interface/Web.pm:794
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "Les droits n'on pas pu être attribués à %1"
+
+#: lib/RT/Interface/Web.pm:827
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "Les droits n'ont pas pu être révoqués pour %1"
+
+#: html/Admin/Global/GroupRights.html:50 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Rôles"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "ApprobationDeRoot"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Sam."
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyLinks.html:38 html/Ticket/ModifyPeople.html:37
+msgid "Save Changes"
+msgstr "Enregistrer les modifications"
+
+#: NOT FOUND IN SOURCE
+msgid "Save changes"
+msgstr "Enregistrer les modifications"
+
+#: html/Admin/Global/Scrip.html:48 html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->id)
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "Scrip n°%1"
+
+#: lib/RT/Scrip_Overlay.pm:175
+msgid "Scrip Created"
+msgstr "Scrip ajouté"
+
+#: html/Admin/Elements/EditScrips:83
+msgid "Scrip deleted"
+msgstr "Scrip supprimé"
+
+#: html/Admin/Elements/QueueTabs:45 html/Admin/Elements/SystemTabs:32 html/Admin/Global/index.html:40
+msgid "Scrips"
+msgstr "Scrips"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrips pour %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Scrips s'appliquant à toutes les queues"
+
+#: html/Elements/SimpleSearch:26 html/Search/Elements/PickRestriction:125 html/Ticket/Elements/Tabs:158
+msgid "Search"
+msgstr "Rechercher"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Critère de recherche"
+
+#: html/Approvals/Elements/PendingMyApproval:38
+msgid "Search for approvals"
+msgstr "Chercher des approbations"
+
+#: bin/rt-crontool:187
+msgid "Security:"
+msgstr "Sécurité:"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "SeeQueue"
+msgstr "VoirQueue"
+
+#: html/Admin/Groups/index.html:39
+msgid "Select a group"
+msgstr "Sélectionner un groupe"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Choisir une queue"
+
+#: html/Admin/Users/index.html:24 html/Admin/Users/index.html:27
+msgid "Select a user"
+msgstr "Sélectionner un utilisateur"
+
+#: html/Admin/Global/CustomField.html:37 html/Admin/Global/CustomFields.html:35
+msgid "Select custom field"
+msgstr "Selectionner le champ personnalisé"
+
+#: html/Admin/Elements/GroupTabs:51 html/User/Elements/GroupTabs:49
+msgid "Select group"
+msgstr "Sélectionner le groupe"
+
+#: lib/RT/CustomField_Overlay.pm:421
+msgid "Select multiple values"
+msgstr "Choisir plusieurs valeurs"
+
+#: lib/RT/CustomField_Overlay.pm:418
+msgid "Select one value"
+msgstr "Choisir une valeur"
+
+#: html/Admin/Elements/QueueTabs:66
+msgid "Select queue"
+msgstr "Selectionner la queue"
+
+#: html/Admin/Global/Scrip.html:36 html/Admin/Global/Scrips.html:35 html/Admin/Queues/Scrip.html:39 html/Admin/Queues/Scrips.html:51
+msgid "Select scrip"
+msgstr "Selectionner le scrip"
+
+#: html/Admin/Global/Template.html:56 html/Admin/Global/Templates.html:35 html/Admin/Queues/Template.html:54 html/Admin/Queues/Templates.html:46
+msgid "Select template"
+msgstr "Selectionner le modèle"
+
+#: html/Admin/Elements/UserTabs:48
+msgid "Select user"
+msgstr "Selectionner l'utilisateur"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectMultiple"
+msgstr "ChoixMultiples"
+
+#: lib/RT/CustomField_Overlay.pm:34
+msgid "SelectSingle"
+msgstr "ChoixSimple"
+
+#: NOT FOUND IN SOURCE
+msgid "Self Service"
+msgstr "Self Service"
+
+#: etc/initialdata:113
+msgid "Send mail to all watchers"
+msgstr "Envoyer un courrier à tous les observateurs"
+
+#: etc/initialdata:109
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Envoyer un courrier à tous les observateurs en tant que \"commentaire\""
+
+#: etc/initialdata:104
+msgid "Send mail to requestors and Ccs"
+msgstr "Envoyer un courrier aux demandeurs et aux CCs"
+
+#: etc/initialdata:99
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Envoyer un courrier aux demandeurs et aux CCs en tant que commentaire"
+
+#: etc/initialdata:78
+msgid "Sends a message to the requestors"
+msgstr "Envoyer un courrier aux demandeurs"
+
+#: etc/initialdata:117 etc/initialdata:121
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Envoyer un courrier aux CCs et Bccs explicitement indiqués"
+
+#: etc/initialdata:94
+msgid "Sends mail to the administrative Ccs"
+msgstr "Envoyer un mail aux AdminCCs"
+
+#: etc/initialdata:90
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Envoyer un mail aux AdminCCs en tant que commentaire"
+
+#: etc/initialdata:82 etc/initialdata:86
+msgid "Sends mail to the owner"
+msgstr "Envoyer un courrier à l'intervenant"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep."
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "Septembre"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Afficher les résultats"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show approved requests"
+msgstr "Afficher les requêtes approuvées"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show basics"
+msgstr "Affichage court"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show denied requests"
+msgstr "Afficher les requêtes refusées"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show details"
+msgstr "Affichage long"
+
+#: html/Approvals/Elements/PendingMyApproval:42
+msgid "Show pending requests"
+msgstr "Afficher les requêtes en attente"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show requests awaiting other approvals"
+msgstr "Afficher les requêtes attendant d'autres approbations"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "Show ticket private commentary"
+msgstr "Afficher les commentaires privés du ticket"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Show ticket summaries"
+msgstr "Afficher les résumés de tickets"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "ShowACL"
+msgstr "AfficherACL"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "ShowScrips"
+msgstr "AfficherScrips"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "ShowTemplate"
+msgstr "AfficherModèle"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowTicket"
+msgstr "AfficherTicket"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "ShowTicketComments"
+msgstr "AfficherCommentairesTickets"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "S'identifier en tant que demandeur ou CC de queue ou de ticket"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "S'identifier en tant qu'AdminCC de ticket ou de queue"
+
+#: html/Admin/Elements/ModifyUser:38 html/Admin/Users/Modify.html:190 html/Admin/Users/Prefs.html:31 html/User/Prefs.html:111
+msgid "Signature"
+msgstr "Signature"
+
+#: NOT FOUND IN SOURCE
+msgid "Signed in as %1"
+msgstr "Connecté en tant que %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:25
+msgid "Single"
+msgstr "Unique"
+
+#: html/Elements/Header:50
+msgid "Skip Menu"
+msgstr "Passer le menu"
+
+#: html/Admin/Elements/AddCustomFieldValue:27
+msgid "Sort"
+msgstr "Trier"
+
+#: NOT FOUND IN SOURCE
+msgid "Sort key"
+msgstr "Ordre de tri"
+
+#: html/Search/Elements/PickRestriction:108
+msgid "Sort results by"
+msgstr "Trier les résultats par"
+
+#: NOT FOUND IN SOURCE
+msgid "SortOrder"
+msgstr "SortOrder"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Bloqué"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Page de début"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/EditDates:31 html/Ticket/Elements/ShowDates:34
+msgid "Started"
+msgstr "Ouvert le"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "La date de démarrage '%1' n'a pas pu être analysée"
+
+#: html/Elements/SelectDateType:30 html/Ticket/Create.html:165 html/Ticket/Elements/EditDates:26 html/Ticket/Elements/ShowDates:30
+msgid "Starts"
+msgstr "Débute"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Débute le"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "La date de début '%1' n'a pas pu être analysée"
+
+#: html/Admin/Elements/ModifyUser:81 html/Admin/Users/Modify.html:137 html/User/Prefs.html:93
+msgid "State"
+msgstr "Etat"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Search/Elements/PickRestriction:73 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:30 html/Ticket/Create.html:41 html/Ticket/Elements/EditBasics:37 html/Ticket/Elements/ShowBasics:30 html/Ticket/Update.html:59 lib/RT/Ticket_Overlay.pm:1200 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Statut"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr "Changement de statut"
+
+#: lib/RT/Transaction_Overlay.pm:528
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Statut modifié de %1 à %2 "
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "ChangementDeStatut"
+
+#: html/Ticket/Elements/Tabs:146
+msgid "Steal"
+msgstr "Voler"
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "Steal tickets"
+msgstr "Voler les tickets "
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "StealTicket"
+msgstr "VolerTicket"
+
+#: lib/RT/Transaction_Overlay.pm:587
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Volé à %1"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28 html/Search/Bulk.html:135 html/Search/Elements/PickRestriction:42 html/SelfService/Create.html:56 html/SelfService/Elements/MyRequests:27 html/SelfService/Update.html:31 html/Ticket/Create.html:83 html/Ticket/Elements/EditBasics:27 html/Ticket/ModifyAll.html:78 html/Ticket/Update.html:75 lib/RT/Ticket_Overlay.pm:1196 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Sujet"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:609
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Sujet modifié en %1"
+
+#: html/Elements/Submit:58
+msgid "Submit"
+msgstr "Valider"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Soumettre flux de travail"
+
+#: lib/RT/Group_Overlay.pm:748
+msgid "Succeeded"
+msgstr "Réussi"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Dim."
+
+#: lib/RT/System.pm:53
+msgid "SuperUser"
+msgstr "SuperUtilisateur"
+
+#: html/User/Elements/DelegateRights:76
+msgid "System"
+msgstr "Système"
+
+#: html/Admin/Elements/SelectRights:80 lib/RT/ACE_Overlay.pm:566 lib/RT/Interface/Web.pm:793 lib/RT/Interface/Web.pm:826
+msgid "System Error"
+msgstr "Erreur système"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "Erreur Système. Droit non délégué."
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "Erreur Système. Droit non délégué"
+
+#: lib/RT/ACE_Overlay.pm:615
+msgid "System error. Right not delegated."
+msgstr "Erreur système. Droit non délégué."
+
+#: lib/RT/ACE_Overlay.pm:145 lib/RT/ACE_Overlay.pm:222 lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:897
+msgid "System error. Right not granted."
+msgstr "Erreur système. Droit non accordé"
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Erreur Système. Imposible de déléguer les droits"
+
+#: html/Admin/Global/GroupRights.html:34 html/Admin/Groups/GroupRights.html:36 html/Admin/Queues/GroupRights.html:35
+msgid "System groups"
+msgstr "Groupes système"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRolegroup à usage interne"
+
+#: lib/RT/CurrentUser.pm:319
+msgid "TEST_STRING"
+msgstr "Chaîne_de_test"
+
+#: html/Ticket/Elements/Tabs:142
+msgid "Take"
+msgstr "Prendre"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "Take tickets"
+msgstr "Prendre les tickets"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "TakeTicket"
+msgstr "PrendreTicket"
+
+#: lib/RT/Transaction_Overlay.pm:573
+msgid "Taken"
+msgstr "Pris"
+
+#: html/Admin/Elements/EditScrip:80
+msgid "Template"
+msgstr "Modèle"
+
+#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:89
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Modèle n°%1"
+
+#: html/Admin/Elements/EditTemplates:88
+msgid "Template deleted"
+msgstr "Modèle supprimé"
+
+#: lib/RT/Scrip_Overlay.pm:152
+msgid "Template not found"
+msgstr "Modèle inconnu"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Modèle inconnu\\n"
+
+#: lib/RT/Template_Overlay.pm:352
+msgid "Template parsed"
+msgstr "Modèle analysé"
+
+#: html/Admin/Elements/QueueTabs:48 html/Admin/Elements/SystemTabs:35 html/Admin/Global/index.html:44
+msgid "Templates"
+msgstr "Modèles"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Modèles pour %1\\n "
+
+#: lib/RT/Interface/Web.pm:894
+msgid "That is already the current value"
+msgstr "Ceci est déjà la valeur actuelle"
+
+#: lib/RT/CustomField_Overlay.pm:242
+msgid "That is not a value for this custom field"
+msgstr "Valeur incorrecte pour ce champ personnalisé."
+
+#: lib/RT/Ticket_Overlay.pm:1917
+msgid "That is the same value"
+msgstr "Valeur identique"
+
+#: lib/RT/ACE_Overlay.pm:287 lib/RT/ACE_Overlay.pm:596
+msgid "That principal already has that right"
+msgstr "Ce groupe/utilisateur dispose déjà de ce droit"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Ce groupe/utilisateur est déjà un %1 pour cette queue"
+
+#: lib/RT/Ticket_Overlay.pm:1451
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Ce groupe/utilisateur est déjà un %1 pour ce ticket"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Ce groupe/utilisateur n'est pas un %1 pour cette queue"
+
+#: lib/RT/Ticket_Overlay.pm:1568
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Ce groupe/utilisateur n'est pas un %1 pour ce ticket"
+
+#: lib/RT/Ticket_Overlay.pm:1913
+msgid "That queue does not exist"
+msgstr "Queue inconnue"
+
+#: lib/RT/Ticket_Overlay.pm:3274
+msgid "That ticket has unresolved dependencies"
+msgstr "Ticket ayant des tickets fils ou dépendants non résolus"
+
+#: NOT FOUND IN SOURCE
+msgid "That user already has that right"
+msgstr "Cet utilisateur possède déjà ce droit."
+
+#: lib/RT/Ticket_Overlay.pm:3084
+msgid "That user already owns that ticket"
+msgstr "Cet utilisateur possède déjà ce ticket."
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "That user does not exist"
+msgstr "Utilisateur inconnu"
+
+#: lib/RT/User_Overlay.pm:376
+msgid "That user is already privileged"
+msgstr "Utilisateur possédant déjà un statut privilégié."
+
+#: lib/RT/User_Overlay.pm:397
+msgid "That user is already unprivileged"
+msgstr "Utilisateur déjà sans privilèges."
+
+#: lib/RT/User_Overlay.pm:389
+msgid "That user is now privileged"
+msgstr "Utilisateur bénéficiant à présent du statut privilégié"
+
+#: lib/RT/User_Overlay.pm:410
+msgid "That user is now unprivileged"
+msgstr "Utilisateur à présent sans statut privilégié "
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "Cet utilisateur a perdu ses droits"
+
+#: lib/RT/Ticket_Overlay.pm:3077
+msgid "That user may not own tickets in that queue"
+msgstr "Cet utilisateur peut ne pas avoir de ticket dans cette queue."
+
+#: lib/RT/Link_Overlay.pm:205
+msgid "That's not a numerical id"
+msgstr "ID non numérique"
+
+#: html/SelfService/Display.html:31 html/Ticket/Create.html:149 html/Ticket/Elements/ShowSummary:27
+msgid "The Basics"
+msgstr "Eléments de base"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The CC of a ticket"
+msgstr "Le CC d'un ticket"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The administrative CC of a ticket"
+msgstr "L'AdminCC d'un ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2244
+msgid "The comment has been recorded"
+msgstr "Commentaire enregistré"
+
+#: bin/rt-crontool:197
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "Cette commande trouve tous les tickets actifs de la queue 'general' et positionne leur priorité à 99 s'ils n'ont pas été touchés depuis quatre heures:"
+
+#: bin/rt-commit-handler:755 bin/rt-commit-handler:765
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Les commandes suivantes n'ont pas été traitées :\\n\\n"
+
+#: lib/RT/Interface/Web.pm:897
+msgid "The new value has been set."
+msgstr "La nouvelle valeur est enregistrée"
+
+#: lib/RT/ACE_Overlay.pm:85
+msgid "The owner of a ticket"
+msgstr "L'intervenant d'un ticket"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The requestor of a ticket"
+msgstr "Le demandeur d'un ticket"
+
+#: html/Admin/Elements/EditUserComments:25
+msgid "These comments aren't generally visible to the user"
+msgstr "Ces commentaires ne sont généralement pas accessibles par l'utilisateur"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Ce ticket %1 %2 (%3)\\n "
+
+#: bin/rt-crontool:188
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Cet outil permet à l'utilisateur de lancer un module perl quelconque depuis RT"
+
+#: lib/RT/Transaction_Overlay.pm:251
+msgid "This transaction appears to have no content"
+msgstr "Cette opération semble ne pas avoir de contenu"
+
+#: html/Ticket/Elements/ShowRequestor:46
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "Les %1 tickets de plus haute priorité de cet utilisateur"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "Les 25 tickets prioritaires de cet utilisateur"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Jeu."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Ticket n°%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "Ticket n°%1 Jumbo update: %2"
+
+#: html/Ticket/ModifyAll.html:24 html/Ticket/ModifyAll.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Ticket n°%1 mise à jour globale: %2"
+
+#: html/Approvals/Elements/ShowDependency:45
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Ticket n°%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:623 lib/RT/Ticket_Overlay.pm:644
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Ticket %1 créé dans la queue '%2'"
+
+#: bin/rt-commit-handler:759
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Ticket %1 chargé\\n "
+
+#: html/Search/Bulk.html:212
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Ticket %1: %2"
+
+#: html/Ticket/History.html:24 html/Ticket/History.html:27
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Historique ticket # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Id"
+msgstr "N° ticket"
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr "Ticket résolu/clos"
+
+#: html/Search/Elements/PickRestriction:62
+msgid "Ticket attachment"
+msgstr "Pièce jointe au ticket"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Contenu du ticket."
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Type du contenu du ticket"
+
+#: lib/RT/Ticket_Overlay.pm:514 lib/RT/Ticket_Overlay.pm:523 lib/RT/Ticket_Overlay.pm:533 lib/RT/Ticket_Overlay.pm:633
+msgid "Ticket could not be created due to an internal error"
+msgstr "Une erreur interne a empêché l'ajout du ticket"
+
+#: lib/RT/Transaction_Overlay.pm:520
+msgid "Ticket created"
+msgstr "Ticket ajouté"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Création de ticket échouée."
+
+#: lib/RT/Transaction_Overlay.pm:525
+msgid "Ticket deleted"
+msgstr "Ticket supprimé."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket id not found"
+msgstr "Id de ticket non trouvée"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "Ticket effacé"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket not found"
+msgstr "Ticket non trouvé"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "Statut de ticket modifié"
+
+#: html/Ticket/Update.html:38
+msgid "Ticket watchers"
+msgstr "Observateurs du ticket"
+
+#: html/Elements/Tabs:46
+msgid "Tickets"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Tickets %1 par %2"
+
+#: html/Elements/ViewUser:25
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Tickets depuis %2"
+
+#: html/Approvals/Elements/ShowDependency:26
+msgid "Tickets which depend on this approval:"
+msgstr "Tickets dépendant de cette approbation:"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:47
+msgid "Time Left"
+msgstr "Temps restant"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:42
+msgid "Time Worked"
+msgstr "Temps passé"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Temps restant"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Temps de calcul"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Temps passé"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "TempsRestant"
+
+#: lib/RT/Ticket_Overlay.pm:1201
+msgid "TimeWorked"
+msgstr "TempsPassé"
+
+#: bin/rt-commit-handler:401
+msgid "To generate a diff of this commit:"
+msgstr "Pour conserver les modifications de cette transaction"
+
+#: bin/rt-commit-handler:390
+msgid "To generate a diff of this commit:\\n"
+msgstr "Pour conserver les modifications de cette transaction :\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1204
+msgid "Told"
+msgstr "Annoncé"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transaction"
+
+#: lib/RT/Transaction_Overlay.pm:691
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "La transaction%1 est supprimée"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transaction ajoutée"
+
+#: lib/RT/Transaction_Overlay.pm:88
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create n'a pas fonctionné car vous n'avez pas spécifié d'identifiant de ticket"
+
+#: lib/RT/Transaction_Overlay.pm:750
+msgid "Transactions are immutable"
+msgstr "Les transactions ne peuvent être transférées"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Tentative de délégation d'un droit : %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Mar."
+
+#: html/Admin/Elements/EditCustomField:43 html/Ticket/Elements/AddWatchers:32 html/Ticket/Elements/AddWatchers:43 html/Ticket/Elements/AddWatchers:53 lib/RT/Ticket_Overlay.pm:1202 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Type"
+
+#: lib/RT/ScripCondition_Overlay.pm:103
+msgid "Unimplemented"
+msgstr "Fonction non disponible"
+
+#: html/Admin/Users/Modify.html:67
+msgid "Unix login"
+msgstr "Identifiant Unix"
+
+#: html/Admin/Elements/ModifyUser:61
+msgid "UnixUsername"
+msgstr "UnixUsername"
+
+#: lib/RT/Attachment_Overlay.pm:266 lib/RT/Attachment_Overlay.pm:298
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Type d'encodage de courrier inconnu: %1"
+
+#: html/Elements/SelectResultsPerPage:36
+msgid "Unlimited"
+msgstr "Illimité"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Non privilégié"
+
+#: lib/RT/Transaction_Overlay.pm:569
+msgid "Untaken"
+msgstr "Non pris"
+
+#: html/Elements/MyTickets:63 html/Search/Bulk.html:32
+msgid "Update"
+msgstr "Mettre à jour"
+
+#: html/Admin/Users/Prefs.html:61
+msgid "Update ID"
+msgstr "Mettre à jour l'ID"
+
+#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:65 html/Ticket/Update.html:65
+msgid "Update Type"
+msgstr "Mettre à jour le type"
+
+#: html/Search/Listing.html:60
+msgid "Update all these tickets at once"
+msgstr "Mise à jour des tickets en masse"
+
+#: html/Admin/Users/Prefs.html:48
+msgid "Update email"
+msgstr "Mettre à jour l'email"
+
+#: html/Admin/Users/Prefs.html:54
+msgid "Update name"
+msgstr "Mettre à jour le nom"
+
+#: lib/RT/Interface/Web.pm:409
+msgid "Update not recorded."
+msgstr "Mise à jour non enregistrée"
+
+#: html/Search/Bulk.html:80
+msgid "Update selected tickets"
+msgstr "Mettre à jour les tickets sélectionnés"
+
+#: html/Admin/Users/Prefs.html:35
+msgid "Update signature"
+msgstr "Mettre à jour la signature"
+
+#: html/Ticket/ModifyAll.html:62
+msgid "Update ticket"
+msgstr "Mettre à jour le ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "Update ticket # %1"
+msgstr "Mettre à jour le ticket n°%1"
+
+#: html/SelfService/Update.html:24 html/SelfService/Update.html:46
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Mettre à jour le ticket n°%1"
+
+#: html/Ticket/Update.html:139
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Mettre à jour le ticket n°%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:407
+msgid "Update type was neither correspondence nor comment."
+msgstr "Le type de mise à jour n'était ni un commentaire ni un courrier."
+
+#: html/Elements/SelectDateType:32 html/Ticket/Elements/ShowDates:50 lib/RT/Ticket_Overlay.pm:1205
+msgid "Updated"
+msgstr "Mis(e) à jour"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Utilisateur %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Mot de passe de l'utilisateur %1 : %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Utilisateur '%1' non trouvé"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Utilisateur '%1' non trouvé\\n"
+
+#: etc/initialdata:124 etc/initialdata:191
+msgid "User Defined"
+msgstr "Utilisateur défini"
+
+#: html/Admin/Users/Prefs.html:58
+msgid "User ID"
+msgstr "Id utilisateur"
+
+#: html/Elements/SelectUsers:25
+msgid "User Id"
+msgstr "Id utilisateur"
+
+#: html/Admin/Elements/GroupTabs:46 html/Admin/Elements/QueueTabs:59 html/Admin/Elements/SystemTabs:46 html/Admin/Global/index.html:58
+msgid "User Rights"
+msgstr "Droits utilisateurs"
+
+#: html/Admin/Users/Modify.html:225
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Utilisateur ne peut pas être créé : %1"
+
+#: lib/RT/User_Overlay.pm:321
+msgid "User created"
+msgstr "Utilisateur créé"
+
+#: html/Admin/Global/GroupRights.html:66 html/Admin/Groups/GroupRights.html:53 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Groupes utilisateur"
+
+#: lib/RT/User_Overlay.pm:575 lib/RT/User_Overlay.pm:592
+msgid "User loaded"
+msgstr "Utilisateur chargé"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Utilisateur informé"
+
+#: html/Admin/Users/Prefs.html:24 html/Admin/Users/Prefs.html:28
+msgid "User view"
+msgstr "Vue utilisateur"
+
+#: html/Admin/Users/Modify.html:47 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:34
+msgid "Username"
+msgstr "Nom d'utilisateur"
+
+#: html/Admin/Elements/SelectNewGroupMembers:25 html/Admin/Elements/Tabs:31 html/Admin/Groups/Members.html:54 html/Admin/Queues/People.html:67 html/Admin/index.html:28 html/User/Groups/Members.html:57
+msgid "Users"
+msgstr "Utilisateurs"
+
+#: html/Admin/Users/index.html:64
+msgid "Users matching search criteria"
+msgstr "Utilisateurs correspondants aux critères de recherche"
+
+#: html/Search/Elements/PickRestriction:50
+msgid "ValueOfQueue"
+msgstr "ValueOfQueue"
+
+#: html/Admin/Elements/EditCustomField:56
+msgid "Values"
+msgstr "Valeurs"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Watch"
+msgstr "Observer"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "WatchAsAdminCc"
+msgstr "ObserverCommeAdminCC"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "Observateur chargé"
+
+#: html/Admin/Elements/QueueTabs:41
+msgid "Watchers"
+msgstr "Observateurs"
+
+#: html/Admin/Elements/ModifyUser:55
+msgid "WebEncoding"
+msgstr "WebEncoding"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Mer."
+
+#: etc/initialdata:503 etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Quand un ticket a été approuvé par tous les approbateurs, ajoute le courrier au ticket source"
+
+#: etc/initialdata:467 etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Quand un ticket a été approuvé par au moins un approbateur, ajoute le courrier au ticket source "
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Quand un ticket est créé"
+
+#: etc/initialdata:400 etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Quand un ticket d'approbation est créé, informer l'intervenant et l'AdminCC de l'élément attendant leur approbation"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Quand quelque chose arrive"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Lorsqu'un ticket quelconque est résolu/clos"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Lorsqu'un ticket quelconque change d'intervenant"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Lorsqu'un ticket quelconque change de queue"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Lorsqu'un ticket quelconque change de statut"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Lorsqu'une condition définie par l'utilisateur est satisfaite"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Lorsque un commentaire arrive"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Lorsque un courrier arrive"
+
+#: html/Admin/Users/Modify.html:163 html/User/Prefs.html:51
+msgid "Work"
+msgstr "Travail"
+
+#: html/Admin/Elements/ModifyUser:69
+msgid "WorkPhone"
+msgstr "Tel. bureau"
+
+#: html/Ticket/Elements/ShowBasics:34 html/Ticket/Update.html:64
+msgid "Worked"
+msgstr "Travaillé"
+
+#: lib/RT/Ticket_Overlay.pm:3187
+msgid "You already own this ticket"
+msgstr "Vous êtes déjà intervenant de ce ticket"
+
+#: html/autohandler:122
+msgid "You are not an authorized user"
+msgstr "Vous n'êtes pas un utilisateur autorisé"
+
+#: lib/RT/Ticket_Overlay.pm:3069
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Vous pouvez seulement réaffecter vos ticket ou ceux qui ne sont pas affectés"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Vous n'êtes pas autorisé à voir ce ticket.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "%1 tickets trouvés dans la queue %2"
+
+#: html/NoAuth/Logout.html:30
+msgid "You have been logged out of RT."
+msgstr "Vous avez été déconnecté de RT."
+
+#: html/SelfService/Display.html:77
+msgid "You have no permission to create tickets in that queue."
+msgstr "Vous n'avez pas l'autorisation de créer des tickets dans cette queue."
+
+#: lib/RT/Ticket_Overlay.pm:1926
+msgid "You may not create requests in that queue."
+msgstr "Vous ne pouvez pas créer de demandes dans cette queue."
+
+#: html/NoAuth/Logout.html:34
+msgid "You're welcome to login again"
+msgstr "Vous êtes invité à vous identifier à nouveau"
+
+#: NOT FOUND IN SOURCE
+msgid "Your %1 requests"
+msgstr "Vos %1 requêtes"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Votre administrateur RT a mal configuré l'alias de mail qui appelle RT"
+
+#: etc/initialdata:484 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Votre demande a été approuvée par %1. D'autres approbations sont peut être toujours en attente"
+
+#: etc/initialdata:522 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Votre demande a été approuvée"
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "Votre demande a été rejetée"
+
+#: etc/initialdata:427 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Votre demande a été rejetée."
+
+#: html/autohandler:144
+msgid "Your username or password is incorrect"
+msgstr "Votre nom d'utilisateur ou votre mot de passe est incorrect"
+
+#: html/Admin/Elements/ModifyUser:83 html/Admin/Users/Modify.html:143 html/User/Prefs.html:95
+msgid "Zip"
+msgstr "Code Postal"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[Pas de sujet]"
+
+#: html/User/Elements/DelegateRights:58
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "comme accordé à %1"
+
+#: html/SelfService/Closed.html:27
+msgid "closed"
+msgstr "fermé"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:33
+msgid "contains"
+msgstr "contient"
+
+#: html/Elements/SelectAttachmentField:25
+msgid "content"
+msgstr "Contenu"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content-type"
+msgstr "Type de contenu"
+
+#: lib/RT/Ticket_Overlay.pm:2313
+msgid "correspondence (probably) not sent"
+msgstr "courrier (probablement) non envoyé"
+
+#: lib/RT/Ticket_Overlay.pm:2323
+msgid "correspondence sent"
+msgstr "courrier envoyé"
+
+#: html/Admin/Elements/ModifyQueue:62 html/Admin/Queues/Modify.html:76 lib/RT/Date.pm:319
+msgid "days"
+msgstr "jours"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "effacé"
+
+#: html/Search/Listing.html:74
+msgid "delete"
+msgstr "effacer"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "deleted"
+msgstr "effacé"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "does not match"
+msgstr "ne correspond pas"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:34
+msgid "doesn't contain"
+msgstr "ne contient pas"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "equal to"
+msgstr "égal à"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "faux"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "filename"
+msgstr "Nom de fichier"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "greater than"
+msgstr "supérieur à"
+
+#: lib/RT/Group_Overlay.pm:193
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "groupe '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "heures"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "n°"
+
+#: html/Elements/SelectBoolean:31 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:35 html/Search/Elements/PickRestriction:46 html/Search/Elements/PickRestriction:75 html/Search/Elements/PickRestriction:87
+msgid "is"
+msgstr "est"
+
+#: html/Elements/SelectBoolean:35 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "isn't"
+msgstr "n'est pas"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "less than"
+msgstr "inférieur à"
+
+#: html/Search/Elements/PickRestriction:66
+msgid "matches"
+msgstr "correspond"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:64
+msgid "minutes"
+msgstr "minutes"
+
+#: bin/rt-commit-handler:764
+msgid "modifications\\n\\n"
+msgstr "modifications\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "mois"
+
+#: lib/RT/Queue_Overlay.pm:57
+msgid "new"
+msgstr "nouveau"
+
+#: html/Admin/Elements/EditScrips:42
+msgid "no value"
+msgstr "Non renseigné"
+
+#: html/Admin/Elements/EditQueueWatchers:26 html/Ticket/Elements/EditWatchers:27
+msgid "none"
+msgstr "aucun"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "not equal to"
+msgstr "différent de"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "necontientpas"
+
+#: html/SelfService/Elements/MyRequests:60 lib/RT/Queue_Overlay.pm:58
+msgid "open"
+msgstr "ouvert"
+
+#: lib/RT/Group_Overlay.pm:198
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "groupe personnel '%1' pour l'utilisateur '%2'"
+
+#: lib/RT/Group_Overlay.pm:206
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "queue %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "rejected"
+msgstr "rejeté"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "resolved"
+msgstr "résolu"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sec"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "stalled"
+msgstr "bloqué"
+
+#: lib/RT/Group_Overlay.pm:201
+#. ($self->Type)
+msgid "system %1"
+msgstr "système %1"
+
+#: lib/RT/Group_Overlay.pm:212
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "groupe système '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:41
+msgid "the calling component did not specify why"
+msgstr "le composant appelant n'a pas spécifié pourquoi"
+
+#: lib/RT/URI/fsck_com_rt.pm:234
+#. ($self->Object->Id)
+msgid "ticket #%1"
+msgstr "ticket n°%1"
+
+#: lib/RT/Group_Overlay.pm:209
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "ticket n°%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "vrai"
+
+#: lib/RT/Group_Overlay.pm:215
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "Groupe %1 non décrit"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "Groupe non décrit %1"
+
+#: lib/RT/Group_Overlay.pm:190
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "utilisateur %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "semaines"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "Avec modèle %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "années"
+
diff --git a/rt/lib/RT/I18N/he.po b/rt/lib/RT/I18N/he.po
new file mode 100644
index 0000000..d3ef20e
--- /dev/null
+++ b/rt/lib/RT/I18N/he.po
@@ -0,0 +1,4871 @@
+# Hebrew Translation of the RT interface by Shimi.
+# Comments: shimi@shimi.net
+
+msgid ""
+msgstr ""
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:50 html/SelfService/Display.html:25 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr ""
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr ""
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3505 lib/RT/Transaction_Overlay.pm:557 lib/RT/Transaction_Overlay.pm:599
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 הוסף"
+msgstr ""
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 לפני %2 ימי×"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3511 lib/RT/Transaction_Overlay.pm:564
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 שונה ל %3"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3508 lib/RT/Transaction_Overlay.pm:560 lib/RT/Transaction_Overlay.pm:605
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 נמחק"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 מקבוצה %3"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 ×¢× ×ª×‘× ×™×ª %3"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 פנייה זו\\n"
+msgstr ""
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1 - %2 מוצגי×"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - ×רגומנט להעביר ×ל %2"
+msgstr ""
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr ""
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 פעולת-סקריפ נטענה"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3538
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 הוסף כערך עבור %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 ×›×™× ×•×™×™× ×“×•×¨×©×™× ×ž×–×”×” פנייה כדי לעבוד עליה×"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 ×›×™× ×•×™×™× ×“×•×¨×©×™× ×ž×–×”×” פנייה כדי לעבוד ×¢×œ×™×”× "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 ×›×™× ×•×™×™× ×“×•×¨×©×™× ×ž×–×”×” פנייה כדי לעבוד ×¢×œ×™×”× (מ %2) %3"
+msgstr ""
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 נר××” כמו ×ובייקט מקומי, ×בל ×”×•× ×ינו × ×ž×¦× ×‘×ž×¡×“ הנתוני×"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:481
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 על ידי %2"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:535 lib/RT/Transaction_Overlay.pm:624 lib/RT/Transaction_Overlay.pm:633 lib/RT/Transaction_Overlay.pm:636
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 שונה מ %2 ל %3"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:891
+msgid "%1 could not be set to %2."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2817
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr ""
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "%1 הפניות ×¢× ×”×¢×“×™×¤×•×ª הגבוהה ביותר בטיפולי..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "%1 הפניות ×¢× ×”×¢×“×™×¤×•×ª הגבוהה ביותר ש×× ×™ פתחתי..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3594
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "זכויות"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr ""
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:433
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(סמן תיבה כדי למחוק חבר בקבוצה)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(סמן תיבה כדי למחוק סקריפ)"
+
+#: html/Admin/Elements/EditCustomFieldValues:25 html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(סמן תיבה כדי למחוק)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(סמן תיבות כדי למחוק)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr ""
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr ""
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr ""
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr ""
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr ""
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr ""
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:534
+msgid "(no value)"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(רק פנייה ×חת)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr ""
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr ""
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"פנייה חדשה ב\">&nbsp;%1"
+
+#: NOT FOUND IN SOURCE
+msgid "??????"
+msgstr ""
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr ""
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr ""
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "מידע ×ודותי"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr ""
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr ""
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr ""
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "הוסף העתק ניהולי"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "הוסף העתק"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "הוסף עוד קבצי×"
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr ""
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "הוסף מבקש"
+
+#: html/Admin/Elements/AddCustomFieldValue:26
+msgid "Add Value"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr ""
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "הוסף הערות ×ו תגובות לפניות הנבחרות"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr ""
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "הוסף ×¦×•×¤×™× ×—×“×©×™×"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "כתובת1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "כתובת2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr ""
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr ""
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr ""
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr ""
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr ""
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "העתק ניהולי"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr ""
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "העתק ניהולי"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr ""
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "×חרי"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr ""
+
+#: html/Elements/Tabs:56
+msgid "Approval"
+msgstr "×ישור"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr ""
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr ""
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:44
+msgid "Approve"
+msgstr ""
+
+#: etc/initialdata:437 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "×פריל"
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "×פריל"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "עולה"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:33 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "צרף"
+
+#: html/SelfService/Create.html:65 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr ""
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "קובץ מצורף"
+
+#: NOT FOUND IN SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:441
+msgid "Attachment created"
+msgstr "קובץ צורף"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "×©× ×§×•×‘×¥ מצורף"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "×§×‘×¦×™× ×ž×¦×•×¨×¤×™×"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "×וגוסט"
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "×וגוסט"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr ""
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr ""
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr ""
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "בסיסי"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "×ל תשכח לשמור ×ת השינויי×"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:320
+msgid "Before"
+msgstr "לפני"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr ""
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr ""
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "הוסף כתובת זו לספר הכתובות כדי לחזור על ×ותו חיפוש"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "תקציר כותרי×"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "עדכון פניות מרוכז"
+
+#: lib/RT/User_Overlay.pm:1352
+msgid "Can not modify system users"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:206
+msgid "Can't add a custom field value without a name"
+msgstr ""
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2794
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2612 lib/RT/Ticket_Overlay.pm:2681
+msgid "Can't specifiy both base and target"
+msgstr ""
+
+#: html/autohandler:99
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr ""
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:49 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "העתק"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr ""
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "סמן תיבה כדי למחוק"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "סמן תיבה כדי לבטל זכות"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:57
+msgid "Children"
+msgstr "ילדי×"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "עיר"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "נסגר"
+
+#: html/SelfService/Closed.html:25
+msgid "Closed Tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Closed requests"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:45
+msgid "Closed tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "הערה"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "הערות (×œ× × ×©×œ×—×•×ª ×ל המבקשי×)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "הערות (×œ× × ×©×œ×—×•×ª ×ל המבקשי×)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "הערות לגבי %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "הערות לגבי משתמש זה"
+
+#: lib/RT/Transaction_Overlay.pm:543
+msgid "Comments added"
+msgstr "הערות נוספו"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr ""
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr ""
+
+#: html/Elements/Tabs:50
+msgid "Configuration"
+msgstr "הגדרות"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr ""
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "תוכן"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr ""
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:539
+msgid "Correspondence added"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3525
+msgid "Could not add new custom field value for ticket. "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3031 lib/RT/Ticket_Overlay.pm:3039 lib/RT/Ticket_Overlay.pm:3055
+msgid "Could not change owner. "
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:85 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr ""
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr ""
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:334
+msgid "Could not create ticket. Queue not set"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:422
+msgid "Could not create user"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr ""
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3535 lib/RT/Ticket_Overlay.pm:3591
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:900
+msgid "Couldn't find row"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:240
+msgid "Couldn't find that value"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr ""
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr ""
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr ""
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr ""
+
+#: html/SelfService/Display.html:109
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "×רץ"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "צור"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "צור פניות"
+
+#: html/Admin/Elements/EditCustomField:75
+msgid "Create a CustomField"
+msgstr ""
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr ""
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "צור קבוצה פרטית חדשה"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "צור תור חדש"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "צור סקריפ חדש"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "צור תבנית חדשה"
+
+#: html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "צור פנייה חדשה"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "צור משתמש חדש"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "צור תור חדש"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "צור תור שנקר×"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a request"
+msgstr "צור בקשה"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr ""
+
+#: html/SelfService/Create.html:25
+msgid "Create a ticket"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr ""
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr ""
+
+#: html/SelfService/Create.html:78
+msgid "Create ticket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr ""
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "נוצר"
+
+#: html/Admin/Elements/EditCustomField:88
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "×™×—×¡×™× × ×•×›×—×™×™×"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr ""
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "קריטריוני החיפוש הנוכחיי×"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "×¦×•×¤×™× × ×•×›×—×™×™×"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:36
+msgid "Custom Fields"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3427
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3577
+msgid "Custom field not found"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:350
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:250
+msgid "Custom field value could not be deleted"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:356
+msgid "Custom field value could not be found"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:248 lib/RT/CustomField_Overlay.pm:358
+msgid "Custom field value deleted"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:548
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr ""
+
+#: html/SelfService/Display.html:39 html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:55 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "ת×ריכי×"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "דצמבר"
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "דצמבר"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr ""
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr ""
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr ""
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr ""
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr ""
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:643
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr ""
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr ""
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr ""
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr ""
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "דלגציות"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "מחק"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:438
+msgid "Deleting this object would violate referential integrity"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr ""
+
+#: html/Approvals/Elements/Approve:45
+msgid "Deny"
+msgstr ""
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:37
+msgid "Depended on by"
+msgstr "×ª×œ×•×™×™× ×‘×•"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr ""
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "תלוי ב"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "יורד"
+
+#: html/SelfService/Create.html:73 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr ""
+
+#: html/Admin/Elements/AddCustomFieldValue:37 html/Admin/Elements/EditCustomField:39 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "תי×ור"
+
+#: NOT FOUND IN SOURCE
+msgid "Details"
+msgstr "פרטי×"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "הצג"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr ""
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "מצב תצוגה"
+
+#: NOT FOUND IN SOURCE
+msgid "Display ticket #%1"
+msgstr "הצג פנייה #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr ""
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "×ל תרענן דף ×–×”."
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "×ל תר××” ×ת תוצ×ות החיפוש"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "הורד"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "ת×ריך יעד"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr ""
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr ""
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr ""
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr ""
+
+#: html/Admin/Queues/Templates.html:42
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr ""
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:118
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:91
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr ""
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2622 lib/RT/Ticket_Overlay.pm:2690
+msgid "Either base or target must be specified"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "××™-מייל"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:51
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "מופעל (מחיקת סימון תיבה זו מבטלת ×ת קבוצה זו)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:107 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:428
+msgid "Enter multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:425
+msgid "Enter one value"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "הכנס פניות ×ו כתובות כדי לקשר פניות ×ליהן. הפרד ×¢×¨×›×™× ×¨×‘×™× ×‘×מצעות רווחי×."
+
+#: html/Elements/Login:39 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr ""
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr ""
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr ""
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr ""
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "פברו×ר"
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "פברו×ר"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr ""
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "עדיפות סופית"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr ""
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Find new/open tickets"
+msgstr ""
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "×ž×¦× ×× ×©×™× ×©"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "×ž×¦× ×¤× ×™×•×ª"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr ""
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "עמוד ר×שון"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr ""
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "הכרח שינוי"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "נמצ×ו %1 פניות"
+
+#: lib/RT/Interface/Web.pm:902
+msgid "Found Object"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr ""
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "שישי"
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "×›×•×ª×¨×™× ×ž×œ××™×"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:593
+#. ($New->Name)
+msgid "Given to %1"
+msgstr ""
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "גלוב×לי"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr ""
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "חפש"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr ""
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr ""
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr ""
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "קבוצה"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "קבוצה %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "זכויות קבוצה"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr ""
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "קבוצות"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr ""
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr ""
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "הסטוריה"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr ""
+
+#: html/Elements/Tabs:44
+msgid "Homepage"
+msgstr "דף הבית"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "זהות"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr ""
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr ""
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "×× ×¢×“×›× ×ª משהו לעיל, ×ל תשכח ל"
+
+#: lib/RT/Interface/Web.pm:894
+msgid "Illegal value for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:897
+msgid "Immutable field"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr ""
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr ""
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3796
+msgid "Internal Error"
+msgstr ""
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr ""
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:899
+msgid "Invalid data"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:439
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr ""
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3434
+msgid "Invalid value for custom field"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:346
+msgid "Invalid value for status"
+msgstr ""
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr ""
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "ינו×ר"
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "ינו×ר"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr ""
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "יולי"
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "יולי"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "ג'מבו"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "יוני"
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "יוני"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr ""
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "מגע ×חרון"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "קשר ×חרון"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "נודע ל×חרונה"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "עדכון ×חרון"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "נותרה"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "תן למשתמש זה לגשת ל R"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "תן ×פשרות להעניק זכויות למשתמש ×–×”"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2704
+msgid "Link already exists"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2716
+msgid "Link could not be created"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2724 lib/RT/Ticket_Overlay.pm:2734
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2645
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2651
+msgid "Link not found"
+msgstr ""
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "קישורי×"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "מיקו×"
+
+#: lib/RT.pm:159
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr ""
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "מחובר כ %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:35 html/Elements/Login:44 html/Elements/Login:54
+msgid "Login"
+msgstr "כניסה"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "יצי××”"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "שנה בעלות ל"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "שנה סטטוס"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "שנה ת×ריך יעד"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "שנה ת×ריך פתרון"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "שנה ת×ריך 'הותחל'"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "שנה ת×ריך התחלה"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "שנע ת×ריך מגע ×חרון"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "שנה עדיפות"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "שנה תור"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "שנה נוש×"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "נהל קבוצות וחברות בקבוצות"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "נהל מ××¤×™×™× ×™× ×•×”×’×“×¨×•×ª ×©×ª×§×¤×™× ×œ×›×œ התורות"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "נהל תורות ומ××¤×™×™× ×™× ×¡×¤×¦×™×¤×™×™× ×œ×ª×•×¨×•×ª"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "נהל ×ž×©×ª×ž×©×™× ×•×¡×¤×¨×™×•×ª"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "מרץ"
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "מרץ"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "מ××™"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "מ××™"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "חבר הוסף"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "חבר נמחק"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "חבר ×œ× × ×ž×—×§"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "חבר ב"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "חברי×"
+
+#: lib/RT/Ticket_Overlay.pm:2891
+msgid "Merge Successful"
+msgstr "מיזוג הצליח"
+
+#: lib/RT/Ticket_Overlay.pm:2811
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "מיזוג נכשל. ×œ× ×™×›×•×œ×ª×™ להגדיר מזהה ×פקטיבי"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "מזג לתוך"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "הודעה"
+
+#: lib/RT/Interface/Web.pm:901
+msgid "Missing a primary key?: %1"
+msgstr "חסר מפתח ר×שי?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "נייד"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "טלפון נייד"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "שנה רשימת בקרת גישה"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr ""
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr ""
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr ""
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr ""
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr ""
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr ""
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr ""
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr ""
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr ""
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr ""
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "שנה פנייה מספר %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "שינוי פנוייה מספר %1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "שינוי פניות"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr ""
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr ""
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "שני"
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "עוד לגבי %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr ""
+
+#: html/SelfService/Elements/MyRequests:49
+#. ($friendly_status)
+msgid "My %1 tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "×”××™×©×•×¨×™× ×©×œ×™"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "×”××™×©×•×¨×™× ×©×œ×™"
+
+#: html/Admin/Elements/AddCustomFieldValue:33 html/Admin/Elements/EditCustomField:34 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "ש×"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "×©× ×‘×©×™×ž×•×©"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr ""
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "חדש"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "×¡×™×¡×ž× ×—×“×©×”"
+
+#: etc/initialdata:317 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "×™×—×¡×™× ×—×“×©×™×"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "חיפוש חדש"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "קבוצה חדשה"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "×¡×™×¡×ž× ×—×“×©×”"
+
+#: lib/RT/User_Overlay.pm:647
+msgid "New password notification sent"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "תור חדש"
+
+#: NOT FOUND IN SOURCE
+msgid "New request"
+msgstr "בקשה חדשה"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "זכויות חדשות"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "סקריפ חדש"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "חיפוש חדש"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:50
+msgid "New template"
+msgstr "תבנית חדשה"
+
+#: html/SelfService/Elements/Tabs:48
+msgid "New ticket"
+msgstr "פנייה חדשה"
+
+#: lib/RT/Ticket_Overlay.pm:2778
+msgid "New ticket doesn't exist"
+msgstr "פנייה חדשה ×œ× ×§×™×™×ž×ª"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "משתמש חדש"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "משתמש חדש שנקר×"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "×¦×•×¤×™× ×—×“×©×™×"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "הב×"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "דף הב×"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "כינוי"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "כינוי"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr ""
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr ""
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr ""
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr ""
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:46
+msgid "No action"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:896
+msgid "No column specified"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr ""
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr ""
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr ""
+
+#: lib/RT/Users_Overlay.pm:145
+msgid "No group specified"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:865
+msgid "No password set"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:342
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:152
+msgid "No permission to create users"
+msgstr ""
+
+#: html/SelfService/Display.html:118
+msgid "No permission to display that ticket"
+msgstr ""
+
+#: html/SelfService/Update.html:52
+msgid "No permission to view update ticket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr ""
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr ""
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr ""
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:478 lib/RT/Transaction_Overlay.pm:516
+msgid "No transaction type specified"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr ""
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr ""
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:893
+msgid "No value sent to _Set!\\n"
+msgstr ""
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "××£ ×חד"
+
+#: lib/RT/Interface/Web.pm:898
+msgid "Nonexistant field?"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Not logged in"
+msgstr "×œ× ×‘×ª×•×š המערכת"
+
+#: html/Elements/Header:59
+msgid "Not logged in."
+msgstr "×œ× ×‘×ª×•×š המערכת."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "×œ× ×”×•×–×Ÿ"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Not yet implemented...."
+msgstr ""
+
+#: html/Approvals/Elements/Approve:49
+msgid "Notes"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:650
+msgid "Notification could not be sent"
+msgstr ""
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr ""
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr ""
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr ""
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr ""
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr ""
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr ""
+
+#: etc/initialdata:319 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr ""
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr ""
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr ""
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr ""
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr ""
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "נובמבר"
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "נובמבר"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr ""
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr ""
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "×וקטובר"
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "×וקטובר"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "ב"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr ""
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr ""
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr ""
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr ""
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr ""
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr ""
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr ""
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "הצג רק ××™×©×•×¨×™× ×¢×‘×•×¨ בקשות שנוצרו ×חרי %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "הצג רק ××™×©×•×¨×™× ×¢×‘×•×¨ בקשות שנוצרו לפני %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "פתוח"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "פתח"
+
+#: NOT FOUND IN SOURCE
+msgid "Open requests"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:42
+msgid "Open tickets"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr ""
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "סידור ומיון"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "×רגון"
+
+#: html/Approvals/Elements/Approve:33
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr ""
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "בעלי×"
+
+#: lib/RT/Ticket_Overlay.pm:3071
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:582
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "הבעלי×"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "ביפר"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:47
+msgid "Parents"
+msgstr "הורי×"
+
+#: html/Elements/Login:52 html/User/Prefs.html:61
+msgid "Password"
+msgstr "סיסמ×"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "מזכיר סיסמ×"
+
+#: lib/RT/User_Overlay.pm:169 lib/RT/User_Overlay.pm:868
+msgid "Password too short"
+msgstr "×¡×™×¡×ž× ×§×¦×¨×” מדי"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "סיסמ×: %1"
+
+#: html/Admin/Users/Modify.html:293
+msgid "Passwords do not match."
+msgstr "הסיסמ×ות ×ינן תו×מות"
+
+#: html/User/Prefs.html:174
+msgid "Passwords do not match. Your password has not been changed"
+msgstr ""
+
+#: html/Ticket/Elements/ShowSummary:45 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "×נשי×"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:101 lib/RT/CustomField_Overlay.pm:202 lib/RT/CustomField_Overlay.pm:234 lib/RT/CustomField_Overlay.pm:511 lib/RT/CustomField_Overlay.pm:91 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2603 lib/RT/Ticket_Overlay.pm:2675 lib/RT/Ticket_Overlay.pm:2769 lib/RT/Ticket_Overlay.pm:2784 lib/RT/Ticket_Overlay.pm:2978 lib/RT/Ticket_Overlay.pm:3206 lib/RT/Ticket_Overlay.pm:3404 lib/RT/Ticket_Overlay.pm:3566 lib/RT/Ticket_Overlay.pm:3618 lib/RT/Ticket_Overlay.pm:3783 lib/RT/Transaction_Overlay.pm:466 lib/RT/Transaction_Overlay.pm:473 lib/RT/Transaction_Overlay.pm:502 lib/RT/Transaction_Overlay.pm:509 lib/RT/User_Overlay.pm:1355 lib/RT/User_Overlay.pm:570 lib/RT/User_Overlay.pm:605 lib/RT/User_Overlay.pm:861 lib/RT/User_Overlay.pm:962
+msgid "Permission Denied"
+msgstr ""
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "קבוצות ×ישיות"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "קבוצות ×ישיות"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "קבוצות ×ישיות"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "מספרי טלפון"
+
+#: NOT FOUND IN SOURCE
+msgid "Placeholder"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr ""
+
+#: html/Elements/Header:52 html/Elements/Tabs:53 html/SelfService/Elements/Tabs:51 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "מ×פייני×"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr ""
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "הקוד×"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "דף קוד×"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:54 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "עדיפות"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr ""
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr ""
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr ""
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr ""
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:33 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "תור"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:44
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr ""
+
+#: html/Ticket/Create.html:205
+msgid "Queue could not be loaded."
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr ""
+
+#: html/SelfService/Display.html:71 lib/RT/CustomField_Overlay.pm:98
+msgid "Queue not found"
+msgstr ""
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "תורי×"
+
+#: html/Elements/Quicksearch:25
+msgid "Quick search"
+msgstr "חיפוש מהיר"
+
+#: html/Elements/Login:44
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr ""
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "ניהול RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr ""
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr ""
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT ממבט כולל"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr ""
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT / %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr ""
+
+#: html/Elements/Login:92
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr ""
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "×©× ×מיתי"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "×©× ×מיתי"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:71
+msgid "Referred to by"
+msgstr "×ž×ª×™×™×—×¡×™× ×ליו"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:61
+msgid "Refers to"
+msgstr "מתייחס ל"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "חדד ×ת החיפוש"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "רענן דף זה כל %1 דקות."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:62 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "×™×—×¡×™× ×¢× ×¤× ×™×•×ª ×חרות"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "הסר העתק ניהולי"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "הסר העתק"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "הסר מבקש"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "הגב"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "מענה לפנייה"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "מבקש"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "כתובת ×”××™-מייל של המבקש"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "מבקש(×™×)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "כתובת הפונה"
+
+#: html/SelfService/Create.html:41 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "מבקשי×"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr ""
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "×פס נתוני×"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "בית"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "פתור"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "פתור פנייה #%1 (%2)"
+
+#: etc/initialdata:308 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "נפתר"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "תגובה למבקשי×"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "תוצ×ות"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "תוצ×ות לעמוד"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "הקלד שנית:"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr ""
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:792
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:825
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr ""
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "שבת"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyLinks.html:39 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "שמור שינויי×"
+
+#: NOT FOUND IN SOURCE
+msgid "Save changes"
+msgstr "שמור שינויי×"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr ""
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr ""
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "חיפוש"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr ""
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr ""
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr ""
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "בחר קבוצה"
+
+#: lib/RT/CustomField_Overlay.pm:422
+msgid "Select multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:419
+msgid "Select one value"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr ""
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55 html/Admin/Queues/Templates.html:47
+msgid "Select template"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Self Service"
+msgstr ""
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr ""
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr ""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr ""
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr ""
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr ""
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr ""
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "ספטמבר"
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "ספטמבר"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "הצג בקשות ש×ושרו"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "הצג בקשות שנדחו"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "הצג בקשות ממתינות"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "הצג בקשות שממתינות ל××™×©×•×¨×™× ×חרי×"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "חתימה"
+
+#: NOT FOUND IN SOURCE
+msgid "Signed in as %1"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr ""
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr ""
+
+#: html/Admin/Elements/AddCustomFieldValue:29
+msgid "Sort"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Sort key"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "סדר תוצ×ות על פי"
+
+#: NOT FOUND IN SOURCE
+msgid "SortOrder"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "מושהה"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr ""
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "התחיל"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr ""
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "מתחיל ב"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "מדינה"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "מצב"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:528
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "גנוב"
+
+#: lib/RT/Transaction_Overlay.pm:587
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "נגנב מ %1"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:57 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:32 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "נוש×"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:609
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "× ×•×©× ×©×•× ×” ל %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "שלח"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "הצליח"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "ר×שון"
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "סופר-משתמש"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "מערכת"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:791 lib/RT/Interface/Web.pm:824
+msgid "System Error"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr ""
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr ""
+
+#: lib/RT/CurrentUser.pm:318
+msgid "TEST_STRING"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "קח"
+
+#: lib/RT/Transaction_Overlay.pm:573
+msgid "Taken"
+msgstr "נלקחה"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr ""
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr ""
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr ""
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:892
+msgid "That is already the current value"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:243
+msgid "That is not a value for this custom field"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That principal already has that right"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3210
+msgid "That ticket has unresolved dependencies"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "That user already has that right"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3020
+msgid "That user already owns that ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2986
+msgid "That user does not exist"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:336
+msgid "That user is already unprivileged"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:328
+msgid "That user is now privileged"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:349
+msgid "That user is now unprivileged"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3012
+msgid "That user may not own tickets in that queue"
+msgstr ""
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr ""
+
+#: html/SelfService/Display.html:32 html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "מידע בסיסי"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr ""
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr ""
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:895
+msgid "The new value has been set."
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr ""
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr ""
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:251
+msgid "This transaction appears to have no content"
+msgstr ""
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr ""
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "חמישי"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr "פנייה"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "פנייה מספר %1 עדכון ג'מבו: %2"
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "פנייה מספר %1 עדכון ג'מבו: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:587 lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr ""
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr ""
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr ""
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Id"
+msgstr ""
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "מצורף לפנייה"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:496 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:520
+msgid "Ticket created"
+msgstr "פנייה נוצרה"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:525
+msgid "Ticket deleted"
+msgstr "פנייה נמחקה"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "מזהה פנייה ×œ× × ×ž×¦×"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "פנייה נמחקה"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "פנייה ×œ× × ×ž×¦××”"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "סטטוס פנייה שונה"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "צופי הפנייה"
+
+#: html/Elements/Tabs:47
+msgid "Tickets"
+msgstr "פניות"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr ""
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr ""
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "זמן נותר"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "זמן עבודה"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "זמן נותר"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "זמן להציג"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "זמן עבודה"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr ""
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr ""
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr ""
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:640
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:699
+msgid "Transactions are immutable"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "שלישי"
+
+#: html/Admin/Elements/EditCustomField:44 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "סוג"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "×œ× ×ž×™×™×•×©×"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr ""
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr ""
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "×œ× ×ž×•×’×‘×œ"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:569
+msgid "Untaken"
+msgstr ""
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "עדכן"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr ""
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "סוג עדכון"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "עדכן ×ת כל הפניות לעיל בבת ×חת"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "עדכן ××™-מייל"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "עדכן ש×"
+
+#: lib/RT/Interface/Web.pm:409
+msgid "Update not recorded."
+msgstr ""
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "עדכן פניות נבחרות"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "עדכן חתימה"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "עדכן פנייה"
+
+#: NOT FOUND IN SOURCE
+msgid "Update ticket # %1"
+msgstr ""
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:47
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr ""
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:407
+msgid "Update type was neither correspondence nor comment."
+msgstr ""
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "עודכן"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr ""
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "מזהה המשתמש"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "מזהה המשתמש"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "זכויות המשתמש"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "×©× ×ž×©×ª×ž×©"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "משתמשי×"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:57
+msgid "Values"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "VrijevormEnkele"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr ""
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "רביעי"
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr ""
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr ""
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "בכל ×¤×¢× ×©×“×‘×¨ ×›×œ×©×”×•× ×§×•×¨×”"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "בכל ×¤×¢× ×©×¤× ×™×™×” נסגרת"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "בכל ×¤×¢× ×©×‘×¢×œ×™ הפנייה משתנה"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "בכל מצב שתור הפנייה משתנה"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "בכל ×¤×¢× ×©×ž×¦×‘ הפנייה משתנה"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "בכל ×¤×¢× ×©×ž×¦×‘ מוגדר על ידי משתמש קורה"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "בכל ×¤×¢× ×©×”×¢×¨×” מגיעה ב"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "בכל ×¤×¢× ×©×ª×›×ª×•×‘×ª מגיעה ב"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "עבודה"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "טלפון בעבודה"
+
+#: html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "זמן טיפול"
+
+#: lib/RT/Ticket_Overlay.pm:3123
+msgid "You already own this ticket"
+msgstr "×תה כבר ×”×‘×¢×œ×™× ×©×œ פנייה זו"
+
+#: html/autohandler:108
+msgid "You are not an authorized user"
+msgstr "×ינך משתמש מורשה"
+
+#: lib/RT/Ticket_Overlay.pm:2998
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "×תה יכול להציב פניה רק ×× ×תה ×”×‘×¢×œ×™× ×©×œ×”, ×ו ש×ין לה בעלי×"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "×ין לך הרש××” כדי לר×ות ×ת פנייה זו.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "מצ×ת %1 פניות בתור %2"
+
+#: html/NoAuth/Logout.html:31
+msgid "You have been logged out of RT."
+msgstr "התנתקת מהמערכת."
+
+#: html/SelfService/Display.html:78
+msgid "You have no permission to create tickets in that queue."
+msgstr "×ין לך הרש×ות ליצור פניות בתור ×–×”."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "×ינך מורשה ליצור פניות בתור ×–×”."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "הנך מוזמן להיכנס שנית"
+
+#: NOT FOUND IN SOURCE
+msgid "Your %1 requests"
+msgstr "%1 הבקשות שלך"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "מנהל המערכת ×œ× ×”×’×“×™×¨ ×ת כתובות הדו×ר שמפעילות ×ת התוכנה כמו שצריך"
+
+#: etc/initialdata:435 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "בקשתך ×ושרה על ידי %1. ייתכן ש××™×©×•×¨×™× × ×•×¡×¤×™× ×¢×“×™×™×Ÿ ממתיני×."
+
+#: etc/initialdata:469 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "בקשתך ×ושרה."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "בקשתך נדחתה"
+
+#: etc/initialdata:390 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "בקשתך נדחתה."
+
+#: html/autohandler:127
+msgid "Your username or password is incorrect"
+msgstr "×©× ×”×ž×©×ª×ž×© ו/×ו ×”×¡×™×¡×ž× ××™× × × ×›×•× ×™×"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "מיקוד"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[×œ×œ× × ×•×©×]"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "שהוענק ל%1"
+
+#: html/SelfService/Closed.html:28
+msgid "closed"
+msgstr "סגור"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "מכיל"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "תוכן"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "סוג התוכן"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "התכתבות (כנר××”) ×œ× × ×©×œ×—×”"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "התכתבות נשלחה"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "ימי×"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr ""
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "מחק"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "מחוק"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "×œ× ×ž×›×™×œ"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "×œ× ×ž×›×™×œ"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "שווה ל"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "×©× ×§×•×‘×¥"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "גדול מ"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "קבוצה %1"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "שעות"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "מזהה"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "הו×"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "×”×•× ×œ×"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "פחות מ"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "מכיל"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "דקות"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "דקות"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr ""
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "חודשי×"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "חדש"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "×ין ערך"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "×ין"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "×œ× ×©×•×•×” ל"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr ""
+
+#: html/SelfService/Elements/MyRequests:61 lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "פתוח"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "נדחה"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "פתור"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "מושהה"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr ""
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr ""
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr ""
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr ""
diff --git a/rt/lib/RT/I18N/i_default.pm b/rt/lib/RT/I18N/i_default.pm
new file mode 100644
index 0000000..1547026
--- /dev/null
+++ b/rt/lib/RT/I18N/i_default.pm
@@ -0,0 +1,86 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::I18N::i_default;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::I18N);
+
+eval "require RT::I18N::i_default_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/I18N/i_default_Vendor.pm});
+eval "require RT::I18N::i_default_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/I18N/i_default_Local.pm});
+
+1;
+
+__END__
+
+This class just zero-derives from the project base class, which
+is English for this project. i-default is "English at least". It
+wouldn't be a bad idea to make our i-default messages be English
+plus, say, French -- i-default is meant to /contain/ English, not
+be /just/ English. If you have all your English messages in
+Whatever::en and all your French messages in Whatever::fr, it
+would be straightforward to define Whatever::i_default's as a subclass
+of Whatever::en, but for every case where a key gets you a string
+(as opposed to a coderef) from %Whatever::en::Lexicon and
+%Whatever::fr::Lexicon, you could make %Whatever::i_default::Lexicon
+be the concatenation of them both. So: "file '[_1]' not found.\n" and
+"fichier '[_1]' non trouve\n" could make for an
+%Whatever::i_default::Lexicon entry of
+"file '[_1]' not found\nfichier '[_1]' non trouve.\n".
+
+There may be entries, however, where that is undesirable.
+And in any case, it's not feasable once you have an _AUTO lexicon
+in the mix, as wo do here.
+
+
+
+RFC 2277 says:
+
+4.5. Default Language
+
+ When human-readable text must be presented in a context where the
+ sender has no knowledge of the recipient's language preferences (such
+ as login failures or E-mailed warnings, or prior to language
+ negotiation), text SHOULD be presented in Default Language.
+
+ Default Language is assigned the tag "i-default" according to the
+ procedures of RFC 1766. It is not a specific language, but rather
+ identifies the condition where the language preferences of the user
+ cannot be established.
+
+ Messages in Default Language MUST be understandable by an English-
+ speaking person, since English is the language which, worldwide, the
+ greatest number of people will be able to get adequate help in
+ interpreting when working with computers.
+
+ Note that negotiating English is NOT the same as Default Language;
+ Default Language is an emergency measure in otherwise unmanageable
+ situations.
+
+ In many cases, using only English text is reasonable; in some cases,
+ the English text may be augumented by text in other languages.
+
+
diff --git a/rt/lib/RT/I18N/it.po b/rt/lib/RT/I18N/it.po
new file mode 100644
index 0000000..55a478b
--- /dev/null
+++ b/rt/lib/RT/I18N/it.po
@@ -0,0 +1,6175 @@
+# translation of it.po to
+# translation of it.po to
+# translation of it.po to
+# translation of it.po to
+# translation of it.po to
+# translation of it.po to
+# Copyright (c) 2002 Jesse Vincent <jesse@bestpractical.com>
+# root <root@delpreterh>, 2003
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: it\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2003-07-21 22:20+0200\n"
+"Last-Translator: root <root@delpreterh>\n"
+"Language-Team: <en@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.0.1\n"
+
+#: html/Elements/MyRequests:27
+#: html/Elements/MyTickets:27
+msgid "#"
+msgstr "n°"
+
+#. ($QueueObj->id)
+#: html/Admin/Queues/Scrip.html:54
+msgid "#%1"
+msgstr "n°%1"
+
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+#: html/Approvals/Elements/Approve:26
+#: html/Approvals/Elements/ShowDependency:49
+#: html/SelfService/Display.html:24
+#: html/Ticket/Display.html:25
+#: html/Ticket/Display.html:29
+msgid "#%1: %2"
+msgstr "n°%1: %2"
+
+#. ($s, $time_unit)
+#: lib/RT/Date.pm:336
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+#: lib/RT/Tickets_Overlay.pm:770
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+#: lib/RT/Date.pm:372
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+#: lib/RT/Ticket_Overlay.pm:3504
+#: lib/RT/Transaction_Overlay.pm:556
+#: lib/RT/Transaction_Overlay.pm:598
+msgid "%1 %2 added"
+msgstr "%1 %2 aggiunto"
+
+#. ($s, $time_unit)
+#: lib/RT/Date.pm:333
+msgid "%1 %2 ago"
+msgstr "%1 %2 fa"
+
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+#: lib/RT/Ticket_Overlay.pm:3510
+#: lib/RT/Transaction_Overlay.pm:563
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 cambiato in %3"
+
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+#: lib/RT/Ticket_Overlay.pm:3507
+#: lib/RT/Transaction_Overlay.pm:559
+#: lib/RT/Transaction_Overlay.pm:604
+msgid "%1 %2 deleted"
+msgstr "%1 %2 eliminato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 %2 of group %3"
+msgstr "%1 %2 del gruppo %3"
+
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+#: html/Admin/Elements/EditScrips:43
+#: html/Admin/Elements/ListGlobalScrips:27
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 con il modello %3"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 questo ticket\\n"
+
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+#: html/Search/Listing.html:56
+msgid "%1 - %2 shown"
+msgstr "Tickets da %1 a %2"
+
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+#: bin/rt-crontool:168
+#: bin/rt-crontool:175
+#: bin/rt-crontool:181
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Un parametro da passare a %2"
+
+#. ("--verbose")
+#: bin/rt-crontool:184
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Lo stato dell'output è stato aggiornato su STDOUT"
+
+#. ("--action")
+#: bin/rt-crontool:178
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Specificare l'azione che si vuole eseguire"
+
+#. ("--condition")
+#: bin/rt-crontool:172
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Specificare la condizione che si vuole utilizzare"
+
+#. ("--search")
+#: bin/rt-crontool:165
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Specificare la ricerca che si vuole utilizzare"
+
+#. ($self->Id)
+#: lib/RT/ScripAction_Overlay.pm:121
+msgid "%1 ScripAction loaded"
+msgstr "%1 ScripAction caricato"
+
+#. ($args{'Value'}, $cf->Name)
+#: lib/RT/Ticket_Overlay.pm:3537
+msgid "%1 added as a value for %2"
+msgstr "%1 aggiunto(i) come valore di %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "gli alias %1 necessitano di un TicketId su cui lavorare"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "gli alias %1 necessitano di un TicketId su cui lavorare"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "gli alias %1 necessitano di un TicketId per funzionare con (dopo %2) %3"
+
+#. ($args{'Base'})
+#. ($args{'Target'})
+#: lib/RT/Link_Overlay.pm:116
+#: lib/RT/Link_Overlay.pm:123
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 sembra essere un oggetto locale, ma è introvabile nel database"
+
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+#: html/Ticket/Elements/ShowDates:51
+#: lib/RT/Transaction_Overlay.pm:480
+msgid "%1 by %2"
+msgstr "%1 per %2"
+
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+#: lib/RT/Transaction_Overlay.pm:534
+#: lib/RT/Transaction_Overlay.pm:623
+#: lib/RT/Transaction_Overlay.pm:632
+#: lib/RT/Transaction_Overlay.pm:635
+msgid "%1 changed from %2 to %3"
+msgstr "%1 cambiato(1) da %2 a %3"
+
+#: lib/RT/Interface/Web.pm:890
+msgid "%1 could not be set to %2."
+msgstr "%1 non può essere impostato a %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 non ha potuto iniziare una transazione (%2)\\n"
+
+#. ($self)
+#: lib/RT/Ticket_Overlay.pm:2816
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 non ho potuto mettere lo stato a risolto. Il database RT può essere inconsistente."
+
+#. ($rows)
+#: html/Elements/MyTickets:24
+msgid "%1 highest priority tickets I own..."
+msgstr "I miei %1 tickets a più alta priorità che possiedo..."
+
+#. ($rows)
+#: html/Elements/MyRequests:24
+msgid "%1 highest priority tickets I requested..."
+msgstr "I miei %1 tickets a più alta priorità che ho richiesto..."
+
+#. ($0)
+#: bin/rt-crontool:160
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 è uno strumento per lavorare sui tickets da uno schedulatore esterno, come cron"
+
+#. ($principal->Object->Name, $args{'Type'})
+#: lib/RT/Queue_Overlay.pm:742
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 non è più un %2 per questa coda."
+
+#. ($principal->Object->Name, $args{'Type'})
+#: lib/RT/Ticket_Overlay.pm:1569
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 non è più un %2 per questo ticket."
+
+#. ($args{'Value'}, $cf->Name)
+#: lib/RT/Ticket_Overlay.pm:3593
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 non è più un valore per il campo personalizzato %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 non è un identificativo di coda valido"
+
+#. ($TimeWorked)
+#: html/Ticket/Elements/ShowBasics:35
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 not shown"
+msgstr "%1 non mostrato"
+
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+#: html/User/Elements/DelegateRights:75
+msgid "%1 rights"
+msgstr "Diritti di %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 riuscito\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "Tipo %1 sconosciuto per $MessageId"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 type unknown for %2"
+msgstr "Tipo %1 sconosciuto per %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 è stato creato senza un CurrentUser\\n"
+
+#. (ref $self)
+#: lib/RT/Action/ResolveMembers.pm:41
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 risolverà tutti i membri di un gruppo di ticket risolto."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 bloccherà una BASE [locale] se dipende o è membro di una richeista linkata."
+
+#. ($self)
+#: lib/RT/Transaction_Overlay.pm:432
+msgid "%1: no attachment specified"
+msgstr "%1: nessun allegato specificato"
+
+#. ($size)
+#: html/Ticket/Elements/ShowTransaction:101
+msgid "%1b"
+msgstr "%1b"
+
+#. (int($size/102.4)/10)
+#: html/Ticket/Elements/ShowTransaction:98
+msgid "%1k"
+msgstr "%1k"
+
+#. ($args{'Status'})
+#: lib/RT/Ticket_Overlay.pm:1139
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' è uno stato non valido"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' non è un'azione conosciuta. "
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(Spunta la casella per cancellare il membro di un gruppo)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Spunta la casella per cancellare uno scrip)"
+
+#: html/Admin/Elements/EditCustomFieldValues:24
+#: html/Admin/Elements/EditQueueWatchers:28
+#: html/Admin/Elements/EditScrips:34
+#: html/Admin/Elements/EditTemplates:35
+#: html/Admin/Groups/Members.html:51
+#: html/Ticket/Elements/EditLinks:32
+#: html/Ticket/Elements/EditPeople:45
+#: html/User/Groups/Members.html:54
+msgid "(Check box to delete)"
+msgstr "(Spunta la casella per cancellare)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(Spunta la casella per cancellare)"
+
+#: html/Ticket/Create.html:177
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Inserire il numero di tickets o gli URL, separati da spazi)"
+
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+#: html/Admin/Queues/Modify.html:53
+#: html/Admin/Queues/Modify.html:59
+msgid "(If left blank, will default to %1"
+msgstr "Se lasciato vuoto, valore di default : %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(No Value)"
+msgstr "(Nessun Valore)"
+
+#: html/Admin/Elements/EditCustomFields:32
+#: html/Admin/Elements/ListGlobalCustomFields:31
+msgid "(No custom fields)"
+msgstr "Non ci sono campi personalizzati"
+
+#: html/Admin/Groups/Members.html:49
+#: html/User/Groups/Members.html:52
+msgid "(No members)"
+msgstr "(Nessun membro)"
+
+#: html/Admin/Elements/EditScrips:31
+#: html/Admin/Elements/ListGlobalScrips:31
+msgid "(No scrips)"
+msgstr "(Nessuno Scrip)"
+
+#: html/Admin/Elements/EditTemplates:30
+msgid "(No templates)"
+msgstr "Nessun modello"
+
+#: html/Ticket/Update.html:84
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Invia per copia nascosta questo aggiornamento ad una lista di indirizzi email separati da virgole. Ciò <b>non cambierà</b> i destinatari dei successivi aggiornamenti.)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Invia per copia nascosta questo aggiornamento ad una lista di indirizzi email separati da virgole. Ciò <b>non cambierà</b> i destinatari dei successivi aggiornamenti.)"
+
+#: html/Ticket/Create.html:78
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Invia una copia di questo aggiornamento ad una lista di indirizzi email amministrativi separati da virgole. Queste persone <b>riceveranno</b> i successivi aggiornamenti.)"
+
+#: html/Ticket/Update.html:80
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Invia una copia di questo aggiornamento ad una lista di indirizzi email separati da virgole. Ciò <b>non cambierà</b> i destinatari dei successivi aggiornamenti.)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Invia una copia di questo aggiornamento ad una lista di indirizzi email separati da virgole. Ciò <b>non cambierà</b> i destinatari dei successivi aggiornamenti.)"
+
+#: html/Ticket/Create.html:68
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Invia una copia di questo aggiornamento ad una lista di indirizzi email separati da virgole. Queste persone <b>riceveranno</b> i successivi aggiornamenti.)"
+
+#: html/Admin/Groups/index.html:32
+#: html/User/Groups/index.html:32
+msgid "(empty)"
+msgstr "(vuoto)"
+
+#: html/Admin/Users/index.html:38
+msgid "(no name listed)"
+msgstr "(nessun nome)"
+
+#: html/Elements/MyRequests:42
+#: html/Elements/MyTickets:44
+msgid "(no subject)"
+msgstr "(nessun oggetto)"
+
+#: html/Admin/Elements/SelectRights:47
+#: html/Elements/SelectCustomFieldValue:29
+#: html/Ticket/Elements/EditCustomField:58
+#: html/Ticket/Elements/ShowCustomFields:35
+#: lib/RT/Transaction_Overlay.pm:533
+msgid "(no value)"
+msgstr "(nessun valore)"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "(only one ticket)"
+msgstr "(solo un ticket)"
+
+#: html/Elements/MyRequests:51
+#: html/Elements/MyTickets:54
+msgid "(pending approval)"
+msgstr "(in attesa di approvazione)"
+
+#: html/Elements/MyRequests:53
+#: html/Elements/MyTickets:56
+msgid "(pending other tickets)"
+msgstr "(in attea di altri tickets)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "(requestor's group)"
+msgstr "(gruppo del richiedente)"
+
+#: html/Admin/Users/Modify.html:49
+msgid "(required)"
+msgstr "(richiesto)"
+
+#: html/Ticket/Elements/ShowTransaction:104
+msgid "(untitled)"
+msgstr "(senza titolo)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "I miei 25 tickets che devo trattare con priorità più alta..."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "I miei 25 tickets che hor ichiesto con priorità più alta..."
+
+#: html/Ticket/Elements/ShowBasics:31
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:26
+msgid "<% $_ %>"
+msgstr ""
+
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+#: docs/design_docs/string-extraction-guide.txt:54
+#: html/Elements/CreateTicket:25
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Crea un ticket in\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Un modello vuoto"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "ACE Deleted"
+msgstr "ACE Eliminata"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "ACE Loaded"
+msgstr "ACE Caricata"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "ACE could not be deleted"
+msgstr "l'ACE non è stato possibile elimanarla"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "ACE could not be found"
+msgstr "l'ACE non è stato possibile trovarla"
+
+#: lib/RT/ACE_Overlay.pm:156
+#: lib/RT/Principal_Overlay.pm:180
+msgid "ACE not found"
+msgstr "ACE non trovata"
+
+#: lib/RT/ACE_Overlay.pm:830
+msgid "ACEs can only be created and deleted."
+msgstr "Le ACE possono essere solo create e cancellate."
+
+#: bin/rt-commit-handler:754
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Interruzione per evitare modifiche di ticket involontarie.\\n"
+
+#: html/User/Elements/Tabs:31
+msgid "About me"
+msgstr "A proposito"
+
+#: html/Admin/Users/Modify.html:79
+msgid "Access control"
+msgstr "Controllo di Accesso"
+
+#: html/Admin/Elements/EditScrip:56
+msgid "Action"
+msgstr "Azione"
+
+#. ($args{'ScripAction'})
+#: lib/RT/Scrip_Overlay.pm:146
+msgid "Action %1 not found"
+msgstr "Azione %1 non trovata"
+
+#: bin/rt-crontool:122
+msgid "Action committed."
+msgstr "Azione eseguita."
+
+#: bin/rt-crontool:118
+msgid "Action prepared..."
+msgstr "Azione preparata..."
+
+#: html/Search/Bulk.html:91
+msgid "Add AdminCc"
+msgstr "Aggiungi AdminCC"
+
+#: html/Search/Bulk.html:89
+msgid "Add Cc"
+msgstr "Aggiungi CC"
+
+#: html/Ticket/Create.html:113
+#: html/Ticket/Update.html:99
+msgid "Add More Files"
+msgstr "Aggiungi Altri Files"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Add Next State"
+msgstr "Aggiungi lo Stato Sucessivo"
+
+#: html/Search/Bulk.html:87
+msgid "Add Requestor"
+msgstr "Aggiungi il Richiedente"
+
+#: html/Admin/Elements/AddCustomFieldValue:24
+msgid "Add Value"
+msgstr "Aggiungi un Valore"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "Aggiungi una selezione di parole chiave a questa coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Add a new a global scrip"
+msgstr "Aggiungi un nuovo scrip globale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Aggiungi uno scrip a questa coda"
+
+#: html/Admin/Global/Scrip.html:54
+msgid "Add a scrip which will apply to all queues"
+msgstr "Aggiungi uno scrip da applicare a tutte le code"
+
+#: html/Search/Bulk.html:117
+msgid "Add comments or replies to selected tickets"
+msgstr "Agiungere commenti o repliche ai tickets selezionati"
+
+#: html/Admin/Groups/Members.html:41
+#: html/User/Groups/Members.html:38
+msgid "Add members"
+msgstr "Aggiungi membri"
+
+#: html/Admin/Queues/People.html:65
+#: html/Ticket/Elements/AddWatchers:27
+msgid "Add new watchers"
+msgstr "Aggiungi nuovi osservatori"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "AddNextState"
+msgstr "AggiungereStatoSuccessivo"
+
+#. ($args{'Type'})
+#: lib/RT/Queue_Overlay.pm:642
+msgid "Added principal as a %1 for this queue"
+msgstr "Aggiunto gruppo/utente come %1 per questa coda"
+
+#. ($self->loc($args{'Type'}))
+#: lib/RT/Ticket_Overlay.pm:1453
+msgid "Added principal as a %1 for this ticket"
+msgstr "Aggiunto gruppo/utente come %1 per questo ticket"
+
+#: html/Admin/Elements/ModifyUser:75
+#: html/Admin/Users/Modify.html:121
+#: html/User/Prefs.html:87
+msgid "Address1"
+msgstr "Inidirizzo1"
+
+#: html/Admin/Elements/ModifyUser:77
+#: html/Admin/Users/Modify.html:126
+#: html/User/Prefs.html:89
+msgid "Address2"
+msgstr "Indirizzo2"
+
+#: html/Ticket/Create.html:73
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr "Commento Amministrativo"
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr "Corrispondenza Amministrativa "
+
+#: html/Admin/Queues/index.html:24
+#: html/Admin/Queues/index.html:27
+msgid "Admin queues"
+msgstr "Amministra le code"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Admin users"
+msgstr "Amministra gli Utenti"
+
+#: html/Admin/Global/index.html:25
+#: html/Admin/Global/index.html:27
+msgid "Admin/Global configuration"
+msgstr "configurazione Amministratore/Globale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Admin/Groups"
+msgstr "Amministra/Gruppi"
+
+#: html/Admin/Queues/Modify.html:24
+#: html/Admin/Queues/Modify.html:28
+msgid "Admin/Queue/Basics"
+msgstr "Amministra/Code/Base"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "AmministraTuttiIGruppiPersonali"
+
+#: etc/initialdata:56
+#: html/Ticket/Elements/ShowPeople:38
+#: html/Ticket/Update.html:49
+#: lib/RT/ACE_Overlay.pm:88
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "AdminComment"
+msgstr "CommentoAmministratore"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "AdminCorrespondence"
+msgstr "CorrispondenzaAmministratore"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "AdminCustomFields"
+msgstr "AmministraCampiPersonalizzati"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "AdminGroup"
+msgstr "AmministraGruppi"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "AdminGroupMembership"
+msgstr "AmministraAppartenenzaGruppi"
+
+#: lib/RT/System.pm:58
+msgid "AdminOwnPersonalGroups"
+msgstr "AmministraPropriGruppiPersonali"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "AdminQueue"
+msgstr "AmministraCode"
+
+#: lib/RT/System.pm:59
+msgid "AdminUsers"
+msgstr "AmministraUtenti"
+
+#: html/Admin/Queues/People.html:47
+#: html/Ticket/Elements/EditPeople:53
+msgid "Administrative Cc"
+msgstr "Cc Amministrativa"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Admins"
+msgstr "Amministratori"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Advanced Search"
+msgstr "Ricerca avanzata"
+
+#: html/Elements/SelectDateRelation:35
+msgid "After"
+msgstr "Dopo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Age"
+msgstr "Età"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Alias for"
+msgstr "Alias per"
+
+#: html/Admin/Elements/EditCustomFields:95
+msgid "All Custom Fields"
+msgstr "Tutti i campi personalizzati"
+
+#: html/Admin/Queues/index.html:52
+msgid "All Queues"
+msgstr "Tutte le code"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Invia sempre un messaggio al richiedente inipendentemente dal mittente"
+
+#: html/Elements/Tabs:55
+msgid "Approval"
+msgstr "Approvazione"
+
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+#: html/Approvals/Display.html:45
+#: html/Approvals/Elements/ShowDependency:41
+#: html/Approvals/index.html:64
+msgid "Approval #%1: %2"
+msgstr "Approvazione n°%1: %2"
+
+#. ($ticket->Id)
+#: html/Approvals/index.html:53
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Approvazione n°%1: Note non registrate a causa di un errore di sistema"
+
+#. ($ticket->Id)
+#: html/Approvals/index.html:51
+msgid "Approval #%1: Notes recorded"
+msgstr "Approvazione n°%1: Note registrate"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Approval Details"
+msgstr "Dettagli dell'approvazione"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Approval diagram"
+msgstr "Diagramma dell'approvazione"
+
+#: html/Approvals/Elements/Approve:43
+msgid "Approve"
+msgstr "Approvare"
+
+#: etc/initialdata:437
+#: etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Note dell'approvatore: %1"
+
+#: lib/RT/Date.pm:413
+msgid "Apr."
+msgstr "Apr."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "April"
+msgstr "Aprile"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Ascending"
+msgstr "Ascendente"
+
+#: html/Search/Bulk.html:126
+#: html/SelfService/Update.html:32
+#: html/Ticket/ModifyAll.html:82
+#: html/Ticket/Update.html:99
+msgid "Attach"
+msgstr "Allegato"
+
+#: html/SelfService/Create.html:64
+#: html/Ticket/Create.html:109
+msgid "Attach file"
+msgstr "Allegare un file"
+
+#: html/Ticket/Create.html:97
+#: html/Ticket/Update.html:88
+msgid "Attached file"
+msgstr "File allegato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr "L'allegato '%1' non può essere caricato"
+
+#: lib/RT/Transaction_Overlay.pm:440
+msgid "Attachment created"
+msgstr "Allegato creato"
+
+#: lib/RT/Tickets_Overlay.pm:1188
+msgid "Attachment filename"
+msgstr "Nome file dell'allegato"
+
+#: html/Ticket/Elements/ShowAttachments:25
+msgid "Attachments"
+msgstr "Allegati"
+
+#: lib/RT/Date.pm:417
+msgid "Aug."
+msgstr "Ago."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "August"
+msgstr "Agosto"
+
+#: html/Admin/Elements/ModifyUser:65
+msgid "AuthSystem"
+msgstr "AuthSystem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "RispostaAutomatica"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Risposta automatica ai richiedenti"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "RispostaAutomaticaAiRichiedenti"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Firma PGP non valida: %1\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Id di allegato errato. Impossibile trovare l'allegato '%1'\\n"
+
+#. ($val)
+#: bin/rt-commit-handler:826
+msgid "Bad data in %1"
+msgstr "Dati incorretti in %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Numero di transazione incorretto per l'allegato. %1 dovrebbe essere %2\\n"
+
+#: html/Admin/Elements/GroupTabs:38
+#: html/Admin/Elements/QueueTabs:38
+#: html/Admin/Elements/UserTabs:37
+#: html/Ticket/Elements/Tabs:89
+#: html/User/Elements/GroupTabs:37
+msgid "Basics"
+msgstr "Essenziale"
+
+#: html/Ticket/Update.html:82
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:87
+#: html/Admin/Global/GroupRights.html:84
+#: html/Admin/Global/Template.html:45
+#: html/Admin/Global/UserRights.html:53
+#: html/Admin/Groups/GroupRights.html:72
+#: html/Admin/Groups/Members.html:80
+#: html/Admin/Groups/Modify.html:55
+#: html/Admin/Groups/UserRights.html:54
+#: html/Admin/Queues/GroupRights.html:84
+#: html/Admin/Queues/Template.html:44
+#: html/Admin/Queues/UserRights.html:53
+#: html/User/Groups/Modify.html:55
+msgid "Be sure to save your changes"
+msgstr "Assicurarsi di salvare le modifiche"
+
+#: html/Elements/SelectDateRelation:33
+#: lib/RT/CurrentUser.pm:319
+msgid "Before"
+msgstr "Prima"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Begin Approval"
+msgstr "Inizio dell'approvazione"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Vuoto"
+
+#: html/Search/Listing.html:78
+msgid "Bookmarkable URL for this search"
+msgstr "URL predefinito per questa ricerca"
+
+#: html/Ticket/Elements/ShowHistory:38
+#: html/Ticket/Elements/ShowHistory:44
+msgid "Brief headers"
+msgstr "Intestazioni brevi"
+
+#: html/Search/Bulk.html:24
+#: html/Search/Bulk.html:25
+msgid "Bulk ticket update"
+msgstr "Modifica di massa dei tickets"
+
+#: lib/RT/User_Overlay.pm:1351
+msgid "Can not modify system users"
+msgstr "Gli utenti di sistema non possono essere modificati"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "Can this principal see this queue"
+msgstr "Il gruppo/utente può vedere questa coda"
+
+#: lib/RT/CustomField_Overlay.pm:205
+msgid "Can't add a custom field value without a name"
+msgstr "Impossibile aggiungere un valore di campo personalizzato senza un nome"
+
+#: lib/RT/Link_Overlay.pm:131
+msgid "Can't link a ticket to itself"
+msgstr "Non è possibile collegare un ticket a se stesso"
+
+#: lib/RT/Ticket_Overlay.pm:2793
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Impossibile unire un ticket ad un ticket già unito. Non dovrebbe mai comparire questo errore"
+
+#: lib/RT/Ticket_Overlay.pm:2611
+#: lib/RT/Ticket_Overlay.pm:2680
+msgid "Can't specifiy both base and target"
+msgstr "Impossibile specificare sia la base che il target"
+
+#. ($msg)
+#: html/autohandler:98
+msgid "Cannot create user: %1"
+msgstr "Impossibile creare l'utente: %1"
+
+#: etc/initialdata:50
+#: html/Admin/Queues/People.html:43
+#: html/SelfService/Create.html:48
+#: html/Ticket/Create.html:63
+#: html/Ticket/Elements/EditPeople:50
+#: html/Ticket/Elements/ShowPeople:34
+#: html/Ticket/Update.html:44
+#: html/Ticket/Update.html:77
+#: lib/RT/ACE_Overlay.pm:87
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:30
+msgid "Change password"
+msgstr "Cambiare la passwrd"
+
+#: html/Ticket/Create.html:100
+#: html/Ticket/Update.html:91
+msgid "Check box to delete"
+msgstr "Spunta la casella per eliminare"
+
+#: html/Admin/Elements/SelectRights:30
+msgid "Check box to revoke right"
+msgstr "Spunta la casella per revocare i diritti"
+
+#: html/Ticket/Create.html:182
+#: html/Ticket/Elements/EditLinks:130
+#: html/Ticket/Elements/EditLinks:68
+#: html/Ticket/Elements/ShowLinks:56
+msgid "Children"
+msgstr "Figli"
+
+#: html/Admin/Elements/ModifyUser:79
+#: html/Admin/Users/Modify.html:131
+#: html/User/Prefs.html:91
+msgid "City"
+msgstr "Città"
+
+#: html/Ticket/Elements/ShowDates:46
+msgid "Closed"
+msgstr "Chiuso"
+
+#: html/SelfService/Closed.html:24
+msgid "Closed Tickets"
+msgstr "Tickets Chiusi"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Closed requests"
+msgstr "Richieste chiuse"
+
+#: html/SelfService/Elements/Tabs:44
+msgid "Closed tickets"
+msgstr "Tickets chiusi"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Command not understood!\\n"
+msgstr "Comando non riconosciuto! \\n"
+
+#: html/Ticket/Elements/ShowTransaction:178
+#: html/Ticket/Elements/Tabs:152
+msgid "Comment"
+msgstr "Commento"
+
+#: html/Admin/Elements/ModifyQueue:44
+#: html/Admin/Queues/Modify.html:57
+msgid "Comment Address"
+msgstr "Inidirizzo di Commento"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Comment not recorded"
+msgstr "Commento non registrato"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Comment on tickets"
+msgstr "Commento sui tickets"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "CommentOnTicket"
+msgstr "CommentoSuiTickets"
+
+#: html/Admin/Elements/ModifyUser:34
+msgid "Comments"
+msgstr "Commenti"
+
+#: html/Ticket/ModifyAll.html:69
+#: html/Ticket/Update.html:69
+msgid "Comments (Not sent to requestors)"
+msgstr "Commenti (Non inviati ai richiedenti)"
+
+#: html/Search/Bulk.html:121
+msgid "Comments (not sent to requestors)"
+msgstr "Commenti (non inviati ai richiedenti)"
+
+#. ($name)
+#: html/Elements/ViewUser:26
+msgid "Comments about %1"
+msgstr "Commenti su %1"
+
+#: html/Admin/Users/Modify.html:184
+#: html/Ticket/Elements/ShowRequestor:43
+msgid "Comments about this user"
+msgstr "Commenti su questo utente"
+
+#: lib/RT/Transaction_Overlay.pm:542
+msgid "Comments added"
+msgstr "Commenti aggiunti"
+
+#: lib/RT/Action/Generic.pm:139
+msgid "Commit Stubbed"
+msgstr "tr(Commit Stubbed)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Compile Restrictions"
+msgstr "Restrizioni di compilazione"
+
+#: html/Admin/Elements/EditScrip:40
+msgid "Condition"
+msgstr "Condizione"
+
+#: bin/rt-crontool:108
+msgid "Condition matches..."
+msgstr "La condizione soddisfa..."
+
+#: lib/RT/Scrip_Overlay.pm:159
+msgid "Condition not found"
+msgstr "Condizione non trovata"
+
+#: html/Elements/Tabs:49
+msgid "Configuration"
+msgstr "Configurazione"
+
+#: html/SelfService/Prefs.html:32
+msgid "Confirm"
+msgstr "Confermare"
+
+#: html/Admin/Elements/ModifyUser:59
+msgid "ContactInfoSystem"
+msgstr "ContactInfoSystem"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "La data di contatto '%1' non può essere analizzata"
+
+#: html/Admin/Elements/ModifyTemplate:43
+#: html/Ticket/ModifyAll.html:86
+msgid "Content"
+msgstr "Contenuto"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Coould not create group"
+msgstr "Non è stato possibile creare il gruppo"
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr "Corrispondenza"
+
+#: html/Admin/Elements/ModifyQueue:38
+#: html/Admin/Queues/Modify.html:50
+msgid "Correspondence Address"
+msgstr "Inidirizzo di corrispondenza"
+
+#: lib/RT/Transaction_Overlay.pm:538
+msgid "Correspondence added"
+msgstr "Corrispondenza aggiunta"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Correspondence not recorded"
+msgstr "Corrispondenza non registrata"
+
+#: lib/RT/Ticket_Overlay.pm:3524
+msgid "Could not add new custom field value for ticket. "
+msgstr "Impossibile aggiungere un nuovo valore di campo personalizzato a questo ticket. "
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "Il valore di campo personalizzato non è stato possibile aggiungerlo. %1"
+
+#: lib/RT/Ticket_Overlay.pm:3030
+#: lib/RT/Ticket_Overlay.pm:3038
+#: lib/RT/Ticket_Overlay.pm:3054
+msgid "Could not change owner. "
+msgstr "Impossibile cambiare il proprietario. "
+
+#. ($msg)
+#: html/Admin/Elements/EditCustomField:84
+#: html/Admin/Elements/EditCustomFields:165
+msgid "Could not create CustomField"
+msgstr "Impossibile creare il campo personalizzato"
+
+#: html/User/Groups/Modify.html:76
+#: lib/RT/Group_Overlay.pm:473
+#: lib/RT/Group_Overlay.pm:480
+msgid "Could not create group"
+msgstr "Impossibile creare il gruppo"
+
+#. ($msg)
+#: html/Admin/Global/Template.html:74
+#: html/Admin/Queues/Template.html:71
+msgid "Could not create template: %1"
+msgstr "Impossibile creare il modello : %1"
+
+#: lib/RT/Ticket_Overlay.pm:1072
+#: lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Impossibile creare il ticket. Queue non impostata"
+
+#: lib/RT/User_Overlay.pm:207
+#: lib/RT/User_Overlay.pm:219
+#: lib/RT/User_Overlay.pm:237
+#: lib/RT/User_Overlay.pm:421
+msgid "Could not create user"
+msgstr "Impossibile creare l'utente"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "Impossibile creare l'osservatore per il richiedente"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Impossibile trovare il ticket numero %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Could not find group %1."
+msgstr "Impossibile trovare il gruppo %1."
+
+#: lib/RT/Queue_Overlay.pm:620
+#: lib/RT/Ticket_Overlay.pm:1421
+msgid "Could not find or create that user"
+msgstr "Impossibile trovare o creare questo utente"
+
+#: lib/RT/Queue_Overlay.pm:681
+#: lib/RT/Ticket_Overlay.pm:1500
+msgid "Could not find that principal"
+msgstr "Impossibile trovare questo gruppo/utente"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Could not find user %1."
+msgstr "Impossibile trovare l'utente %1."
+
+#: html/Admin/Groups/Members.html:87
+#: html/User/Groups/Members.html:89
+#: html/User/Groups/Modify.html:81
+msgid "Could not load group"
+msgstr "Impossibile caricare questo gruppo"
+
+#. ($args{'Type'})
+#: lib/RT/Queue_Overlay.pm:640
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Impossibile rendere questo gruppo/utente un %1 per questa coda"
+
+#. ($self->loc($args{'Type'}))
+#: lib/RT/Ticket_Overlay.pm:1442
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Impossibile rendere questo gruppo/utente un %1 per questo ticket"
+
+#. ($args{'Type'})
+#: lib/RT/Queue_Overlay.pm:739
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Impossibile eliminare questo gruppo/utente come un %1 per questa coda"
+
+#. ($args{'Type'})
+#: lib/RT/Ticket_Overlay.pm:1558
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Impossibile eliminare questo gruppo/utente come un %1 per questo ticket"
+
+#: lib/RT/Group_Overlay.pm:984
+msgid "Couldn't add member to group"
+msgstr "Impossibile aggiungere un membro a questo gruppo"
+
+#. ($Msg)
+#: lib/RT/Ticket_Overlay.pm:3534
+#: lib/RT/Ticket_Overlay.pm:3590
+msgid "Couldn't create a transaction: %1"
+msgstr "Impossibile creare una transazione : %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Impossibile capire che cosa fare con questa risposta gpg\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Gruppo introvabile\\n"
+
+#: lib/RT/Interface/Web.pm:899
+msgid "Couldn't find row"
+msgstr "Riga introvabile"
+
+#: lib/RT/Group_Overlay.pm:958
+msgid "Couldn't find that principal"
+msgstr "Gruppo/utente introvabile"
+
+#: lib/RT/CustomField_Overlay.pm:239
+msgid "Couldn't find that value"
+msgstr "Valore introvabile"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't find that watcher"
+msgstr "Osservatore introvabile"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Utente introvabile\\n"
+
+#. ($self->Id)
+#: lib/RT/CurrentUser.pm:111
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Impossibile caricare %1 dal database degli utenti.\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "KeywordSelects non è stato possibile caricarlo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Impossibile caricare il file di configurazione RT '%1' %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Impossibile caricare gli Scrips"
+
+#. ($id)
+#: html/Admin/Groups/GroupRights.html:87
+#: html/Admin/Groups/UserRights.html:74
+msgid "Couldn't load group %1"
+msgstr "Impossibile caricare il gruppo %1"
+
+#: lib/RT/Link_Overlay.pm:174
+#: lib/RT/Link_Overlay.pm:183
+#: lib/RT/Link_Overlay.pm:210
+msgid "Couldn't load link"
+msgstr "Impossibile caricare il link"
+
+#. ($id)
+#: html/Admin/Elements/EditCustomFields:146
+#: html/Admin/Queues/People.html:120
+msgid "Couldn't load queue"
+msgstr "Impossibile caricare la coda"
+
+#. ($id)
+#: html/Admin/Queues/GroupRights.html:99
+#: html/Admin/Queues/UserRights.html:71
+msgid "Couldn't load queue %1"
+msgstr "Impossibile caricare la coda %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't load scrip"
+msgstr "Impossibile caricare lo Scrip"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Couldn't load template"
+msgstr "Impossibile caricare il modello"
+
+#. ($id)
+#: html/Admin/Users/Prefs.html:78
+msgid "Couldn't load that user (%1)"
+msgstr "Impossibile caricare questo utente (%1)"
+
+#. ($id)
+#: html/SelfService/Display.html:108
+msgid "Couldn't load ticket '%1'"
+msgstr "Impossibile caricare il ticket '%1'"
+
+#: html/Admin/Elements/ModifyUser:85
+#: html/Admin/Users/Modify.html:148
+#: html/User/Prefs.html:97
+msgid "Country"
+msgstr "Stato"
+
+#: html/Admin/Elements/CreateUserCalled:25
+#: html/Ticket/Create.html:134
+#: html/Ticket/Create.html:194
+msgid "Create"
+msgstr "Crea"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Crea tickets"
+
+#: html/Admin/Elements/EditCustomField:74
+msgid "Create a CustomField"
+msgstr "Crea un campo Personalizzato"
+
+#. ($QueueObj->Name())
+#: html/Admin/Queues/CustomField.html:47
+msgid "Create a CustomField for queue %1"
+msgstr "Crea un campo Custom per la coda %1"
+
+#: html/Admin/Global/CustomField.html:47
+msgid "Create a CustomField which applies to all queues"
+msgstr "Crea un campo Personalizzato valido per tutte le code"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a new Custom Field"
+msgstr "Crea un nuovo campo Personalizzato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a new global scrip"
+msgstr "Crea un nuovo scrip globale"
+
+#: html/Admin/Groups/Modify.html:66
+#: html/Admin/Groups/Modify.html:92
+msgid "Create a new group"
+msgstr "Crea un nuovo gruppo"
+
+#: html/User/Groups/Modify.html:66
+#: html/User/Groups/Modify.html:91
+msgid "Create a new personal group"
+msgstr "Crea un nuovo gruppo personale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a new queue"
+msgstr "Crea una nuova coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a new scrip"
+msgstr "Crea un nuovo scrip"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a new template"
+msgstr "Crea un nuovo modello"
+
+#: html/Ticket/Create.html:24
+#: html/Ticket/Create.html:27
+#: html/Ticket/Create.html:35
+msgid "Create a new ticket"
+msgstr "Crea un nuovo ticket"
+
+#: html/Admin/Users/Modify.html:213
+#: html/Admin/Users/Modify.html:240
+msgid "Create a new user"
+msgstr "Crea un nuovo utente"
+
+#: html/Admin/Queues/Modify.html:102
+msgid "Create a queue"
+msgstr "Crea una coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a queue called"
+msgstr "Crea una nuova coda chiamata"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create a request"
+msgstr "Crea una richiesta"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/Scrip.html:58
+msgid "Create a scrip for queue %1"
+msgstr "Crea uno scrip per la coda %1"
+
+#: html/Admin/Global/Template.html:68
+#: html/Admin/Queues/Template.html:64
+msgid "Create a template"
+msgstr "Crea un modello"
+
+#: html/SelfService/Create.html:24
+msgid "Create a ticket"
+msgstr "Crea un ticket"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Eccezione durante la creazione: %1 / %2 / %3"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "Eccezione durante la creazione: %1/%2/%3"
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Creare nuovi tickets basati su questo modello di scrip"
+
+#: html/SelfService/Create.html:77
+msgid "Create ticket"
+msgstr "Crea un ticket"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Create tickets in this queue"
+msgstr "Crea dei tickets in questa coda"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Create, delete and modify custom fields"
+msgstr "Crea, elimina e modifica campi personalizzati"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Create, delete and modify queues"
+msgstr "Crea, elimina e modifica le code"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Crea, elimina e modifica i membri dei gruppi personali di un qualunque utente"
+
+#: lib/RT/System.pm:58
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Crea, elimina e modifica i membri dei gruppi personali "
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify users"
+msgstr "Crea, elimina e modifica gli utenti"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "CreateTicket"
+msgstr "CreaTicket"
+
+#: html/Elements/SelectDateType:25
+#: html/Ticket/Elements/ShowDates:26
+#: lib/RT/Ticket_Overlay.pm:1166
+msgid "Created"
+msgstr "Creato"
+
+#. ($CustomFieldObj->Name())
+#: html/Admin/Elements/EditCustomField:87
+msgid "Created CustomField %1"
+msgstr "Campo Personalizzato %1 creato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Created template %1"
+msgstr "Modello %1 creato"
+
+#: html/Ticket/Elements/EditLinks:27
+msgid "Current Relationships"
+msgstr "Relazioni attuali"
+
+#: html/Admin/Elements/EditScrips:29
+msgid "Current Scrips"
+msgstr "Scrips attuali"
+
+#: html/Admin/Groups/Members.html:38
+#: html/User/Groups/Members.html:41
+msgid "Current members"
+msgstr "Membri attuali"
+
+#: html/Admin/Elements/SelectRights:28
+msgid "Current rights"
+msgstr "Diritti attuali"
+
+#: html/Search/Listing.html:70
+msgid "Current search criteria"
+msgstr "Criterio di ricerca corrente"
+
+#: html/Admin/Queues/People.html:40
+#: html/Ticket/Elements/EditPeople:44
+msgid "Current watchers"
+msgstr "Osservatori attuali"
+
+#. ($CustomField)
+#: html/Admin/Global/CustomField.html:54
+msgid "Custom Field #%1"
+msgstr "Campo Personalizzato n°%1"
+
+#: html/Admin/Elements/QueueTabs:52
+#: html/Admin/Elements/SystemTabs:39
+#: html/Admin/Global/index.html:49
+#: html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Campi Personalizzati"
+
+#: html/Admin/Elements/EditScrip:72
+msgid "Custom action cleanup code"
+msgstr "Programma di pulizia dell'azione personalizzata"
+
+#: html/Admin/Elements/EditScrip:64
+msgid "Custom action preparation code"
+msgstr "Programma di preparazione dell'azione personalizzata"
+
+#: html/Admin/Elements/EditScrip:48
+msgid "Custom condition"
+msgstr "Condizione personalizzata"
+
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+#: lib/RT/Tickets_Overlay.pm:1617
+msgid "Custom field %1 %2 %3"
+msgstr "Campi personalizzati %1 %2 %3"
+
+#. ($CF->Name)
+#: lib/RT/Tickets_Overlay.pm:1612
+msgid "Custom field %1 has a value."
+msgstr "Il campo personalizzato %1 ha un valore"
+
+#. ($CF->Name)
+#: lib/RT/Tickets_Overlay.pm:1609
+msgid "Custom field %1 has no value."
+msgstr "Il campo personalizzato %1 non ha valore"
+
+#. ($args{'Field'})
+#: lib/RT/Ticket_Overlay.pm:3426
+msgid "Custom field %1 not found"
+msgstr "Il campo personalizzato %1 è introvabile"
+
+#: html/Admin/Elements/EditCustomFields:196
+msgid "Custom field deleted"
+msgstr "Campo Personalizzato cancellato"
+
+#: lib/RT/Ticket_Overlay.pm:3576
+msgid "Custom field not found"
+msgstr "Il campo personalizzato è introvabile"
+
+#. ($args{'Content'}, $self->Name)
+#: lib/RT/CustomField_Overlay.pm:349
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Il valore del campo personalizzato %1 non è stato possibile trovarlo per il campo personalizzato %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Il valore del campo personalizzato è stato modificato da %1 à %2"
+
+#: lib/RT/CustomField_Overlay.pm:249
+msgid "Custom field value could not be deleted"
+msgstr "Il valore del campo personalizzato non è stato possibile eliminarlo"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Custom field value could not be found"
+msgstr "Il valore del campo personalizzato non è stato possibile trovarlo"
+
+#: lib/RT/CustomField_Overlay.pm:247
+#: lib/RT/CustomField_Overlay.pm:357
+msgid "Custom field value deleted"
+msgstr "Il valore del vampo personalizzato è stato eliminato"
+
+#: lib/RT/Transaction_Overlay.pm:547
+msgid "CustomField"
+msgstr "CampoPersonalizzato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Data error"
+msgstr "Errore nei dati"
+
+#: html/SelfService/Display.html:38
+#: html/Ticket/Create.html:160
+#: html/Ticket/Elements/ShowSummary:54
+#: html/Ticket/Elements/Tabs:92
+#: html/Ticket/ModifyAll.html:43
+msgid "Dates"
+msgstr "Date"
+
+#: lib/RT/Date.pm:421
+msgid "Dec."
+msgstr "Dic."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "December"
+msgstr "Dicembre"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Modello di default per la risposta automatica"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Modello di default per la risposta automatica"
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr "Modello di default per il commento amministrativo"
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr "Modello di default per la corrispondenza amministrativa"
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr "Modello di default per la corrispondenza"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Modello di default per la transazione"
+
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+#: lib/RT/Transaction_Overlay.pm:642
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Defaut: %1/%2 modificato da %3 à %4"
+
+#: html/User/Delegation.html:24
+#: html/User/Delegation.html:27
+msgid "Delegate rights"
+msgstr "Delega i diritti"
+
+#: lib/RT/System.pm:62
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Delega dei diritti specifici che ti sono stati accordati"
+
+#: lib/RT/System.pm:62
+msgid "DelegateRights"
+msgstr "DelegaDiritti"
+
+#: html/User/Elements/Tabs:37
+msgid "Delegation"
+msgstr "Delega"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Delete"
+msgstr "Elimina"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "Delete tickets"
+msgstr "Elimina dei tickets"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "DeleteTicket"
+msgstr "EliminaTicket"
+
+#: lib/RT/Transaction_Overlay.pm:186
+msgid "Deleting this object could break referential integrity"
+msgstr "Eliminare quest'oggetto può interrompere l'integrità referenziale"
+
+#: lib/RT/Queue_Overlay.pm:291
+msgid "Deleting this object would break referential integrity"
+msgstr "Eliminare quest'oggetto interomperà l'integrità referenziale"
+
+#: lib/RT/User_Overlay.pm:437
+msgid "Deleting this object would violate referential integrity"
+msgstr "Eliminare quest'oggetto violerà l'integrità referenziale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Eliminare quest'oggetto violerà l'integrità referenziale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "Eliminare quest'oggetto violerà l'integrità referenziale. Malissimo!"
+
+#: html/Approvals/Elements/Approve:44
+msgid "Deny"
+msgstr "Negare"
+
+#: html/Ticket/Create.html:180
+#: html/Ticket/Elements/EditLinks:122
+#: html/Ticket/Elements/EditLinks:46
+#: html/Ticket/Elements/ShowDependencies:31
+#: html/Ticket/Elements/ShowLinks:36
+msgid "Depended on by"
+msgstr "Usato come dipendenza da"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Dependencies: \\n"
+msgstr "Dipendenze : \\n"
+
+#: html/Elements/SelectLinkType:26
+#: html/Ticket/Create.html:179
+#: html/Ticket/Elements/EditLinks:118
+#: html/Ticket/Elements/EditLinks:35
+#: html/Ticket/Elements/ShowDependencies:24
+#: html/Ticket/Elements/ShowLinks:26
+msgid "Depends on"
+msgstr "Dipende da"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "DependsOn"
+msgstr "DipendeDa"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Descending"
+msgstr "Discendente"
+
+#: html/SelfService/Create.html:72
+#: html/Ticket/Create.html:118
+msgid "Describe the issue below"
+msgstr "Descrivere il problema qui sotto"
+
+#: html/Admin/Elements/AddCustomFieldValue:35
+#: html/Admin/Elements/EditCustomField:38
+#: html/Admin/Elements/EditScrip:33
+#: html/Admin/Elements/ModifyQueue:35
+#: html/Admin/Elements/ModifyTemplate:35
+#: html/Admin/Groups/Modify.html:48
+#: html/Admin/Queues/Modify.html:47
+#: html/Elements/SelectGroups:26
+#: html/User/Groups/Modify.html:48
+msgid "Description"
+msgstr "Descrizione"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Details"
+msgstr "Dettagli"
+
+#: html/Ticket/Elements/Tabs:84
+msgid "Display"
+msgstr "Mostra"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Display Access Control List"
+msgstr "Mostra la Lista Controllo Accessi"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "Display Scrip templates for this queue"
+msgstr "Mostra i modelli di Scrips per questa coda"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "Display Scrips for this queue"
+msgstr "Mostra gli Scrips per questa coda"
+
+#: html/Ticket/Elements/ShowHistory:34
+msgid "Display mode"
+msgstr "Modalità visualizzazione"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Display ticket #%1"
+msgstr "Mostra il ticket n°%1"
+
+#: lib/RT/System.pm:53
+msgid "Do anything and everything"
+msgstr "Fare di tutto e non importa cosa"
+
+#: html/Elements/Refresh:29
+msgid "Don't refresh this page."
+msgstr "Non aggiornare questa pagina."
+
+#: html/Search/Elements/PickRestriction:113
+msgid "Don't show search results"
+msgstr "Non mostrare i risultati della ricerca"
+
+#: html/Ticket/Elements/ShowTransaction:104
+msgid "Download"
+msgstr "Download"
+
+#: html/Elements/SelectDateType:31
+#: html/Ticket/Create.html:166
+#: html/Ticket/Elements/EditDates:44
+#: html/Ticket/Elements/ShowDates:42
+#: lib/RT/Ticket_Overlay.pm:1170
+msgid "Due"
+msgstr "Termine"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "La data termine '%1' non è stata interpretata"
+
+#. ($1, $msg)
+#: bin/rt-commit-handler:753
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ERRORE: impossibile caricare il ticket '%1' : %2.\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Edit"
+msgstr "Modifica"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Edit Conditions"
+msgstr "Modifica Condizioni"
+
+#. ($Queue->Name)
+#: html/Admin/Queues/CustomFields.html:44
+msgid "Edit Custom Fields for %1"
+msgstr "Modifica i Campi Personalizzati per %1"
+
+#: html/Ticket/ModifyLinks.html:35
+msgid "Edit Relationships"
+msgstr "Modifica Relazioni"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/Templates.html:41
+msgid "Edit Templates for queue %1"
+msgstr "Modifica i modelli per la coda %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Edit keywords"
+msgstr "Modifica parole chiave"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Edit scrips"
+msgstr "Modifica scrips"
+
+#: html/Admin/Global/index.html:45
+msgid "Edit system templates"
+msgstr "Modifca i modelli di sistema"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Edit templates for %1"
+msgstr "Modifica i modelli per %1"
+
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+#: html/Admin/Elements/ModifyQueue:24
+#: html/Admin/Queues/Modify.html:117
+msgid "Editing Configuration for queue %1"
+msgstr "Modifica la Configurazione per la coda %1"
+
+#. ($UserObj->Name)
+#: html/Admin/Elements/ModifyUser:24
+msgid "Editing Configuration for user %1"
+msgstr "Modifica la Configurazione per l'utente %1"
+
+#. ($CustomFieldObj->Name())
+#: html/Admin/Elements/EditCustomField:90
+msgid "Editing CustomField %1"
+msgstr "Modifica il CampoPersonalizzato %1"
+
+#. ($Group->Name)
+#: html/Admin/Groups/Members.html:31
+msgid "Editing membership for group %1"
+msgstr "Modifica i membri per il gruppo %1"
+
+#. ($Group->Name)
+#: html/User/Groups/Members.html:128
+msgid "Editing membership for personal group %1"
+msgstr "Modifica i membri per il gruppo personale %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Editing template %1"
+msgstr "Modifica il modello %1"
+
+#: lib/RT/Ticket_Overlay.pm:2621
+#: lib/RT/Ticket_Overlay.pm:2689
+msgid "Either base or target must be specified"
+msgstr "Uno almeno tra base e target deve essere specificato"
+
+#: html/Admin/Users/Modify.html:52
+#: html/Admin/Users/Prefs.html:45
+#: html/Elements/SelectUsers:26
+#: html/Ticket/Elements/AddWatchers:55
+#: html/User/Prefs.html:41
+msgid "Email"
+msgstr "Email"
+
+#: lib/RT/User_Overlay.pm:187
+msgid "Email address in use"
+msgstr "Inidirizzo email in uso"
+
+#: html/Admin/Elements/ModifyUser:41
+msgid "EmailAddress"
+msgstr "IndirizzoEmail"
+
+#: html/Admin/Elements/ModifyUser:53
+msgid "EmailEncoding"
+msgstr "EmailEncoding"
+
+#: html/Admin/Elements/EditCustomField:50
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Abilitato (Togliere il segno di spunta disabilita questo campo personalizzato)"
+
+#: html/Admin/Groups/Modify.html:52
+#: html/User/Groups/Modify.html:52
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Abilitato (Togliere il segno di spunta disabilita questo gruppo)"
+
+#: html/Admin/Queues/Modify.html:83
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Abilitato (Togliere il segno di spunta disabilita questa coda)"
+
+#: html/Admin/Elements/EditCustomFields:98
+msgid "Enabled Custom Fields"
+msgstr "Campi Personalizzati Abilitati"
+
+#: html/Admin/Queues/index.html:55
+msgid "Enabled Queues"
+msgstr "Code Abilitate"
+
+#. (loc_fuzzy($msg))
+#: html/Admin/Elements/EditCustomField:106
+#: html/Admin/Groups/Modify.html:116
+#: html/Admin/Queues/Modify.html:139
+#: html/Admin/Users/Modify.html:282
+#: html/User/Groups/Modify.html:116
+msgid "Enabled status %1"
+msgstr "Stato %1 abilitato"
+
+#: lib/RT/CustomField_Overlay.pm:427
+msgid "Enter multiple values"
+msgstr "Inserire valori multipli"
+
+#: lib/RT/CustomField_Overlay.pm:424
+msgid "Enter one value"
+msgstr "Inserire un valore"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Inserire tickets o URI di tickets da collegare. Separare più valori con spazi."
+
+#: html/Elements/Login:38
+#: html/SelfService/Error.html:24
+#: html/SelfService/Error.html:25
+msgid "Error"
+msgstr "Errore"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Error adding watcher"
+msgstr "Errore cercando di aggiungere un osservatore"
+
+#: lib/RT/Queue_Overlay.pm:554
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Errore nei parametri di Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:712
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Errore nei parametri di Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1355
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Errore nei parametri di Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1531
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Errore nei parametri di Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Chiunque"
+
+#: bin/rt-crontool:193
+msgid "Example:"
+msgstr "Esempio:"
+
+#: html/Admin/Elements/ModifyUser:63
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:57
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:72
+msgid "Extra info"
+msgstr "Informazioni aggiuntive"
+
+#: lib/RT/User_Overlay.pm:301
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Impossibile trovare il pseudogruppo 'Privilegiato' di utenti."
+
+#: lib/RT/User_Overlay.pm:308
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Impossibile trovare il pseudogruppo 'Non Privilegiato' di utenti."
+
+#. ($modname, $@)
+#: bin/rt-crontool:137
+msgid "Failed to load module %1. (%2)"
+msgstr "Errore nel caricare il modulo %1. (%2)"
+
+#: lib/RT/Date.pm:411
+msgid "Feb."
+msgstr "Feb."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "February"
+msgstr "Febbraio"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Fin"
+msgstr "Fin"
+
+#: html/Ticket/Create.html:154
+#: html/Ticket/Elements/EditBasics:58
+#: lib/RT/Tickets_Overlay.pm:1090
+msgid "Final Priority"
+msgstr "Priorità Finale"
+
+#: lib/RT/Ticket_Overlay.pm:1161
+msgid "FinalPriority"
+msgstr "PrioritàFinale"
+
+#: html/Admin/Queues/People.html:60
+#: html/Ticket/Elements/EditPeople:33
+msgid "Find group whose"
+msgstr "Cerca il gruppo che"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Find new/open tickets"
+msgstr "Cerca tickets nuovi/aperti"
+
+#: html/Admin/Queues/People.html:56
+#: html/Admin/Users/index.html:45
+#: html/Ticket/Elements/EditPeople:29
+msgid "Find people whose"
+msgstr "Cerca le persone che"
+
+#: html/Search/Listing.html:107
+msgid "Find tickets"
+msgstr "Cerca tickets"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Finish Approval"
+msgstr "Approvazione Finale"
+
+#: html/Ticket/Elements/Tabs:57
+msgid "First"
+msgstr "Primo"
+
+#: html/Search/Listing.html:40
+msgid "First page"
+msgstr "Prima Pagina"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:86
+msgid "Force change"
+msgstr "Forza il cambiamento"
+
+#. ($ticketcount)
+#: html/Search/Listing.html:105
+msgid "Found %quant(%1,ticket)"
+msgstr "Trovati %quant(%1,ticket)"
+
+#: lib/RT/Interface/Web.pm:901
+msgid "Found Object"
+msgstr "Trovato Oggetto"
+
+#: html/Admin/Elements/ModifyUser:43
+msgid "FreeformContactInfo"
+msgstr "FreeformContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformMultiple"
+msgstr "FreeformMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "FreeformSingle"
+msgstr "FreeformSingle"
+
+#: lib/RT/Date.pm:391
+msgid "Fri."
+msgstr "Gio."
+
+#: html/Ticket/Elements/ShowHistory:40
+#: html/Ticket/Elements/ShowHistory:50
+msgid "Full headers"
+msgstr "Intestazioni Estese"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Sto prendendo l'utente corrente da una firma pgp\\n"
+
+#. ($New->Name)
+#: lib/RT/Transaction_Overlay.pm:592
+msgid "Given to %1"
+msgstr "Assegnato a %1"
+
+#: html/Admin/Elements/Tabs:40
+#: html/Admin/index.html:37
+msgid "Global"
+msgstr "Globale"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Global Keyword Selections"
+msgstr "Selezione Globale delle Parole Chiave"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Global Scrips"
+msgstr "Scrips Globali"
+
+#. (loc($Template->Name))
+#: html/Admin/Elements/SelectTemplate:37
+msgid "Global template: %1"
+msgstr "Modello globale: %1"
+
+#: html/Admin/Elements/EditCustomFields:74
+#: html/Admin/Queues/People.html:58
+#: html/Admin/Queues/People.html:62
+#: html/Admin/Queues/index.html:43
+#: html/Admin/Users/index.html:48
+#: html/Ticket/Elements/EditPeople:31
+#: html/Ticket/Elements/EditPeople:35
+#: html/index.html:40
+msgid "Go!"
+msgstr "Vai!"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Firma pgp valida da %1\\n"
+
+#: html/Search/Listing.html:49
+msgid "Goto page"
+msgstr "Vai a pagina"
+
+#: html/Elements/GotoTicket:24
+#: html/SelfService/Elements/GotoTicket:24
+msgid "Goto ticket"
+msgstr "Vai al ticket"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Grand"
+msgstr "Grand"
+
+#: html/Ticket/Elements/AddWatchers:45
+#: html/User/Elements/DelegateRights:77
+msgid "Group"
+msgstr "Gruppo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Gruppo %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:44
+#: html/Admin/Elements/QueueTabs:56
+#: html/Admin/Elements/SystemTabs:43
+#: html/Admin/Global/index.html:54
+msgid "Group Rights"
+msgstr "Diritti di Gruppo"
+
+#: lib/RT/Group_Overlay.pm:964
+msgid "Group already has member"
+msgstr "Il gruppo ha già il membro"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Group could not be created."
+msgstr "Il gruppo non può essere creato."
+
+#. ($create_msg)
+#: html/Admin/Groups/Modify.html:76
+msgid "Group could not be created: %1"
+msgstr "Il gruppo non può essere creato: %1"
+
+#: lib/RT/Group_Overlay.pm:496
+msgid "Group created"
+msgstr "Gruppo creato"
+
+#: lib/RT/Group_Overlay.pm:1132
+msgid "Group has no such member"
+msgstr "Il gruppo non ho questo membro"
+
+#: lib/RT/Group_Overlay.pm:944
+#: lib/RT/Queue_Overlay.pm:627
+#: lib/RT/Queue_Overlay.pm:687
+#: lib/RT/Ticket_Overlay.pm:1428
+#: lib/RT/Ticket_Overlay.pm:1506
+msgid "Group not found"
+msgstr "Gruppo non trovato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Group not found.\\n"
+msgstr "Gruppo non trovato.\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Group not specified.\\n"
+msgstr "Gruppo non specificato.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:34
+#: html/Admin/Elements/Tabs:34
+#: html/Admin/Groups/Members.html:63
+#: html/Admin/Queues/People.html:82
+#: html/Admin/index.html:31
+#: html/User/Groups/Members.html:66
+msgid "Groups"
+msgstr "Gruppi"
+
+#: lib/RT/Group_Overlay.pm:970
+msgid "Groups can't be members of their members"
+msgstr "I gruppi non possono essere membri dei loro membri"
+
+#: lib/RT/Interface/CLI.pm:72
+msgid "Hello!"
+msgstr "Ciao!"
+
+#. ($name)
+#: docs/design_docs/string-extraction-guide.txt:40
+msgid "Hello, %1"
+msgstr "Ciao, %1"
+
+#: html/Ticket/Elements/ShowHistory:29
+#: html/Ticket/Elements/Tabs:87
+msgid "History"
+msgstr "Storia"
+
+#: html/Admin/Elements/ModifyUser:67
+msgid "HomePhone"
+msgstr "TelefonoCasa"
+
+#: html/Elements/Tabs:43
+msgid "Homepage"
+msgstr "Homepage"
+
+#. (6)
+#: lib/RT/Base.pm:73
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "Ho %quant(%1,concrete mixer)."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "Ho [quant,_1,concrete mixer]."
+
+#: html/Ticket/Elements/ShowBasics:26
+#: lib/RT/Tickets_Overlay.pm:1017
+msgid "Id"
+msgstr "Id"
+
+#: html/Admin/Users/Modify.html:43
+#: html/User/Prefs.html:38
+msgid "Identity"
+msgstr "Identità"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Se una richiesta di approvazione è rifiutata, rifiuta l'originale e elimina le richieste di approvazione pendenti"
+
+#: bin/rt-crontool:189
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Se questo strumento viene utilizzato con setgid, un utente locale mlintenzionato può usrae questo strumento per ottenere accesso amministrativo su RT."
+
+#: html/Admin/Queues/People.html:104
+#: html/Ticket/Modify.html:38
+#: html/Ticket/ModifyAll.html:93
+#: html/Ticket/ModifyPeople.html:37
+msgid "If you've updated anything above, be sure to"
+msgstr "Se hai aggiornato qualchecosa qui sopra, assicurati di"
+
+#: lib/RT/Interface/Web.pm:893
+msgid "Illegal value for %1"
+msgstr "Valore non valido per %1"
+
+#: lib/RT/Interface/Web.pm:896
+msgid "Immutable field"
+msgstr "Campo immutabile"
+
+#: html/Admin/Elements/EditCustomFields:73
+msgid "Include disabled custom fields in listing."
+msgstr "Includi nella lista i campi personalizzati disabilitati."
+
+#: html/Admin/Queues/index.html:42
+msgid "Include disabled queues in listing."
+msgstr "Includi nella lista le code disabilitate."
+
+#: html/Admin/Users/index.html:46
+msgid "Include disabled users in search."
+msgstr "Includi nella ricerca gli utenti disabilitati."
+
+#: lib/RT/Tickets_Overlay.pm:1066
+msgid "Initial Priority"
+msgstr "Priorità Iniziale"
+
+#: lib/RT/Ticket_Overlay.pm:1160
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "InitialPriority"
+msgstr "PrioritàIniziale"
+
+#: lib/RT/ScripAction_Overlay.pm:104
+msgid "Input error"
+msgstr "Errore in Input"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Interest noted"
+msgstr "Interesse annotato"
+
+#: lib/RT/Ticket_Overlay.pm:3795
+msgid "Internal Error"
+msgstr "Errore Interno"
+
+#. ($id->{error_message})
+#: lib/RT/Record.pm:142
+msgid "Internal Error: %1"
+msgstr "Errore Interno: %1"
+
+#: lib/RT/Group_Overlay.pm:643
+msgid "Invalid Group Type"
+msgstr "Tipo di Gruppo non valido"
+
+#: lib/RT/Principal_Overlay.pm:127
+msgid "Invalid Right"
+msgstr "Diritto non valido"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Invalid Type"
+msgstr "Tipo non valido"
+
+#: lib/RT/Interface/Web.pm:898
+msgid "Invalid data"
+msgstr "Dati non validi"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Proprietraio non valido. Verrà usato il default 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:133
+#: lib/RT/Template_Overlay.pm:250
+msgid "Invalid queue"
+msgstr "Coda non valida"
+
+#: lib/RT/ACE_Overlay.pm:243
+#: lib/RT/ACE_Overlay.pm:252
+#: lib/RT/ACE_Overlay.pm:258
+#: lib/RT/ACE_Overlay.pm:269
+#: lib/RT/ACE_Overlay.pm:274
+msgid "Invalid right"
+msgstr "Diritto non valido"
+
+#. ($key)
+#: lib/RT/Record.pm:117
+msgid "Invalid value for %1"
+msgstr "Valore non valido per %1"
+
+#: lib/RT/Ticket_Overlay.pm:3433
+msgid "Invalid value for custom field"
+msgstr "Valore non valido per il campo personalizzato"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Valore non valido per lo stato"
+
+#: bin/rt-crontool:190
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "E' estremamente importante che agli utenti non previlegiati non sia consentito eseguire questo strumento."
+
+#: bin/rt-crontool:191
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+
+#: bin/rt-crontool:162
+msgid "It takes several arguments:"
+msgstr "Richide molteplici argomenti:"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Items pending my approval"
+msgstr "Oggetti in attesa della mia approvazione"
+
+#: lib/RT/Date.pm:410
+msgid "Jan."
+msgstr "Gen."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "January"
+msgstr "Gennaio"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Join or leave this group"
+msgstr "Unisciti o lascia questo gruppo"
+
+#: lib/RT/Date.pm:416
+msgid "Jul."
+msgstr "Lug."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "July"
+msgstr "Luglio"
+
+#: html/Ticket/Elements/Tabs:98
+msgid "Jumbo"
+msgstr "Jumbo"
+
+#: lib/RT/Date.pm:415
+msgid "Jun."
+msgstr "Giu."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "June"
+msgstr "Giugno"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Keyword"
+msgstr "Parola chiave"
+
+#: html/Admin/Elements/ModifyUser:51
+msgid "Lang"
+msgstr "Linguaggio"
+
+#: html/Ticket/Elements/Tabs:72
+msgid "Last"
+msgstr "Ultimo"
+
+#: html/Ticket/Elements/EditDates:37
+#: html/Ticket/Elements/ShowDates:38
+msgid "Last Contact"
+msgstr "Ultimo Contatto"
+
+#: html/Elements/SelectDateType:28
+msgid "Last Contacted"
+msgstr "Ultimo Contatto"
+
+#: html/Search/Elements/TicketHeader:40
+msgid "Last Notified"
+msgstr "Ultima Notifica"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Updated"
+msgstr "Ultimo Aggiornamento"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "LastUpdated"
+msgstr "UltimoAggiornamento"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Left"
+msgstr "Rimasti"
+
+#: html/Admin/Users/Modify.html:82
+msgid "Let this user access RT"
+msgstr "Consenti a questo utente di accedere a RT"
+
+#: html/Admin/Users/Modify.html:86
+msgid "Let this user be granted rights"
+msgstr "Concedi a questo utente che gli vengano assegnati i diritti"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Limitare il proprietario %1 %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Limitare la coda a %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2703
+msgid "Link already exists"
+msgstr "Il collegamento già esiste"
+
+#: lib/RT/Ticket_Overlay.pm:2715
+msgid "Link could not be created"
+msgstr "Il collegamento non può essere creato"
+
+#. ($TransString)
+#: lib/RT/Ticket_Overlay.pm:2723
+#: lib/RT/Ticket_Overlay.pm:2733
+msgid "Link created (%1)"
+msgstr "Collegamento creato (%1)"
+
+#. ($TransString)
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link deleted (%1)"
+msgstr "Collegamento eliminato (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2650
+msgid "Link not found"
+msgstr "Collegamento non trovato"
+
+#. ($Ticket->Id)
+#: html/Ticket/ModifyLinks.html:24
+#: html/Ticket/ModifyLinks.html:28
+msgid "Link ticket #%1"
+msgstr "Collega ticket n°%1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Link ticket %1"
+msgstr "Collega ticket %1"
+
+#: html/Ticket/Elements/Tabs:96
+msgid "Links"
+msgstr "Collegamenti"
+
+#: html/Admin/Users/Modify.html:113
+#: html/User/Prefs.html:84
+msgid "Location"
+msgstr "Località"
+
+#. ($RT::LogDir)
+#: lib/RT.pm:159
+msgid ""
+"Log directory %1 not found or couldn't be written.\\n"
+" RT can't run."
+msgstr ""
+"Directory di log %1 non trovata o non scrivibile.\\n"
+" RT non può essere eseguito."
+
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+#: html/Elements/Header:56
+msgid "Logged in as %1"
+msgstr "Collegato come %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71
+#: html/Elements/Login:34
+#: html/Elements/Login:43
+#: html/Elements/Login:53
+msgid "Login"
+msgstr "Collegamento"
+
+#: html/Elements/Header:53
+msgid "Logout"
+msgstr "Scollegati"
+
+#: html/Search/Bulk.html:85
+msgid "Make Owner"
+msgstr "Crea Proprietario"
+
+#: html/Search/Bulk.html:101
+msgid "Make Status"
+msgstr "Crea Stato"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Due"
+msgstr "Crea data Scadenza"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Resolved"
+msgstr "Crea data Risolto"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Started"
+msgstr "Crea data Iniziato"
+
+#: html/Search/Bulk.html:105
+msgid "Make date Starts"
+msgstr "Crea data Inizia"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Told"
+msgstr "Crea data Detto"
+
+#: html/Search/Bulk.html:98
+msgid "Make priority"
+msgstr "Crea priorità"
+
+#: html/Search/Bulk.html:99
+msgid "Make queue"
+msgstr "Crea coda"
+
+#: html/Search/Bulk.html:97
+msgid "Make subject"
+msgstr "Crea oggetto"
+
+#: html/Admin/index.html:32
+msgid "Manage groups and group membership"
+msgstr "Gestisci i gruppi e le appartenenze"
+
+#: html/Admin/index.html:38
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Gestisci le proprietà e le configurazioni che si applicano a tutte le code"
+
+#: html/Admin/index.html:35
+msgid "Manage queues and queue-specific properties"
+msgstr "Gestisci le code e le propietà specifiche delle code"
+
+#: html/Admin/index.html:29
+msgid "Manage users and passwords"
+msgstr "Gestisci gli utenti e le password"
+
+#: lib/RT/Date.pm:412
+msgid "Mar."
+msgstr "Mar."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "March"
+msgstr "Marzo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "May"
+msgstr "Maggio"
+
+#: lib/RT/Date.pm:414
+msgid "May."
+msgstr "Mag."
+
+#: lib/RT/Group_Overlay.pm:981
+msgid "Member added"
+msgstr "Aggiunto membro"
+
+#: lib/RT/Group_Overlay.pm:1139
+msgid "Member deleted"
+msgstr "Eliminato membro"
+
+#: lib/RT/Group_Overlay.pm:1143
+msgid "Member not deleted"
+msgstr "Membro non eliminato"
+
+#: html/Elements/SelectLinkType:25
+msgid "Member of"
+msgstr "Membro di"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "MemberOf"
+msgstr "MembroDi"
+
+#: html/Admin/Elements/GroupTabs:41
+#: html/User/Elements/GroupTabs:41
+msgid "Members"
+msgstr "Membri"
+
+#: lib/RT/Ticket_Overlay.pm:2890
+msgid "Merge Successful"
+msgstr "Unione avvenuta con Successo"
+
+#: lib/RT/Ticket_Overlay.pm:2810
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Unione fallita. Impossibile impostare EffectiveId"
+
+#: html/Ticket/Elements/EditLinks:114
+msgid "Merge into"
+msgstr "Unisci in"
+
+#: html/Ticket/Update.html:101
+msgid "Message"
+msgstr "Messaggio"
+
+#: lib/RT/Interface/Web.pm:900
+msgid "Missing a primary key?: %1"
+msgstr "Manca una chiave primaria?: %1"
+
+#: html/Admin/Users/Modify.html:168
+#: html/User/Prefs.html:53
+msgid "Mobile"
+msgstr "Cellulare"
+
+#: html/Admin/Elements/ModifyUser:71
+msgid "MobilePhone"
+msgstr "TelefonoCellulare"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Modify Access Control List"
+msgstr "Modifca la Lista Controllo Accessi"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Modifica il Campo Personalizzato %1"
+
+#: html/Admin/Global/CustomFields.html:43
+#: html/Admin/Global/index.html:50
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Modifica i Campi Personalizzati validi per tutte le code"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Modify Scrip templates for this queue"
+msgstr "Modifica i modelli di Scips per questa coda"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Modify Scrips for this queue"
+msgstr "Modifica gli Scrips per questa coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify System ACLS"
+msgstr "Modifica le LCA di Sistema"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify Template %1"
+msgstr "Modifica il Modello %1"
+
+#. ($QueueObj->Name())
+#: html/Admin/Queues/CustomField.html:44
+msgid "Modify a CustomField for queue %1"
+msgstr "Modifica un CampoPersonalizzato per la coda %1"
+
+#: html/Admin/Global/CustomField.html:52
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Modifica un CampoPersonalizzato valido per tutte le code"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/Scrip.html:53
+msgid "Modify a scrip for queue %1"
+msgstr "Modifica uno scrip per la coda %1"
+
+#: html/Admin/Global/Scrip.html:47
+msgid "Modify a scrip which applies to all queues"
+msgstr "Modifica uno scrip valido per tutte le code"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify dates for # %1"
+msgstr "Modifica le date per n° %1"
+
+#. ($TicketObj->Id)
+#: html/Ticket/ModifyDates.html:24
+#: html/Ticket/ModifyDates.html:28
+msgid "Modify dates for #%1"
+msgstr "Modifica le date per n°%1"
+
+#. ($TicketObj->Id)
+#: html/Ticket/ModifyDates.html:34
+msgid "Modify dates for ticket # %1"
+msgstr "Modifica le date per il ticket n° %1"
+
+#: html/Admin/Global/GroupRights.html:24
+#: html/Admin/Global/GroupRights.html:27
+#: html/Admin/Global/index.html:55
+msgid "Modify global group rights"
+msgstr "Modifica i diritti di gruppo globali"
+
+#: html/Admin/Global/GroupRights.html:32
+msgid "Modify global group rights."
+msgstr "Modifica i diritti di gruppo globali."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify global rights for groups"
+msgstr "Modifica i diritti di gruppo globali"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify global rights for users"
+msgstr "Modifica i diritti globali per gli utenti"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify global scrips"
+msgstr "Modifica gli scrips globali"
+
+#: html/Admin/Global/UserRights.html:24
+#: html/Admin/Global/UserRights.html:27
+#: html/Admin/Global/index.html:59
+msgid "Modify global user rights"
+msgstr "Modifica i diritti globali per gli utenti"
+
+#: html/Admin/Global/UserRights.html:32
+msgid "Modify global user rights."
+msgstr "Modifica i diritti globali per gli utenti."
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "Modify group metadata or delete group"
+msgstr "Modifica i metadati di gruppo o elimina un gruppo"
+
+#. ($GroupObj->Name)
+#: html/Admin/Groups/GroupRights.html:24
+#: html/Admin/Groups/GroupRights.html:28
+#: html/Admin/Groups/GroupRights.html:34
+msgid "Modify group rights for group %1"
+msgstr "Modifica i diritti di gruppo per il gruppo %1"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/GroupRights.html:24
+#: html/Admin/Queues/GroupRights.html:28
+msgid "Modify group rights for queue %1"
+msgstr "Modifica i diritti di gruppo per la coda %1"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "Modify membership roster for this group"
+msgstr "Modofica i membri di questo gruppo"
+
+#: lib/RT/System.pm:60
+msgid "Modify one's own RT account"
+msgstr "Modifica il proprio account RT"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/People.html:24
+#: html/Admin/Queues/People.html:28
+msgid "Modify people related to queue %1"
+msgstr "Modifica le persone relative alla coda %1"
+
+#. ($Ticket->id)
+#. ($Ticket->Id)
+#: html/Ticket/ModifyPeople.html:24
+#: html/Ticket/ModifyPeople.html:28
+#: html/Ticket/ModifyPeople.html:34
+msgid "Modify people related to ticket #%1"
+msgstr "Modifica le persone relative al ticket n°%1"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/Scrips.html:43
+msgid "Modify scrips for queue %1"
+msgstr "Modifica gli scrips per la coda %1"
+
+#: html/Admin/Global/Scrips.html:43
+#: html/Admin/Global/index.html:41
+msgid "Modify scrips which apply to all queues"
+msgstr "Modifica gli scrips validi per tutte le code"
+
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+#: html/Admin/Global/Template.html:24
+#: html/Admin/Global/Template.html:29
+#: html/Admin/Global/Template.html:80
+#: html/Admin/Queues/Template.html:77
+msgid "Modify template %1"
+msgstr "Modifica modello %1"
+
+#: html/Admin/Global/Templates.html:43
+msgid "Modify templates which apply to all queues"
+msgstr "Modifica i modelli validi per tutte le code"
+
+#. ($Group->Name)
+#: html/Admin/Groups/Modify.html:86
+#: html/User/Groups/Modify.html:85
+msgid "Modify the group %1"
+msgstr "Modifica il gruppo %1"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify the queue watchers"
+msgstr "Modifica gli osservatori della coda"
+
+#. ($UserObj->Name)
+#: html/Admin/Users/Modify.html:235
+msgid "Modify the user %1"
+msgstr "Modifica l'utente %1"
+
+#. ($Ticket->Id)
+#: html/Ticket/ModifyAll.html:36
+msgid "Modify ticket # %1"
+msgstr "Modifica il ticket n° %1"
+
+#. ($TicketObj->Id)
+#: html/Ticket/Modify.html:24
+#: html/Ticket/Modify.html:27
+#: html/Ticket/Modify.html:33
+msgid "Modify ticket #%1"
+msgstr "Modifica il ticket n°%1"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Modify tickets"
+msgstr "Modifica i tickets"
+
+#. ($GroupObj->Name)
+#: html/Admin/Groups/UserRights.html:24
+#: html/Admin/Groups/UserRights.html:28
+#: html/Admin/Groups/UserRights.html:34
+msgid "Modify user rights for group %1"
+msgstr "Modifica i diritti utente per il gruppo %1"
+
+#. ($QueueObj->Name)
+#: html/Admin/Queues/UserRights.html:24
+#: html/Admin/Queues/UserRights.html:28
+msgid "Modify user rights for queue %1"
+msgstr "Modifica i diritti dell'utente per la coda %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Modifica gli osservatori per la coda '%1'"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ModifyACL"
+msgstr "ModificaLCA"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "ModifyOwnMembership"
+msgstr "ModificaPropriaAppartenenza"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyQueueWatchers"
+msgstr "ModificaOsservatoriCoda"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ModifyScrips"
+msgstr "ModificaScrips"
+
+#: lib/RT/System.pm:60
+msgid "ModifySelf"
+msgstr "ModificaSeStesso"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "ModifyTemplate"
+msgstr "ModificaModello"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "ModifyTicket"
+msgstr "ModificaTicket"
+
+#: lib/RT/Date.pm:387
+msgid "Mon."
+msgstr "Lun."
+
+#. ($name)
+#: html/Ticket/Elements/ShowRequestor:41
+msgid "More about %1"
+msgstr "Altre info su %1"
+
+#: html/Admin/Elements/EditCustomFields:60
+msgid "Move down"
+msgstr "Move down"
+
+#: html/Admin/Elements/EditCustomFields:52
+msgid "Move up"
+msgstr "Move up"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Multiple"
+msgstr "Multiple"
+
+#: lib/RT/User_Overlay.pm:178
+msgid "Must specify 'Name' attribute"
+msgstr "Must specify 'Name' attribute"
+
+#. ($friendly_status)
+#: html/SelfService/Elements/MyRequests:48
+msgid "My %1 tickets"
+msgstr "I miei%1 tickets"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "My Approvals"
+msgstr "Le mie richieste di Approvazione"
+
+#: html/Approvals/index.html:24
+#: html/Approvals/index.html:25
+msgid "My approvals"
+msgstr "Le mie richieste di approvazione"
+
+#: html/Admin/Elements/AddCustomFieldValue:31
+#: html/Admin/Elements/EditCustomField:33
+#: html/Admin/Elements/ModifyTemplate:27
+#: html/Admin/Elements/ModifyUser:29
+#: html/Admin/Groups/Modify.html:43
+#: html/Elements/SelectGroups:25
+#: html/Elements/SelectUsers:27
+#: html/User/Groups/Modify.html:43
+msgid "Name"
+msgstr "Nome"
+
+#: lib/RT/User_Overlay.pm:185
+msgid "Name in use"
+msgstr "Name in use"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Need approval from system administrator"
+msgstr "Need approval from system administrator"
+
+#: html/Ticket/Elements/ShowDates:51
+msgid "Never"
+msgstr "Never"
+
+#: html/Elements/Quicksearch:29
+msgid "New"
+msgstr "Nuovo"
+
+#: html/Admin/Elements/ModifyUser:31
+#: html/Admin/Users/Modify.html:92
+#: html/User/Prefs.html:64
+msgid "New Password"
+msgstr "Nuova Password"
+
+#: etc/initialdata:317
+#: etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "New Pending Approval"
+
+#: html/Ticket/Elements/EditLinks:110
+msgid "New Relationships"
+msgstr "New Relationships"
+
+#: html/Ticket/Elements/Tabs:35
+msgid "New Search"
+msgstr "Nuova Ricerca"
+
+#: html/Admin/Global/CustomField.html:40
+#: html/Admin/Global/CustomFields.html:38
+#: html/Admin/Queues/CustomField.html:51
+#: html/Admin/Queues/CustomFields.html:39
+msgid "New custom field"
+msgstr "Nuovo campo Personalizzato"
+
+#: html/Admin/Elements/GroupTabs:53
+#: html/User/Elements/GroupTabs:51
+msgid "New group"
+msgstr "Nuovo gruppo"
+
+#: html/SelfService/Prefs.html:31
+msgid "New password"
+msgstr "Nuova password"
+
+#: lib/RT/User_Overlay.pm:646
+msgid "New password notification sent"
+msgstr "New password notification sent"
+
+#: html/Admin/Elements/QueueTabs:69
+msgid "New queue"
+msgstr "Nuova coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "New request"
+msgstr "Nuova richiesta"
+
+#: html/Admin/Elements/SelectRights:41
+msgid "New rights"
+msgstr "Nuovi diritti"
+
+#: html/Admin/Global/Scrip.html:39
+#: html/Admin/Global/Scrips.html:38
+#: html/Admin/Queues/Scrip.html:42
+#: html/Admin/Queues/Scrips.html:52
+msgid "New scrip"
+msgstr "Nuovo scrip"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "New search"
+msgstr "Nuova ricerca"
+
+#: html/Admin/Global/Template.html:59
+#: html/Admin/Global/Templates.html:38
+#: html/Admin/Queues/Template.html:57
+#: html/Admin/Queues/Templates.html:49
+msgid "New template"
+msgstr "Nuovo modello"
+
+#: html/SelfService/Elements/Tabs:47
+msgid "New ticket"
+msgstr "Nuovo ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2777
+msgid "New ticket doesn't exist"
+msgstr "Il nuovo ticket non esiste"
+
+#: html/Admin/Elements/UserTabs:51
+msgid "New user"
+msgstr "Nuovo utente"
+
+#: html/Admin/Elements/CreateUserCalled:25
+msgid "New user called"
+msgstr "New user called"
+
+#: html/Admin/Queues/People.html:54
+#: html/Ticket/Elements/EditPeople:28
+msgid "New watchers"
+msgstr "Nuovo osservatore"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "New window setting"
+msgstr "New window setting"
+
+#: html/Ticket/Elements/Tabs:68
+msgid "Next"
+msgstr "Succesivo"
+
+#: html/Search/Listing.html:47
+msgid "Next page"
+msgstr "Pagina succesiva"
+
+#: html/Admin/Elements/ModifyUser:49
+msgid "NickName"
+msgstr "NickName"
+
+#: html/Admin/Users/Modify.html:62
+#: html/User/Prefs.html:45
+msgid "Nickname"
+msgstr "Soprannome"
+
+#: html/Admin/Elements/EditCustomField:89
+#: html/Admin/Elements/EditCustomFields:104
+msgid "No CustomField"
+msgstr "No CustomField"
+
+#: html/Admin/Groups/GroupRights.html:83
+#: html/Admin/Groups/UserRights.html:70
+msgid "No Group defined"
+msgstr "No Group defined"
+
+#: html/Admin/Queues/GroupRights.html:95
+#: html/Admin/Queues/UserRights.html:67
+msgid "No Queue defined"
+msgstr "No Queue defined"
+
+#: bin/rt-crontool:55
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "No RT user found. Please consult your RT administrator.\\n"
+
+#: html/Admin/Global/Template.html:78
+#: html/Admin/Queues/Template.html:75
+msgid "No Template"
+msgstr "Nessun Modello"
+
+#: bin/rt-commit-handler:763
+msgid "No Ticket specified. Aborting ticket "
+msgstr "No Ticket specified. Aborting ticket "
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid ""
+"No Ticket specified. Aborting ticket modifications\\n"
+"\\n"
+msgstr ""
+"No Ticket specified. Aborting ticket modifications\\n"
+"\\n"
+
+#: html/Approvals/Elements/Approve:45
+msgid "No action"
+msgstr "No action"
+
+#: lib/RT/Interface/Web.pm:895
+msgid "No column specified"
+msgstr "No column specified"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "No command found\\n"
+msgstr "No command found\\n"
+
+#: html/Elements/ViewUser:35
+#: html/Ticket/Elements/ShowRequestor:44
+msgid "No comment entered about this user"
+msgstr "No comment entered about this user"
+
+#: lib/RT/Ticket_Overlay.pm:2188
+#: lib/RT/Ticket_Overlay.pm:2256
+msgid "No correspondence attached"
+msgstr "No correspondence attached"
+
+#. (ref $self)
+#: lib/RT/Action/Generic.pm:149
+#: lib/RT/Condition/Generic.pm:175
+#: lib/RT/Search/ActiveTicketsInQueue.pm:55
+#: lib/RT/Search/Generic.pm:112
+msgid "No description for %1"
+msgstr "Nessuna descrizione per %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "No group specified"
+
+#: lib/RT/User_Overlay.pm:864
+msgid "No password set"
+msgstr "No password set"
+
+#: lib/RT/Queue_Overlay.pm:258
+msgid "No permission to create queues"
+msgstr "No permission to create code"
+
+#. ($QueueObj->Name)
+#: lib/RT/Ticket_Overlay.pm:341
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "No permission to create tickets in the coda '%1'"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "No permission to create users"
+
+#: html/SelfService/Display.html:117
+msgid "No permission to display that ticket"
+msgstr "No permission to display that ticket"
+
+#: html/SelfService/Update.html:51
+msgid "No permission to view update ticket"
+msgstr "No permission to view update ticket"
+
+#: lib/RT/Queue_Overlay.pm:674
+#: lib/RT/Ticket_Overlay.pm:1487
+msgid "No principal specified"
+msgstr "No principal specified"
+
+#: html/Admin/Queues/People.html:153
+#: html/Admin/Queues/People.html:163
+msgid "No principals selected."
+msgstr "No principals selected."
+
+#: html/Admin/Queues/index.html:34
+msgid "No queues matching search criteria found."
+msgstr "No code matching search criteria found."
+
+#: html/Admin/Elements/SelectRights:80
+msgid "No rights found"
+msgstr "Nessun diritto trovato"
+
+#: html/Admin/Elements/SelectRights:32
+msgid "No rights granted."
+msgstr "Nessun diritto concesso."
+
+#: html/Search/Bulk.html:148
+msgid "No search to operate on."
+msgstr "No search to operate on."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "No ticket id specified"
+msgstr "No ticket id specified"
+
+#: lib/RT/Transaction_Overlay.pm:477
+#: lib/RT/Transaction_Overlay.pm:515
+msgid "No transaction type specified"
+msgstr "No transaction type specified"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "No user or email address specified"
+msgstr "No user or email address specified"
+
+#: html/Admin/Users/index.html:35
+msgid "No users matching search criteria found."
+msgstr "No users matching search criteria found."
+
+#: bin/rt-commit-handler:643
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+
+#: lib/RT/Interface/Web.pm:892
+msgid "No value sent to _Set!\\n"
+msgstr "No value sent to _Set!\\n"
+
+#: html/Search/Elements/TicketRow:36
+msgid "Nobody"
+msgstr "Nessuno"
+
+#: lib/RT/Interface/Web.pm:897
+msgid "Nonexistant field?"
+msgstr "Nonexistant field?"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Not logged in"
+msgstr "Not logged in"
+
+#: html/Elements/Header:58
+msgid "Not logged in."
+msgstr "Non collegato."
+
+#: lib/RT/Date.pm:368
+msgid "Not set"
+msgstr "Non valorizzato"
+
+#: html/NoAuth/Reminder.html:26
+msgid "Not yet implemented."
+msgstr "Not yet implemented."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Not yet implemented...."
+msgstr "Not yet implemented...."
+
+#: html/Approvals/Elements/Approve:48
+msgid "Notes"
+msgstr "Notes"
+
+#: lib/RT/User_Overlay.pm:649
+msgid "Notification could not be sent"
+msgstr "Notification could not be sent"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Notify AdminCcs"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Notify AdminCcs as Comment"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Notify Other Recipients"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Notify Other Recipients as Comment"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Notify Proprietario"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Notify Proprietario as Comment"
+
+#: etc/initialdata:319
+#: etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Notify Proprietari and AdminCcs of new items pending their approval"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Notifica al Richiedente"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Notifica ai Richiedenti e ai Ccs"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Notifica ai Richiedenti e ai Ccs come Commento"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Notifica ai Richiedenti, Ccs e AdminCcs"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Notifica ai Richiedenti, Ccs a AdminCcs come Commento"
+
+#: lib/RT/Date.pm:420
+msgid "Nov."
+msgstr "Nov."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "November"
+msgstr "November"
+
+#: lib/RT/Record.pm:156
+msgid "Object could not be created"
+msgstr "Object could not be created"
+
+#: lib/RT/Record.pm:175
+msgid "Object created"
+msgstr "Object created"
+
+#: lib/RT/Date.pm:419
+msgid "Oct."
+msgstr "Oct."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "October"
+msgstr "October"
+
+#: html/Elements/SelectDateRelation:34
+msgid "On"
+msgstr "On"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "On Comment"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "On Correspond"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "On Create"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "On Owner Change"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "On Queue Change"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "On Resolve"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "On Status Change"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "On Transaction"
+
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+#: html/Approvals/Elements/PendingMyApproval:49
+msgid "Only show approvals for requests created after %1"
+msgstr "Mostra le approvazioni solo per le richieste create dopo %1"
+
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+#: html/Approvals/Elements/PendingMyApproval:47
+msgid "Only show approvals for requests created before %1"
+msgstr "Mostra le approvazioni solo per le richieste create prima %1"
+
+#: html/Elements/Quicksearch:30
+msgid "Open"
+msgstr "Aperto"
+
+#: html/Ticket/Elements/Tabs:135
+msgid "Open it"
+msgstr "Aprilo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Open requests"
+msgstr "Richieste aperte"
+
+#: html/SelfService/Elements/Tabs:41
+msgid "Open tickets"
+msgstr "Open tickets"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in a new window"
+msgstr "Open tickets (from listing) in a new window"
+
+#: html/Admin/Users/Prefs.html:39
+msgid "Open tickets (from listing) in another window"
+msgstr "Open tickets (from listing) in another window"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Open tickets on correspondence"
+
+#: html/Search/Elements/PickRestriction:100
+msgid "Ordering and sorting"
+msgstr "Visualizzazione e Ordinamento"
+
+#: html/Admin/Elements/ModifyUser:45
+#: html/Admin/Users/Modify.html:116
+#: html/Elements/SelectUsers:28
+#: html/User/Prefs.html:85
+msgid "Organization"
+msgstr "Azienda"
+
+#. ($approving->Id, $approving->Subject)
+#: html/Approvals/Elements/Approve:32
+msgid "Originating ticket: #%1"
+msgstr "Originating ticket: n°%1"
+
+#: html/Admin/Elements/ModifyQueue:54
+#: html/Admin/Queues/Modify.html:68
+msgid "Over time, priority moves toward"
+msgstr "Se scade il tempo, la priorità sale di"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Own tickets"
+msgstr "Own tickets"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "OwnTicket"
+msgstr "PossiediTicket"
+
+#: etc/initialdata:38
+#: html/Elements/MyRequests:31
+#: html/SelfService/Elements/MyRequests:29
+#: html/Ticket/Create.html:47
+#: html/Ticket/Elements/EditPeople:42
+#: html/Ticket/Elements/EditPeople:43
+#: html/Ticket/Elements/ShowPeople:26
+#: html/Ticket/Update.html:62
+#: lib/RT/ACE_Overlay.pm:85
+#: lib/RT/Tickets_Overlay.pm:1243
+msgid "Owner"
+msgstr "Proprietario"
+
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+#: lib/RT/Ticket_Overlay.pm:3070
+msgid "Owner changed from %1 to %2"
+msgstr "Proprietario changed from %1 to %2"
+
+#. ($Old->Name , $New->Name)
+#: lib/RT/Transaction_Overlay.pm:581
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Owner forcibly changed from %1 to %2"
+
+#: html/Search/Elements/PickRestriction:30
+msgid "Owner is"
+msgstr "Il Proprietario è"
+
+#: html/Admin/Users/Modify.html:173
+#: html/User/Prefs.html:55
+msgid "Pager"
+msgstr "Pager"
+
+#: html/Admin/Elements/ModifyUser:73
+msgid "PagerPhone"
+msgstr "PagerPhone"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:181
+#: html/Ticket/Elements/EditLinks:126
+#: html/Ticket/Elements/EditLinks:57
+#: html/Ticket/Elements/ShowLinks:46
+msgid "Parents"
+msgstr "Genitori"
+
+#: html/Elements/Login:51
+#: html/User/Prefs.html:60
+msgid "Password"
+msgstr "Password"
+
+#: html/NoAuth/Reminder.html:24
+msgid "Password Reminder"
+msgstr "Password Reminder"
+
+#: lib/RT/User_Overlay.pm:168
+#: lib/RT/User_Overlay.pm:867
+msgid "Password too short"
+msgstr "Password too short"
+
+#. (loc_fuzzy($msg))
+#: html/Admin/Users/Modify.html:290
+#: html/User/Prefs.html:171
+msgid "Password: %1"
+msgstr "Password: %1"
+
+#: html/Admin/Users/Modify.html:292
+msgid "Passwords do not match."
+msgstr "Passwords do not match."
+
+#: html/User/Prefs.html:173
+msgid "Passwords do not match. Your password has not been changed"
+msgstr "Passwords do not match. Your password has not been changed"
+
+#: html/Ticket/Elements/ShowSummary:44
+#: html/Ticket/Elements/Tabs:95
+#: html/Ticket/ModifyAll.html:50
+msgid "People"
+msgstr "Persone"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Perform a user-defined action"
+
+#: lib/RT/ACE_Overlay.pm:230
+#: lib/RT/ACE_Overlay.pm:236
+#: lib/RT/ACE_Overlay.pm:562
+#: lib/RT/ACE_Overlay.pm:572
+#: lib/RT/ACE_Overlay.pm:582
+#: lib/RT/ACE_Overlay.pm:647
+#: lib/RT/CurrentUser.pm:82
+#: lib/RT/CurrentUser.pm:91
+#: lib/RT/CustomField_Overlay.pm:100
+#: lib/RT/CustomField_Overlay.pm:201
+#: lib/RT/CustomField_Overlay.pm:233
+#: lib/RT/CustomField_Overlay.pm:510
+#: lib/RT/CustomField_Overlay.pm:90
+#: lib/RT/Group_Overlay.pm:1094
+#: lib/RT/Group_Overlay.pm:1098
+#: lib/RT/Group_Overlay.pm:1107
+#: lib/RT/Group_Overlay.pm:1158
+#: lib/RT/Group_Overlay.pm:1162
+#: lib/RT/Group_Overlay.pm:1168
+#: lib/RT/Group_Overlay.pm:425
+#: lib/RT/Group_Overlay.pm:517
+#: lib/RT/Group_Overlay.pm:595
+#: lib/RT/Group_Overlay.pm:603
+#: lib/RT/Group_Overlay.pm:700
+#: lib/RT/Group_Overlay.pm:704
+#: lib/RT/Group_Overlay.pm:710
+#: lib/RT/Group_Overlay.pm:903
+#: lib/RT/Group_Overlay.pm:907
+#: lib/RT/Group_Overlay.pm:920
+#: lib/RT/Queue_Overlay.pm:539
+#: lib/RT/Queue_Overlay.pm:549
+#: lib/RT/Queue_Overlay.pm:563
+#: lib/RT/Queue_Overlay.pm:698
+#: lib/RT/Queue_Overlay.pm:707
+#: lib/RT/Queue_Overlay.pm:720
+#: lib/RT/Queue_Overlay.pm:930
+#: lib/RT/Scrip_Overlay.pm:125
+#: lib/RT/Scrip_Overlay.pm:136
+#: lib/RT/Scrip_Overlay.pm:196
+#: lib/RT/Scrip_Overlay.pm:429
+#: lib/RT/Template_Overlay.pm:283
+#: lib/RT/Template_Overlay.pm:87
+#: lib/RT/Template_Overlay.pm:93
+#: lib/RT/Ticket_Overlay.pm:1340
+#: lib/RT/Ticket_Overlay.pm:1350
+#: lib/RT/Ticket_Overlay.pm:1364
+#: lib/RT/Ticket_Overlay.pm:1517
+#: lib/RT/Ticket_Overlay.pm:1526
+#: lib/RT/Ticket_Overlay.pm:1539
+#: lib/RT/Ticket_Overlay.pm:1874
+#: lib/RT/Ticket_Overlay.pm:2012
+#: lib/RT/Ticket_Overlay.pm:2176
+#: lib/RT/Ticket_Overlay.pm:2243
+#: lib/RT/Ticket_Overlay.pm:2602
+#: lib/RT/Ticket_Overlay.pm:2674
+#: lib/RT/Ticket_Overlay.pm:2768
+#: lib/RT/Ticket_Overlay.pm:2783
+#: lib/RT/Ticket_Overlay.pm:2977
+#: lib/RT/Ticket_Overlay.pm:3205
+#: lib/RT/Ticket_Overlay.pm:3403
+#: lib/RT/Ticket_Overlay.pm:3565
+#: lib/RT/Ticket_Overlay.pm:3617
+#: lib/RT/Ticket_Overlay.pm:3782
+#: lib/RT/Transaction_Overlay.pm:465
+#: lib/RT/Transaction_Overlay.pm:472
+#: lib/RT/Transaction_Overlay.pm:501
+#: lib/RT/Transaction_Overlay.pm:508
+#: lib/RT/User_Overlay.pm:1354
+#: lib/RT/User_Overlay.pm:569
+#: lib/RT/User_Overlay.pm:604
+#: lib/RT/User_Overlay.pm:860
+#: lib/RT/User_Overlay.pm:961
+msgid "Permission Denied"
+msgstr "Permission Denied"
+
+#: html/User/Elements/Tabs:34
+msgid "Personal Groups"
+msgstr "Gruppi Personali"
+
+#: html/User/Groups/index.html:29
+#: html/User/Groups/index.html:39
+msgid "Personal groups"
+msgstr "Gruppi personali"
+
+#: html/User/Elements/DelegateRights:36
+msgid "Personal groups:"
+msgstr "Gruppi personali:"
+
+#: html/Admin/Users/Modify.html:155
+#: html/User/Prefs.html:48
+msgid "Phone numbers"
+msgstr "Numeri Telefonici"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Placeholder"
+msgstr "Placeholder"
+
+#: html/Elements/Header:51
+#: html/Elements/Tabs:52
+#: html/SelfService/Elements/Tabs:50
+#: html/SelfService/Prefs.html:24
+#: html/User/Prefs.html:24
+#: html/User/Prefs.html:27
+msgid "Preferences"
+msgstr "Preferenze"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Prefs"
+msgstr "Prefs"
+
+#: lib/RT/Action/Generic.pm:159
+msgid "Prepare Stubbed"
+msgstr "Prepare Stubbed"
+
+#: html/Ticket/Elements/Tabs:60
+msgid "Prev"
+msgstr "Precedente"
+
+#: html/Search/Listing.html:43
+msgid "Previous page"
+msgstr "Previous page"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#. ($args{'PrincipalId'})
+#: lib/RT/ACE_Overlay.pm:132
+#: lib/RT/ACE_Overlay.pm:207
+#: lib/RT/ACE_Overlay.pm:551
+msgid "Principal %1 not found."
+msgstr "Principal %1 not found."
+
+#: html/Search/Elements/PickRestriction:53
+#: html/Ticket/Create.html:153
+#: html/Ticket/Elements/EditBasics:53
+#: html/Ticket/Elements/ShowBasics:38
+#: lib/RT/Tickets_Overlay.pm:1041
+msgid "Priority"
+msgstr "Priorità"
+
+#: html/Admin/Elements/ModifyQueue:50
+#: html/Admin/Queues/Modify.html:64
+msgid "Priority starts at"
+msgstr "La priorità inizia da"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilegiato"
+
+#. (loc_fuzzy($msg))
+#: html/Admin/Users/Modify.html:270
+#: html/User/Prefs.html:162
+msgid "Privileged status: %1"
+msgstr "Stato previlegiato: %1"
+
+#: html/Admin/Users/index.html:61
+msgid "Privileged users"
+msgstr "Utenti privilegiati"
+
+#: etc/initialdata:23
+#: etc/initialdata:29
+#: etc/initialdata:35
+#: etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudogroup for internal use"
+
+#: html/Elements/MyRequests:29
+#: html/Elements/MyTickets:29
+#: html/Elements/Quicksearch:28
+#: html/Search/Elements/PickRestriction:45
+#: html/SelfService/Create.html:32
+#: html/Ticket/Create.html:37
+#: html/Ticket/Elements/EditBasics:63
+#: html/Ticket/Elements/ShowBasics:42
+#: html/User/Elements/DelegateRights:79
+#: lib/RT/Tickets_Overlay.pm:882
+msgid "Queue"
+msgstr "Coda"
+
+#. ($Queue)
+#. ($id)
+#: html/Admin/Queues/CustomField.html:41
+#: html/Admin/Queues/Scrip.html:49
+#: html/Admin/Queues/Scrips.html:45
+#: html/Admin/Queues/Templates.html:43
+msgid "Queue %1 not found"
+msgstr "Queue %1 not found"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Queue '%1' not found\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Queue Keyword Selections"
+msgstr "Queue Keyword Selections"
+
+#: html/Admin/Elements/ModifyQueue:30
+#: html/Admin/Queues/Modify.html:42
+msgid "Queue Name"
+msgstr "Nome della coda"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Queue Scrips"
+msgstr "Queue Scrips"
+
+#: lib/RT/Queue_Overlay.pm:262
+msgid "Queue already exists"
+msgstr "Queue already exists"
+
+#: lib/RT/Queue_Overlay.pm:271
+#: lib/RT/Queue_Overlay.pm:277
+msgid "Queue could not be created"
+msgstr "Queue could not be created"
+
+#: html/Ticket/Create.html:204
+msgid "Queue could not be loaded."
+msgstr "Queue could not be loaded."
+
+#: docs/design_docs/string-extraction-guide.txt:83
+#: lib/RT/Queue_Overlay.pm:281
+msgid "Queue created"
+msgstr "Queue created"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Queue is not specified."
+msgstr "Queue is not specified."
+
+#: html/SelfService/Display.html:70
+#: lib/RT/CustomField_Overlay.pm:97
+msgid "Queue not found"
+msgstr "Queue not found"
+
+#: html/Admin/Elements/Tabs:37
+#: html/Admin/index.html:34
+msgid "Queues"
+msgstr "Code"
+
+#: html/Elements/Quicksearch:24
+msgid "Quick search"
+msgstr "Ricerca veloce"
+
+#. ($RT::VERSION)
+#: html/Elements/Login:43
+msgid "RT %1"
+msgstr "RT %1"
+
+#. ($RT::VERSION, $RT::rtname)
+#: docs/design_docs/string-extraction-guide.txt:70
+msgid "RT %1 for %2"
+msgstr "RT %1 per %2"
+
+#. ($RT::VERSION)
+#: html/Elements/Footer:31
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 da <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:24
+#: html/Admin/index.html:25
+msgid "RT Administration"
+msgstr "RT Administration"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Authentication error."
+msgstr "RT Authentication error."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT Bounce: %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Configuration error"
+msgstr "RT Configuration error"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RT Critical error. Message not recorded!"
+
+#: html/Elements/Error:40
+#: html/SelfService/Error.html:40
+msgid "RT Error"
+msgstr "RT Error"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT Received mail (%1) from itself."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT Recieved mail (%1) from itself."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Self Service / Closed Tickets"
+
+#: html/index.html:24
+#: html/index.html:27
+msgid "RT at a glance"
+msgstr "Colpo d'occhio di RT"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT couldn't authenticate you"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT couldn't find requestor via its external database lookup"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT couldn't find the coda: %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT couldn't validate this PGP signature. \\n"
+
+#. ($RT::rtname)
+#: html/Elements/PageLayout:25
+msgid "RT for %1"
+msgstr "RT per %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT for %1: %2"
+msgstr "RT per %1: %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT has proccessed your commands"
+
+#. ('2003')
+#: html/Elements/Login:91
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. RT viene distribuito con la <a href=\"http://www.gnu.org/copyleft/gpl.html\">Versione 2 della GNU General Public License.</a>"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. RT viene distribuito con la <a href=\"http://www.gnu.org/copyleft/gpl.html\">Versione 2 della GNU General Public License.</a>"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT thinks this message may be a bounce"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT will process this message as if it were unsigned.\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+
+#: html/Admin/Users/Modify.html:57
+#: html/Admin/Users/Prefs.html:51
+#: html/User/Prefs.html:43
+msgid "Real Name"
+msgstr "Nome Reale"
+
+#: html/Admin/Elements/ModifyUser:47
+msgid "RealName"
+msgstr "RealName"
+
+#: html/Ticket/Create.html:184
+#: html/Ticket/Elements/EditLinks:138
+#: html/Ticket/Elements/EditLinks:93
+#: html/Ticket/Elements/ShowLinks:70
+msgid "Referred to by"
+msgstr "Riferito da"
+
+#: html/Elements/SelectLinkType:27
+#: html/Ticket/Create.html:183
+#: html/Ticket/Elements/EditLinks:134
+#: html/Ticket/Elements/EditLinks:79
+#: html/Ticket/Elements/ShowLinks:60
+msgid "Refers to"
+msgstr "Fa riferimento a"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RefersTo"
+msgstr "RefersTo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Refine"
+msgstr "Refine"
+
+#: html/Search/Elements/PickRestriction:26
+msgid "Refine search"
+msgstr "Rifinisci la ricerca"
+
+#. ($value/60)
+#: html/Elements/Refresh:35
+msgid "Refresh this page every %1 minutes."
+msgstr "Aggiorna questa pagina ogni %1 minuti."
+
+#: html/Ticket/Create.html:173
+#: html/Ticket/Elements/ShowSummary:61
+#: html/Ticket/ModifyAll.html:56
+msgid "Relationships"
+msgstr "Relazioni"
+
+#: html/Search/Bulk.html:92
+msgid "Remove AdminCc"
+msgstr "Remove AdminCc"
+
+#: html/Search/Bulk.html:90
+msgid "Remove Cc"
+msgstr "Remove Cc"
+
+#: html/Search/Bulk.html:88
+msgid "Remove Requestor"
+msgstr "Rimuovi il RIchiedente"
+
+#: html/Ticket/Elements/ShowTransaction:172
+#: html/Ticket/Elements/Tabs:121
+msgid "Reply"
+msgstr "Risposta"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Reply to tickets"
+msgstr "Rispondi ai tickets"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "ReplyToTicket"
+msgstr "RispondiAlTicket"
+
+#: etc/initialdata:44
+#: html/Ticket/Update.html:39
+#: lib/RT/ACE_Overlay.pm:86
+msgid "Requestor"
+msgstr "Richiedente"
+
+#: html/Search/Elements/PickRestriction:37
+msgid "Requestor email address"
+msgstr "Indirizzo emaildel richiedente"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Requestor(s)"
+msgstr "Richiedente(i)"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RequestorAddresses"
+msgstr "IndirizzoRichiedente"
+
+#: html/SelfService/Create.html:40
+#: html/Ticket/Create.html:55
+#: html/Ticket/Elements/EditPeople:47
+#: html/Ticket/Elements/ShowPeople:30
+msgid "Requestors"
+msgstr "Richiedenti"
+
+#: html/Admin/Elements/ModifyQueue:60
+#: html/Admin/Queues/Modify.html:74
+msgid "Requests should be due in"
+msgstr "Le richieste devono essere soddisfatte in"
+
+#: html/Elements/Submit:61
+msgid "Reset"
+msgstr "Azzera"
+
+#: html/Admin/Users/Modify.html:158
+#: html/User/Prefs.html:49
+msgid "Residence"
+msgstr "Casa"
+
+#: html/Ticket/Elements/Tabs:131
+msgid "Resolve"
+msgstr "Risolvi"
+
+#. ($Ticket->id, $Ticket->Subject)
+#: html/Ticket/Update.html:132
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Risolvi il ticket n°%1 (%2)"
+
+#: etc/initialdata:308
+#: html/Elements/SelectDateType:27
+#: lib/RT/Ticket_Overlay.pm:1169
+msgid "Resolved"
+msgstr "Risolto"
+
+#: html/Search/Bulk.html:122
+#: html/Ticket/ModifyAll.html:72
+#: html/Ticket/Update.html:72
+msgid "Response to requestors"
+msgstr "Risposta ai richiedenti"
+
+#: html/Elements/ListActions:25
+msgid "Results"
+msgstr "Risultati"
+
+#: html/Search/Elements/PickRestriction:104
+msgid "Results per page"
+msgstr "Risultati per pagina"
+
+#: html/Admin/Elements/ModifyUser:32
+#: html/Admin/Users/Modify.html:99
+#: html/User/Prefs.html:71
+msgid "Retype Password"
+msgstr "Ridigita Password"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:612
+msgid "Right Delegated"
+msgstr "Right Delegated"
+
+#: lib/RT/ACE_Overlay.pm:302
+msgid "Right Granted"
+msgstr "Right Granted"
+
+#: lib/RT/ACE_Overlay.pm:160
+msgid "Right Loaded"
+msgstr "Right Loaded"
+
+#: lib/RT/ACE_Overlay.pm:677
+#: lib/RT/ACE_Overlay.pm:692
+msgid "Right could not be revoked"
+msgstr "Right could not be revoked"
+
+#: html/User/Delegation.html:63
+msgid "Right not found"
+msgstr "Right not found"
+
+#: lib/RT/ACE_Overlay.pm:542
+#: lib/RT/ACE_Overlay.pm:637
+msgid "Right not loaded."
+msgstr "Right not loaded."
+
+#: lib/RT/ACE_Overlay.pm:688
+msgid "Right revoked"
+msgstr "Right revoked"
+
+#: html/Admin/Elements/UserTabs:40
+msgid "Rights"
+msgstr "Diritti"
+
+#. ($object_type)
+#: lib/RT/Interface/Web.pm:791
+msgid "Rights could not be granted for %1"
+msgstr "I diritti non possono essere concessi per %1"
+
+#. ($object_type)
+#: lib/RT/Interface/Web.pm:824
+msgid "Rights could not be revoked for %1"
+msgstr "I diritti non possono essere revocaqti per %1"
+
+#: html/Admin/Global/GroupRights.html:50
+#: html/Admin/Queues/GroupRights.html:51
+msgid "Roles"
+msgstr "Ruoli"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "RootApproval"
+msgstr "RootApproval"
+
+#: lib/RT/Date.pm:392
+msgid "Sat."
+msgstr "Sab."
+
+#: html/Admin/Queues/People.html:104
+#: html/Ticket/Modify.html:38
+#: html/Ticket/ModifyAll.html:93
+#: html/Ticket/ModifyLinks.html:38
+#: html/Ticket/ModifyPeople.html:37
+msgid "Save Changes"
+msgstr "Salva i Cambiamenti"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Save changes"
+msgstr "Salva i cambiamenti"
+
+#. ($ARGS{'id'})
+#: html/Admin/Global/Scrip.html:48
+msgid "Scrip #%1"
+msgstr "Scrip n°%1"
+
+#: lib/RT/Scrip_Overlay.pm:175
+msgid "Scrip Created"
+msgstr "Scrip Created"
+
+#: html/Admin/Elements/EditScrips:83
+msgid "Scrip deleted"
+msgstr "Scrip eliminato"
+
+#: html/Admin/Elements/QueueTabs:45
+#: html/Admin/Elements/SystemTabs:32
+#: html/Admin/Global/index.html:40
+msgid "Scrips"
+msgstr "Scrips"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrips per %1\\n"
+
+#: html/Admin/Queues/Scrips.html:32
+msgid "Scrips which apply to all queues"
+msgstr "Scrips which apply to all code"
+
+#: html/Elements/SimpleSearch:26
+#: html/Search/Elements/PickRestriction:125
+#: html/Ticket/Elements/Tabs:158
+msgid "Search"
+msgstr "Cerca"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Search Criteria"
+msgstr "Crieri di Ricerca"
+
+#: html/Approvals/Elements/PendingMyApproval:38
+msgid "Search for approvals"
+msgstr "Ricerca le richieste di approvazione"
+
+#: bin/rt-crontool:187
+msgid "Security:"
+msgstr "Security:"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "SeeQueue"
+msgstr "VediCoda"
+
+#: html/Admin/Groups/index.html:39
+msgid "Select a group"
+msgstr "Seleziona un gruppo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Select a queue"
+msgstr "Seleziona una coda"
+
+#: html/Admin/Users/index.html:24
+#: html/Admin/Users/index.html:27
+msgid "Select a user"
+msgstr "Seleziona un utente"
+
+#: html/Admin/Global/CustomField.html:37
+#: html/Admin/Global/CustomFields.html:35
+msgid "Select custom field"
+msgstr "Seleziona un campo personalizzato"
+
+#: html/Admin/Elements/GroupTabs:51
+#: html/User/Elements/GroupTabs:49
+msgid "Select group"
+msgstr "Seleziona gruppo"
+
+#: lib/RT/CustomField_Overlay.pm:421
+msgid "Select multiple values"
+msgstr "Seleziona valori multipli"
+
+#: lib/RT/CustomField_Overlay.pm:418
+msgid "Select one value"
+msgstr "Seleziona un volore solo"
+
+#: html/Admin/Elements/QueueTabs:66
+msgid "Select queue"
+msgstr "Seleziona una coda"
+
+#: html/Admin/Global/Scrip.html:36
+#: html/Admin/Global/Scrips.html:35
+#: html/Admin/Queues/Scrip.html:39
+#: html/Admin/Queues/Scrips.html:49
+msgid "Select scrip"
+msgstr "Seleziona uno scrip"
+
+#: html/Admin/Global/Template.html:56
+#: html/Admin/Global/Templates.html:35
+#: html/Admin/Queues/Template.html:54
+#: html/Admin/Queues/Templates.html:46
+msgid "Select template"
+msgstr "Seleziona un modello"
+
+#: html/Admin/Elements/UserTabs:48
+msgid "Select user"
+msgstr "Seleziona utente"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectMultiple"
+msgstr "SelectMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:34
+msgid "SelectSingle"
+msgstr "SelectSingle"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Self Service"
+msgstr "Self Service"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Invia una mail a tutti gli osservatori"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Invia una mail atutti gli osservatori come un \"commento\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Invia mail ai richiedenti e Ccs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Invia mail ai richiedenti e Ccs come commento"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Invia un messaggio ai richiedenti"
+
+#: etc/initialdata:118
+#: etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Sends mail to explicitly listed Ccs and Bccs"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Sends mail to the administrative Ccs"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Sends mail to the administrative Ccs as a comment"
+
+#: etc/initialdata:83
+#: etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Sends mail to the owner"
+
+#: lib/RT/Date.pm:418
+msgid "Sep."
+msgstr "Sep."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "September"
+msgstr "September"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Show Results"
+msgstr "Mostra i Risultati"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show approved requests"
+msgstr "Mostra le richieste approvate"
+
+#: html/Ticket/Create.html:143
+#: html/Ticket/Create.html:33
+msgid "Show basics"
+msgstr "Mostra info di base"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show denied requests"
+msgstr "Mostra le richieste negate"
+
+#: html/Ticket/Create.html:143
+#: html/Ticket/Create.html:33
+msgid "Show details"
+msgstr "Mostra i dettagli"
+
+#: html/Approvals/Elements/PendingMyApproval:42
+msgid "Show pending requests"
+msgstr "Mostra le richieste in attesa"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show requests awaiting other approvals"
+msgstr "Mostra le richieste in attesa di altre approvazioni"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "Show ticket private commentary"
+msgstr "Show ticket private commentary"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Show ticket summaries"
+msgstr "Show ticket summaries"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "ShowACL"
+msgstr "MostraLCA"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "ShowScrips"
+msgstr "MostraScrips"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "ShowTemplate"
+msgstr "MostraModello"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowTicket"
+msgstr "MostraTicket"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "ShowTicketComments"
+msgstr "MostraICommentiAlTicket"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Sign up as a ticket Requestor or ticket or coda Cc"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Sign up as a ticket or coda AdminCc"
+
+#: html/Admin/Elements/ModifyUser:38
+#: html/Admin/Users/Modify.html:190
+#: html/Admin/Users/Prefs.html:31
+#: html/User/Prefs.html:111
+msgid "Signature"
+msgstr "Firma"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Signed in as %1"
+msgstr "Signed in as %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:25
+msgid "Single"
+msgstr "Single"
+
+#: html/Elements/Header:50
+msgid "Skip Menu"
+msgstr "Skip Menu"
+
+#: html/Admin/Elements/AddCustomFieldValue:27
+msgid "Sort"
+msgstr "Ordina"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Sort key"
+msgstr "Sort key"
+
+#: html/Search/Elements/PickRestriction:108
+msgid "Sort results by"
+msgstr "Ordina i risultati per"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "SortOrder"
+msgstr "SortOrder"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Stalled"
+msgstr "In stallo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Start page"
+msgstr "Pagina iniziale"
+
+#: html/Elements/SelectDateType:26
+#: html/Ticket/Elements/EditDates:31
+#: html/Ticket/Elements/ShowDates:34
+msgid "Started"
+msgstr "Iniziato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Started date '%1' could not be parsed"
+
+#: html/Elements/SelectDateType:30
+#: html/Ticket/Create.html:165
+#: html/Ticket/Elements/EditDates:26
+#: html/Ticket/Elements/ShowDates:30
+msgid "Starts"
+msgstr "Inizia"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Starts By"
+msgstr "Inizia Da"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Starts date '%1' could not be parsed"
+
+#: html/Admin/Elements/ModifyUser:81
+#: html/Admin/Users/Modify.html:137
+#: html/User/Prefs.html:93
+msgid "State"
+msgstr "Provincia"
+
+#: html/Elements/MyRequests:30
+#: html/Elements/MyTickets:30
+#: html/Search/Elements/PickRestriction:73
+#: html/SelfService/Elements/MyRequests:28
+#: html/SelfService/Update.html:30
+#: html/Ticket/Create.html:41
+#: html/Ticket/Elements/EditBasics:37
+#: html/Ticket/Elements/ShowBasics:30
+#: html/Ticket/Update.html:59
+#: lib/RT/Ticket_Overlay.pm:1163
+#: lib/RT/Tickets_Overlay.pm:907
+msgid "Status"
+msgstr "Stato"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr "Cambiamento di Stato"
+
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Status changed from %1 to %2"
+msgstr "Cambiato lo Stato da %1 a %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "StatusChange"
+msgstr "StatusChange"
+
+#: html/Ticket/Elements/Tabs:146
+msgid "Steal"
+msgstr "Ruba"
+
+#. ($Old->Name)
+#: lib/RT/Transaction_Overlay.pm:586
+msgid "Stolen from %1 "
+msgstr "Rubato da %1 "
+
+#: html/Elements/MyRequests:28
+#: html/Elements/MyTickets:28
+#: html/Search/Bulk.html:125
+#: html/Search/Elements/PickRestriction:42
+#: html/SelfService/Create.html:56
+#: html/SelfService/Elements/MyRequests:27
+#: html/SelfService/Update.html:31
+#: html/Ticket/Create.html:83
+#: html/Ticket/Elements/EditBasics:27
+#: html/Ticket/ModifyAll.html:78
+#: html/Ticket/Update.html:76
+#: lib/RT/Ticket_Overlay.pm:1159
+#: lib/RT/Tickets_Overlay.pm:986
+msgid "Subject"
+msgstr "Oggetto"
+
+#. ($self->Data)
+#: docs/design_docs/string-extraction-guide.txt:89
+#: lib/RT/Transaction_Overlay.pm:608
+msgid "Subject changed to %1"
+msgstr "Subject changed to %1"
+
+#: html/Elements/Submit:58
+msgid "Submit"
+msgstr "Invia"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Submit Workflow"
+msgstr "Submit Workflow"
+
+#: lib/RT/Group_Overlay.pm:748
+msgid "Succeeded"
+msgstr "Succeeded"
+
+#: lib/RT/Date.pm:393
+msgid "Sun."
+msgstr "Dom."
+
+#: lib/RT/System.pm:53
+msgid "SuperUser"
+msgstr "SuperUtente"
+
+#: html/User/Elements/DelegateRights:76
+msgid "System"
+msgstr "Sistema"
+
+#: html/Admin/Elements/SelectRights:80
+#: lib/RT/ACE_Overlay.pm:566
+#: lib/RT/Interface/Web.pm:790
+#: lib/RT/Interface/Web.pm:823
+msgid "System Error"
+msgstr "Errore di Sistema"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "System Error. Right not granted."
+msgstr "Errore di Sistema. Diritto non concesso."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "System Error. right not granted"
+msgstr "Errore di Sistema. Diritto non concesso"
+
+#: lib/RT/ACE_Overlay.pm:615
+msgid "System error. Right not delegated."
+msgstr "Errore di Sistema. Diritto non delegato."
+
+#: lib/RT/ACE_Overlay.pm:145
+#: lib/RT/ACE_Overlay.pm:222
+#: lib/RT/ACE_Overlay.pm:305
+#: lib/RT/ACE_Overlay.pm:897
+msgid "System error. Right not granted."
+msgstr "Errore di Sistema. Diritto non concesso."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Errore di sistema. Impossibile concedere i diritti."
+
+#: html/Admin/Global/GroupRights.html:34
+#: html/Admin/Groups/GroupRights.html:36
+#: html/Admin/Queues/GroupRights.html:35
+msgid "System groups"
+msgstr "Gruppi di sistema"
+
+#: etc/initialdata:41
+#: etc/initialdata:47
+#: etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRolegroup for internal use"
+
+#: lib/RT/CurrentUser.pm:317
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:142
+msgid "Take"
+msgstr "Prendi"
+
+#: lib/RT/Transaction_Overlay.pm:572
+msgid "Taken"
+msgstr "Preso"
+
+#: html/Admin/Elements/EditScrip:80
+msgid "Template"
+msgstr "Modello"
+
+#. ($TemplateObj->Id())
+#: html/Admin/Global/Template.html:90
+#: html/Admin/Queues/Template.html:89
+msgid "Template #%1"
+msgstr "Modello n°%1"
+
+#: html/Admin/Elements/EditTemplates:88
+msgid "Template deleted"
+msgstr "Modello eliminato"
+
+#: lib/RT/Scrip_Overlay.pm:152
+msgid "Template not found"
+msgstr "Modello non trovato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Template not found\\n"
+msgstr "Modello non trovato\\n"
+
+#: lib/RT/Template_Overlay.pm:346
+msgid "Template parsed"
+msgstr "Modello elaborato"
+
+#: html/Admin/Elements/QueueTabs:48
+#: html/Admin/Elements/SystemTabs:35
+#: html/Admin/Global/index.html:44
+msgid "Templates"
+msgstr "Modelli"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Templates for %1\\n"
+msgstr "Modelli per %1\\n"
+
+#: lib/RT/Interface/Web.pm:891
+msgid "That is already the current value"
+msgstr "That is already the current value"
+
+#: lib/RT/CustomField_Overlay.pm:242
+msgid "That is not a value for this custom field"
+msgstr "That is not a value for this custom field"
+
+#: lib/RT/Ticket_Overlay.pm:1885
+msgid "That is the same value"
+msgstr "That is the same value"
+
+#: lib/RT/ACE_Overlay.pm:287
+#: lib/RT/ACE_Overlay.pm:596
+msgid "That principal already has that right"
+msgstr "That principal already has that right"
+
+#. ($args{'Type'})
+#: lib/RT/Queue_Overlay.pm:632
+msgid "That principal is already a %1 for this queue"
+msgstr "That principal is already a %1 for this coda"
+
+#. ($self->loc($args{'Type'}))
+#: lib/RT/Ticket_Overlay.pm:1433
+msgid "That principal is already a %1 for this ticket"
+msgstr "That principal is already a %1 for this ticket"
+
+#. ($args{'Type'})
+#: lib/RT/Queue_Overlay.pm:731
+msgid "That principal is not a %1 for this queue"
+msgstr "That principal is not a %1 for this coda"
+
+#. ($args{'Type'})
+#: lib/RT/Ticket_Overlay.pm:1550
+msgid "That principal is not a %1 for this ticket"
+msgstr "That principal is not a %1 for this ticket"
+
+#: lib/RT/Ticket_Overlay.pm:1881
+msgid "That queue does not exist"
+msgstr "That coda does not exist"
+
+#: lib/RT/Ticket_Overlay.pm:3209
+msgid "That ticket has unresolved dependencies"
+msgstr "That ticket has unresolved dependencies"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "That user already has that right"
+msgstr "That user already has that right"
+
+#: lib/RT/Ticket_Overlay.pm:3019
+msgid "That user already owns that ticket"
+msgstr "That user already owns that ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2985
+msgid "That user does not exist"
+msgstr "That user does not exist"
+
+#: lib/RT/User_Overlay.pm:314
+msgid "That user is already privileged"
+msgstr "Questo utente è già previlegiato"
+
+#: lib/RT/User_Overlay.pm:335
+msgid "That user is already unprivileged"
+msgstr "Questo utente è già non previlegiato"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Ora questo utente è previlegiato"
+
+#: lib/RT/User_Overlay.pm:348
+msgid "That user is now unprivileged"
+msgstr "Ora questo utente è non previlegiato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "Questo utente ora è non previlegiato"
+
+#: lib/RT/Ticket_Overlay.pm:3011
+msgid "That user may not own tickets in that queue"
+msgstr "That user may not own tickets in that coda"
+
+#: lib/RT/Link_Overlay.pm:205
+msgid "That's not a numerical id"
+msgstr "That's not a numerical id"
+
+#: html/SelfService/Display.html:31
+#: html/Ticket/Create.html:149
+#: html/Ticket/Elements/ShowSummary:27
+msgid "The Basics"
+msgstr "Dati di base"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The CC of a ticket"
+msgstr "The CC of a ticket"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The administrative CC of a ticket"
+msgstr "The administrative CC of a ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2212
+msgid "The comment has been recorded"
+msgstr "The comment has been recorded"
+
+#: bin/rt-crontool:197
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "The following command will find all active tickets in the coda 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+
+#: bin/rt-commit-handler:755
+#: bin/rt-commit-handler:765
+msgid ""
+"The following commands were not proccessed:\\n"
+"\\n"
+msgstr ""
+"The following commands were not proccessed:\\n"
+"\\n"
+
+#: lib/RT/Interface/Web.pm:894
+msgid "The new value has been set."
+msgstr "The new value has been set."
+
+#: lib/RT/ACE_Overlay.pm:85
+msgid "The owner of a ticket"
+msgstr "The owner of a ticket"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The requestor of a ticket"
+msgstr "Il richiedente di un ticket"
+
+#: html/Admin/Elements/EditUserComments:25
+msgid "These comments aren't generally visible to the user"
+msgstr "These comments aren't generally visible to the user"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "This ticket %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:188
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "This tool allows the user to run arbitrary perl modules from within RT."
+
+#: lib/RT/Transaction_Overlay.pm:250
+msgid "This transaction appears to have no content"
+msgstr "This transaction appears to have no content"
+
+#. ($rows)
+#: html/Ticket/Elements/ShowRequestor:46
+msgid "This user's %1 highest priority tickets"
+msgstr "I %1 tickets di questo utente a più alta priorità"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "I 25 tickets a più alta priorità di questo utente"
+
+#: lib/RT/Date.pm:390
+msgid "Thu."
+msgstr "Gio."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket"
+msgstr ""
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Ticket n° %1 %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "Ticket n° %1 aggiornamento Jumbo: %2"
+
+#. ($Ticket->Id, $Ticket->Subject)
+#: html/Ticket/ModifyAll.html:24
+#: html/Ticket/ModifyAll.html:28
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Ticket n° %1 Jumbo update: %2"
+
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+#: html/Approvals/Elements/ShowDependency:45
+msgid "Ticket #%1: %2"
+msgstr "Ticket n°%1: %2"
+
+#. ($self->Id, $QueueObj->Name)
+#: lib/RT/Ticket_Overlay.pm:586
+#: lib/RT/Ticket_Overlay.pm:607
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Ticket %1 created in coda '%2'"
+
+#. ($Ticket->Id)
+#: bin/rt-commit-handler:759
+msgid "Ticket %1 loaded\\n"
+msgstr "Ticket %1 loaded\\n"
+
+#. ($Ticket->Id,$_)
+#: html/Search/Bulk.html:180
+msgid "Ticket %1: %2"
+msgstr "Ticket %1: %2"
+
+#. ($Ticket->Id, $Ticket->Subject)
+#: html/Ticket/History.html:24
+#: html/Ticket/History.html:27
+msgid "Ticket History # %1 %2"
+msgstr "Ticket History n° %1 %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket Id"
+msgstr "Ticket Id"
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr "Ticket Risolto"
+
+#: html/Search/Elements/PickRestriction:62
+msgid "Ticket attachment"
+msgstr "Allegato al ticket: il "
+
+#: lib/RT/Tickets_Overlay.pm:1165
+msgid "Ticket content"
+msgstr "Contenuto del ticket"
+
+#: lib/RT/Tickets_Overlay.pm:1211
+msgid "Ticket content type"
+msgstr "Ticket content type"
+
+#: lib/RT/Ticket_Overlay.pm:495
+#: lib/RT/Ticket_Overlay.pm:596
+msgid "Ticket could not be created due to an internal error"
+msgstr "Ticket could not be created due to an internal error"
+
+#: lib/RT/Transaction_Overlay.pm:519
+msgid "Ticket created"
+msgstr "Ticket creato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket creation failed"
+msgstr "Ticket creation failed"
+
+#: lib/RT/Transaction_Overlay.pm:524
+msgid "Ticket deleted"
+msgstr "Ticket eliminato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket id not found"
+msgstr "Ticket id not found"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket killed"
+msgstr "Ticket killed"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Ticket not found"
+msgstr "Ticket not found"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "Ticket status changed"
+
+#: html/Ticket/Update.html:38
+msgid "Ticket watchers"
+msgstr "Osservatori del ticket"
+
+#: html/Elements/Tabs:46
+msgid "Tickets"
+msgstr ""
+
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+#: lib/RT/Tickets_Overlay.pm:1382
+msgid "Tickets %1 %2"
+msgstr ""
+
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+#: lib/RT/Tickets_Overlay.pm:1347
+msgid "Tickets %1 by %2"
+msgstr "Tickets %1 by %2"
+
+#. ($name)
+#: html/Elements/ViewUser:25
+msgid "Tickets from %1"
+msgstr "Tickets from %1"
+
+#: html/Approvals/Elements/ShowDependency:26
+msgid "Tickets which depend on this approval:"
+msgstr "Tickets which depend on this approval:"
+
+#: html/Ticket/Create.html:156
+#: html/Ticket/Elements/EditBasics:47
+msgid "Time Left"
+msgstr "Tempo RImasto"
+
+#: html/Ticket/Create.html:155
+#: html/Ticket/Elements/EditBasics:42
+msgid "Time Worked"
+msgstr "Tempo Lavorato"
+
+#: lib/RT/Tickets_Overlay.pm:1138
+msgid "Time left"
+msgstr "Tempo rimasto"
+
+#: html/Elements/Footer:35
+msgid "Time to display"
+msgstr "Time to display"
+
+#: lib/RT/Tickets_Overlay.pm:1114
+msgid "Time worked"
+msgstr "Tempo lavorato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "TimeLeft"
+msgstr "TempoRimasto"
+
+#: lib/RT/Ticket_Overlay.pm:1164
+msgid "TimeWorked"
+msgstr "TempoLavorato"
+
+#: bin/rt-commit-handler:401
+msgid "To generate a diff of this commit:"
+msgstr "To generate a diff of this commit:"
+
+#: bin/rt-commit-handler:390
+msgid "To generate a diff of this commit:\\n"
+msgstr "To generate a diff of this commit:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1167
+msgid "Told"
+msgstr "Told"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transaction"
+
+#. ($self->Data)
+#: lib/RT/Transaction_Overlay.pm:639
+msgid "Transaction %1 purged"
+msgstr "Transaction %1 purged"
+
+#: lib/RT/Transaction_Overlay.pm:176
+msgid "Transaction Created"
+msgstr "Transaction Created"
+
+#: lib/RT/Transaction_Overlay.pm:88
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create couldn't, as you didn't specify a ticket id"
+
+#: lib/RT/Transaction_Overlay.pm:698
+msgid "Transactions are immutable"
+msgstr "Transactions are immutable"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Sto cercando di eliminare un diritto: %1"
+
+#: lib/RT/Date.pm:388
+msgid "Tue."
+msgstr "Mar."
+
+#: html/Admin/Elements/EditCustomField:43
+#: html/Ticket/Elements/AddWatchers:32
+#: html/Ticket/Elements/AddWatchers:43
+#: html/Ticket/Elements/AddWatchers:53
+#: lib/RT/Ticket_Overlay.pm:1165
+#: lib/RT/Tickets_Overlay.pm:958
+msgid "Type"
+msgstr "Type"
+
+#: lib/RT/ScripCondition_Overlay.pm:103
+msgid "Unimplemented"
+msgstr "Unimplemented"
+
+#: html/Admin/Users/Modify.html:67
+msgid "Unix login"
+msgstr "Unix login"
+
+#: html/Admin/Elements/ModifyUser:61
+msgid "UnixUsername"
+msgstr "UnixUsername"
+
+#. ($self->ContentEncoding)
+#: lib/RT/Attachment_Overlay.pm:264
+msgid "Unknown ContentEncoding %1"
+msgstr "Unknown ContentEncoding %1"
+
+#: html/Elements/SelectResultsPerPage:36
+msgid "Unlimited"
+msgstr "Unlimited"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Non previlegiato"
+
+#: lib/RT/Transaction_Overlay.pm:568
+msgid "Untaken"
+msgstr "Untaken"
+
+#: html/Elements/MyTickets:63
+#: html/Search/Bulk.html:32
+msgid "Update"
+msgstr "Aggiornamento"
+
+#: html/Admin/Users/Prefs.html:61
+msgid "Update ID"
+msgstr "ID Aggiornamento"
+
+#: html/Search/Bulk.html:119
+#: html/Ticket/ModifyAll.html:65
+#: html/Ticket/Update.html:66
+msgid "Update Type"
+msgstr "Tipo Aggiornamento"
+
+#: html/Search/Listing.html:60
+msgid "Update all these tickets at once"
+msgstr "Aggiorna tutti questi tickets in una sola volta"
+
+#: html/Admin/Users/Prefs.html:48
+msgid "Update email"
+msgstr "Email aggiornamento"
+
+#: html/Admin/Users/Prefs.html:54
+msgid "Update name"
+msgstr "Nome aggiornamento"
+
+#: lib/RT/Interface/Web.pm:408
+msgid "Update not recorded."
+msgstr "Aggiornamento non registrato."
+
+#: html/Search/Bulk.html:80
+msgid "Update selected tickets"
+msgstr "Aggiorna i tickets selezionati"
+
+#: html/Admin/Users/Prefs.html:35
+msgid "Update signature"
+msgstr "Aggiorna la firma"
+
+#: html/Ticket/ModifyAll.html:62
+msgid "Update ticket"
+msgstr "Aggiorna il ticket"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Update ticket # %1"
+msgstr "Aggiorna il ticket n° %1"
+
+#. ($Ticket->id)
+#: html/SelfService/Update.html:24
+#: html/SelfService/Update.html:46
+msgid "Update ticket #%1"
+msgstr "Aggiorna il ticket n°%1"
+
+#. ($Ticket->id, $Ticket->Subject)
+#: html/Ticket/Update.html:134
+msgid "Update ticket #%1 (%2)"
+msgstr "Aggiorna il ticket n°%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:406
+msgid "Update type was neither correspondence nor comment."
+msgstr "Update type was neither correspondence nor comment."
+
+#: html/Elements/SelectDateType:32
+#: html/Ticket/Elements/ShowDates:50
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Updated"
+msgstr "Aggiornato"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "User %1 %2: %3\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "User %1 Password: %2\\n"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "User '%1' not found"
+msgstr "User '%1' not found"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "User '%1' not found\\n"
+msgstr "User '%1' not found\\n"
+
+#: etc/initialdata:125
+#: etc/initialdata:191
+msgid "User Defined"
+msgstr "Definito da Utente"
+
+#: html/Admin/Users/Prefs.html:58
+msgid "User ID"
+msgstr "User ID"
+
+#: html/Elements/SelectUsers:25
+msgid "User Id"
+msgstr "User Id"
+
+#: html/Admin/Elements/GroupTabs:46
+#: html/Admin/Elements/QueueTabs:59
+#: html/Admin/Elements/SystemTabs:46
+#: html/Admin/Global/index.html:58
+msgid "User Rights"
+msgstr "Diritti Utente"
+
+#. ($msg)
+#: html/Admin/Users/Modify.html:225
+msgid "User could not be created: %1"
+msgstr "User could not be created: %1"
+
+#: lib/RT/User_Overlay.pm:261
+msgid "User created"
+msgstr "User created"
+
+#: html/Admin/Global/GroupRights.html:66
+#: html/Admin/Groups/GroupRights.html:53
+#: html/Admin/Queues/GroupRights.html:67
+msgid "User defined groups"
+msgstr "Gruppi definiti dall'utente"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "User notified"
+msgstr "User notified"
+
+#: html/Admin/Users/Prefs.html:24
+#: html/Admin/Users/Prefs.html:28
+msgid "User view"
+msgstr "User view"
+
+#: html/Admin/Users/Modify.html:47
+#: html/Elements/Login:50
+#: html/Ticket/Elements/AddWatchers:34
+msgid "Username"
+msgstr "Username"
+
+#: html/Admin/Elements/SelectNewGroupMembers:25
+#: html/Admin/Elements/Tabs:31
+#: html/Admin/Groups/Members.html:54
+#: html/Admin/Queues/People.html:67
+#: html/Admin/index.html:28
+#: html/User/Groups/Members.html:57
+msgid "Users"
+msgstr "Utenti"
+
+#: html/Admin/Users/index.html:64
+msgid "Users matching search criteria"
+msgstr "Utenti che soddisfano il criterio di ricerca"
+
+#: html/Search/Elements/PickRestriction:50
+msgid "ValueOfQueue"
+msgstr "ValueOfQueue"
+
+#: html/Admin/Elements/EditCustomField:56
+msgid "Values"
+msgstr "Valori"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Watch"
+msgstr "Osserva"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "WatchAsAdminCc"
+msgstr "OsservaComeAdminCc"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Watcher loaded"
+msgstr "Osservatore caricato"
+
+#: html/Admin/Elements/QueueTabs:41
+msgid "Watchers"
+msgstr "Osservatori"
+
+#: html/Admin/Elements/ModifyUser:55
+msgid "WebEncoding"
+msgstr "WebEncoding"
+
+#: lib/RT/Date.pm:389
+msgid "Wed."
+msgstr "Mer."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "When a ticket has been approved by any approver, add correspondence to the original ticket"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "When a ticket is created"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "When anything happens"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Whenever a ticket is resolved"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Whenever a ticket's owner changes"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Whenever a ticket's coda changes"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Whenever a ticket's status changes"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Whenever a user-defined condition occurs"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Whenever comments come in"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Whenever correspondence comes in"
+
+#: html/Admin/Users/Modify.html:163
+#: html/User/Prefs.html:51
+msgid "Work"
+msgstr "Lavoro"
+
+#: html/Admin/Elements/ModifyUser:69
+msgid "WorkPhone"
+msgstr "TelefonoLavoro"
+
+#: html/Ticket/Elements/ShowBasics:34
+#: html/Ticket/Update.html:64
+msgid "Worked"
+msgstr "Lavoro"
+
+#: lib/RT/Ticket_Overlay.pm:3122
+msgid "You already own this ticket"
+msgstr "You already own this ticket"
+
+#: html/autohandler:107
+msgid "You are not an authorized user"
+msgstr "You are not an authorized user"
+
+#: lib/RT/Ticket_Overlay.pm:2997
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "You can only reassign tickets that you own or that are unowned"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Non hai i permessi per visualizzare questo ticket.\\n"
+
+#. ($num, $queue)
+#: docs/design_docs/string-extraction-guide.txt:47
+msgid "You found %1 tickets in queue %2"
+msgstr "Hai trovato %1 tickets nella coda %2"
+
+#: html/NoAuth/Logout.html:30
+msgid "You have been logged out of RT."
+msgstr "Ti sei scollegato da RT."
+
+#: html/SelfService/Display.html:77
+msgid "You have no permission to create tickets in that queue."
+msgstr "Non hai permessi per creare tickets in questa coda."
+
+#: lib/RT/Ticket_Overlay.pm:1894
+msgid "You may not create requests in that queue."
+msgstr "Non puoi creare richieste in questa coda."
+
+#: html/NoAuth/Logout.html:35
+msgid "You're welcome to login again"
+msgstr "Collegati di nuovo"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Your %1 requests"
+msgstr "Le tue %1 richieste"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Your RT administrator has misconfigured the mail aliases which invoke RT"
+
+#: etc/initialdata:435
+#: etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Your request has been approved by %1. Other approvals may still be pending."
+
+#: etc/initialdata:469
+#: etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Your request has been approved."
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "Your request was rejected"
+msgstr "Your request was rejected"
+
+#: etc/initialdata:390
+#: etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Your request was rejected."
+
+#: html/autohandler:126
+msgid "Your username or password is incorrect"
+msgstr "Il tuo username o la tua password non sono corretti"
+
+#: html/Admin/Elements/ModifyUser:83
+#: html/Admin/Users/Modify.html:143
+#: html/User/Prefs.html:95
+msgid "Zip"
+msgstr "CAP"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "[no subject]"
+msgstr "[nessun oggetto]"
+
+#. ($right->PrincipalObj->Object->SelfDescription)
+#: html/User/Elements/DelegateRights:58
+msgid "as granted to %1"
+msgstr "come concesso a %1"
+
+#: html/SelfService/Closed.html:27
+msgid "closed"
+msgstr "chiuso"
+
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectMatch:33
+msgid "contains"
+msgstr "contiene"
+
+#: html/Elements/SelectAttachmentField:25
+msgid "content"
+msgstr "contenuto"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content-type"
+msgstr "content-type"
+
+#: lib/RT/Ticket_Overlay.pm:2281
+msgid "correspondence (probably) not sent"
+msgstr "corrispondenza (probabilmente) non inviata"
+
+#: lib/RT/Ticket_Overlay.pm:2291
+msgid "correspondence sent"
+msgstr "corrispondenza inviata"
+
+#: html/Admin/Elements/ModifyQueue:62
+#: html/Admin/Queues/Modify.html:76
+#: lib/RT/Date.pm:318
+msgid "days"
+msgstr "giorni"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "dead"
+msgstr "morto"
+
+#: html/Search/Listing.html:74
+msgid "delete"
+msgstr "elimina"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "deleted"
+msgstr "eliminato"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "does not match"
+msgstr "non corrisponde a"
+
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectMatch:34
+msgid "doesn't contain"
+msgstr "non contiene"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "equal to"
+msgstr "uguale a"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "false"
+msgstr "falso"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "filename"
+msgstr "nome file"
+
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectEqualityOperator:37
+msgid "greater than"
+msgstr "più grande di"
+
+#. ($self->Name)
+#: lib/RT/Group_Overlay.pm:193
+msgid "group '%1'"
+msgstr "gruppo '%1'"
+
+#: lib/RT/Date.pm:314
+msgid "hours"
+msgstr "ore"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "id"
+msgstr "id"
+
+#: html/Elements/SelectBoolean:31
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectMatch:35
+#: html/Search/Elements/PickRestriction:46
+#: html/Search/Elements/PickRestriction:75
+#: html/Search/Elements/PickRestriction:87
+msgid "is"
+msgstr "è"
+
+#: html/Elements/SelectBoolean:35
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectMatch:36
+#: html/Search/Elements/PickRestriction:47
+#: html/Search/Elements/PickRestriction:76
+#: html/Search/Elements/PickRestriction:88
+msgid "isn't"
+msgstr "non è"
+
+#: html/Elements/SelectCustomFieldOperator:37
+#: html/Elements/SelectEqualityOperator:37
+msgid "less than"
+msgstr "minore di"
+
+#: html/Search/Elements/PickRestriction:66
+msgid "matches"
+msgstr "corrisponde a"
+
+#: lib/RT/Date.pm:310
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:65
+msgid "minutes"
+msgstr "minuti"
+
+#: bin/rt-commit-handler:764
+msgid ""
+"modifications\\n"
+"\\n"
+msgstr ""
+"modifiche\\n"
+"\\n"
+
+#: lib/RT/Date.pm:326
+msgid "months"
+msgstr "mesi"
+
+#: lib/RT/Queue_Overlay.pm:57
+msgid "new"
+msgstr "nuovo"
+
+#: html/Admin/Elements/EditScrips:42
+msgid "no value"
+msgstr "nessun valore"
+
+#: html/Ticket/Elements/EditWatchers:27
+msgid "none"
+msgstr "nessuno"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "not equal to"
+msgstr "diverso da"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "notlike"
+msgstr "diverso da"
+
+#: html/SelfService/Elements/MyRequests:60
+#: lib/RT/Queue_Overlay.pm:58
+msgid "open"
+msgstr "aperto"
+
+#. ($self->Name, $user->Name)
+#: lib/RT/Group_Overlay.pm:198
+msgid "personal group '%1' for user '%2'"
+msgstr "Gruppo personale '%1' per l'utente '%2'"
+
+#. ($queue->Name, $self->Type)
+#: lib/RT/Group_Overlay.pm:206
+msgid "queue %1 %2"
+msgstr "coda %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "rejected"
+msgstr "rifiutato"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "resolved"
+msgstr "risolto"
+
+#: lib/RT/Date.pm:306
+msgid "sec"
+msgstr "sec"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "stalled"
+msgstr "in stallo"
+
+#. ($self->Type)
+#: lib/RT/Group_Overlay.pm:201
+msgid "system %1"
+msgstr "sistema %1"
+
+#. ($self->Type)
+#: lib/RT/Group_Overlay.pm:212
+msgid "system group '%1'"
+msgstr "gruppo di sistema '%1'"
+
+#: html/Elements/Error:41
+#: html/SelfService/Error.html:41
+msgid "the calling component did not specify why"
+msgstr "the calling component did not specify why"
+
+#. ($self->Instance, $self->Type)
+#: lib/RT/Group_Overlay.pm:209
+msgid "ticket #%1 %2"
+msgstr "ticket n°%1 %2"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "true"
+msgstr "vero"
+
+#. ($self->Id)
+#: lib/RT/Group_Overlay.pm:215
+msgid "undescribed group %1"
+msgstr "undescribed group %1"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "undescripbed group %1"
+msgstr "undescripbed group %1"
+
+#. ($user->Object->Name)
+#: lib/RT/Group_Overlay.pm:190
+msgid "user %1"
+msgstr "utente %1"
+
+#: lib/RT/Date.pm:322
+msgid "weeks"
+msgstr "settimane"
+
+#: NOT
+#: FOUND
+#: IN
+#: SOURCE
+msgid "with template %1"
+msgstr "con il modello %1"
+
+#: lib/RT/Date.pm:330
+msgid "years"
+msgstr "anni"
+
diff --git a/rt/lib/RT/I18N/ja.po b/rt/lib/RT/I18N/ja.po
new file mode 100644
index 0000000..c0401f8
--- /dev/null
+++ b/rt/lib/RT/I18N/ja.po
@@ -0,0 +1,4822 @@
+# Japanese translation by Interactive Artists LLC
+# 0.1 2002 08 15
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 2.1.x\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2002-05-13 02:00+0800\n"
+"Last-Translator: Jesse Vincent <jesse@bestpractical.com>\n"
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr ""
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr ""
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr ""
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%3ã«å¤‰æ›´ã•ã‚ŒãŸ%1 %2 "
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 of group %3"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 ã“ã®ãƒã‚±ãƒƒãƒˆ\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr ""
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr ""
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr ""
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1スクリプトアクションロードã—ã¾ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%2ã¨åŒã˜ãƒãƒªãƒ¥ãƒ¼ã®%1ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1aliasesã‚’å‹•ã‹ã™ãŸã‚ã«ãƒã‚±ãƒƒãƒˆIDãŒå¿…è¦ã§ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1aliasesã‚’å‹•ã‹ã™ãŸã‚ã«ãƒã‚±ãƒƒãƒˆIDãŒå¿…è¦ã§ã™ "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1aliasesã‚’å‹•ã‹ã™ãŸã‚ã«ãƒã‚±ãƒƒãƒˆIDãŒå¿…è¦ã§ã™(%2ã‹ã‚‰) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%2ã«ã‚ˆã‚‹%1"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1ã¯%2ã‹ã‚‰%3ã«å¤‰æ›´ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1ã¯ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ã‚’ã¯ã˜ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ(%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1ã¯åˆ†è§£ã™ã‚‹ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’設定ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚RTã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã«ä¸€è²«æ€§ãŒãªã„å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚"
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr ""
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr ""
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1ã¯ã“ã®ã‚­ãƒ¥ãƒ¼ã§ã¯%2ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1ã¯ã“ã®ãƒã‚±ãƒƒãƒˆã§ã¯%2ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1ã¯ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰%2ã®ãƒãƒªãƒ¥ãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1ã¯æœ‰åŠ¹ãªã‚­ãƒ¥ãƒ¼IDã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1分"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1表示ã•ã‚Œã¾ã›ã‚“"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 æˆåŠŸã—ã¾ã—ãŸ\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1タイプã¯$MessageIdIDã§ã¯ä¸æ˜Žã§ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1タイプã¯%2ã§ã¯ä¸æ˜Žã§ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr ""
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1ã¯åˆ†è§£ã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ãƒã‚±ãƒƒãƒˆã®ã™ã¹ã¦ã®ãƒ¡ãƒ³ãƒãƒ¼ã‚’分解ã—ã¾ã™ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1ãŒã‚‚ã—リンクã•ã‚ŒãŸãƒªã‚¯ã‚¨ã‚¹ãƒˆã®å¾“属者(もã—ãã¯ãƒ¡ãƒ³ãƒãƒ¼ï¼‰ã§ã‚ã‚‹ã¨ã€[ローカル]ベースを行ãè©°ã¾ã‚‰ã›ã‚‹ã“ã¨ã«ãªã‚Šã¾ã™ã€‚"
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1:アタッãƒãƒ¡ãƒ³ãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "%1'ã¯ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã«ç„¡åŠ¹åãƒãƒªãƒ¥ãƒ¼ã§ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "%1' アクションをèªè­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(スクリプトを削除ã™ã‚‹ãŸã‚ã®ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ï¼‰"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(削除ã®ãŸã‚ã®ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹ï¼‰"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr ""
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(ãƒã‚±ãƒƒãƒˆIDã¾ãŸã¯URLsを空欄ã§åŒºåˆ‡ã£ã¦å…¥åŠ›ã—ã¦ãã ã•ã„)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(メンãƒãƒ¼ãªã—)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(スクリプトãªã—)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr ""
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Eメールアドレスã«ãŠã‘るカンマã§åŒºåˆ‡ã‚‰ã‚ŒãŸãƒªã‚¹ãƒˆã¸ã®æ­£ç¢ºãªã‚¢ãƒƒãƒ—デートã®ãƒ–ラインドコピーをé€ã‚‹ã€‚今後ã®ã‚¢ãƒƒãƒ—デートを誰ãŒå—ä¿¡ã™ã‚‹ã‹ã¯<b>変更ã§ãã¾ã›ã‚“</b>)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Eメールアドレスã«ãŠã‘るカンマã§åŒºåˆ‡ã‚‰ã‚ŒãŸãƒªã‚¹ãƒˆã¸ã®æ­£ç¢ºãªã‚¢ãƒƒãƒ—デートã®ã‚³ãƒ”ーをé€ã‚‹ã€‚今後ã®ã‚¢ãƒƒãƒ—デートを誰ãŒå—ä¿¡ã™ã‚‹ã‹ã¯<b>変更ã§ãã¾ã›ã‚“</b>)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(空)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr ""
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(サブジェクトãªã—)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(ãƒãƒªãƒ¥ãƒ¼ãªã—)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(ã²ã¨ã¤ã®ãƒã‚±ãƒƒãƒˆã®ã¿ï¼‰"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr ""
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(必è¦ã§ã™ï¼‰"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "ç§ãŒæ‰€æœ‰ã—ã¦ã„ã‚‹25ã®æœ€ã‚‚é‡è¦ãªå„ªå…ˆæ¨©"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "ç§ãŒãƒªã‚¯ã‚¨ã‚¹ãƒˆã—ãŸ25ã®æœ€ã‚‚é‡è¦ãªå„ªå…ˆæ¨©"
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr ""
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"æ–°ã—ã„ãƒã‚±ãƒƒãƒˆ\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "エースã¯ã¿ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "エースã¯ä½œæˆã€å‰Šé™¤ã®ã¿ã§ã™ã€‚"
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "æ„図的ã§ãªã„ãƒã‚±ãƒƒãƒˆã®ä¿®æ­£ã‚’防ããŸã‚ã«å¼·åˆ¶çµ‚了ã—ã¾ã™ã€‚\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "アクセスコントロール"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "アクション"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "アクション%1ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr ""
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr ""
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "管ç†Ccを追加ã™ã‚‹"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Ccを追加ã™ã‚‹"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr ""
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "リクエストã™ã‚‹äººã‚’を追加ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "æ–°ã—ã„グローãƒãƒ«ã‚¹ã‚¯ãƒªãƒ—トを追加ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "ã“ã®ã‚­ãƒ¥ãƒ¼ã«ã‚¹ã‚¯ãƒªãƒ—トを追加ã™ã‚‹"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "ã™ã¹ã¦ã®ã‚­ãƒ¥ãƒ¼ã«é©å¿œã™ã‚‹ã‚¹ã‚¯ãƒªãƒ—トを追加ã™ã‚‹"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "é¸æŠžã•ã‚ŒãŸãƒã‚±ãƒƒãƒˆã¸ã®ã‚³ãƒ¡ãƒ³ãƒˆã¾ãŸã¯è¿”事を追加ã™ã‚‹"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "メンãƒãƒ¼ã‚’追加ã™ã‚‹"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "æ–°ã—ã„ウォッãƒãƒ£ãƒ¼ã‚’追加ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "ã“ã®ã‚­ãƒ¥ãƒ¼ã«%1ã®è²¬ä»»è€…を追加ã—ã¾ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "ã“ã®ãƒã‚±ãƒƒãƒˆã«%1ã®è²¬ä»»è€…を追加ã—ã¾ã—ãŸ"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "ä½æ‰€1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "ä½æ‰€2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "管ç†Cc"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr ""
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr ""
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "管ç†ã‚­ãƒ¥ãƒ¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "管ç†ãƒ¦ãƒ¼ã‚¶ãƒ¼"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "管ç†/グローãƒãƒ«è¨­å®š"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "管ç†/グループ"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "管ç†/キュー/基本"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr ""
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "管ç†Cc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr ""
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "管ç†è€…Cc"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "絞込ã¿æ¤œç´¢"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "後"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "ã™ã¹ã¦ã®ã‚­ãƒ¥ãƒ¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr ""
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr ""
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr ""
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr ""
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr ""
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr ""
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "四月"
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "昇順"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "添付"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "添付ファイル"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr ""
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "添付ファイル%1ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "添付ファイルãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "添付ファイルå"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "添付ファイル"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "八月"
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "自動システム"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr ""
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "悪ã„PGPç½²å: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "悪ã„添付IDã§ã™ã€‚添付ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“'%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "%1ã®æ‚ªã„データã§ã™"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "添付ファイルã«ã¨ã£ã¦æ‚ªã„トランザクションナンãƒãƒ¼ã§ã™ã€‚%1ã¯%2\\nã®ã¯ãšã§ã™"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "基本"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "本当ã«å¤‰æ›´ã‚’ä¿å­˜ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "å‰"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr ""
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr ""
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "ã“ã®æ¤œç´¢ã«ãƒ–ックマークã®ã§ãã‚‹URLã§ã™"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "短ã„ヘッダー"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "ãƒã‚±ãƒƒãƒˆã®ä¸€æ‹¬ã‚¢ãƒƒãƒ—デート"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "システムユーザーを修正ã§ãã¾ã›ã‚“"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "æ°åãªã—ã«ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ãƒãƒªãƒ¥ãƒ¼ã®è¿½åŠ ã¯ã§ãã¾ã›ã‚“"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "ãƒã‚±ãƒƒãƒˆè‡ªä½“ã«ã¯ãƒªãƒ³ã‚¯ã§ãã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "ã™ã§ã«çµåˆã—ãŸãƒã‚±ãƒƒãƒˆã«ã¯çµåˆã§ãã¾ã›ã‚“。ã“ã®ã‚¨ãƒ©ãƒ¼ã¯æ±ºã—ã¦å‡ºã•ãªã„ã§ãã ã•ã„"
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "ベースã¨ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’特定ã§ãã¾ã›ã‚“"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "ユーザー: %1を作æˆã§ãã¾ã›ã‚“"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "パスワードを変更ã™ã‚‹"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "権利を無効ã«ã™ã‚‹ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "å­ä¾›"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "町"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "終了ã—ãŸãƒªã‚¯ã‚¨ã‚¹ãƒˆã§ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "ç†è§£ã—ã¦ã„ãªã„コマンド!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "コメント"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "コメントアドレス"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "記録ã•ã‚Œã¦ã„ãªã„コメント"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "コメント"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "コメント(リクエスã¨ã—ãŸäººã«ã¯é€ä¿¡ã•ã‚Œã¾ã›ã‚“)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "コメント(リクエスã¨ã—ãŸäººã«ã¯é€ä¿¡ã•ã‚Œã¾ã›ã‚“)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "%1ã«ã¤ã„ã¦ã®ã‚³ãƒ¡ãƒ³ãƒˆ"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¤ã„ã¦ã®ã‚³ãƒ¡ãƒ³ãƒˆ"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "コメントãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "コメントãŒçŸ­ãã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "コンパイルè¦åˆ¶"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "コンディション"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "コンディションãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "設定"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "確èª"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "コンタクト情報"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "コンタクトã•ã‚ŒãŸæ—¥ã«ã¡'%1'を解æžã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "情報"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr ""
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "メールアドレス"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "通信ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "記録ã•ã‚Œã¦ã„ãªã„通信"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "ãƒã‚±ãƒƒãƒˆã®æ–°ã—ã„カスタムフィールドãƒãƒªãƒ¥ãƒ¼ã‚’追加ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "オーナー変更ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "カスタムフィールドã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "グループã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "テンプレート: %1ã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "ãƒã‚±ãƒƒãƒˆã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚キューãŒã‚»ãƒƒãƒˆã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "ユーザーã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "ãƒã‚±ãƒƒãƒˆã¨ãã®ID%1ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "グループ %1ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’作æˆã¾ãŸã¯è¦‹ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "ãã®è²¬ä»»è€…を見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "ユーザー%1を見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "グループをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "ã“ã®ã‚­ãƒ¥ãƒ¼ã§ãã®è²¬ä»»è€…ã‚’%1ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "ã“ã®ãƒã‚±ãƒƒãƒˆã§ãã®è²¬ä»»è€…ã‚’%1ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "ã“ã®ã‚­ãƒ¥ãƒ¼ã§ãã®è²¬ä»»è€…ã‚’%1ã¨ã—ã¦å‰Šé™¤ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "ã“ã®ãƒã‚±ãƒƒãƒˆã§ãã®è²¬ä»»è€…ã‚’%1ã¨ã—ã¦å‰Šé™¤ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "グループã«ãƒ¡ãƒ³ãƒãƒ¼ã®è¿½åŠ ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "トランザクション: %1ã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "GPGã®è¿”事\\nã‹ã‚‰ä½•ã‚’è¡Œã£ãŸã‚‰ã‚ˆã„ã®ã‹ã‚ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "グループ\\nãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "責任者ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "ãã®ãƒãƒªãƒ¥ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "ユーザー\\nãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "ユーザーデータベース\\nã‹ã‚‰%1をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "RT設定ファイル'%1' %2をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "スクリプトをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "グループ%1をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "リンクをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "キューをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "キュー%1をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "スクリプトをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "テンプレートをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ï¼ˆ%1)をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "ãƒã‚±ãƒƒãƒˆ'%1'をロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "国"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "作æˆ"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "カスタムフィールドã®ä½œæˆ"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "æ–°ã—ã„カスタムフィールドã®ä½œæˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "æ–°ã—ã„グローãƒãƒ«ã‚¹ã‚¯ãƒªãƒ—トã®ä½œæˆ"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "æ–°ã—ã„グループã®ä½œæˆ"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "æ–°ã—ã„個人グループã®ä½œæˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "æ–°ã—ã„キューã®ä½œæˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "æ–°ã—ã„テンプレートã®ä½œæˆ"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "æ–°ã—ã„ãƒã‚±ãƒƒãƒˆã®ä½œæˆ"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "æ–°ã—ã„ユーザーã®ä½œæˆ"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "キューã®ä½œæˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "呼ã³å‡ºã•ã‚ŒãŸã‚­ãƒ¥ãƒ¼ã®ä½œæˆ"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "リクエストã®ä½œæˆ"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "テンプレートã®ä½œæˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr ""
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr ""
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "ãƒã‚±ãƒƒãƒˆã®ä½œæˆ"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr ""
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "作æˆã—ã¾ã—ãŸ"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "カスタムフィールド%1を作æˆã—ã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "テンプレート%1を作æˆã—ã¾ã—ãŸ"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "ç¾åœ¨ã®é–¢ä¿‚"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "ç¾åœ¨ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "ç¾åœ¨ã®æ¨©åˆ©"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr ""
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "ç¾åœ¨ã®ã‚¦ã‚©ãƒƒãƒãƒ£ãƒ¼"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "カスタムフィールド"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr ""
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "カスタムフィールド%1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "カスタムフィールド%1ã¯ãƒãƒªãƒ¥ãƒ¼ãŒã‚ã‚Šã¾ã™"
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "カスタムフィールド%1ã¯ãƒãƒªãƒ¥ãƒ¼ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "カスタムフィールド%1ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "カスタムフィールドãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "カスタムフィールド%2ã®ãŸã‚ã®ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ãƒãƒªãƒ¥ãƒ¼%1ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "カスタムフィールドãŒ%1ã‹ã‚‰%2ã«å¤‰æ›´ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "カスタムフィールドãƒãƒªãƒ¥ãƒ¼ã¯å‰Šé™¤ã•ã‚Œã¾ã›ã‚“"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "カスタムフィールドãƒãƒªãƒ¥ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "カスタムフィールドãƒãƒªãƒ¥ãƒ¼ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr ""
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "日付"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "å二月"
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr ""
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr ""
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr ""
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr ""
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr ""
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr ""
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "代表者ã®æ¨©åˆ©"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr ""
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr ""
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "削除"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "ã“ã®ã‚ªãƒ–ジェクトを削除ã™ã‚‹ã¨æŒ‡ç¤ºã®å®Œå…¨æ€§ãŒããšã•ã‚Œã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "ã“ã®ã‚ªãƒ–ジェクトを削除ã™ã‚‹ã¨æŒ‡ç¤ºã®å®Œå…¨æ€§ãŒããšã•ã‚Œã¾ã™"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "ã“ã®ã‚ªãƒ–ジェクトを削除ã™ã‚‹ã¨æŒ‡ç¤ºã®å®Œå…¨æ€§ãŒå¦¨å®³ã•ã‚Œã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr ""
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr ""
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "次ã®ã‚‚ã®æ¬¡ç¬¬ã§ã‚ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "従属ãƒã‚±ãƒƒãƒˆ: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "ã«ã‚ˆã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "é™é †ã™ã‚‹"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "下ã®å•é¡Œç‚¹ã‚’表ã™"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "記述"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "詳細"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "表ã™"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr ""
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "モードを表ã™"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "ãƒã‚±ãƒƒãƒˆ#%1を表ã™"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr ""
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã‚’æ›´æ–°ã—ãªã„ã§ãã ã•ã„"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "ダウンロード"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "期é™åˆ‡ã‚Œ"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "期é™ãŒåˆ‡ã‚Œã‚‹æ—¥'%1'ã¯è§£æžã•ã‚Œã¾ã›ã‚“"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ERROR: ã¯ãƒã‚±ãƒƒãƒˆ '%1': %2.\\nをロードã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "編集"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr ""
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "%1ã®ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã‚’編集ã™ã‚‹"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "関係を編集ã™ã‚‹"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "スクリプトを編集ã™ã‚‹"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "システムテンプレートを編集ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "%1ã®ãƒ†ãƒ³ãƒ—レートを編集ã™ã‚‹"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "キュー%1ã®è¨­å®šã‚’編集ã™ã‚‹"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "ユーザー%1ã®è¨­å®šã‚’編集ã™ã‚‹"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "カスタムフィールド%1を編集ã™ã‚‹"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "グループ%1ã®ä¼šå“¡ã‚’編集ã™ã‚‹"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "個人グループ%1ã®ä¼šå“¡ã‚’編集ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "テンプレート%1を編集ã™ã‚‹"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "ベースもã—ãã¯ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’特定ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Eメール"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "ãŠä½¿ã„ã®Eメールアドレス"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "Eメールアドレス"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "Eメールエンコーディング"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "有効ã«ãªã‚Šã¾ã—ãŸï¼ˆã‚‚ã†ä¸€åº¦ã“ã®ãƒœãƒƒã‚¯ã‚¹ã‚’ãƒã‚§ãƒƒã‚¯ã™ã‚‹ã¨ã“ã®ã‚­ãƒ¥ãƒ¼ã¯æœ‰åŠ¹ã§ãªããªã‚Šã¾ã™ï¼‰"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "有効ãªã‚­ãƒ¥ãƒ¼"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "有効ãªã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹%1"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "ãƒã‚±ãƒƒãƒˆã‚’リンクã™ã‚‹ãƒã‚±ãƒƒãƒˆã¾ãŸã¯URLsを入力ã—ã¦ãã ã•ã„。入力ã™ã‚‹é …ç›®ãŒã„ãã¤ã‹ã‚ã‚‹å ´åˆã«ã¯ã‚¹ãƒšãƒ¼ã‚¹ã§åŒºåˆ‡ã£ã¦ãã ã•ã„。"
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "エラー"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "パラメーターã®ã‚¨ãƒ©ãƒ¼Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "パラメーターã®ã‚¨ãƒ©ãƒ¼Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "パラメーターã®ã‚¨ãƒ©ãƒ¼Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "パラメーターã®ã‚¨ãƒ©ãƒ¼Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr ""
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "外部ã®èªè¨¼ID"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "外部ã®ã‚³ãƒ³ã‚¿ã‚¯ãƒˆæƒ…å ±ID"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "ãã®ä»–ã®æƒ…å ±"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "'特権ã®ã‚ã‚‹'ユーザーã®æ“¬ä¼¼ã‚°ãƒ«ãƒ¼ãƒ—ã®æ¤œç´¢ãŒå¤±æ•—ã—ã¾ã—ãŸ"
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "'特権ã®ãªã„'ユーザーã®æ“¬ä¼¼ã‚°ãƒ«ãƒ¼ãƒ—ã®æ¤œç´¢ãŒå¤±æ•—ã—ã¾ã—ãŸ"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr ""
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "二月"
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "終了"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "最終優先順ä½"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr ""
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr ""
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "æ–°ã—ã„/é–‹ããƒã‚±ãƒƒãƒˆã‚’見ã¤ã‘ã‚‹"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "人々を見ã¤ã‘ã‚‹"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "最åˆã®"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "最åˆã®ãƒšãƒ¼ã‚¸"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "ã°ã‹ï¼"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "変更を強制ã—ã¾ã™"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "フリーフォームコンタクト情報"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr ""
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "金曜日"
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "フルヘッダー"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "pgp sig\\nã‹ã‚‰ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’å¾—ã‚‹"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr ""
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "グローãƒãƒ«"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "グローãƒãƒ«ã‚¹ã‚¯ãƒªãƒ—ト"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "è¡Œãï¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "%1\\nã‹ã‚‰ã®è‰¯ã„pgp sig"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "ページã¸è¡Œã"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "ãƒã‚±ãƒƒãƒˆã«è¡Œã"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr ""
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "グループ%1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "グループ権利"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "グループã«ã¯ã™ã§ã«ãƒ¡ãƒ³ãƒãƒ¼ãŒã„ã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "グループãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "グループãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "グループãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "グループãŒç‰¹å®šã§ãã¾ã›ã‚“。\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "グループ"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "グループã¯å½¼ã‚‰ã®ãƒ¡ãƒ³ãƒãƒ¼ã«ã¯ãªã‚Œã¾ã›ã‚“"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "ã“ã‚“ã«ã¡ã¯ï¼"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "ã“ã‚“ã«ã¡ã¯ã€%1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "ヒストリー"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "自宅ã®é›»è©±"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "ホームページ"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "ç§ã¯[quant,_1,concrete mixer]ãŒã‚ã‚Šã¾ã™ã€‚"
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "ID"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "身分証明書"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr ""
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr ""
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "上ã®ä½•ã‹ã‚’アップデートã—ãŸãªã‚‰ã€æ¬¡ã®ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr ""
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "リストã®ç„¡åŠ¹ãªã‚­ãƒ¥ãƒ¼ã‚’å«ã‚€"
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "検索ã®ç„¡åŠ¹ãªãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’å«ã‚€"
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "最åˆã®å„ªå…ˆæ¨©"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "入力エラー"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr ""
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "無効ãªã‚°ãƒ«ãƒ¼ãƒ—タイプã§ã™"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "無効ãªã‚ªãƒ¼ãƒŠãƒ¼ã§ã™ã€‚ '誰ã§ã‚‚ãªã„'ã«åˆæœŸè¨­å®šã—ã¾ã™."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "無効ãªã‚­ãƒ¥ãƒ¼ã§ã™"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "無効ãªæ¨©åˆ©ã§ã™"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "%1ã«ã¯ç„¡åŠ¹ãªãƒãƒªãƒ¥ãƒ¼ã§ã™"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "カスタムフィールドã«ã¯ç„¡åŠ¹ãªãƒãƒªãƒ¥ãƒ¼ã§ã™"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "ステータスã«ã¯ç„¡åŠ¹ãªãƒãƒªãƒ¥ãƒ¼ã§ã™"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr ""
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr ""
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "一月"
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr ""
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "七月"
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "大ãã„"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "六月"
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "キーワード"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "é•·ã„"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "最後ã®"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "最後ã®ã‚³ãƒ³ã‚¿ã‚¯ãƒˆ"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "最後ã«ã‚³ãƒ³ã‚¿ã‚¯ãƒˆã—ãŸ"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr ""
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "最後ã«ã‚¢ãƒƒãƒ—デートã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "残ã£ãŸ"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’RTã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¾ã™"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æ¨©åˆ©ã‚’èªã‚ã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "オーナーを%1 %2ã«åˆ¶é™ã—ã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "キューを%1 %2ã«åˆ¶é™ã—ã¾ã™"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "リンクã¯ã™ã§ã«å­˜åœ¨ã—ã¦ã„ã¾ã™"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "リンクãŒä½œæˆã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "リンクãŒä½œæˆã•ã‚Œã¾ã—ãŸï¼ˆ%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "リンクãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸï¼ˆ%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "リンクãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "リンクãƒã‚±ãƒƒãƒˆ#%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "リンク"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "場所"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "ログディレクトリー%1ãŒè¦‹ã¤ã‹ã‚‰ãªã„ã€ã¾ãŸã¯æ›¸ã出ã›ã¾ã›ã‚“。\\n RTãŒå‹•ãã¾ã›ã‚“"
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "%1ã¨ã—ã¦ã‚µã‚¤ãƒ³ã™ã‚‹"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "ログイン"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "ログアウト"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "オーナーを決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "ステータスを決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "期é™æœŸæ—¥ã‚’決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "解æžæœŸæ—¥ã‚’決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "開始ã—ãŸæ—¥ã‚’決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "開始日を決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "ã„ã‚ã‚ŒãŸæ—¥ã‚’決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "優先順ä½ã‚’決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "キューを決ã‚ã‚‹"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "サブジェクトを決ã‚ã‚‹"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr ""
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr ""
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr ""
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr ""
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "三月"
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr ""
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "五月"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "メンãƒãƒ¼ãŒè¿½åŠ ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "メンãƒãƒ¼ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "メンãƒãƒ¼ãŒå‰Šé™¤ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "メンãƒãƒ¼"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "çµåˆãŒæˆåŠŸã—ã¾ã—ãŸ"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "çµåˆãŒå¤±æ•—ã—ã¾ã—ãŸã€‚有効ãªIDãŒè¨­å®šã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "ã«çµåˆ"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "æºå¸¯"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "æºå¸¯é›»è©±"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "カスタムフィールド%1を修正ã™ã‚‹"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr ""
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr ""
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "#%1ã®æœŸæ—¥ã‚’修正ã™ã‚‹"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "ãƒã‚±ãƒƒãƒˆ#%1ã®æœŸæ—¥ã‚’修正ã™ã‚‹"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "グローãƒãƒ«ã‚°ãƒ«ãƒ¼ãƒ—ã®æ¨©åˆ©ã‚’修正ã™ã‚‹"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "グローãƒãƒ«ã‚°ãƒ«ãƒ¼ãƒ—ã®æ¨©åˆ©ã‚’修正ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "グローãƒãƒ«ã‚¹ã‚¯ãƒªãƒ—トを修正ã™ã‚‹"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "グローãƒãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æ¨©åˆ©ã‚’修正ã™ã‚‹"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "%1ã®ã‚°ãƒ«ãƒ¼ãƒ—権利を修正ã™ã‚‹"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "キュー%1ã®ã‚°ãƒ«ãƒ¼ãƒ—権利を修正ã™ã‚‹"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr ""
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "キュー%1ã«é–¢ä¿‚ã®ã‚る人々を修正ã™ã‚‹"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "ãƒã‚±ãƒƒãƒˆ#%1ã«é–¢ä¿‚ã®ã‚る人々を修正ã™ã‚‹"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "キュー%1ã®ã‚¹ã‚¯ãƒªãƒ—トを修正ã™ã‚‹"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr ""
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "テンプレート%1を修正ã™ã‚‹"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "グループ%1を修正ã™ã‚‹"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "ユーザー%1を修正ã™ã‚‹"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "ãƒã‚±ãƒƒãƒˆ# %1を修正ã™ã‚‹"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "ãƒã‚±ãƒƒãƒˆ#%1を修正ã™ã‚‹"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr ""
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "グループ%1ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼æ¨©åˆ©ã‚’修正ã™ã‚‹"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "キュー%1ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼æ¨©åˆ©ã‚’修正ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "キュー'%1'ã®ã‚¦ã‚©ãƒƒãƒãƒ£ãƒ¼ã‚’修正ã™ã‚‹"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr ""
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "月曜日"
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "ã•ã‚‰ã«%1ã«ã¤ã„ã¦"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "多ãã®"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "'åå‰'ã®å±žæ€§ã‚’特定ã—ã¦ãã ã•ã„"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr ""
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr ""
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "åå‰"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "ç¾åœ¨ãŠä½¿ã„ã®åå‰"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr ""
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "æ–°ã—ã„"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "æ–°ã—ã„パスワード"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "æ–°ã—ã„関係"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr ""
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "æ–°ã—ã„パスワード"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "æ–°ã—ã„パスワード情報ãŒé€ã‚‰ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "æ–°ã—ã„リクエスト"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "æ–°ã—ã„権利"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "æ–°ã—ã„検索"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "æ–°ã—ã„ãƒã‚±ãƒƒãƒˆã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr ""
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "æ–°ã—ã„ユーザーãŒå‘¼ã°ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "æ–°ã—ã„ウォッãƒãƒ£ãƒ¼"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "æ–°ã—ã„ウインドウ設定"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "次ã¸"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "次ã®ãƒšãƒ¼ã‚¸"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "ニックãƒãƒ¼ãƒ "
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "カスタムフィールドãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "グループãŒå®šç¾©ã•ã‚Œã¾ã›ã‚“"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "キューãŒå®šç¾©ã•ã‚Œã¾ã›ã‚“"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "RTユーザーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。RT管ç†è€…ã«ç›¸è«‡ã—ã¦ãã ã•ã„。\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "テンプレートãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "ãƒã‚±ãƒƒãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“。ãƒã‚±ãƒƒãƒˆã‚’終了ã—ã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "ãƒã‚±ãƒƒãƒˆãŒç‰¹å®šã§ãã¾ã›ã‚“。ãƒã‚±ãƒƒãƒˆã®ä¿®æ­£ã‚’終了ã—ã¾ã™\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "コマンドãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é–¢ã—ã¦ã®ã‚³ãƒ¡ãƒ³ãƒˆã¯å…¥åŠ›ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "通信文書ã®æ·»ä»˜ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "%1記述ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "グループãŒç‰¹å®šã§ãã¾ã›ã‚“"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "パスワードãŒè¨­å®šã•ã‚Œã¾ã›ã‚“"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "キューを作æˆã™ã‚‹è¨±å¯ãŒã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "ユーザーを作æˆã™ã‚‹è¨±å¯ãŒã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "ãã®ãƒã‚±ãƒƒãƒˆã‚’表示ã™ã‚‹è¨±å¯ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "アップデートãƒã‚±ãƒƒãƒˆã‚’見る許å¯ãŒã•ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "責任者ãŒç‰¹å®šã§ãã¾ã›ã‚“"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "責任者ãŒé¸æŠžã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "検索基準ã«ã‚ã£ãŸã‚­ãƒ¥ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "権利ãŒè¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "æ“作ã®ãŸã‚ã®æ¤œç´¢ãŒã§ãã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "ãƒã‚±ãƒƒãƒˆIDãŒç‰¹å®šã§ãã¾ã›ã‚“"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "トランザクションタイプãŒç‰¹å®šã§ãã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr ""
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "検索基準ã«ã‚ã£ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "有効ãªRTユーザーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。RT cvcãƒãƒ³ãƒ‰ãƒ©ãŒåˆ†é›¢ã—ã¦ã„ã¾ã™ã€‚RT管ç†è€…ã«ç›¸è«‡ã—ã¦ãã ã•ã„。\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr ""
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr ""
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr ""
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "ログインã§ãã¾ã›ã‚“"
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "セットã§ãã¾ã›ã‚“"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "ã¾ã å®Ÿè¡Œã§ãã¾ã›ã‚“"
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "ã¾ã å®Ÿè¡Œã§ãã¾ã›ã‚“。。。"
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "ãŠçŸ¥ã‚‰ã›ã‚’é€ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr ""
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr ""
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr ""
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr ""
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr ""
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr ""
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr ""
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr ""
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr ""
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr ""
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr ""
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "å一月"
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr ""
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr ""
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr ""
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "å月"
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr ""
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "ã«"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr ""
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr ""
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr ""
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr ""
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr ""
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr ""
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr ""
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr ""
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "é–‹ã"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "ãれを開ã"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "リクエストを開ã"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚’(リストã‹ã‚‰ï¼‰æ–°ã—ã„ウインドウã‹ã‚‰é–‹ã"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚’(リストã‹ã‚‰ï¼‰ã»ã‹ã®ã‚¦ã‚¤ãƒ³ãƒ‰ã‚¦ã‹ã‚‰é–‹ã"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "整列ã¨ä¸¦ã³æ›¿ãˆ"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "組織"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "時間切れã§ã™ã€å„ªå…ˆé †ä½ãŒã†ã¤ã‚Šã¾ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr ""
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "オーナー"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "オーナーã¯å¼·åˆ¶çš„ã«%1ã‹ã‚‰%2を変更ã—ã¾ã—ãŸ"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "オーナーã¯"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "ãƒã‚±ãƒƒãƒˆãƒ™ãƒ«"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "ãƒã‚±ãƒƒãƒˆãƒ™ãƒ«é›»è©±"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "両親"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "パスワード"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "パスワードã®ãŠçŸ¥ã‚‰ã›"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "パスワードãŒçŸ­ã™ãŽã¾ã™"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "パスワード: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "人々"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "許å¯ãŒä¸‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr ""
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "個人グループ"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "個人グループ:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "電話番å·"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "代替物"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr "ãŠæ°—ã«å…¥ã‚Š"
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "ãŠæ°—ã«å…¥ã‚Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr ""
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Prepare Stubbed"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "å‰ã®"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "å‰ã®ãƒšãƒ¼ã‚¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "優先権"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "優先権"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "優先順ä½ã¯æ¬¡ã®ã‚ˆã†ã«å§‹ã¾ã‚Šã¾ã™"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "特権ステータス: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "特権ã®ã‚るユーザー"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr ""
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "キュー"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "キュー'%1'ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "キューã®åå‰"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "キュースクリプト"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "キューã¯ã™ã§ã«å­˜åœ¨ã—ã¦ã„ã¾ã™"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "キューã®ä½œæˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "キューã®ãƒ­ãƒ¼ãƒ‰ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "キューãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr ""
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "キューãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "キュー"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "%2ã®RT %1"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "<a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>ã‹ã‚‰ã®RT%1。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "RT管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RTèªè¨¼ã‚¨ãƒ©ãƒ¼"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RTãƒã‚¦ãƒ³ã‚¹ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT設定エラー"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RTé‡å¤§ãªã‚¨ãƒ©ãƒ¼ã€‚メッセージãŒè¨˜éŒ²ã•ã‚Œã¾ã›ã‚“"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "RTエラー"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RTå—信メール(%1)自身ã‹ã‚‰ã®ãƒ¡ãƒ¼ãƒ« "
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr ""
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "RTセルフサービス/クローズã•ã‚ŒãŸãƒã‚±ãƒƒãƒˆ"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RTã§ã¯ã€ãŸã ã„ã¾ãŠä½¿ã„ã®æ–¹ã®èªè¨¼ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RTã¯å¤–部ã®ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãƒ«ãƒƒã‚¯ã‚¢ãƒƒãƒ—を使ã£ã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã™ã‚‹äººã‚’見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RTã¯ã‚­ãƒ¥ãƒ¼: %1を見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RTã¯ã“ã®PGPサインを有効ã«ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚\\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RTã¯ã‚ãªãŸã®ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’処ç†ã—ã¾ã—ãŸ"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RTã¯&コピー; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;。ã“ã‚Œã¯<a href=\"http://www.gnu.org/copyleft/gpl.html\">GNUジェãƒãƒ©ãƒ«ãƒ‘ブリックライセンスã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³2ã§é…ä¿¡ã•ã‚Œã¦ã„ã¾ã™ã€‚</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RTã«ã‚ˆã‚‹ã¨ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¯ãƒã‚¦ãƒ³ã‚¹ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RTã¯ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒã¾ã‚‹ã§ã‚µã‚¤ãƒ³ã•ã‚Œã¦ã„ãªã„よã†ã«å‡¦ç†ã—ã¾ã™ã€‚\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RTã®Eメールコマンドモードã§ã¯PGPèªè¨¼ãŒå¿…è¦ã«ãªã‚Šã¾ã™ã€‚è²´æ–¹ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã«ã‚µã‚¤ãƒ³ã—ãªã‹ã£ãŸã€ã‚‚ã—ãã¯ã¯ã‚µã‚¤ãƒ³ãŒæœ‰åŠ¹ã§ã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "本å"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "本å"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "次ã®ã‚‚ã®ã«ã‚ˆã£ã¦å‚ç…§ã—ãŸ"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "å‚ç…§ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "絞り込む"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "絞込ã¿æ¤œç´¢"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã‚’%1分ãŠãã«æ›´æ–°ã—ã¦ãã ã•ã„"
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "関係"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "管ç†Ccを削除ã™ã‚‹"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Ccを削除ã™ã‚‹"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "リクエストã™ã‚‹äººã‚’削除ã™ã‚‹"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "返信"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr ""
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "リクエストã™ã‚‹äºº"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "リクエストã™ã‚‹äººã®Eメールアドレス"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr ""
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "リクエストã™ã‚‹äºº"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "リクエストã¯æ¬¡ã®æ—¥ã¾ã§ã«è¡Œã‚ã‚Œãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "リセット"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "ä½æ‰€"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "分解ã™ã‚‹"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr ""
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "分解ã—ãŸ"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "リクエストã™ã‚‹äººã«è¿”ç­”ã™ã‚‹"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "çµæžœ"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "ページã”ã¨ã®çµæžœ"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "パスワードã®å†å…¥åŠ›"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "%2 %3ã®æ¨©åˆ©%1ãŒé ˜åŸŸ%4 %5\\nã§è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "権利ãŒå§”託ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "権利ãŒè¨±å¯ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "権利ãŒãƒ­ãƒ¼ãƒ‰ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "権利を無効ã«ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "権利ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "権利ãŒãƒ­ãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "権利ãŒç„¡åŠ¹ã«ãªã‚Šã¾ã—ãŸ"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "権利"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "役割"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr ""
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "土曜日"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "変更をä¿å­˜ã™ã‚‹"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "変更をä¿å­˜ã™ã‚‹"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "スクリプトãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "スクリプト"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "%1\\nã®ã‚¹ã‚¯ãƒªãƒ—ト"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr ""
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "検索"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "検索基準"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr ""
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr ""
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "グループã®é¸æŠž"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "キューã®é¸æŠž"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "ユーザーã®é¸æŠž"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr ""
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr ""
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "セルフサービス"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr ""
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr ""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr ""
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr ""
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr ""
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr ""
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "ä¹æœˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "çµæžœã‚’見る"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr ""
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "基本を見る"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr ""
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "詳細を見る"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr ""
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "サイン"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "ã²ã¨ã¤ã®"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "並ã¹æ›¿ãˆã®ã‚­ãƒ¼"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "次ã®é …ç›®ã”ã¨ã®ä¸¦ã³æ›¿ãˆ"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "並ã³é †"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "åœæ­¢ã—ã¦ã„ã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "開始ページ"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "開始ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "開始日'%1'ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "開始ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "次ã®æ—¥æ™‚ã¾ã§ã«é–‹å§‹ã™ã‚‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "開始日'%1'を解æžã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "状態"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "ステータス"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "ステータスãŒ%1ã‹ã‚‰%2ã«å¤‰æ›´ã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "盗用ã™ã‚‹"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "%1ã‹ã‚‰ç›—用ã—ãŸ"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "サブジェクト"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr ""
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "æ出"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr ""
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "日曜日"
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "システムエラー"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "システムエラー。権利ãŒå§”ä»»ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "システムエラー。権利ãŒèªå¯ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "システムグループ"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr ""
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "テスト_ストリング"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "ã¨ã‚‹"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "ã¨ã‚‰ã‚ŒãŸ"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "テンプレート"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr ""
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "テンプレートãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "テンプレートãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "テンプレートãŒè§£æžã•ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "テンプレート"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "%1\\nã®ãƒ†ãƒ³ãƒ—レート"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "ãã‚Œã¯ã“ã®ã‚«ã‚¹ã‚¿ãƒ ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰ã®ãƒãƒªãƒ¥ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "ãã‚Œã¯åŒã˜ãƒãƒªãƒ¥ãƒ¼ã§ã™"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "ãã®è²¬ä»»è€…ã¯ã™ã§ã«ã“ã®ã‚­ãƒ¥ãƒ¼ã®%1ã§ã™"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "ãã®è²¬ä»»è€…ã¯ã™ã§ã«ã“ã®ãƒã‚±ãƒƒãƒˆã®%1ã§ã™"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "ãã®è²¬ä»»è€…ã¯ã“ã®ã‚­ãƒ¥ãƒ¼ã®%1ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "ãã®è²¬ä»»è€…ã¯ã“ã®ãƒã‚±ãƒƒãƒˆã®%1ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "ãã®ã‚­ãƒ¥ãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "ãã®ãƒã‚±ãƒƒãƒˆã¯å¾“属物をã™ã§ã«åˆ†è§£ã—ã¾ã—ãŸ"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã™ã§ã«æ¨©åˆ©ãŒã‚ã‚Šã¾ã™"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã™ã§ã«ãƒã‚±ãƒƒãƒˆã‚’所有ã—ã¦ã„ã¾ã™"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯å­˜åœ¨ã—ã¾ã›ã‚“"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã™ã§ã«ç‰¹æ¨©ãŒä¸Žãˆã‚‰ã‚Œã¦ã„ã¾ã™"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¯ã™ã§ã«ç‰¹æ¨©ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ä»Šç‰¹æ¨©ã‚’与ãˆã‚‰ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ä»Šç‰¹æ¨©ã‚’失ã„ã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã“ã®ã‚­ãƒ¥ãƒ¼ã§ã¯ãƒã‚±ãƒƒãƒˆã‚’所有ã—ã¦ã„ãªã„å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "ãã‚Œã¯æ•°å­—ã®IDã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "基本"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "コメントã¯è¨˜éŒ²ã•ã‚Œã¾ã—ãŸ"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr ""
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "次ã®ã‚³ãƒžãƒ³ãƒ‰ã¯å‡¦ç†ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr ""
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "ãれらã®ã‚³ãƒ¡ãƒ³ãƒˆã¯å®Ÿéš›ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã¯è¦‹ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "ã“ã®ãƒã‚±ãƒƒãƒˆ%1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "ã“ã®ãƒˆãƒ©ãƒ³ã‚¶ã‚¯ã‚·ãƒ§ãƒ³ã«ã¯ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®25ã®ã‚‚ã£ã¨ã‚‚高ã„優先ãƒã‚±ãƒƒãƒˆ"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "木曜日"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "ãƒã‚±ãƒƒãƒˆ# %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "ãƒã‚±ãƒƒãƒˆã€€#%1 大ãã„アップデート: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "ãƒã‚±ãƒƒãƒˆ %1ãŒã‚­ãƒ¥ãƒ¼ '%2'ã§ä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "ãƒã‚±ãƒƒãƒˆ%1ãŒãƒ­ãƒ¼ãƒ‰ã•ã‚Œã¾ã—ãŸ\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "ãƒã‚±ãƒƒãƒˆã€€%1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "ãƒã‚±ãƒƒãƒˆãƒ’ストリー # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "ãƒã‚±ãƒƒãƒˆID"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "ãƒã‚±ãƒƒãƒˆæ·»ä»˜"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚³ãƒ³ãƒ†ãƒ³ãƒ„"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚³ãƒ³ãƒ†ãƒ³ãƒ„タイプ"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "ãƒã‚±ãƒƒãƒˆãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "ãƒã‚±ãƒƒãƒˆã®ä½œæˆãŒå¤±æ•—ã—ã¾ã—ãŸ"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "ãƒã‚±ãƒƒãƒˆãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸ"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr ""
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr ""
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr ""
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚¦ã‚©ãƒƒãƒãƒ£ãƒ¼"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr ""
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "%1ã‹ã‚‰ã®ãƒã‚±ãƒƒãƒˆ"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr ""
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "時間ãŒæ®‹ã£ã¦ã„ã¾ã™"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "使ã£ãŸæ™‚é–“"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "残ã£ã¦ã„る時間"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "表示ã™ã‚‹æ™‚é–“"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "使ã£ãŸæ™‚é–“"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr ""
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ã‚£ãƒ•ã‚’ã¤ãã‚‹ãŸã‚ã«:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã®ãƒ‡ã‚£ãƒ•ã‚’ã¤ãã‚‹ãŸã‚ã«:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "言ã£ãŸ"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "トランザクション%1ãŒæ¶ˆåŽ»ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "トランザクションãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "トランザクションã¯å¤‰æ›´ã•ã‚Œã‚‹ã“ã¨ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "権利: %1を削除ã—ã¦ã„ã¾ã™"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "ç«æ›œæ—¥"
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "タイプ"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "å°Žå…¥ã•ã‚Œã¦ã„ãªã„"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unixログイン"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Unixユーザーãƒãƒ¼ãƒ "
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "ä¸æ˜Žãªã‚³ãƒ³ãƒ†ãƒ³ãƒ„エンコーディング%1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "制é™ã•ã‚Œã¦ã„ãªã„"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "ã¨ã‚‰ã‚Œã¦ã„ãªã„"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "アップデート"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "アップデートID"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "アップデートタイプ"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "ã™ã¹ã¦ã®ãƒã‚±ãƒƒãƒˆã‚’一度ã«ã‚¢ãƒƒãƒ—デートã™ã‚‹"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "アップデートEメール"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "アップデートãƒãƒ¼ãƒ "
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "アップデートã¯è¨˜éŒ²ã•ã‚Œã¾ã›ã‚“ã§ã—ãŸ"
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "é¸æŠžã•ã‚ŒãŸãƒã‚±ãƒƒãƒˆã‚’アップデートã™ã‚‹"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "サインをアップデートã™ã‚‹"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "ãƒã‚±ãƒƒãƒˆã‚’アップデートã™ã‚‹"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "アップデートãƒã‚±ãƒƒãƒˆ # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "アップデートãƒã‚±ãƒƒãƒˆ #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "アップデートタイプã¯é€šçŸ¥ã§ã‚‚コメントã§ã‚‚ã‚ã‚Šã¾ã›ã‚“"
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "アップデートã—ã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "ユーザー%1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "ユーザー%1パスワード: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "ユーザー'%1'ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "ユーザー'%1'ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "ユーザーID"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "ユーザーID"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "ユーザー権利"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "ユーザーを作æˆã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã§ã—ãŸ: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "ユーザーãŒä½œæˆã•ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "ユーザーãŒã‚°ãƒ«ãƒ¼ãƒ—を決定ã—ã¾ã—ãŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "ユーザーã«é€šå‘Šã•ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "ユーザービュー"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "ユーザーãƒãƒ¼ãƒ "
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "ユーザー"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "ユーザーãŒæ¤œç´¢åŸºæº–ã«ã‚ã£ã¦ã„ã¾ã™"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "キューã®ãƒãƒªãƒ¥ãƒ¼"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "ãƒãƒªãƒ¥ãƒ¼"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "ウォッãƒãƒ£ãƒ¼"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "ウェブエンコーディング"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "水曜日"
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr ""
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr ""
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr ""
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr ""
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr ""
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr ""
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr ""
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr ""
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr ""
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "仕事"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "仕事先ã®é›»è©±"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Worked"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "ã‚ãªãŸã¯ã™ã§ã«ã“ã®ãƒã‚±ãƒƒãƒˆã‚’所有ã—ã¦ã„ã¾ã™"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "ã‚ãªãŸã¯èªè¨¼ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "ã‚ãªãŸã¯æ‰€æœ‰ã€ã¾ãŸã¯æ‰€æœ‰ã•ã‚Œã¦ã„ãªã„ãƒã‚±ãƒƒãƒˆã®ã¿ã‚’æ­¢ã‚ã‚‹ã“ã¨ãŒã§ãã¾ã™"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "ã‚ãªãŸã¯ãã®ãƒã‚±ãƒƒãƒˆã‚’見る許å¯ãŒã‚ã‚Šã¾ã›ã‚“。\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "ã‚ãªãŸã¯%2ã§ãƒã‚±ãƒƒãƒˆ%1を見ã¤ã‘ã¾ã—ãŸ"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "ã‚ãªãŸã¯RTã‹ã‚‰ãƒ­ã‚°ã‚¢ã‚¦ãƒˆã—ãŸã¾ã¾ã§ã™"
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "ã‚ãªãŸã¯ã“ã®ã‚­ãƒ¥ãƒ¼ã§ãƒã‚±ãƒƒãƒˆä½œæˆã®è¨±å¯ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "ã‚ãªãŸã¯ã“ã®ã‚­ãƒ¥ãƒ¼ã§ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®ä½œæˆãŒã§ãã‚‹ã§ã—ょã†"
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "ãœã²ã¾ãŸãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "ã‚ãªãŸã®%1ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "ã‚ãªãŸã®RT管ç†è€…ã¯RTを呼ã³å‡ºã™ãƒ¡ãƒ¼ãƒ«aliasesを設定ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr ""
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr ""
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr ""
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "ã‚ãªãŸã®ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒãƒ¼ãƒ ã¨ãƒ‘スワードãŒé–“é•ã£ã¦ã„ã¾ã™"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "ジップ"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "%1ã¸ã®è¨±å¯"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "å«ã‚€"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "通知ã¯ï¼ˆãŠãらã)é€ä¿¡ã•ã‚Œã¦ã„ã¾ã›ã‚“"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "通知ãŒé€ä¿¡ã•ã‚Œã¾ã—ãŸ"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "æ—¥"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr ""
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "削除"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "削除ã•ã‚ŒãŸ"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "ã‚ã„ã¾ã›ã‚“"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "å«ã¿ã¾ã›ã‚“"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "ç­‰ã—ã„"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr ""
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "より大ãã„"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "グループ'%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "時間"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "ID"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "ã§ã™"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "ã§ãªã„"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "より少ãªã„"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "åˆã†"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "最低"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "分"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "修正\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "月"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "æ–°ã—ã„"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr ""
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "ãªã—"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "ç­‰ã—ããªã„"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "é–‹ã"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "ユーザー '%2' ã®ãƒ‘ーソナルグループ '%1' "
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "キュー %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "æ‹’å¦ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "分解ã•ã‚Œã¾ã—ãŸ"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "秒"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "æ­¢ã¾ã‚Šã¾ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "システム %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "システムグループ '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "呼ã³å‡ºã—ã¦ã„るコンãƒãƒ¼ãƒãƒ³ãƒˆãŒãªãœæ¬¡ã®ã‚ˆã†ãªã“ã¨ãŒèµ·ã“ã‚‹ã®ã‹ç‰¹å®šã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "ãƒã‚±ãƒƒãƒˆã€€#%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "表示ã•ã‚Œãªã„グループ %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "ユーザー %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "週"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "テンプレート %1ã¨"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "å¹´"
+
+#: NOT FOUND IN SOURCE
+msgid "ニックãƒãƒ¼ãƒ "
+msgstr ""
+
diff --git a/rt/lib/RT/I18N/nl.po b/rt/lib/RT/I18N/nl.po
new file mode 100644
index 0000000..0c35499
--- /dev/null
+++ b/rt/lib/RT/I18N/nl.po
@@ -0,0 +1,4819 @@
+msgid ""
+msgstr ""
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 toegevoegd"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 geleden"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 veranderd naar %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 verwijderd"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 of group %3"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 met sjabloon %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 dit ticket\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr ""
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Een argument om door te geven aan %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Uitvoer status herzieningen naar STDOUT"
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Specificeer de actie module die u wenst te gebruiken"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Specificeer de conditie module die u wenst te gebruiken"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Specificeer de zoek module die u wenst te gebruiken"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1 ScripAction geladen"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 toegevoegd als waarde voor %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 aliassen hebben een TicketId nodig om mee te werken"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1 aliassen hebben een TicketId nodig om mee te werken"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 aliassen hebben een TicketId nodig om mee te werken (van %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 door %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 veranderd van %2 naar %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "%1 kon niet veranderd worden naar %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 kon geen transactie initiëren (%2)"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 kon status niet veranderen naar opgelost. RT's Database zou inconsistent kunnen zijn"
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "De %1 hoogste prioriteit tickets die ik bezit..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "De %1 hoogste prioriteit tickets waarom ik verzocht heb..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 is een gereedschap om te reageren op tickets van een extern rooster programma, zoals cron"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 is niet langer een %2 voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 is niet langer een %2 voor dit ticket"
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 is niet langer een waarde voor specifiek veld %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 is niet een geldig Rij id"
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 niet afgebeeld"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 rechten"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 gelukt\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1 type onbekend voor $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1 type onbekend voor %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr ""
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 zal alle leden van een opgelost groep ticket omzetten."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: geen aanhechting gespecificeerd"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1 is een ongeldige waarde voor status"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' onherkende actie. "
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr ""
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Vink hokje af om te verwijderen)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr ""
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Vul ticket ids of URLs in, gescheiden door spaties)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(Geen Waarde)"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr ""
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Geen Leden)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Geen scrips)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr ""
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Stuurt een blinde carbon copy van deze herziening naar een comma gescheiden lijst van email adressen. Wie er toekomstige herzieningen zal ontvangen, zal <b>niet</b> veranderen.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Stuurt een carbon copy van deze herziening naar een comma gescheiden lijst van email adressen. Wie er toekomstige herzieningen zal ontvangen, zal <b>niet</b> veranderen.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr ""
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(leeg)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr ""
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(geen onderwerp)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(geen waarde)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(slechts één ticket)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(wacht op goedkeuring)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(wacht op andere tickets)"
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(verplicht)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(zonder titel)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Nieuw ticket in\">&nbsp;%1"
+
+#: NOT FOUND IN SOURCE
+msgid "??????"
+msgstr ""
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Een leeg sjabloon"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE Verwijderd"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE Geladen"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE niet gevonden"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEs kunnen allen gecreëerd of verwijderd worden."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Afbraak om ongewenste ticket aanpassing te voorkomen.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Toegangscontrole"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Actie"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Actie %1 niet gevonden"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Actie uitgevoerd."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Actie voorbereid..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Voeg AdminCc toe"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Voeg Cc toe"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Voeg Meer Bestanden Toe"
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr ""
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Voeg Verzoeker Toe"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr "Voeg een Scrip toe aan deze rij"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Voeg een scrip toe welke voor alle rijen zal gelden"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Voeg commentaar of reacties toe aan geselecteerde tickets"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Voeg leden toe"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Voeg nieuwe toeschouwers toe"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "VoegVolgendeStaatToe"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Hoofd toegevoegd als %1 voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Hoofd toegevoegd als %1 voor dit ticket"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Adres1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Adres2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Beheerder Cc"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr ""
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr ""
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Beheerder rijen"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Beheerder gebruikers"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Beheerder/Globale configuratie"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Beheerder/Groepen"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Beheerder/Rij/Basis"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr ""
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "BeheerderCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "BeheerderCommentaar"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "BeheerderCorrespondentie"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr "BeheerderSpecifiekeVelden"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr "BeheerderGroep"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr "BeheerderGroepLidmaatschap"
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr "BeheerderBezitPersoonlijkeGroepen"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr "BeheerderRij"
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr "BeheerderGebruikers"
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Administratieve Cc"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Uitgebreid Zoeken"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Nadat"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Leeftijd"
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Alle Rijen"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Stuurt altijd een bericht naar de verzoekers ongeacht de verzender van het bericht"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Goedkeuring"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Goedkeuring #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Goedkeuring #%1: Notities niet bewaard vanwege een systeem fout"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Goedkeuring #%1: Notities bewaard"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Goedkeuring Details"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Goedkeuring diagram"
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Goedkeuring"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Notities van de goedkeurer: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Ggk."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "april"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Oplopend"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Aanhechten"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Hecht bestand aan"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Aangehecht bestand"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Aanhechting '%1' kon niet geladen worden"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Aanhechting gecreëerd"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Aanhechting bestandsnaam"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Aanhechtingen"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "aug."
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "augustus"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "AuthenticatieSysteem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Automatisch-antwoord"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "Automatisch-antwoordAanVerzoekers"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Ongeldige PGP Signatuur: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Ongeldig aanhechtings id. Kon aanhechting '%1' niet vinden\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Ongeldige data in %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Ongeldig transactienummer voor aanhechting. %1 zou %2 moeten zijn\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Basis"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Zorg ervoor dat u uw veranderingen bewaard"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "Voorheen"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "Begin Goedkeuring"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Blanco"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "XXX URL voor deze zoekopdracht"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Korte koppen"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Bulk ticketherziening"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Kan systeemgebruikers niet wijzigen"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Kan dit hoofd deze rij zien"
+
+#: lib/RT/CustomFieLd_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Kan geen specifiek veld toevoegen zonder een naam"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Kan een ticket niet koppelen aan zichzelf"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Kan niet samenvoegen met een reeds samengevoegd ticket. U zou deze boodschap nooit mogen krijgen"
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Kan niet zowel basis als doel specificeren"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Kan gebruiker %1 niet aanmaken"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Wijzig wachtwoord"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Vink hokje af om te verwijderen"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Vink hokje af om recht te verwijderen"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Kinderen"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Stad"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Gesloten verzoeken"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Commando niet begrepen!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Commentaar"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Commentaar Adres"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Commentaar niet bewaard"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Commentaar op tickets"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr "CommentaarOpTicket"
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Commentaar"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Commentaar (Wordt niet verstuurd aan verzoekers)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Commentaar (Wordt niet verstuurd aan verzoekers)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Commentaar over %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Commentaar over deze gebruiker"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Commentaar toegevoegd"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Compilatie Restricties"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Voorwaarde"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "Voorwaarde komt overeen..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Voorwaarde niet gevonden"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Configuratie"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Bevestig"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "ContactInfoSysteem"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Contact datum '%1' kon niet ontleed worden"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Inhoud"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr ""
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr "Correspondentie"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Correspondentieadres"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Correspondentie toegevoegd"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Correspondentie niet bewaard"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Kon nieuw specifiek veld niet toevoegen voor dit ticket. "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "Kon nieuw specifiek veld niet toevoegen voor dit ticket. %1"
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Kon eigenaar niet wijzigen. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Kon SpecifiekVeld niet creëren"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Kon groep niet creëren"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Kon sjabloon niet creëren: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Kon ticket niet creëren. Rij niet ingesteld"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Kon gebruiker niet creëren"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "Kon toeschouwer niet creëren voor verzoeker"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Kon geen ticket vinden met id %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Kon groep %1 niet vinden. "
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Kon deze gebruiker niet vinden of creëren"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Kon dat hoofd niet vinden"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Kon gebruiker %1 niet vinden."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Kon groep niet laden"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Kon dat hoofd geen %1 maken voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Kon dat hoofd geen %1 maken voor dit ticket"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Kon dat hoofd niet verwijderen als %1 voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Kon dat hoofd niet verwijderen als %1 voor dit ticket"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Kon lid niet toevoegen aan groep"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Kon geen transactie creëren: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Kon niet bepalen welke actie te ondernemen aan de hand van gpg's antwoord\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Kon groep niet vinden\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr "Kon rij niet vinden"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Kon dat hoofd niet vinden"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Kon die waarde niet vinden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "Kon die toeschouwer niet vinden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Kon gebruiker niet vinden\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Kon %1 niet laden uit de gebruikersdatabase.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "Kon KeywordSelects niet laden."
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Kon RT configuratie bestand niet laden '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Kon Scrips niet laden"
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Kon groep %1 niet laden"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Kon link niet laden"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Kon rij niet laden"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Kon rij %1 niet laden "
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Kon scrip niet laden"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Kon sjabloon niet laden"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Kon die gebruiker (%1) niet laden"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Kon ticket '%1' niet laden"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "Land"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Creëer"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Creëer Tickets"
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Creëer een SpecifiekVeld"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Creëer een niuew Specifiek Veld"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr "Creëer een nieuw globaal Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Creëer een nieuwe groep"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Creëer een nieuwe persoonlijke groep"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Creëer een nieuwe rij"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Creëer een nieuw scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Creëer een nieuw template"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Creëer een nieuw ticket"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Creëer een nieuwe gebruiker"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Creëer een rij"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Creëer een rij genaamd"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Creëer een verzoek"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Creëer een scrip voor rij %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Creëer een sjabloon"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Creatie mislukt: %1 / %2 / %3 "
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr ""
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Creëer nieuwe tickets gebaseerd op het sjabloon van dit scrip"
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Creëer ticket"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Creëer tickets in deze rij"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Creëer, verwijder en wijzig specifieke velden"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Creëer, verwijder en wijzig rijen"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Creëer, verwijder en wijzig de leden van persoonlijke groepen"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Creëer, verwijder en wijzig gebruikers"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "CreëerTicket"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Gecreëerd"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "SpecifiekVeld %1 gecreëerd"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Sjabloon %1 Gecreëerd"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Huidige Relaties"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Huidige Scrips"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Huidige leden"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Huidige rechten"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr ""
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Huidige toeschouwers"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Specifieke Velden"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Specifieke actie opruim code"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Specifieke actie voorbereidings code"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Specifieke voorwaarde"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Specifiek veld %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Specifiek veld %1 heeft een waarde."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Specifiek veld %1 heeft geen waarde."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Specifiek veld %1 niet gevonden"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Specifiek veld niet gevonden"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Specifiek veld waarde %1 kon niet gevonden worden voor specifiek veld %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Specifiek veld waarde veranderd van %1 naar %2"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "Specifiek veld waarde kon niet verwijderd worden"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "Specifiek veld waarde kon niet gevonden worden"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Specifiek veld waarde verwijderd"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "Data fout"
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Data"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "dec."
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "december"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Standaard Auto-antwoord Sjabloon"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr ""
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Standaard admin commentaar sjabloon"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr "Standaard admin correspondentie sjabloon"
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr "Standaard correspondentie sjabloon"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Standaard transactie sjabloon"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Standaard: %1/%2 verandered van %3 naar %4"
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Delegeer rechten"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Delegeer specifieke rechten die aan u verleend zijn."
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr "DelegeerRechten"
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Verwijder tickets"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr "VerwijderTicket"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Het verwijderen van dit object zou de referentiële integriteit kunnen ondermijnen"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Het verwijderen van dit object zou de referentiële integriteit ondermijnen"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Het verwijderen van dit object zou de referentiële integriteit ondermijnen"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Het verwijderen van dit object zou de referentiële integriteit ondermijnen"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr ""
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Wijs af"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Afhankelijkheid van"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Afhankelijkheden: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Is afhankelijk van"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Aflopend"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Omschrijf onderstaande kwestie"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Omschrijving"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Details"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Toon"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Toon Toegangs Controle Lijst"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Toon Scrip sjablonen voor deze rij"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Toon Scrips voor deze rij"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Toon modus"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Toon ticket #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Doe iets en alles"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Ververs deze pagina niet"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Download"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Verwacht"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Verwachte datum '%1' kon niet ontleed worden"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "FOUT: Kon ticket '%1' niet laden: %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Wijzig"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr ""
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Wijzig Specifieke Velden voor %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Wijzig Relaties"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr ""
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Wijzig systeem sjablonen"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Wijzig sjablonen voor %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Bezig met wijzigen van de configuratie voor rij %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Bezit met het wijzigen van de configuratie voor gebruiker %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Bezit met het wijzigen van SpecifiekVeld %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Bezit met het wijzigen van lidmaatschap voor groep %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Bezit met het wijzigen van lidmaatschap voor persoonlijke groep %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Bezit met het wijzigen van sjabloon %1"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Of basis of doel moeten gespecificeerd zijn"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "E-mail"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "E-mailadres in gebruik"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "E-mailAdres"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "E-mailCodering"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr ""
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Actief (Het uitvinken van dit hokje zal deze rij deactiveren)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr ""
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Actieve Rijen"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Actieve status %1"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Vul tickets of URIs in om deze tickets aan te koppelen. Scheidt meerdere elementen met spaties."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Fout"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Fout in paramaters naar Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Fout in paramaters naar Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Fout in paramaters naar Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Fout in paramaters naar Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Iedereen"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Voorbeeld:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "ExternAuteurId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "ExternContactInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Extra informatie"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Kon de gebruikers pseudogroep 'Privileged' niet vinden."
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Kon de gebruikers pseudogroep 'Unprivileged' niet vinden."
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Kon module %1 niet laden. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "feb."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr ""
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Uiteindelijke Prioriteit"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr "UiteindelijkePrioriteit"
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr ""
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Zoek nieuwe/open tickets"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Zoek mensen wier"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "Beëindig Goedkeuring"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Eerste"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Eerste pagina"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Aap Noot Mies"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Aap!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr ""
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr "Gevonden Object"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "VrijevormContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "VrijevormMeerdere"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr ""
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Vr."
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Volledige Kop"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Bezig met het ophalen van de huidige gebruiker middels een pgp handtekening"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Aan %1 gegeven"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Globaal"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr ""
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Globaal sjabloon: %1"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Ga!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Goede pgp handtekening van %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Ga naar pagina"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Ga naar ticket"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr ""
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Groep"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Groep %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Groeps rechten"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Groep heeft al een lid"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Groep kon niet gecreërd worden: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Groep gecreërd"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "Groep heeft geen lid onder die naam"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Groep niet gevonden"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Groep niet gevonden.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Groep niet gespecificeerd.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Groepen"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Groepen kunnen geen leden zijn van hun leden"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Hallo!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hallo, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Geschiedenis"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "ThuisNummer"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Homepage"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr ""
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Id"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identiteit"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Als een goedkeuring afgewezen is, wijs het origineel af en verwijder hangende goedkeuringen"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Als dit gereedschap setgid zou zijn, zou een kwaadwillende lokale gebruiker dit gereedschap kunnen gebruiken om administratieve toegang te verkrijgen tot RT"
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Als u een van de bovenstaande elemented ververst heeft, zorg dan dat u"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr "Illegale waarde voor %1"
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr "Niet-wijzigbaar veld"
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr ""
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Neem inactieve rijen op in de weergave"
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Neem inactieve gebruiker op in de zoek opdracht"
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Initiële Prioriteit"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr "InitiëlePrioriteit"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Invoer fout"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "Interne Fout"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Interne Fout: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Ongeldig Groep Type"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "Ongeldig Type"
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr "Ongeldige data"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Ongeldige eigenaar. Val terug op 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Ongeldige rij"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Ongeldige recht"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Ongeldige waarde voor %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Ongeldige waarde voor specifiek veld"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Ongeldige waarde voor status"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Het is ontzettend belangrijk dat onbevoorrechtigde gebruikers geen toestemming hebben om dit gereedschap te gebruiken."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "We stellen voor dat u een onbevoorrechtigde unix gebruiker aanmaakt met het juiste groep lidmaatschap en RT toegang om dit gereedschap te gebruiken."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Het accepteerd meerdere argumenten:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Zaken die wachten op mijn goedkeuring"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "jan."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "januari"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "Sluit u aan of verlaat deze groep"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "jul."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Jumbo"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "jun."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Sleutelwoord"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Taal"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Laatste"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Laatste Contact"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Laatst Gecontacteerd"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr ""
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Laatst Ververst"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Over"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Geef deze gebruiker toegang tot RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Geef deze gebruiker rechten"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Eigenaar wordt gelimieteerd tot %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Rij wordt gelimiteerd tot %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "Koppeling bestaat al"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "Koppeling kon niet gecreëerd worden"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Koppeling gecreëerd (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Koppelink verwijderd (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Koppeling niet gevonden"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Koppel ticket #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Koppelingen"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Locatie"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Log folder %1 niet gevonden of niet toegankelijk.\\n RT kan niet starten."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Aangemeld als %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Aanmelden"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Afmelden"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Maak Eigenaar"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Maak Status"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Maak verwachtingsdatum"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Make oplossingsdatum"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Maak startdatum"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Maak datum gestart"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Maak datum gemeld"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Maak prioriteit"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Maak rij"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Maak onderwerp"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Beheer groepen en groeplidmaatschap"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Beheer eigenschappen en configuraties welke betrekking hebben op alle rijen"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Beheer rijen en rij-specifieke eigenschappen"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Beheer gebruikers en wachtwoorden"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "maa."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "maart"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "mei"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "mei."
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Lid toegevoegd"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Lid verwijderd"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Lid niet verwijderd"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Lid van"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "LidVan"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Leden"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "Samenvoeging Succesvol"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Samenvoeging mislukt. Kon EffectiefId niet instellen"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Voeg samen in"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Bericht"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr "Mist primaire sleutel?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Mobiel"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "MobieleTelefoon"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Wijzig Toegangs Controle Lijst"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Wijzig Specifiek Veld %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Wijzit Scrip sjabloon voor deze rij"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Wijzig Scrips voor deze rij"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Wijzig Sjabloon %1"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Wijzig een scrip voor deze rij %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Wijzig een scrip welke betrekking heeft op alle rijen"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "Wijzig data voor # %1"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Wijzig data voor #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Wijzig data voor ticket # %1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Wijzig globale groepsrechten"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Wijzig globale groepsrechten"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr ""
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Wijzig globale gebruikersrechten"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Wijzig groepsmetadata of verwijder groep"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Wijzig groepsrechten voor groep %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Wijzig groepsrechten voor rij %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Wijzig lidmaatschap rooster voor dze groep"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Wijzig uw eigen RT "
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Wijzig mensen gekoppeld aan rij %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Wijzig mensen gekoppeld aan ticket #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Wijzig scrips voor rij %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Wijzig scrips welke betrekking hebben op alle rijen"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Wijzig sjabloon %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Wijzig de groep %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Wijzig de toeschouwers van de rij"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Wijzig de gebruiker %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Wijzig ticket # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Wijzig ticket #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Wijzig tickets"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Wijzig gebruikersrechten voor groep %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Wijzig gebruikersrechten voor rij %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Wijzig toeschouwers voor rij '%1'"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr "WijzigACL"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr "WijzigEigenLidmaatschap"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr "WijzigRijToeschouwers"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr "WijzigScrips"
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr "WijzigZelf"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr "WijzigSjabloon"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr "WijzigTicket"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Ma."
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Meer over %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Meerdere"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "Specificeren van 'Naam' attribuut verplicht"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Mijn Goedkeuringen"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr ""
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Naam"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Naam in gebruik"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Goedkeuring benodigd van de systeem beheerder"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Nooit"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Nieuw"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Nieuw Wachtwoord"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Nieuwe Hangende Goedkeuring"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Nieuwe Relaties"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr ""
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Nieuw wachtwoord"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Bericht voor nieuw wachtwoord verzonden"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Nieuw verzoek"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Nieuwe rechten"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nieuwe zoekopdracht"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "Nieuw ticket bestaat niet"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr ""
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Nieuwe gebruiker genaamd"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Nieuwe toeschouwers"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Nieuwe venster instelling"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Volgende"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Volgende pagina"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Bijnaam"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Bijnaam"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Geen SpecifiekVeld"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Geen Groep gedefinieerd"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Geen Rij gedefinieerd"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Geen RT gebruiker gevonden. Raadpleeg uw RT beheerder.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Geen Sjabloon"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Geen ticket gespecificeerd. Ticket afgebroken "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Geen ticket gespecificeerd. Ticket wijzigingen afgebroken\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "Geen actie"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr "Geen kolom gespecificeerd"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Geen commando gevonden\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Geen commentaar ingevuld over deze gebruiker"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Geen correspondentie aangehecht"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Geen omschrijving voor %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Geen groep gespecificeerd"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "Geen wachtwoord ingesteld"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Geen rechten om rijen te creëren"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Geen rechten om tickets te creëren in de rij '%1'"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Geen rechten om gebruikers te creëren"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Geen rechten om dat ticket te tonen"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Geen rechten om verversing ticket te bekijken"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Geen hoofd gespecificeerd"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Geen hoofden geselecteerd"
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Geen rijen gevonden die aan de zoekcriteria voldoen"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Geen rechten toegekend"
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Geen zoek opdracht om uit te voeren."
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Geen ticket id gespecificeerd"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Geen transactie type gespecificeerd"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "Geen gebruiker of email-adres gespecificeerd"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Geen gebruikers gevonden die aan de zoekcriteria voldoen"
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Geen geldige RT gebruiker gevonden. RT cvs behandelaar losgemaakt. Neemt u alstublieft contact op met uw RT beheerder.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr "Geen waarde gestuurd naar _Set!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr "Nietbestaand veld?"
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr ""
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Niet aangemeld."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Niet gezet"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Nog niet geïmplementeerd."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Nog niet geïmplementeerd...."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Notities"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "Bericht kon niet verstuurd worden"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Bericht AdminCcs"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Bericht AdminCcs als Commentaar"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Bericht Andere Ontvangers"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Bericht Andere Ontvangers als Commentaar"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Bericht Eigenaar"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Bericht Eigenaar als Commentaar"
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Bericht Eigenaars en AdminCcs van nieuwe zaken welke hangende hun goedkeuring zijn"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Bericht Aanvragers"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Bericht Aanvragers en Ccs"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Bericht Aanvragers en Ccs als Commentaar"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Bericht Aanvragers, Ccs en AdminCcs"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Bericht Aanvragers, Ccs en AdminCcs als Commentaar"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "nov."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr ""
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Object kon niet gecreëerd worden"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Object gecreëerd"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "oct."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr ""
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "Bij"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Bij Commentaar"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Bij Overeenkomst"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Bij Creatie"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Bij Eigenaarwijziging"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Bij Rijwijziging"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Bij Oplossing"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Bij Statuswijziging"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Bij Transactie"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Toon alleen goedkeuringen voor verzoeken gecreëerd na %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Toon alleen goedkeuringen voor verzoeken gecreëerd voor %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Open"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Open"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Open verzoeken"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Open tickets (van lijst) in een nieuw venster"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Open tickets (van lijst) in een ander venster"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Ordening en sortering"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organisatie"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Voortgekomen uit ticket: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Naar mate de tijd vordert, verschuift de prioriteit richting"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Eigen tickets"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr "EigenTicket"
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Eigenaar"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Eigenaar veranderd van %1 naar %2"
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Eigenaar gedwongen veranderd van %1 naar %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Eigenaar is"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Pieper"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Pieper"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Ouders"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Wachtwoord"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Wachtwoord Herinerring"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Wachtwoord te kort"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Wachtwoord: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Mensen"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Verricht een gebruiker gedefiniëerde actie"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Toestemming Geweigerd"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr ""
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Persoonlijke groepen"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Persoonlijke groepen:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Telefoonnummers"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Plaatshouder"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr ""
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Voorkeuren"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Voorkeuren"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Bereid Plaatshouder Voor"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Vorige"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Vorige pagina"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Hoofd %1 niet gevonden."
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Prioriteit"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Prioriteit begint bij"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Gerechtigd"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Gerechtigde status: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Gerechtigde gebruikers"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseudogroep voor intern gebruik"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Rij"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Rij %1 niet gevonden"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Rij '%1' niet gevonden\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Rij Naam"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Rij Scrips"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Rij bestaat al"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Rij kon niet aangemaakt worden"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "Rij kon niet geladen worden."
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Rij aangemaakt"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "Rij is niet gespecificeerd"
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Rij niet gevonden"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Rijen"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 voor %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 van <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "RT Beheer"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RT Authenticatie fout"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT Doorgestuurd: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT Configuratie fout"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RT Kritieke fout: Bericht niet bewaard!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "RT Fout"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT Ontving mail (%1) van zichzelf."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr ""
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Zelfbediening / Afgesloten Tickets"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT kon u niet authenticeren"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT kon de verzoeker niet vinden in zijn interne database"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT kon de rij %1 niet vinden"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT kon deze PGP signatuur niet valideren. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT voor %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT voor %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT heeft uw commando's verwerkt"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Het is gedistribueerd onder <a href=\"http://www.gnu.org/copyleft/gpl.html\">Versie 2 van de GNU General Public License.</a>""
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT denkt dat dit bericht onbestelbaar zou kunnen zijn"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT zal dit bericht verwerken als of het ongesigneerd is.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT's email commando modus vereist PGP authenticatie. Of u heeft uw bericht niet gesigneerd, of uw signatuur kon niet geverifieerd worden."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Echte Naam"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "EchteNaam"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Naar gerefeerd door"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Refereert aan"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Verfijn"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Verfijn Zoekopdracht"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Ververs deze pagina elke %1 minuten."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Relaties"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Verwijder AdminCc"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Verwijder Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Verwijder Verzoeker"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Antwoord"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Antwoord op tickets"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "AntwoordOpTicket"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Verzoeker"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Verzoeker email adres"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Verzoeker(s)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr ""
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Verzoekers"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Verzoek is terug verwacht"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Herstel"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Woonplaats"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Los op"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Los ticket #%1 (%2) op"
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Opgelost"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Antwoord aan verzoekers"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Resultaten"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Resultaten per pagina"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Type wachtwoord opnieuw"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Recht %1 niet gevonden voor %2 %3 in bereik %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Recht Gedelegeerd"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Recht Toegekend"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Recht Geladen"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Recht kon niet afgenomen worden"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Recht niet gevonden"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Recht niet geladen"
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Rechten"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Rollen"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "RootGoedkeuring"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Za."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Bewaarwijzigingen"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Bewaarwijzigingen"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Scrip aangemaakt"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Script verwijderd"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Scrips"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrips voor %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Scrips welke betrekking hebben op alle rijen"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Zoek"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Zoek Criteria"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr ""
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "Veiligheid"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr "ZieRij"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Selecteer een groep"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Selecteer een rij"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Selecteer een gebruiker"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr ""
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr ""
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr ""
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "SelecteerMeerdere"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "SelecteerEnkele"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Zelfbediening"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Stuurt mail naar alle toeschouwers"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Stuurt mail naar alle toeschouwers als een \"commentaar\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Stuurt mail naar alle verzoekers en Ccs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Stuurt mail naar alle verzoekers en Ccs als een \"commentaar\""
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Stuurt een bericht aan de verzoekers"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Stuurt mail aan expliciet genoemde Ccs en Bccs"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Stuurt mail aan de administratieve Ccs"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Stuurt mail aan de administratieve Ccs als een \"commentaar\""
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Stuurt mail aan de eigenaar"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep."
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Toon Resultaten"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Toon goedgekeurde verzoeken"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Toon beginselen"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Toon afgewezen verzoeken"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Toon details"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Toon hangende verzoeken"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Toon verzoeken die wachten op andere goedkeuringen"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Toon ticket privé commentaar"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Toon ticket samenvattingen"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr "ToonACL"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr "ToonScrips"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr "ToonSjabloon"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr "ToonTicket"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr "ToonTicketCommentaar"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Schrijf in als een ticket Verzoeker of ticket of rij Cc"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Schrijf in als een ticket of rij AdminCc"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Signatuur"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Enkel"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Sorteer sleutel"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Sorteer resultaten op"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "SorteerVolgorde"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Blijft Steken"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Start pagina"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Gestart"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Startum '%1' kon niet ontleed worden"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Begint"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Begint op"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Begindatum '%1' kon niet ontleed worden"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Staat"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Status"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Status veranderd van %1 naar %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "StatusVerandering"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Steel"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Gestolen van %1"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Onderwerp"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Onderwerp veranderd naar %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Registreer"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Registreer Workflow"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Gelukt"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Zo."
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "SuperGebruiker"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "Systeem"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Systeem Fout"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Systeem fout. Recht niet gedelegeerd."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Systeem fout. Recht niet toegekend."
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Systeem fout. Niet mogelijk om rechten toe te kennen"
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Systeem groepen"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SysteemRolgroep voor intern gebruik"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Neem"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Genomen"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Sjabloon"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr ""
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Sjabloon niet gevonden"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Sjabloon niet gevonden\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Sjabloon ontleed"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Sjablonen"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Sjablonen voor %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Dat is al de huidige waarde"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Dat is geen waarde voor dit specifieke veld"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Dat is de zelfde waarde"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Dat hoofd is reeds een %1 voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Dat hoofd is reeds een %1 voor dit ticket"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Dat hoofd is geen %1 voor deze rij"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Dat hoofd is geen %1 voor dit ticket"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Die rij bestaat niet"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Dat ticket heeft onopgeloste afhankelijkheden"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Die gebruiker heeft dat recht reeds"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Die gebruiker is al eigenaar van dat ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Die gebruiker bestaat niet"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Die gebruiker is al gerechtigd"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Die gebruiker is reeds ontrechtigd"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Die gebruiker is nu gerechtigd"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Die gebruiker is nu ontrechtigd"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Die gebruiker mag geen eigenaar zijn van tickets in die rij"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Dat is niet een numeriek ID"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "De Beginselen"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "De CC van een ticket"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "De administratieve CC van een ticket"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "Het commentaar is bewaard"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "Het volgende commando zal alle actieve tickets in de rij 'general' vinden en hun prioriteit op 99 zetten als ze meer dan 4 uur niet aangeraakt zijn:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "De volgende commando's zijn niet verwerkt:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "De waarde is gezet."
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "De eigenaar van een ticket"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "De verzoeker van een ticket"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Dit commentaar is gewoonlijk niet zichtbaar voor de gebruiker"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Dit ticket %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Dit gereedschap stelt de gebruiker in staat arbitraire perl modules te gebruiken vanuit RT"
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Het lijkt erop alsof deze transactie geen inhoud heeft"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "De 25 hoogste prioriteit tickets van deze gebruiker"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Do."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr ""
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Ticket #%1 Jumbo actualisering: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Ticket #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Ticket %1 aangemaakt in rij '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Toclet %1 geladen\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Ticket %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Ticket Historie # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Ticket Id"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr "Ticket Opgelost"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Ticket aanhechting"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Ticket inhoud"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Ticket inhoud type"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "Ticket kong niet aangemaakt worden vanwege een interne fout"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Ticket aangemaakt"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Ticket aanmaken gefaald"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Ticket verwijderd"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Ticket id niet gevonden"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr ""
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Ticket niet gevonden"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "Ticket status gewijzigd"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Ticket toeschouwers"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Tickets %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Tickets %1 door %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Tickets van %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Tickets welke afhankelijk zijn van deze goedkeuring"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Tijd Over"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Tijd Gewerkt"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Tijd over"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Tijd om te tonen"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Tijd gewerkt"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "TijdOver"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "TijdGewerkt"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Om een diff van deze uitvoering te genereren:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Om een diff van deze uitvoering te genereren:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Verteld"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transactie"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transactie %1 gezuiverd"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transactie Gecreëerd"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transactie->Creëer kon niet, aangezien u geen ticket id gespecificeerd heeft"
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Transacties zijn onwijzigbaar"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Tracht een recht te verwijderen: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Di."
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Type"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Niet geïmplementeerd"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unix aanmelden"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "UnixGebruikersnaam"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Onbekende InhoudCodering %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Ongelimiteerd"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Ongerechtigd"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Vrij"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Ververs"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Ververs ID"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Ververs Type"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Ververs al deze tickets in eens"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Ververs email"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Ververs naam"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Verversing niet opgeslagen."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Ververs geselecteerde tickets"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Ververs signatuur"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Ververs ticket"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Ververs ticket # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Ververs ticket #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Ververs ticket #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "Verversingstype was noch correspondentie, noch commentaar"
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Ververst"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Gebruiker %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Gebruiker %1 Wachtwoord: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Gebruiker '%1' niet gevonden"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Gebruiker '%1' niet gevonden\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Gebruiker Gedifiniëerd"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "GebruikersID"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Gebruiker Id"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Gebruikersrechten"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Gebruiker kon niet aangemaakt worden: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Gebruiker aangemaakt"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Door gebruiker gedefiniëerde groepen"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Gebruiker verwittigd"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Gebruikers aanzicht"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Gebruikersnaam"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Gebruikers"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Gebruikers die voldoen aan de zoek criteria"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "WaardeVanRij"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Waarden"
+
+#: NOT FOUND IN SOURCE
+msgid "VrijevormEnkele"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr "Schouw toe"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr "SchouwToeAlsAdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "Toeschouwer geladen"
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Toeschouwers"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "WebCodering"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Wo."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Wanneer een ticket goedgekeurd is door alle goedkeurders, voeg correspondentie toe aan het orginele ticket"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Wanneer een ticket goedgekeurd is door een goedkeurder, voeg correspondentie toe aan het orginele ticket"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Wanneer een ticket is aangemaakt"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Wanneer een goedkeuringsticket is aangemaakts, verwittig de Eigenaar en de AdminCc van het onderwerp dat op hun goedkeuring wacht"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Wanneer iets gebeurt"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Wanneer een ticket is opgelost"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Wanneer de eigenaar van een ticket verandert"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Wanneer de rij van een ticket verandert"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Wanneer de status van een ticket verandert"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Wanneer een door de gebruiker gedifiniëerde voorwaarde gebeurt"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Wanneer commentaar binnenkomt"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Wanneer correspondentie binnenkomt"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Werk"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "WerkTelefoon"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Gewerkt"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "U bent al eigenaar van dit ticket"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "U bent geen geauthorizeerde gebruiker"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "U kunt alleen tickets opnieuw toe bedelen die van u zijn, of van niemand"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "U heeft geen toestemming om dat ticket te bekijken"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "U vond %1 tickets in rij %2"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "U bent afgemeld bij RT"
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "U heeft geen toestemming om tickets aan te maken in die rij."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "U mag geen verzoeken aanmaken in die rij"
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "U mag zich weer aanmelden"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Uw %1 verzoeken"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Uw RT beheerder heeft de mail-aliasses welke RT aanroepen verkeerd geconfigureerd"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Uw verzoek is goedgekeurd door %1. Er zijn wellicht nog andere hangende goedkeuringen."
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Uw verzoek is goedgekeurd."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr ""
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Uw verzoek was geweigerd."
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Uw gebruikersnaam of wachtwoord zijn onjuist"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "Postcode"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr ""
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "zoals gegeven aan %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "bevat"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "inhoud"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "inhoud-type"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "correspondentie (waarschijnlijk) niet verstuurd"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "correspondentie verstuurd"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "dagen"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "dood"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "verwijder"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "verwijderd"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "voldoet niet aan"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "bevat niet"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "gelijk aan"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr ""
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "bestandsnaam"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "groter dan"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "groep '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "uren"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "id"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "is"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "is niet"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "minder dan"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "voldoet aan"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minuten"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "wijzigingen\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "maanden"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "nieuw"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr ""
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "geen"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "niet gelijk aan"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "open"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "persoonlijke groep '%1' voor gebruiker '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "rij %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "geweigerd"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "opgelost"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sec"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "bleef steken"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "systeem %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "systeem groep '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "het aanroepende component specificeerde niet waarom"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "ticket #%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "onbeschreven groep %1"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "gebruiker %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "weken"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "met sjabloon %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "jaren"
+
diff --git a/rt/lib/RT/I18N/no.po b/rt/lib/RT/I18N/no.po
new file mode 100644
index 0000000..5b1ab05
--- /dev/null
+++ b/rt/lib/RT/I18N/no.po
@@ -0,0 +1,4879 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 3.0.1\n"
+"POT-Creation-Date: 2003-04-01 06:06+0200\n"
+"PO-Revision-Date: 2003-05-01 04:47+0200\n"
+"Last-Translator: Marcus Ramberg <marcus@thefeed.no>\n"
+"Language-Team: RT Norwegian <rt@thefeed.no>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr "#%1"
+
+#: html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:50 html/SelfService/Display.html:25 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %3. %2 %7 %4:%5:%6"
+
+#: lib/RT/Ticket_Overlay.pm:3505 lib/RT/Transaction_Overlay.pm:557 lib/RT/Transaction_Overlay.pm:599
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 lagt til"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 siden"
+
+#: lib/RT/Ticket_Overlay.pm:3511 lib/RT/Transaction_Overlay.pm:564
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 ble endret til %3"
+
+#: lib/RT/Ticket_Overlay.pm:3508 lib/RT/Transaction_Overlay.pm:560 lib/RT/Transaction_Overlay.pm:605
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 slettet"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 of group %3"
+msgstr "%1 %2 av gruppen %3"
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 med mal %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 denne biletten\\n""
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1 - %2 vist"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Et parameter til %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Viser statusoppdateringer til STDOUT"
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Oppgi kommandomodulen du ønsker  bruke"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Oppgiv betingelsesmodulen du ønsker  bruke"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Oppgi søkemodulen du ønsker  bruke"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1 KommandoScript lastet"
+
+#: lib/RT/Ticket_Overlay.pm:3538
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 ble lagt til som verdi for %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 alias trenger en ReferanseId å jobbe mot"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1 alias trenger en saksnummer å jobbe mot "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 alias trenger et saksnummer å jobbe mot (fra %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 ser ut til å være et lokalt objekt, men kan ikke finnes i databasen"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:481
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 av %2"
+
+#: lib/RT/Transaction_Overlay.pm:535 lib/RT/Transaction_Overlay.pm:624 lib/RT/Transaction_Overlay.pm:633 lib/RT/Transaction_Overlay.pm:636
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 ble endret fra %2 til %3"
+
+#: lib/RT/Interface/Web.pm:891
+msgid "%1 could not be set to %2."
+msgstr "%1 kunne ikke settes til %2."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 kunne ikke starte en transaksjon (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2817
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 kunne ikke sette status til løst. RT-basen kan være inkonsistent."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "Mine %1 høyst prioriterte saker..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "Mine %1 høyst prioriterte forespørsler..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 er et verktøy for å behandle saker fra eksterne verktøy, slik som cron."
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 er ikke lenger en %2 for denne køen."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 er ikke lenger en %2 for denne saken."
+
+#: lib/RT/Ticket_Overlay.pm:3594
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 er ikke lenger en verdi for fleksifeltet %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 er ikke et gyldig saksnummer."
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 vises ikke"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 rettigheter"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 var velykket\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1 er ukjent type for $saksnummer"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1 er ukjent type for %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 ble opprettet uten en aktiv bruker\\n"
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 vil løse alle medlemmer av en løst gruppesak."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 vil stoppe en [lokal] BASE hvis den er avhengig av/medlem av en tilkoblet sak."
+
+#: lib/RT/Transaction_Overlay.pm:433
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: ingen vedlegg oppgitt"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' er en ugyldig statusverdi"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' er ikke en kjent handling"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(Merk for å slette gruppemedlem)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Merk for å slette Scrip)"
+
+#: html/Admin/Elements/EditCustomFieldValues:25 html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Merk for å slette)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(Merk boksene for å slette)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Skriv inn referansenummer eller URler, separert med mellomrom)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(Standard er %1);H
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(Ingen Verdi)"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(Ingen fleksifelt)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Ingen medlemmer)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Ingen scrips)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Ingen maler)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Sender en kopi av denne oppdateringen til en kommaseparert liste med epostaddresser. Endrer <b>ikke</b> hvem som vil motta fremtidige oppdatreinger.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Sender en kopi av denne oppdateringen til en kommaseparert liste med epostaddresser. Endrer <b>ikke</b> hvem som vil motta fremtidige oppdateringer.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Sender en kopi av denne oppdateringen til en kommaseparert liste av administrative epostaddresser. Disse vil <b>vil</b> motta fremtidige oppdateringer.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Sender en kopi av denne oppdateringen til en komma-separert liste av epostaddresser. Endrer <b>ikke</b> hvem som vil motta fremtidige oppdateringer.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Sender en kopi av denne oppdateringen til en kommaseparert liste med epost-addresser. Endrer <b->ikke</b> hvem som vi motta fremtige utfordrer dere nå."
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Sender en kopi av dette oppdateringen til en kommaseparert liste med epostaddresser. Disse <b>vill</b> motta fremtidige oppdateringer.)"
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(tom)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr "(navn ikke oppgitt)"
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(ingen overskrift)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:534
+msgid "(no value)"
+msgstr "(ingen verdi)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(bare en sak)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(Venter på godkjenning)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(venter på andre saker)"
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr "(kundens gruppe)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(nødvendig)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(ingen tittel)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "Mine 25 høyst prioriterte saker..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "Mine 25 høyst priorterte forespørsler..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket-:Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Ny sak i\">&nbsp;%1"
+
+#: NOT FOUND IN SOURCE
+msgid "??????"
+msgstr "??????"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "En tom mal"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE slettet"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE lastet"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "ACE kunne ikke slettes"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "fant ikke ACE"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE ikke funnet"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEr kan bare opprettes og slettes."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Avbryter for  ung uønsket saksendring"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "Om meg"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Aksesskontroll"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Handling"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Handling %1 finnes ikke"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Handling skrevet."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Handling forberedt"
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Legg til AdminCc"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Legg til Cc"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Legg til flere filer"
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr "Legg til neste status"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Legg til kunde"
+
+#: html/Admin/Elements/AddCustomFieldValue:26
+msgid "Add Value"
+msgstr "Legg til verdi"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr "Legg til Scrip i denne køen"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr "Legg til et Scrip som gjelder for alle køer"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "Legg til et nøkkelordvalg p denne køen"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Legg til et globalt Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Legg til et Scrip til denne køen"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Legg til et Scrip som vil gjelde for alle køer"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Legg til kommentarer eller svar til denne saken"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Legg til medlemmer"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Legg til overvåkere"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "AddNextState"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "La til primær som en %1 for denne køen"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "La til primær som en %1 for denne saken"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Adresse1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Adresse2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr "Admin Kommentar"
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr "Admin-korrespondanse"
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Adminkøer"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Adminbrukere"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Admin/Global konfigurasjon"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Admin/Grupper"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Admin/Køer/Grunnleggende"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "AdminAllePersonalGrupper"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "AdminKommentar"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "AdminKorrespondanse"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr "AdminFleksifelt"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr "AdminGruppe"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr "AdminGruppeMedlemskap"
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr "AdminEgnePersonligeGrupper"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr "AdminKø"
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr "AdminBrukere"
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Administrativ Cc"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr "Admin"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Avansert Søk"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Etter"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Alder"
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr "Alias"
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr "Alias for"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Alle Fleksifelt"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Alle køer"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Send alltid en melding til kunden uavhengig av meldingssender"
+
+#: html/Elements/Tabs:56
+msgid "Approval"
+msgstr "Godkjennelse"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Godkjennelse #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Godkjenning # %1: Notater kunne ikke lagres pga. systemfeil"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Godkjenning #%1: Notater lagret"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Godkjenning - Detaljer"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Godkjenningsdiagram"
+
+#: html/Approvals/Elements/Approve:44
+msgid "Approve"
+msgstr "Godkjenn"
+
+#: etc/initialdata:437 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Godkjenners notater: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Apr."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "April"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Stigende"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:33 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Legg Ved"
+
+#: html/SelfService/Create.html:65 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Legg ved fil"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Vedlagt fil"
+
+#: NOT FOUND IN SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr "Vedlegg '%1' kunne ikke lastes"
+
+#: lib/RT/Transaction_Overlay.pm:441
+msgid "Attachment created"
+msgstr "Vedlegg opprettet"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Vedleggsnavn"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Vedlegg"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Aug."
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "August"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "AutSystem"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Autosvar"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Autosvar Til Kunde"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "AutosvarTilKunde"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Ugyldig PGP-signatur: %1\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Ugyldig vedleggsid. Kunne ikke finne vedlegg '%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Ugyldig data i %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Ugyldig transaksjonsnummer for vedlegg. %1 skulle vært %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Detaljer"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Sørg for  lagre endringene dine"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:320
+msgid "Before"
+msgstr "Før"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "Begynn Godkjenning"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Blank"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "URL som kan brukes som bokmerke for dette søket"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Begrens headere"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Masseoppdatering av saker"
+
+#: lib/RT/User_Overlay.pm:1352
+msgid "Can not modify system users"
+msgstr "Kan ikke endre systembrukere"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Kan denne primæren se denne køen"
+
+#: lib/RT/CustomField_Overlay.pm:206
+msgid "Can't add a custom field value without a name"
+msgstr "Kan ikke legge til en verdi for et fleksifelt uten navn"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Kan ikke koble en sak til seg selv"
+
+#: lib/RT/Ticket_Overlay.pm:2794
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Kan ikke flette inn i en flettet sak. Denne meldingen bør ikke forekomme"
+
+#: lib/RT/Ticket_Overlay.pm:2612 lib/RT/Ticket_Overlay.pm:2681
+msgid "Can't specifiy both base and target"
+msgstr "Kan ikke spesifisere både base og mål."
+
+#: html/autohandler:99
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Kunne ikke oprette bruker: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:49 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Endre passord"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Merk for å slette"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Merk for å trekke tilbake rettighet"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:57
+msgid "Children"
+msgstr "Barn"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "By"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "Lukket"
+
+#: html/SelfService/Closed.html:25
+msgid "Closed Tickets"
+msgstr "Lukkede Saker"
+
+#: NOT FOUND IN SOURCE
+msgid "Closed requests"
+msgstr "Lukkede forespørsler"
+
+#: html/SelfService/Elements/Tabs:45
+msgid "Closed tickets"
+msgstr "Lukkede saker"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr "Kode"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Kunne ikke tolke kommando!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Kommenter"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Kommentaraddresse"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Kommentaren ble ikke lagret"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Kommenter saker"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr "KommenterSak"
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Kommentarer"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Kommentarer (Ikke send til kunder)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Kommentarer (ikke sendt til kunder)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Kommentarer til %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Kommentarer om denne brukeren"
+
+#: lib/RT/Transaction_Overlay.pm:543
+msgid "Comments added"
+msgstr "La til kommentarer "
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "Lagring forkortet"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Kompilatorrestriksjoner"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Forutsetning"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "Forutsetning gjelder..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Forutsetning ikke funnet"
+
+#: html/Elements/Tabs:50
+msgid "Configuration"
+msgstr "Konfigurasjon"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Bekreft"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "KontaktInfoSystem"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Kontatdato '%1' kunne ikke tolkes"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Innhold"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "Kunne ikke opprette gruppen"
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr "Korrespondanse"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Korrespondanseaddresse"
+
+#: lib/RT/Transaction_Overlay.pm:539
+msgid "Correspondence added"
+msgstr "Korrespondanse lagt til"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Korrespondansen ble ikke lagret"
+
+#: lib/RT/Ticket_Overlay.pm:3525
+msgid "Could not add new custom field value for ticket. "
+msgstr "Kunne ikke legge til nye fleksifeltverdier for saken. "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "Kunne ikke legge til nye fleksifeltverdier for saken. %1 "
+
+#: lib/RT/Ticket_Overlay.pm:3031 lib/RT/Ticket_Overlay.pm:3039 lib/RT/Ticket_Overlay.pm:3055
+msgid "Could not change owner. "
+msgstr "Kunne ikke endre eier. "
+
+#: html/Admin/Elements/EditCustomField:85 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Kunne ikke opprette fleksifelt"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Kunne ikke opprette gruppe"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Kunne ikke opprette mal: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:334
+msgid "Could not create ticket. Queue not set"
+msgstr "Kunne ikke opprette sak. Kø ikke satt"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:422
+msgid "Could not create user"
+msgstr "Kunne ikke opprette bruker"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "Kunne ikke opprette overvåker for kunde"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Kunne ikke finne en sak med id %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Kunne ikke finne gruppen %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Kunne ikke finne eller lage den brukeren"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Kunne ikke finne den primæren"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Kunne ikke finne brukeren %1."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Kunne ikke hente gruppen"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Kunne ikke sette den primæren som %1 for denne køen"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Kunne ikke sette den primæren som %1 for denne saken"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Kunne ikke fjerne den primæren som %1 for denne køen"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Knne ikke fjære den primæren som %1 for denne saken"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Kunne ikke legge til medlemmmer i gruppen"
+
+#: lib/RT/Ticket_Overlay.pm:3535 lib/RT/Ticket_Overlay.pm:3591
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Kunne ikke opprette en transaksjon: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Kunne ikke tolke gpgs svar\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Kunne ikke finne gruppen\\n"
+
+#: lib/RT/Interface/Web.pm:900
+msgid "Couldn't find row"
+msgstr "Kunne ikke finne raden"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Kunne ikke finne primæren"
+
+#: lib/RT/CustomField_Overlay.pm:240
+msgid "Couldn't find that value"
+msgstr "Kunne ikke finne verdien"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "Kunne ikke finne den overvåkern"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Kunne ikke finne bruker\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Kunne ikke laste %1 fra brukerdatabasen.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "Kunne ikke laste NøkkelordValg."
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Kunne ikke laste RTs konfigurasjonsfil '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Kunne ikke laste Scripsene."
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Kunne ikke laste gruppen %1"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Kunne ikke laste linken"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Kunne ikke laste køen"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Kunne ikke laste køen %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Kunne ikke laste scripet"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Kunne ikke finne mal"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Kunne ikke laste den brukeren (%1)"
+
+#: html/SelfService/Display.html:109
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Kunne ikke laste saken '%1'"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "Land"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Opprett"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Opprett Saker"
+
+#: html/Admin/Elements/EditCustomField:75
+msgid "Create a CustomField"
+msgstr "Oprett et fleksifelt"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Opprett et fleksifelt for køen %1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Opprett et fleksifelt for alle køer"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Opprett et nytt fleksifelt"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr "Opprett et globalt Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Opprett et nytt globalt scrip"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Opprett en ny gruppe"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Opprett en ny personlig gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Opprett en ny kø"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Opprett et nytt scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Opprett en ny mal"
+
+#: html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Opprett en ny sak"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Opprett en ny bruker"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Opprett en ny kø"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Opprett en kø kalt"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a request"
+msgstr "Opprett en forespørsel"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Opprett et scrip for køen %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Opprett en mal"
+
+#: html/SelfService/Create.html:25
+msgid "Create a ticket"
+msgstr "Opprett en sak"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Opprettelse feilet: %1 / %2 / %3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "Opprettelse feilet: %1/%2/%3"
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Opprett nye saker basert på dette scripets mal"
+
+#: html/SelfService/Create.html:78
+msgid "Create ticket"
+msgstr "Opprett sak"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Opprett saker i denne køen"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Opprett, slett og modifiser fleksifelt"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Opprett, slett og endre køer"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Opprett, slett og modifiser medlemmene av en brukers personlige grupper"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Opprett, slett og modifiser medlemmene av personlige grupper"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Opprett, slett og modifiser brukere"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "OpprettSak"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Opprettet"
+
+#: html/Admin/Elements/EditCustomField:88
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Opprettet Fleksifelt %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Opprettet malen %1"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Eksisterende Forhold"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Eksisterende Scrips"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Eksisterende medlemmer"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Eksisterende rettigheter"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Eksisterende søkekriterier"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Eksisterende overvåkere"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Fleksifeltet #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:36
+msgid "Custom Fields"
+msgstr "Fleksifelt"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Avsluttningskode"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Forberedelseskode"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Forutsetning"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Fleksifeltet %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Fleksifeltet %1 har en verdi."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Fleksifeltet %1 har ingen verdi."
+
+#: lib/RT/Ticket_Overlay.pm:3427
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Fleksifeltet %1 kunne ikke finnes"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr "Fleksifeltet slettet"
+
+#: lib/RT/Ticket_Overlay.pm:3577
+msgid "Custom field not found"
+msgstr "Fleksifeltet kunne ikke finnes"
+
+#: lib/RT/CustomField_Overlay.pm:350
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Verdien %1 for fleksifeltet %2 kunne ikke finnes"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Fleksifeltets verdi endret fra %1 til %2"
+
+#: lib/RT/CustomField_Overlay.pm:250
+msgid "Custom field value could not be deleted"
+msgstr "Fleksifeltets verdi kunne ikke slettes"
+
+#: lib/RT/CustomField_Overlay.pm:356
+msgid "Custom field value could not be found"
+msgstr "Fleksifeltets verdi kunne ikke finnes"
+
+#: lib/RT/CustomField_Overlay.pm:248 lib/RT/CustomField_Overlay.pm:358
+msgid "Custom field value deleted"
+msgstr "Fleksifeltverdi slettet"
+
+#: lib/RT/Transaction_Overlay.pm:548
+msgid "CustomField"
+msgstr "FleksiFelt"
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "Datafeil"
+
+#: html/SelfService/Display.html:39 html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:55 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Datoer"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Des."
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "Desember"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Standard Autosvarmal"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Standard Autosvarmal"
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr "Standard Adminkommentarmal"
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr "Standard Adminkorrespondensemal"
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr "Standard korrespondensemal"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Standard transaksjonsmal"
+
+#: lib/RT/Transaction_Overlay.pm:643
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Standard: %1/%2 endret seg fra %3 til %4"
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Deleger rettigheter"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Deleger spesifikke rettigheter som har blitt gitt til deg."
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr "DelegerRettigheter"
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Delegering"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Slett"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Slett saker"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr "SlettSak"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Sletting av dette objektet kan føre til inkonsistens"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Sletting av dette objektet vil føre til inkonsistens"
+
+#: lib/RT/User_Overlay.pm:438
+msgid "Deleting this object would violate referential integrity"
+msgstr "Sletting av dette objektet ville føre til inkonsistens"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Sletting av dette objektet ville føre til inkonsisistens."
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "Sletting av dette objektet ville føre til inkonsistens. Det er uheldig."
+
+#: html/Approvals/Elements/Approve:45
+msgid "Deny"
+msgstr "Nekt"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:37
+msgid "Depended on by"
+msgstr "Avhengighet fra"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Avhengigheter: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Avhengig av"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "AvhengigAv"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Synkende"
+
+#: html/SelfService/Create.html:73 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Beskriv problemet under"
+
+#: html/Admin/Elements/AddCustomFieldValue:37 html/Admin/Elements/EditCustomField:39 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Beskrivelse"
+
+#: NOT FOUND IN SOURCE
+msgid "Details"
+msgstr "Detaljer"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Vis"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Vis Rettigheter"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Vis Scrip-maler for denne køen"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Vis Scrip-maler for denne køen"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Visningsmodus"
+
+#: NOT FOUND IN SOURCE
+msgid "Display ticket #%1"
+msgstr "Vis saken #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Gjør hva som helst"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Ikke last denne siden p nytt"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "Ikke vis søkeresultat"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Last ned"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Innen"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Innendato '%1' kunne ikke tolkes""
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "FEIL: Kunne ikke laste sak '%1': %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Rediger"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr "Rediger Forhold"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Rediger fleksifelt for %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Rediger Forhold"
+
+#: html/Admin/Queues/Templates.html:42
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Rediger Maler for køen %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "Rediger nøkkelord"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Rediger scrips"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Rediger systemmal"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Rediger maler for %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:118
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Rediger Konfigurasjon for køen %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Redigerer Konfigurasjonen av brukern %1"
+
+#: html/Admin/Elements/EditCustomField:91
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Redigerer Fleksifeltet %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Redigerer medlemsskap for gruppen %1""
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Redigerer medlemsskap for den personlige gruppen %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Redigerer malen %1"
+
+#: lib/RT/Ticket_Overlay.pm:2622 lib/RT/Ticket_Overlay.pm:2690
+msgid "Either base or target must be specified"
+msgstr "Enten base eller mål må oppgis"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Epost"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "Epostaddresse i bruk"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "EpostAddresse"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "EpostFormat"
+
+#: html/Admin/Elements/EditCustomField:51
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Aktivt (Fjern merkingen for  deaktivere dette fleksifeltet)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Aktiv (Fjern merkingen for  deaktivere denne gruppen)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Aktiv (Fjern merkingen for  deaktivere denne køen)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Aktive Fleksifelt"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Aktive Køer"
+
+#: html/Admin/Elements/EditCustomField:107 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Aktiv status %1"
+
+#: lib/RT/CustomField_Overlay.pm:428
+msgid "Enter multiple values"
+msgstr "Skriv multiple verdier"
+
+#: lib/RT/CustomField_Overlay.pm:425
+msgid "Enter one value"
+msgstr "Skriv en verdi"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Skriv saker og/eller URIer som det skal linkes til. Separer dem med mellomrom"
+
+#: html/Elements/Login:39 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Feil"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "Feilet ved opprettelse av Overvåker"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Feil i parameterne til Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Feil i parameterne til Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Feil i parameterne til Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Feil i parameterne til Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Alle"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Eksempel:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "EksternAutId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "EksternKontaktInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Ekstra info"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Kunne ikke finne pseudogruppen 'Privilgerte' brukere."
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Kunne ikke finne 'pseudogruppen 'Upriviligerte' brukere"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Kunne ikke laste modulen %1. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Feb."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "Februar"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "End"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Endelig Prioritet"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr "EndeligPrioritet"
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Finn grupper hvor"
+
+#: NOT FOUND IN SOURCE
+msgid "Find new/open tickets"
+msgstr "Finn nye/Âpne saker"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Finn folk hvor"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "Finn saker"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "Fullfør godkjennelse"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Først"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Første side"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Tving gjennom endring"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "Fant %quant(%1,sak)"
+
+#: lib/RT/Interface/Web.pm:902
+msgid "Found Object"
+msgstr "Fant Objektet"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "FriforkKontaktInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "FriformMultipel"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr "FriformSingel"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Fre."
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Fulle headere"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Henter brukerinfo fra pgp signatur\\n"
+
+#: lib/RT/Transaction_Overlay.pm:593
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Gitt til %1"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Global"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "Globale Nøkkelordvalg"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Globale Scrip"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Globale maler: %1"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Start!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Gyldig pgp sig fra %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "GÃ¥ til siden"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "GÃ¥ til saken"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr "Stor"
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Gruppen %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Grupperettigheter"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Alt medlem av gruppen"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "Gruppen kunne ikke lastes."
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Gruppen kunne ikke opprettes: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Gruppen opprettet"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "Gruppen har ikke det medlemmet"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Fant ikke gruppen"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Fant ikke gruppen.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Ikke spesifisert gruppe.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Grupper"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Grupper kan ikke være medlemmer av sine medlemmer"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Hallo!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hallo, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Historikk"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "HjemmeTelefon"
+
+#: html/Elements/Tabs:44
+msgid "Homepage"
+msgstr "Hjemmeside"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "Jeg har %quant(%1, sementblandere)."
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "Jeg har [quant,_1,sementblandere]."
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Id"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identitet"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Hvis en godkjenner blir avvist, avvis orginalen, og slett ventende godkjenninger"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Hvis dette verktøyet var setgid kunne en fiendtlig lokal bruker bruke dette verktøyet for å oppnå administrativ tilgang til RT."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Hvis du har oppdatert noe over, sørg for at"
+
+#: lib/RT/Interface/Web.pm:894
+msgid "Illegal value for %1"
+msgstr "Ugyldig verdig for %1"
+
+#: lib/RT/Interface/Web.pm:897
+msgid "Immutable field"
+msgstr "LÃ¥st felt"
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Inkluder deaktiverte fleksifelt i listen."
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Inkluder deaktiverte køer i listen."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Inkluder deaktiverte brukere i søket."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Startprioritet"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr "StartPrioritet"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Feil i inntasting"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "Interesse registrert"
+
+#: lib/RT/Ticket_Overlay.pm:3796
+msgid "Internal Error"
+msgstr "Intern Feil"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Intern Feil: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Ugyldig gruppetype"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr "Ugyldige rettigheter"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "Ugyldig Type"
+
+#: lib/RT/Interface/Web.pm:899
+msgid "Invalid data"
+msgstr "Ugyldig data"
+
+#: lib/RT/Ticket_Overlay.pm:439
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Ugydlig eier. Setter til 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Ugyldig kø"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Ugyldige rettigheter"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Ugyldig verdi for %1"
+
+#: lib/RT/Ticket_Overlay.pm:3434
+msgid "Invalid value for custom field"
+msgstr "Ugyldig verdi for fleksifeltet."
+
+#: lib/RT/Ticket_Overlay.pm:346
+msgid "Invalid value for status"
+msgstr "Ugyldig verdi for status"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Det er ekstremt viktig at ikkepriviligerte brukere ikke har tilgang til dette verktøyet."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Det er anbefalt at du oppretter en upriviligert unixbruker med korrekt gruppemedlemsskap og tilgang til RT for  kjøre dette verktøyet."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Det tar flere parametere:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Ting som venter p min godkjenning"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Jan."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "Januar"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "Bli med i eller forlat denne gruppen"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "Juli"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Total"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "Juni"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Nøkkelord"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Språk"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Siste"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Siste Kontakt"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Sist kontaktet"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "Sist Informert"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Sist Oppdatert"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "SistOppdatert"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Igjen"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "La denne brukeren få tilgang til RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "La denne brukeren få rettigheter"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Begrenser eier til %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Begrenser køen til %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2704
+msgid "Link already exists"
+msgstr "Lenke finnes alt"
+
+#: lib/RT/Ticket_Overlay.pm:2716
+msgid "Link could not be created"
+msgstr "Lenke kunne ikke opprettes"
+
+#: lib/RT/Ticket_Overlay.pm:2724 lib/RT/Ticket_Overlay.pm:2734
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Lenke opprettet (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2645
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Lenke slettet (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2651
+msgid "Link not found"
+msgstr "Lenke ble ikke funnet"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Knytt sak #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "Knytt sak %1"
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Lenker"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Lokasjon"
+
+#: lib/RT.pm:159
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Logkatalogen %1 ble ikke funnet eller kunne ikke skrives til.\\nRT kan ikke kjøre."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Logget inn som %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:35 html/Elements/Login:44 html/Elements/Login:54
+msgid "Login"
+msgstr "Innlogging"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Logg av"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Sett Eier"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Sett Status"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Sett tidsfrist"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Sett løsningsdato"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Sett startdato"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Sett startdato"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Sett informert dato"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Sett prioritet"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Sett Kø"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Sett Emne"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Sett grupper og gruppemedlemsskap"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Rediger egenskaper og konfigurasjon som gjelder for alle køer"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Rediger køer og kø-spesifike egenskaper"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Rediger brukere og passord"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mar."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "Mars"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "Mai"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Mai."
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Medlem lagt til"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Medlem slettet"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Medlem ikke slettet"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Medlem av"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "MedlemAv"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Medlemmer"
+
+#: lib/RT/Ticket_Overlay.pm:2891
+msgid "Merge Successful"
+msgstr "Fletting vellykket"
+
+#: lib/RT/Ticket_Overlay.pm:2811
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Fletting feilet. Kunne ikke sette EffektivId"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Flett inn i"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Melding"
+
+#: lib/RT/Interface/Web.pm:901
+msgid "Missing a primary key?: %1"
+msgstr "Mangler en primærnøkkel?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Mobil"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "MobilTelefon"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Endre Tilgangslister"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Endre Fleksifeltet %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Endre Fleksifelt som gjelder for alle køer"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Endre Scripmaler for denne køen"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Endre Scrips for denne køen"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "Endre SystemACLer"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Endre Malen %1"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Endre et fleksifelt for køen %1"
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Endre et fleksifelt som gjelder for alle køer"
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Endre et scrip for køen %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Endre et scrip som gjelder for alle køer"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "Endre datoer for # %1"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Endre datoer for #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Endre datoer for sak # %1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Endre globale grupperettigheter"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Endre globale grupperettigheter"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "Endre globale rettigheter for grupper"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "Endre globale rettigheter for brukere"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Endre globale scrips"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Endre globale brukerrettigheter"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Endre globale brukerrettigheter"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Endre gruppens metadata eller slette gruppen"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Endre grupperettigheter for %1 gruppen"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Endre grupperettigheter %1 køen"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Endre medlemsliste for denne gruppen"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Endre sin egen RT konto"
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Endre hvem som er relatert til %1 køen"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Endre hvem som er relater til sak #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Endre scrips for %1 køen"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Endre scrips som gjelder alle køer"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Endre mal %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Endre maler som gjelder for alle køer"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Endre gruppen %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Endre overvåkere for køen"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Endre brukeren %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Endre sak # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Endre sak #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Endre saker"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Endre brukerrettigheter for %1 gruppen"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Endre brukerrettigheter for %1 køen"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Endre overvåkere for '%1' køen"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr "EndreACL"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr "EndreEgetMedlemskap"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr "EndreKøOvervåkere"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr "EndreScrips"
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr "EndreSegSelv"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr "EndreMal"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr "EndreSak"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Man."
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Mer om %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr "Flytt ned"
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr "Flytt opp"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Flere"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "MÃ¥ spesifisere attributten 'Navn'"
+
+#: html/SelfService/Elements/MyRequests:49
+#. ($friendly_status)
+msgid "My %1 tickets"
+msgstr "Mine %1 saker"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Mine saker til godkjenning"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Mine saker til godkjenning"
+
+#: html/Admin/Elements/AddCustomFieldValue:33 html/Admin/Elements/EditCustomField:34 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Navn"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Navnet er i bruk"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Trenger godkjennelse fra systemadministrator"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Aldri"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Ny"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Nytt Passord"
+
+#: etc/initialdata:317 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Ny, Venter på Godkjennelse"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Nye forhold"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Nytt Søk"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Nytt fleksifelt"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "Ny gruppe"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Nytt passord"
+
+#: lib/RT/User_Overlay.pm:647
+msgid "New password notification sent"
+msgstr "Melding om nytt passord sendt"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "Ny kø"
+
+#: NOT FOUND IN SOURCE
+msgid "New request"
+msgstr "Ny forespørsel"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Nye rettigheter"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Nytt scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nytt søk"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:50
+msgid "New template"
+msgstr "Ny mal"
+
+#: html/SelfService/Elements/Tabs:48
+msgid "New ticket"
+msgstr "Ny sak"
+
+#: lib/RT/Ticket_Overlay.pm:2778
+msgid "New ticket doesn't exist"
+msgstr "Ny sak eksistere ikke"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Ny bruker"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Ny bruker kalt"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Ny overvåker"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Instillinger for nytt vindu"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Neste"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Neste side"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "KalleNavn"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Kallenavn"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Ingen FleksiFelt"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Ingen grupper definert"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Ingen kø definert"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Ingen RT bruker funnet. Vennligst referer til manualen.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Ingen Mal"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Ingen sak oppgitt. Avbryter sak "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Ingen Sak oppgitt. Avbryter saksendring\\n\\n"
+
+#: html/Approvals/Elements/Approve:46
+msgid "No action"
+msgstr "Ingen handling"
+
+#: lib/RT/Interface/Web.pm:896
+msgid "No column specified"
+msgstr "Ingen kolonne spesifisert"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Ingen kommando funnet\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Ingen kommentar skrevet om denne brukeren"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Ingen korrespondanse vedlagt"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Ingen beskrivelse for %1"
+
+#: lib/RT/Users_Overlay.pm:145
+msgid "No group specified"
+msgstr "Ingen gruppe spesifisert"
+
+#: lib/RT/User_Overlay.pm:865
+msgid "No password set"
+msgstr "Passordet er ikke satt"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Ingen tilgang til å opprette køer"
+
+#: lib/RT/Ticket_Overlay.pm:342
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Ikke tilgang til å opprette saker for køen '%1'"
+
+#: lib/RT/User_Overlay.pm:152
+msgid "No permission to create users"
+msgstr "Ikke tilgang til å opprette brukere"
+
+#: html/SelfService/Display.html:118
+msgid "No permission to display that ticket"
+msgstr "Ikke tilgang til å vise den saken"
+
+#: html/SelfService/Update.html:52
+msgid "No permission to view update ticket"
+msgstr "Ingen tilgang til å se oppdatering av saken"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Ingen primær spesifisert"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Ingen primære spesifisert"
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Det er ingen køer som matcher søkekriteriet"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "Ingen rettigheter funnet"
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Ingen rettigheter tildelt"
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Ingen søk  behandle"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Ingen saksid oppgitt"
+
+#: lib/RT/Transaction_Overlay.pm:478 lib/RT/Transaction_Overlay.pm:516
+msgid "No transaction type specified"
+msgstr "Transaksjonstype ikke spesifisert"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "Ingen bruker eller epostaddresse oppgitt"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Fant ingen brukere som treffer søkekriteriene."
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Fant ingen gyldig RT bruker. RT cvs handler avstengt. Kontakt din RT administrator.\\n"
+
+#: lib/RT/Interface/Web.pm:893
+msgid "No value sent to _Set!\\n"
+msgstr "Ingen verdi sendt til _Set!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "Ingen"
+
+#: lib/RT/Interface/Web.pm:898
+msgid "Nonexistant field?"
+msgstr "Ukjent felt?"
+
+#: NOT FOUND IN SOURCE
+msgid "Not logged in"
+msgstr "Ikke logget inn"
+
+#: html/Elements/Header:59
+msgid "Not logged in."
+msgstr "Ikke logget inn."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Ikke satt"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Ikke implementert enda."
+
+#: NOT FOUND IN SOURCE
+msgid "Not yet implemented...."
+msgstr "Ikke implementert enda...."
+
+#: html/Approvals/Elements/Approve:49
+msgid "Notes"
+msgstr "Notater"
+
+#: lib/RT/User_Overlay.pm:650
+msgid "Notification could not be sent"
+msgstr "Melding kunne ikke sendes"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Raporter til AdminCc"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Rapporter til AdminCc som kommentar"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Rapporter til andre mottakere"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Rapporter til andre mottakere som kommentar"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Rapporter til eier"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Rapportert til eier som kommentar"
+
+#: etc/initialdata:319 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Rapporter til Eiere og AdminCc om nye ting som venter på godkjenning"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Rapporter til kunde"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Rapporter til Kunder og Cc"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Rapporter til Kunder og Cc som kommentar"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Rapporter til Kunder Cc og AdminCc"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Rapporter til Kunder Cc og AdminCc som Kommentar"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "November"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Objekter kunne ikke opprettes"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Objektet ble opprettet"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Okt."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "Oktober"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "Ved"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Ved Kommentar"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Ved Korrespondanse"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Ved Opprettelse"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Ved Eierskifte"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Ved Køendring"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Ved Løsning"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Ved statusendring"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Ved Transaksjon"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Vis kun godkjennelse for saker opprettet etter %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Bare vis godkjennelse for saker opprettet før %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Ã…pne"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Ã…pne den"
+
+#: NOT FOUND IN SOURCE
+msgid "Open requests"
+msgstr "Åpne forespørsler"
+
+#: html/SelfService/Elements/Tabs:42
+msgid "Open tickets"
+msgstr "Ã…pne saker"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Ã…pne saker (fra utlisting) i et nytt vindu"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Ã…pne saker (fra utlisting) it et annet vinud"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Ã…pne saker ved korrespondanse"
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Rekkefølge og sortering"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organisasjon"
+
+#: html/Approvals/Elements/Approve:33
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Opprinnelig sak: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Over tid beveger prioriteten seg mot"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Eie saker"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr "EieSak"
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Eier"
+
+#: lib/RT/Ticket_Overlay.pm:3071
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Eier endret fra %1 til %2"
+
+#: lib/RT/Transaction_Overlay.pm:582
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Eier ble tvunget til å endres fra %1 til %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Eier er"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Personsøker"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "PersonSøker"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr "Forelder"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:47
+msgid "Parents"
+msgstr "Foreldre"
+
+#: html/Elements/Login:52 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Passord"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Passordhint"
+
+#: lib/RT/User_Overlay.pm:169 lib/RT/User_Overlay.pm:868
+msgid "Password too short"
+msgstr "For kort passord"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Passord: %1"
+
+#: html/Admin/Users/Modify.html:293
+msgid "Passwords do not match."
+msgstr "Passordene stemmer ikke overens."
+
+#: html/User/Prefs.html:174
+msgid "Passwords do not match. Your password has not been changed"
+msgstr "Passordene stemmer ikke overrens. Passordet ble ikke endret"
+
+#: html/Ticket/Elements/ShowSummary:45 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Folk"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Kjør en brukerdefinert handling"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:101 lib/RT/CustomField_Overlay.pm:202 lib/RT/CustomField_Overlay.pm:234 lib/RT/CustomField_Overlay.pm:511 lib/RT/CustomField_Overlay.pm:91 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2603 lib/RT/Ticket_Overlay.pm:2675 lib/RT/Ticket_Overlay.pm:2769 lib/RT/Ticket_Overlay.pm:2784 lib/RT/Ticket_Overlay.pm:2978 lib/RT/Ticket_Overlay.pm:3206 lib/RT/Ticket_Overlay.pm:3404 lib/RT/Ticket_Overlay.pm:3566 lib/RT/Ticket_Overlay.pm:3618 lib/RT/Ticket_Overlay.pm:3783 lib/RT/Transaction_Overlay.pm:466 lib/RT/Transaction_Overlay.pm:473 lib/RT/Transaction_Overlay.pm:502 lib/RT/Transaction_Overlay.pm:509 lib/RT/User_Overlay.pm:1355 lib/RT/User_Overlay.pm:570 lib/RT/User_Overlay.pm:605 lib/RT/User_Overlay.pm:861 lib/RT/User_Overlay.pm:962
+msgid "Permission Denied"
+msgstr "Ingen Tilgang"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Personlige Grupper"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Personlige grupper"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Personlige grupper:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Telefonnummer"
+
+#: NOT FOUND IN SOURCE
+msgid "Placeholder"
+msgstr "Stedholder"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr "Pref"
+
+#: html/Elements/Header:52 html/Elements/Tabs:53 html/SelfService/Elements/Tabs:51 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Instillinger"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Pref"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Klargjør Forkortet"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Forrige"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Forrige side"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Primær %1 ikke funnet."
+
+#: html/Search/Elements/PickRestriction:54 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Prioritet"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Prioritet starter på"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Priviligert"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Priviligert status: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Priviligerte brukere"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Pseduogruppe for intern bruk"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:33 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Kø"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:44
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Køen %1 kunne ikke finnes"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Køen '%1' ikke funnet\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "Nøkkelordvalg for kø"
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Kønavn"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Køscrip"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Køen eksisterer allerede"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Køen kunne ikke opprettes"
+
+#: html/Ticket/Create.html:205
+msgid "Queue could not be loaded."
+msgstr "Køen kunne ikke lastes."
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Køen opprettet"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "Køen er ikke oppgitt."
+
+#: html/SelfService/Display.html:71 lib/RT/CustomField_Overlay.pm:98
+msgid "Queue not found"
+msgstr "Køen ikke funnet"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Køer"
+
+#: html/Elements/Quicksearch:25
+msgid "Quick search"
+msgstr "Raskt søk"
+
+#: html/Elements/Login:44
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 for %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 fra <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "RT-administrasjon"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RT Autentiseringsfeil."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT Avvisning: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT Konfigurasjonsfeil"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "Kritisk RT feil. Meldingen ble ikke lagret!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "RT Feil"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT Mottok mail (%1) fra seg selv."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT Mottok mail (%1) fra seg selv."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT Selvbetjening / Lukkede Saker"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT oversikt"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT kunne ikke autentisere deg"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT kunne ikke finne kunde via sitt eksterne databaseoppslag"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT kunne ikke finne køen: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT kunne ikke validere denne PGP signaturen. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT for %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT for %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT har behandlet dine kommandoer"
+
+#: html/Elements/Login:92
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT er &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Den er distribuert under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>""
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT er &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Den er distribuert under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>""
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT tror denne meldingen kan være en returmail"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT vil behandle denne meldingen som om den var usignert"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT's epost kommandomodus krever PGP autentisering. Meldingen din var enten ikke signert, eller signaturen din kunne ikke bekreftes."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Ekte Navn"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "EkteNavn"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:71
+msgid "Referred to by"
+msgstr "Referert til av"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:61
+msgid "Refers to"
+msgstr "Refererer til"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "RefererTil"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Redefiner"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Redefiner søket"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Last siden p nytt hvert %1 minutt."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:62 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Forhold"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Fjern AdminCc"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Fjern Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Fjern Kunde"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Svar"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Svar p sak"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "SvarPÂSak"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Kunde"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Kundens epostaddresse"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Kunde(r)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "KundeAddresser"
+
+#: html/SelfService/Create.html:41 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Kunder"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "Forespørsler skal være behandlet innen"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Reset"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Hjemme"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Løs"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Løs saknr #%1 (%2)"
+
+#: etc/initialdata:308 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Løst"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Svar til kunder"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Resultater"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Resultater per side"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Skriv Passord igjen"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Rettighet %1 kunne ikke finnes for %2 %3 in scope %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Rettighet Deligert"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Rettighet Tildelt"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Rettighet lastet"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Rettigheten kunne ikke trekkes tilbake"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Rettighet ikke funnet"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Rettighet ikke lastet."
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Rettighet fjernet"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Rettigheter"
+
+#: lib/RT/Interface/Web.pm:792
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "Rettigheter kunne ikke tildeles for %1"
+
+#: lib/RT/Interface/Web.pm:825
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "Rettigheter kunne ikke trekkes tilbake for %1"
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Roller"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "RootGodkjenning"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Lør."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyLinks.html:39 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Lagre Endringer"
+
+#: NOT FOUND IN SOURCE
+msgid "Save changes"
+msgstr "Lage endringer"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "Scrip #%1"
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Scrip Opprettet"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Scrip slettet"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrip for %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Scrip som gjelder for alle køer"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Søk"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Søkekriteria"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "Søk etter godkjenninger"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "Sikkerhet:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr "SeKø"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Velg en gruppe"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Velg en kø"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Velg en bruker"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr "Velg fleksifelt"
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Velg gruppe"
+
+#: lib/RT/CustomField_Overlay.pm:422
+msgid "Select multiple values"
+msgstr "Velg flere verdier"
+
+#: lib/RT/CustomField_Overlay.pm:419
+msgid "Select one value"
+msgstr "Velg en verdi"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Velg kø"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Velg scrip"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55 html/Admin/Queues/Templates.html:47
+msgid "Select template"
+msgstr "Velg mal"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Velg bruker"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "VelgFlere"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "VelgEnkelt"
+
+#: NOT FOUND IN SOURCE
+msgid "Self Service"
+msgstr "Selvbetjening"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Send epost til alle overvåkere"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Send epost til alle overvåkere som \"kommentar\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Send epost til kunder og Cc"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Send epost til kunder og Cc som kommentar"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Sender en melding til kundene"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Send epost til eksplisit oppgitte Ccer og Bccer"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Send epost til Administrative Ccer"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Sender epost til de administrative Ccene som kommentar"
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Sender epost til eieren"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Sep."
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "September"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Vis Resultater"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Vis godkjente forespørsler"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Vis basisinfo"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Vis avviste forespørsler"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Vis detaljer"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Vis ventende forespørsler"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Vis forespørsler som venter på andre godkjenninger"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Vis sakens private kommentarer"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Vis sakssammendrag"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr "VisACL"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr "VisScrip"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr "VisMal"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr "VisSak"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr "VisSaksKommentarer"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Meld deg på som saksforespørrer eller sak/kø Cc"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Meld deg på som sak/kø AdminCc"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Signatur"
+
+#: NOT FOUND IN SOURCE
+msgid "Signed in as %1"
+msgstr "Logget inn som %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Enkel"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr "Dropp Meny"
+
+#: html/Admin/Elements/AddCustomFieldValue:29
+msgid "Sort"
+msgstr "Sorter"
+
+#: NOT FOUND IN SOURCE
+msgid "Sort key"
+msgstr "Sorter nøkkel"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Sorter resultater etter"
+
+#: NOT FOUND IN SOURCE
+msgid "SortOrder"
+msgstr "SorteringsRekkefølge"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Pauset"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Startside"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Startet"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Startdato '%1' kunne ikke tolkes"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Starter"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Starter Etter"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Startdato '%1' kunne ikke tolkes"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Stat"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Status"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr "Statusendring"
+
+#: lib/RT/Transaction_Overlay.pm:528
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Status endret fra %1 til %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "EndreStatus"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Stjel"
+
+#: lib/RT/Transaction_Overlay.pm:587
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Stjålet fra %1 "
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:57 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:32 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Emne"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:609
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Endre emne til %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Oppdater"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Send Arbeidsflyt"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Lykkes"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Søn."
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "SuperBruker"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "System"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:791 lib/RT/Interface/Web.pm:824
+msgid "System Error"
+msgstr "Systemfeil"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "Systemfeil. Rettighet ikke tildelt."
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "Systemfeil. rettigheter ikke tildelt"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Systemfeil. Rettighet ikke tildelt."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Systemfeil. Rettighet ikke tildelt."
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Systemfeil. Kunne ikke tildele rettigheter."
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Systemgrupper"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRollegruppe for intern bruk"
+
+#: lib/RT/CurrentUser.pm:318
+msgid "TEST_STRING"
+msgstr "TEST_STRENG"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Ta"
+
+#: lib/RT/Transaction_Overlay.pm:573
+msgid "Taken"
+msgstr "Tatt"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Mal"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Mal #%1"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Mal slettet"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Kunne ikke finne mal"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Kunne ikke finne mal\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Mal tolket"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Maler"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Maler for %1\\n"
+
+#: lib/RT/Interface/Web.pm:892
+msgid "That is already the current value"
+msgstr "Verdien er allerede satt"
+
+#: lib/RT/CustomField_Overlay.pm:243
+msgid "That is not a value for this custom field"
+msgstr "Det er ikke en verdi for dette fleksifeltet"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Det er den samme verdien"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That principal already has that right"
+msgstr "Den primæren har allerede den rettigheten"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Den primæren er allerede en %1 for denne køen"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Den primæren er allerede en %1 for denne køen"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Den primæren er ikke en %1 for denne køen"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Den primæren er ikke en %1 for denne saken"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Den køen eksisterer ikke"
+
+#: lib/RT/Ticket_Overlay.pm:3210
+msgid "That ticket has unresolved dependencies"
+msgstr "Denne saken har uløste avhengigheter"
+
+#: NOT FOUND IN SOURCE
+msgid "That user already has that right"
+msgstr "Den brukeren har allerede den rettigheten"
+
+#: lib/RT/Ticket_Overlay.pm:3020
+msgid "That user already owns that ticket"
+msgstr "Den brukeren eier allerede den saken"
+
+#: lib/RT/Ticket_Overlay.pm:2986
+msgid "That user does not exist"
+msgstr "Den brukeren finnes ikke"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Den brukeren er allerede priviligert"
+
+#: lib/RT/User_Overlay.pm:336
+msgid "That user is already unprivileged"
+msgstr "Den brukeren er allerede upriviligert"
+
+#: lib/RT/User_Overlay.pm:328
+msgid "That user is now privileged"
+msgstr "Denne brukeren er nå priviligert"
+
+#: lib/RT/User_Overlay.pm:349
+msgid "That user is now unprivileged"
+msgstr "Dette brukeren er nå upriviligert"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "Den brukeren er allerede upriviligert"
+
+#: lib/RT/Ticket_Overlay.pm:3012
+msgid "That user may not own tickets in that queue"
+msgstr "Den brukeren kan ikke eie saker i den køen"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Dette er ikke en numerisk id"
+
+#: html/SelfService/Display.html:32 html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Detaljer"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "CCen til en sak"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "Administrative CCer for en sak"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "Kommentarer er lagret"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "De følgende kommandoene vil finne alle aktive saker i køen 'general' og sette deres prioritet til 99 hvis de ikke har blitt rørt de siste 4 timene:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "De følgende kommandoene ble ikke behandlet:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:895
+msgid "The new value has been set."
+msgstr "Den nye verdien har blitt satt."
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "Eieren av en sak"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "Forespørren av en sak"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Disse kommentarene er generelt ikke synlig for brukeren"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Denne saken %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Dette verktøyet tillater brukeren å kjøre perlmoduler fra inni RT."
+
+#: lib/RT/Transaction_Overlay.pm:251
+msgid "This transaction appears to have no content"
+msgstr "Denne transaksjonen ser ikke ut til å ha noe innhold"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "Denne brukerens %1 høyst prioriterte saker"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "Denne brukerens 23 høys prioriterte saker"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Tor."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr "Sak"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Sak # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "Sak $ %1 Jumbo oppdater: %2"
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Sak #%1 Jumbo oppdatering: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Sak #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:587 lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Sak %1 opprettet i '%2' køen"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Sak %1 lastet\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Sak %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Sakshistorikk # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Id"
+msgstr "SaksId"
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr "Løst Sak"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Saks-vedlegg"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Saks-innhold"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Sakens innholdstype"
+
+#: lib/RT/Ticket_Overlay.pm:496 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "Saken kunne ikke opprettes på grunn av en intern feil"
+
+#: lib/RT/Transaction_Overlay.pm:520
+msgid "Ticket created"
+msgstr "Sak opprettet"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Saksopprettelse feilet"
+
+#: lib/RT/Transaction_Overlay.pm:525
+msgid "Ticket deleted"
+msgstr "Sak slettet"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Saksid ikke funnet"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "Sak drept"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Sak ikke funnet"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "Saksstatus endret"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Saksovervåkere"
+
+#: html/Elements/Tabs:47
+msgid "Tickets"
+msgstr "Saker"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Saker %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Saker %1 av %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Saker fra %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Saker som er avhengige av denne godkjennelsen:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Tid Igjen"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Arbeidstid"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Tid igjen"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Tid å vise"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Arbeidstid"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "TidIgjen"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "ArbeidsTid"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "For å generere en diff av denne bekreftelsen:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "For å genere en diff av denne bekreftelsen"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Fortalt"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transaksjon"
+
+#: lib/RT/Transaction_Overlay.pm:640
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transaksjon %1 slettet"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transaksjon Opprettet"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create kunne ikke, siden du ikke spesifiserte en saksid"
+
+#: lib/RT/Transaction_Overlay.pm:699
+msgid "Transactions are immutable"
+msgstr "Transaksjoner er låst"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Prøver å slette en rettighet: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Tir."
+
+#: html/Admin/Elements/EditCustomField:44 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Type"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Uimplementert"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Unix login"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "UnixBrukerNavn"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Ukjent InnholdsFormatering %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Ubegrenset"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Upriviligert"
+
+#: lib/RT/Transaction_Overlay.pm:569
+msgid "Untaken"
+msgstr "Ikke tatt"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Oppdater"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Oppdater ID"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Oppdater Type"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Oppdater alle disse sakene samtidig"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Oppdater epost"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Oppdater navn"
+
+#: lib/RT/Interface/Web.pm:409
+msgid "Update not recorded."
+msgstr "Oppdatering ikke lagret."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Oppdater valgte saker"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Oppdater signatur"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Oppdater sak"
+
+#: NOT FOUND IN SOURCE
+msgid "Update ticket # %1"
+msgstr "Ooppdater sak # %1"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:47
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Oppdater sak #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Oppdater sak #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:407
+msgid "Update type was neither correspondence nor comment."
+msgstr "Oppdateringstype var verken korrespondanse eller kommentar."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Oppdatert"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Bruker %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Bruker %1 Passord: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Brukeren '%1' ble ikke funnet"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Brukeren '%1' ble ikke funnet"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Bruker Definert"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "BrukerID"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "BrukerId"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Brukerrettigheter"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Bruker kunne ikke opprettes: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Bruker opprettet"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Brukerdefinerte grupper"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Bruker informert"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Brukervisning"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Brukernavn"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Brukere"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Brukere som treffer søkekriteria"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "KøVerdi"
+
+#: html/Admin/Elements/EditCustomField:57
+msgid "Values"
+msgstr "Verdier"
+
+#: NOT FOUND IN SOURCE
+msgid "VrijevormEnkele"
+msgstr "VrijevormEnkele"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr "Overvåk"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr "OvervåkSomAdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "Overvåker lastet"
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Overvåkere"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "WebFormatering"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Ons."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "NÃ¥r en sak har blitt godkjent av alle godkjennere, legg til korrespondanse for den opprinnelige saken"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "NÃ¥r en sak har blitt godkjent av en godkjenner, legg til korrespondanse til den orginale saken"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "NÃ¥r er sak er opprettet"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Når er godkjennelsessak blir opprettet, gi melding til Eier og AdminCc om saken som venter på deres godkjenning"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "NÃ¥r noe skjer"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Når en sak er løst"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Når en sak får ny eier"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Når en sak flyttes til en ny kø"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "NÃ¥r en saks status endres"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "NÃ¥r brukerdefinerte forhold intreffer"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "NÃ¥r kommentarer kommer inn"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "NÃ¥r korrespondanse kommer inn"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Arbeid"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "ArbeidsTelefon"
+
+#: html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Arbeidet"
+
+#: lib/RT/Ticket_Overlay.pm:3123
+msgid "You already own this ticket"
+msgstr "Du eier allerede denne saken"
+
+#: html/autohandler:108
+msgid "You are not an authorized user"
+msgstr "Du er ikke en autorisert bruker"
+
+#: lib/RT/Ticket_Overlay.pm:2998
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Du kan bare omfordele saker som du eier eller som ikke har en eier"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Du har ikke tilgang til å se den saken.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Du fant %1 saker i %2 køen"
+
+#: html/NoAuth/Logout.html:31
+msgid "You have been logged out of RT."
+msgstr ""
+
+#: html/SelfService/Display.html:78
+msgid "You have no permission to create tickets in that queue."
+msgstr "Du har ikke tilgang til å opprette saker i den køen."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "Du kan ikke opprette forespørsler i den køen."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Velkommen tilbake"
+
+#: NOT FOUND IN SOURCE
+msgid "Your %1 requests"
+msgstr "Dine %1 forespørsler"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Din RT administrastor har feilkonfigurert mail aliasene som kaller RT"
+
+#: etc/initialdata:435 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Din forespørsel har blitt godkjent av %1. Andre godkjennelser avventer kanskje fortsatt"
+
+#: etc/initialdata:469 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Din forespørsel ble godkjent."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "Din forespørsel ble avvist"
+
+#: etc/initialdata:390 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Din forespørsel ble avvist"
+
+#: html/autohandler:127
+msgid "Your username or password is incorrect"
+msgstr "Ditt brukernavn/passord er ugyldig"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "Zip"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[ikke noe emne]"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "som tildelt til %1"
+
+#: html/SelfService/Closed.html:28
+msgid "closed"
+msgstr "lukket"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "inneholder"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "innhold"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "innholdstype"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "korrespondanse (sansynligvis) ikke sendt"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "korrespondanse sendt"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "dager"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "død"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "slett"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "slettet"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "treffer ikke"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "inneholder ikke"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "lik som"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "usant"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "filnavn"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "større enn"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "gruppe '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "timer"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "id"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "er"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "er ikke"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "mindre enn"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "treffer"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minutter"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "endringer\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "måneder"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "ny"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "ingen verdi"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "ingen"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "ikke lik som"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "ikkelik"
+
+#: html/SelfService/Elements/MyRequests:61 lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "Ã¥pen"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "personlig gruppe '%1' for bruker '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "kø %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "avvist"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "løst"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "sek"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "pauset"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "system %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "systemgruppe '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "den kallende komponenten oppga ikke hvorfor"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "sak #%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "sant"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "ubeskrevet gruppe %1"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "ubeskrevet gruppe %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "bruker %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "uker"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "med malen %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "Ã¥r"
+
+#: NOT FOUND IN SOURCE
+msgid "ニックネーム"
+msgstr "????"
+
diff --git a/rt/lib/RT/I18N/pt_br.po b/rt/lib/RT/I18N/pt_br.po
new file mode 100644
index 0000000..6962ecb
--- /dev/null
+++ b/rt/lib/RT/I18N/pt_br.po
@@ -0,0 +1,4829 @@
+# $Id: pt_br.po,v 1.1 2003-07-15 13:16:28 ivan Exp $
+msgid ""
+msgstr ""
+"Project-Id-Version: RT 2.1.x\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2002-12-07 23:20-02:00\n"
+"Last-Translator: Gustavo Chaves <gustavo@cpqd.com.br>\n"
+"Language-Team: rt-devel <rt-devel@lists.fsck.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "#"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr "%1 %2 adicionado"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 atrás"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 alterado para %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr "%1 %2 removido"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 %2 of group %3"
+msgstr "%1 %2 do grupo %3"
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "%1 %2 com modelo %3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 este tíquete\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "%1 - %2 apresentados"
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - Um argumento para passar para %2"
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - Mostra atualizações de estado no STDOUT"
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - Especifica o módulo de ação que você quer usar"
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - Especifica o módulo de condição que você quer usar"
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - Especifica o módulo de busca que você quer usar"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "ScripAction %1 carregado"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 usado como um valor de %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "Aliases %1 requerem um TicketId no qual trabalhar"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "Aliases %1 requerem um TicketId no qual trabalhar "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "Aliases %1 requerem um TicketId no qual trabalhar (de %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 parece ser um objeto local, mas não pode ser encontrado no banco de dados"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 por %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 alterado de %2 para %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "%1 não pôde ser alterado para %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 não pôde iniciar uma transação (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 não pôde alterar estado para resolvido. O banco de dados do RT pode estar inconsistente."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "%1 tíquetes de mais alta prioridade que eu possuo..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "%1 tíquetes de mais alta prioridade que eu requeri..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 é uma ferramenta para modificar tíquetes a partir de uma ferramenta de agenda externa, como o cron."
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 não é mais um %2 para esta fila."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 não é mais um %2 para este tíquete."
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 não é mais um valor para o campo personalizado %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 não é um identificador de fila válido."
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 min"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 não mostrado"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 direitos"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 teve sucesso\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "Tipo %1 desconhecido para $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "Tipo %1 desconhecido para %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 foi criado sem um CurrentUser\\n"
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 resolverá todos os membros de um grupo de tíquetes resolvidos."
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 colocará como pendente uma BASE [local] se for dependente [ou membro] de uma requisição ligada."
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: nenhum arquivo anexo especificado"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr "%1b"
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k"
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' é um valor inválido para o estado"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1' não é uma ação reconhecida."
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(Assinale para remover o membro do grupo)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(Assinale para remover o scrip)"
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Assinale para remover)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(Assinale para remover)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Entre com identificadores de tíquetes ou URLs, separados por espaços)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(Se deixado em branco, será entendido como %1"
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(Sem Valor)"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(Nenhum campo personalizado)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Sem membros)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Sem scrips)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Nenhum esquema)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envia uma cópia-cega (Bcc) desta atualização para uma lista de endereços de email separados por vírgula. <b>Não</b> altera quem vai receber atualizações futuras.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Envia uma cópia-cega (Bcc) desta atualização para uma lista de endereços eletrônicos separados por vírgulas. <b>Não</b> altera o destinatário de atualizações futuras.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envia uma cópia-cega (Bcc) desta atualização para uma lista de endereços eletrônicos separados por vírgulas. <b>Não</b> altera o destinatário de atualizações futuras.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Envia uma cópia-cega (Bcc) desta atualização para uma lista de endereços eletrônicos separados por vírgulas. <b>Não</b> altera o destinatário de atualizações futuras.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Envia uma cópia desta atualização para uma lista de endereços eletrônicos separados por vírgulas. <b>Não</b> altera o destinatário de atualizações futuras.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Envia uma cópia desta atualização para uma lista de endereços eletrônicos separados por vírgulas. Estas pessoas <b>receberão</b> as atualizações futuras.)"
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(vazio)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr "(nenhum nome listado)"
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(Sem assunto)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(sem valor)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(somente um tíquete)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(aguardando aprovação)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(aguardando outros tíquetes)"
+
+#: NOT FOUND IN SOURCE
+msgid "(requestor's group)"
+msgstr "(grupo do requisitante)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(requerido)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(sem título)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "25 tíquetes mais prioritários que possuo..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "25 tíquetes mais prioritários que requisitei..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Novo tíquete em\">&nbsp;%1"
+
+#: NOT FOUND IN SOURCE
+msgid "??????"
+msgstr ""
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "Um modelo vazio"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE Removida"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE Carregada"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "ACE não pôde ser removida"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "ACE não pode ser encontrada"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE não encontrado"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEs só podem ser criados e removidos."
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Abortando para evitar modificações indesejadas no tíquete.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "Sobre mim"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Controle de acesso"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "Ação"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "Ação %1 não encontrada"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "Ação confirmada."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "Ação preparada..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Adicionar AdminCc"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Adicionar Cc"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Adicionar Mais Arquivos"
+
+#: NOT FOUND IN SOURCE
+msgid "Add Next State"
+msgstr "Adicionar Próximo Estado"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Adicionar Requisitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr "Adicionar um Scrip nesta fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr "Adicionar um Scrip que será aplicado a todas as filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "Adicionar uma seleção de teclado a esta fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "Adicionar um novo scrip global"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "Adicionar um scrip a esta fila"
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Adicionar um scrip que se aplicará a todas as filas"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Adicionar comentários ou respostas aos tíquetes selecionados"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Adicionar membros"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Adicionar novos observadores"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "AddNextState"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Principal adicionado como um %1 para esta fila"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Principal adicionado como um %1 para este tíquete"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "Endereço 1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "Endereço 2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "Admin Cc"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr "Comentário do Administrador"
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr "Correspondência do Administrador"
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Administração de filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Administração de usuários"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Administração da configuração global"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Administração de Grupos"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Administração de uma fila"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "AdminAllPersonalGroups"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "AdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "AdminComment"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "AdminCorrespondence"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr "AdminCustomFields"
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr "AdminGroup"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr "AdminGroupMembership"
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr "AdminOwnPersonalGroups"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr "AdminQueue"
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr "AdminUsers"
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "Cc Administrativo"
+
+#: NOT FOUND IN SOURCE
+msgid "Admins"
+msgstr "Administradores"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "Busca avançada"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "Depois"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "Idade"
+
+#: NOT FOUND IN SOURCE
+msgid "Alias"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Alias for"
+msgstr "Alias para"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Todos os Campos Personalizados"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Todas as filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "Sempre envia uma mensagem para os requisitantes independentemente do remetente"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Aprovação"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Aprovação #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Aprovação #%1: Notas não registradas devido a um erro de sistema"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Aprovação #%1: Notas registradas"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Details"
+msgstr "Detalhes da Aprovação"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval diagram"
+msgstr "Diagrama da aprovação"
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Aprove"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr "Notas do aprovador: %1"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Abr."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "Abril"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Ascendente"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Anexar"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Anexar arquivo"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Arquivo anexado"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Arquivo anexo '%1' não pôde ser carregado"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Arquivo anexo criado"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Nome do arquivo anexo"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "Arquivos anexos"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Ago."
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "Agosto"
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "Sistema de autenticação"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Autoreply"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Autoreply para Requisitantes"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "AutoreplyToRequestors"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "Assinatura PGP inválida: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Identificador de arquivo anexo inválido. Não pude encontrar o arquivo '%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "Dados inválidos em %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Número inválido de transação para o arquivo anexo. %1 deveria ser %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Básicos"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Bcc"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Não se esqueça de salvar suas alterações"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "Antes"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr "Incício da Aprovação"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "Vazio"
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "URL para guardar esta busca em seus marcadores"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Cabeçalhos resumidos"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Atualização de tíquetes em lote"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Não posso modificar os usuários do sistema"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr "Este principal pode ver esta fila"
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Não posso adicionar um valor de campo personalizado sem um nome"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Não posso ligar um tíquete a ele mesmo"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Não posso unir a um tíquete já unido. Você nunca deve obter este erro"
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Não especifique origem e destino simultaneamente"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Não posso criar o usuário: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "Cc"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Mudar a senha"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr "Assinale para remover"
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Assinalar para revogar o direito de acesso"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Filhos"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Cidade"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "Fechado"
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Requisições fechadas"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr "Código"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Comando não entendido!\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Comentário"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "Endereço de Comentário"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Comentário não registrado"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr "Comente sobre os tíquetes"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr "CommentOnTicket"
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Comentários"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Comentários (não enviados aos requisitantes)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Comentários (não enviados aos requisitantes)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Comentários sobre %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Comentários sobre este usuário"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Comentários adicionados"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Compilar restrições"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "Condição"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "Condição satisfeita..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "Condição não encontrada"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "Configuração"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Confirmar"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "Informação de contato"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Data de contato '%1' não pôde ser entendida"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "Conteúdo"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "Não pude criar o grupo"
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr "Correspondência"
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "Endereço de correspondência"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Correspondência adicionada"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Correspondência não registrada"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Não pude adicionar novo valor de campo personalizado para o tíquete. "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "Não pude adicionar novo valor de campo personalizado para o tíquete. %1"
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Não pude alterar o proprietário. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Não pude criar CampoPersonalizado"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Não pude criar o grupo"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Não pude criar o modelo: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Não pude criar o tíquete. Fila não selecionada"
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Não pude criar o usuário"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "Não pude criar um observador para o requisitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "Não pude encontrar um tíquete com identificador %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Não pude encontrar o grupo %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Não pude encontrar ou criar o usuário"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Não pude encontrar este principal"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Não pude encontrar o usuário %1."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Não pude carregar o grupo"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Não pude fazer este principal um %1 para esta fila"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Não pude fazer este principal um %1 para este tíquete"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Não pude remover este principal como um %1 para esta fila"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Não pude remover este principal como um %1 para este tíquete"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Não pude adicionar o membro no grupo"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Não pude criar uma transação: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Não sei o que fazer com a resposta do gpg\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Não encontrei o grupo\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr "Não pude encontrar o registro"
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Não encontrei este principal"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Não encontrei este valor"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "Não pude encontrar este observador"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Não pude encontrar o usuário\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Não pude carregar %1 do banco de dados de usuários.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "Não pude carregar os KeywordSelects."
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Não pude carregar o arquivo de configuração do RT '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "Não pude carregar os Scrips."
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Não pude carregar o grupo %1"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Não pude carregar a ligação"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Não pude carregar a fila"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Não pude carregar a fila %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Não pude carregar o scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Não pude carregar o modelo"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Não pude carregar este usuário (%1)"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Não pude carregar o tíquete '%1'"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "País"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Criar"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr "Criar Tíquetes"
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Criar um CampoPersonalizado"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Criar um Campo Personalizado para a fila %1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Criar um Campo Personalizado para todas as filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Criar um novo Campo Personalizado"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr "Criar um novo Scrip global"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "Criar um novo scrip global"
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Criar um novo grupo"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Criar um novo grupo pessoal"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Criar uma nova fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Criar um novo scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Criar um novo modelo"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Criar um novo tíquete"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Criar um novo usuário"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Criar uma fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Criar uma fila chamada"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Criar uma requisição"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Criar um scrip para a fila %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Criar um modelo"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "Criação falhou: %1 / %2 / %3 "
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "Criação falhou: %1/%2/%3"
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr "Criar novos tíquetes baseados no esquema deste scrip"
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Criar um tíquete"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr "Criar tíquetes nesta fila"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr "Criar, remover e modificar campos personalizados"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr "Criar, remover e modificar filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "Criar, remover e modificar os membros dos grupos pessoais de qualquer usuário"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr "Criar, remover e modificar os membros de grupos pessoais"
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr "Criar, remover e modificar usuários"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "CreateTicket"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Criado"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "CampoPersonalizado %1 criado"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Modelo %1 criado"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Relações atuais"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Scrips correntes"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Membros atuais"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Direitos de acesso atuais"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Critério de busca atual"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Observadores atuais"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Campo Personalizado #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Campos Personalizados"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "Código de finalização da ação customizada"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "Código de preparação da ação customizada"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "Condição customizada"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Campo personalizado %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "O campo personalizado %1 tem um valor."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "O campo personalizado %1 não tem valor."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Campo personalizado %1 não encontrado"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr "Campo personalizado removido"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Campo personalizado não encontrado"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "O valor de campo %1 não pôde ser encontrado para o campo personalizado %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "O valor do campo personalizado foi alterado de %1 para %2"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "O valor do campo personalizado não pôde ser removido"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "O valor de campo personalizado não pôde ser encontrado"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Valor do campo personalizado removido"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "Erro de dado"
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Datas"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Dez."
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "Dezembro"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "Esquema Padrão de Autoresposta"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Esquema padrão de Autoresposta"
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Esquema padrão de comentário administrativo"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr "Esquema padrão de correspondência administrativa"
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr "Esquema padrão de correspondência"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Esquema padrão de transação"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "Padrão: %1/%2 mudou de %3 para %4"
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Delegar direitos de acesso"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Delegar direitos específicos que foram outorgados a você."
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr "DelegateRights"
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Delegação"
+
+#: NOT FOUND IN SOURCE
+msgid "Delete"
+msgstr "Remover"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Remover tíquetes"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr "DeleteTicket"
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Ao remover este objeto você pode quebrar a integridade referencial"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Ao remover este objeto você quebra a integridade referencial"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Ao remover este objeto você viola a integridade referencial"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "Remover este objeto violaria a integridade referencial"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "Remover este objeto violaria a integridade referencial. Isto é mau."
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Negue"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "Dependem deste tíquete"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "Dependências: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "Depende de"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "DependsOn"
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Descendente"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Descreva o problema abaixo"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "Descrição"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "Detalhes"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Apresentação"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr "Mostrar Lista de Controle de Acesso"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr "Mostras os esquemas de Scrip para esta fila"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr "Mostrar os Scrips para esta fila"
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Modo de apresentação"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Apresentar o tíquete #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr "Fazer qualquer coisa"
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Não recarregar esta página."
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "Não mostrar resultados da busca"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Baixar"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Vencido"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "A data de vencimento '%1' não pôde ser entendida"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ERRO: Não pude carregar o tíquete '%1': %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Editar"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr "Editar Condições"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Editar Campos Personalizados para %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Editar Relacionamentos"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Editar Esquemas para a fila %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "Editar palavras chave"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "Editar scrips"
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Editar os modelos do sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Editar os modelos para %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Editando a configuração para a fila %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Editando a configuração para o usuário %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Editando o campo %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Editando os membros do grupo %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Editando os membros do grupo pessoal %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Editando o modelo %1"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Você deve especificar a origem ou o destinatário"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Email"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "O endereço de email já está em uso"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "Correio Eletrônico"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "Codificação de Email"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Habilitado (Deselecionando este ítem desabilita este campo personalizado)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Habilitado (Deselecionando este ítem desabilita este grupo)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Habilitado (desassinalando desabilita esta fila)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Campos Personalizados Habilitados"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Filas Habilitadas"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Estado %1 habilitado"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr "Entre com múltiplos valores"
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr "Entre com um valor"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Entre com identificadores de tíquete ou URIs que levam ao tíquete. Separe entradas múltiplas com espaços."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Erro"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "Erro ao adicionar um observador"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Erro nos parâmetros para Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Erro nos parâmetros para Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Erro nos parâmetros para Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Erro nos parâmetros para Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "Todos"
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Exemplo:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Informação adicional"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Não pude encontrar o pseudogrupo de usuários 'Privileged'."
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Não pude encontrar o pseudogrupo de usuários 'Unprivileged'"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "Falhou ao carregar o módulo %1. (%2)"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Fev."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "Fevereiro"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Fin"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Prioridade Final"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr "FinalPriority"
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Encontrar o grupo cujo"
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Encontrar tíquetes novos/abertos"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Encontrar pessoas que"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "Encontrar tíquetes"
+
+#: NOT FOUND IN SOURCE
+msgid "Finish Approval"
+msgstr "Terminar Aprovação"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Primeiro"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "Primeira página"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Force alteração"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "Encontrado %quant(%1,tíquete)"
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr "Objeto Encontrado"
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "FreeformContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr "FreeformMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr "FreeformSingle"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Sex."
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Cabeçalhos completos"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Obtendo o usuário corrente a partir de uma assinatura pgp\\n"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "Dado a %1"
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Global"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "Seleções de Palavras Chave Globais"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "Scrips Globais"
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "Esquema global: %1"
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Ir!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Assinatura pgp válida de %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Ir para a página"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Ir para o tíquete"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr ""
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Grupo"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Grupo %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Direitos de Acesso do Grupo"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "O grupo já tem um membro"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "O grupo não pôde ser criado."
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "O grupo não pôde ser criado: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Grupo criado"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr "O grupo não contém este membro"
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Grupo não encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Grupo não encontrado.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Grupo não especificado.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Grupos"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Grupos não podem ser membros de seus próprios membros"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "Olá!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Olá, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "Histórico"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "Telefone Residencial"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Homepage"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "Eu tenho %quant(%1,concrete mixer)."
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "Tenho [quant,_1,concrete mixer]."
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Identificador"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "Identidade"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "Se uma aprovação é rejeitada, rejeite a original e remova as aprovações pendentes"
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "Se esta ferramenta fosse setgid, um usuário local mal-intencionado poderia usá-la para obter acesso administrativo ao RT."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "Se você alterou qualquer coisa acima, não se esqueça de"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr "Valor ilegal para %1"
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr "Campo imutável"
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Incluir campoas personalizados desabilitados na listagem."
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Incluir filas desabilitadas na listagem."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Incluir usuários desabilitados na busca."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Prioridade Inicial"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr "InitialPriority"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Erro de entrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "Interesse notado"
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "Erro Interno"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "Erro Interno: %1"
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Tipo Inválido de Grupo"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr "Direito Inválido"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "Tipo Inválido"
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr "Dado inválido"
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Proprietário inválido. Usando 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "Fila inválida"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Direito de acesso inválido"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Valor inválido para %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Valor inválido para o campo personalizado"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Valor inválido para o estado"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "É extremamente importante que usuários não privilegiados não possam executar esta ferramenta."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "Sugere-se que você crie um usuário UNIX não privilegiado com o grupo e acesso RT corretos para executar esta ferramenta."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Requer vários argumentos:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Itens requerendo minha aprovação"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Jan."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "Janeiro"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr "Entre ou deixe este grupo"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Jul."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "Julho"
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Jumbo"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Jun."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "Junho"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Palavra chave"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Líng"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Último"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Último Contato"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Contactado em"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr "Notificado em"
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Atualizado em"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "LastUpdated"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "Resta"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Deixar este usuário acessar RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Deixar este usuário receber direitos de acesso adicionais"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Limitando proprietário a %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Limitando fila a %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "A ligação já existe"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "A ligação não pôde ser criada"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "Ligação criada (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Ligação removida (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "Ligação não encontrada"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "Ligar o tíquete #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "Ligar o tíquete %1"
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "Ligações"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "Localização"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "O diretório de log %1 não foi encontrado ou não pôde ser alterado.\\n RT não pode funcionar desta maneira."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "Assinado como %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Entrar"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Sair"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Definir como proprietário"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Definir o estado"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Definir o prazo final"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Definir a data de resolução"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Definir a data de iniciado"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Definir a data início"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Definir a data de última alteração"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Definir a prioridade"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Definir a fila"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Definir o assunto"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "Administrar grupos e seus membros"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "Administrar propriedades e configurações aplicáveis a todas as filas"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "Administrar filas e suas propriedades específicas"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "Administrar usuários e senhas"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Mar."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "Março"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "Maio"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Mai."
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Membro adicionado"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Membro removido"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Membro não removido"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Membro de"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "MemberOf"
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Membros"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "União bem sucedida"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "União falhou. Não pude definir o EffectiveId"
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Unir a"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "Mensagem"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr "Faltando uma chave primária?: %1"
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Móvel"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "Celular"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Modificar Lista de Controle de Acesso"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Modificar o campo personalizado %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Modificar Campos Personalizados que se aplicam a todas as filas"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Modificar esquemas de Scrip para esta fila"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Modificar Scrips para esta fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "Modificar ACLs do Sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "Modificar Esquema %1"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "Modificar um Campo Personalizado para a fila %1"
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr "Modificar um Campo Personalizado que se aplica a todas as filas"
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Modificar um scrip para a fila %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Modificar um scrip aplicável a todas as filas"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "Modificar datas para # %1"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Modificar as datas para #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Modificar as datas para o tíquete # %1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Modificar direitos de acesso globais de grupo"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Modificar direitos de acesso globais de grupo."
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "Modificar direitos globais para grupos"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "Modificar direitos globais para usuários"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "Modificar scrips globais"
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Modificar direitos de acesso globais de usuário"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Modificar direitos de acesso globais de usuário."
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Modificar metadados do grupo ou removê-lo"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Modificar os direitos de acesso do grupo %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Modificar os direitos de acesso de grupo para a fila %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr "Modificar lista de membros deste grupo"
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr "Modificar sua própria conta RT"
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Modificar as pessoas relacionadas à fila %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Modificar as pessoas relacionadas ao tíquete #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Modificar os scrips da fila %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Modificar scrips aplicáveis a todas as filas"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Modificar o modelo %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Modificar esquemas que se aplicam a todas as filas"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "Modificar o grupo %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Modificar os observadores da fila"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "Modificar o usuário %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Modificar o tíquete # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Modificar o tíquete #%1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Modificar tíquetes"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Modificar os direitos de acesso de usuário para o grupo %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Modificar os direitos de acesso de usuário para a fila %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Modificar os observadores para a fila '%1'"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr "ModifyACL"
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr "ModifyOwnMembership"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr "ModifyQueueWatchers"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr "ModifyScrips"
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr "ModifySelf"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr "ModifyTemplate"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr "ModifyTicket"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Seg."
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Mais sobre %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr "Descer"
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr "Subir"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "Múltiplo"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "O atributo 'Name' deve ser especificado"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Minhas Aprovações"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Minhas aprovações"
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "Nome"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Nome em uso"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "Precisa de aprovação do administrador do sistema"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "Nunca"
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Novo"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Nova Senha"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr "Nova Aprovação Pendente"
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Novos Relacionamentos"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Nova busca"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Novo campo personalizado"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "Novo grupo"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Nova senha"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Notificação de nova senha enviada"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "Nova fila"
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Nova requisição"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Novos direitos de acesso"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Novo scrip"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Nova busca"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr "Novo esquema"
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "O novo tíquete não existe"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Novo usuário"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Novo usuário chamado"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Novos observadores"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Abrir nova janela"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Próximo"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Próxima página"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "Apelido"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "Apelido"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Não há Campo Personalizado"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Não há Grupo definido"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Não há Fila definida"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Nenhum usuário RT foi encontrado. Favor consultar o administrador do RT.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Não há Modelo"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Não há Tíquete especificado. Abortando o tíquete "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Não há Tíquete especificado. Abortando modificações no tíquete\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "Não há ação"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr "Não há coluna especificada"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Comando não encontrado\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Não há comentário sobre este usuário"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "Não há nenhum arquivo anexado"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Não há descrição para %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Não há grupo especificado"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "Não há senha especificada"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "Não há permissão para criar filas"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "Sem permissão para criar tíquetes na fila '%1'"
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Sem permissão para criar usuários"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Sem permissão para mostrar o tíquete"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "sem permissão para ver modificar o tíquete"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Não há principal especificado"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Não há principal selecionado."
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Não há fila satisfazendo o critério de busca."
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "Nenhum direito encontrado"
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Nenhum direito outorgado."
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Não há busca a realizar"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Não há identificador de tíquete especificado"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Não há tipo de transação especificada"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "Não há usuário ou endereço de email especificado"
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Nenhum usuário satisfazendo o critério de busca foi encontrado."
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Nenhum usuário RT válido foi encontrado. O tratador de CVS do RT está desabilitado. Por favor, consulte o administrador do RT.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr "Nenhum valor enviado a _Set!\\n"
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr "Ninguém"
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr "Campo inexistente?"
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr "Não logado"
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Não entrou."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Não definido"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Ainda não implementado."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Ainda não implementado..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Notas"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "A notificação não pôde ser enviada"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr "Notificar AdminCcs"
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr "Notificar AdminCcs como Comentário"
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr "Notificar Outros Destinatários"
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr "Notificar Outros Destinatários como Comentário"
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr "Notificar Proprietário"
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr "Notificar Proprietário como Comentário"
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "Notificar Proprietários e AdminCcs sobre novos itens pendendo suas aprovações"
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr "Notificar Requisitantes"
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr "Notificar Requisitantes e Ccs"
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "Notificar Requisitantes e Ccs como Comentário"
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "Notificar Requisitantes, Ccs e AdminCcs"
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "Notificar Requisitantes, Ccs e AdminCcs como Comentário"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "Nov."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "Novembro"
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Objeto não pôde ser criado"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Objeto criado"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Out."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "Outubro"
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "Em"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Sobre Comentário"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "Sobre Correspondência"
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Sobre Criação"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Sobre Mudança de Propriedade"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Sobre Mudança de Fila"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "Sobre Resolução"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "Sobre Mudança de Estado"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Sobre Transação"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Só mostrar aprovações para requisições criadas depois de %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Só mostrar aprovações para requisições criadas antes de %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Aberto"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Abrir"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Requisições abertas"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Abrir tíquetes (da listagem) em uma nova janela"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Abrir tíquetes (da listagem) em outra janela"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr "Abrir tíquetes na correspondência"
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "Requisitando e ordenando"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "Organização"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "Tíquete originador: #%1"
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Após a data, a prioridade tende a"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr "Próprios tíquetes"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr "OwnTicket"
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Proprietário"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr "Proprietário mudou de %1 para %2"
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Proprietário alterado à força de %1 para %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "O proprietário é"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Pager"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Telefone do Pager"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr "Pai"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Pais"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Senha"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "Lembrete de Senha"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Senha muito curta"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Senha: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Pessoas"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr "Realizar uma ação definida pelo usuário"
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Permissão Negada"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Grupoas Pessoais"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Grupos pessoais"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Grupos pessoais:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Telefones"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr ""
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "Preferências"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "Prefs"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Anterior"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "Página anterior"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Pri"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "Principal %1 não encontrado."
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Prioridade"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "A prioridade inicia em"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Privilegiado"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "Estado privilegiado: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Usuários privilegiados"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "Falso-grupo para uso interno"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Fila"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Fila %1 não encontrada"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "A fila '%1' não foi encontrada\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "Seleções de Palavras-chave da Fila"
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Nome da Fila"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "Scrips da Fila"
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "A fila já existe"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "A fila não pôde ser criada"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "A fila não pôde ser carregada"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Fila criada"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr "A fila não foi especificada."
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Fila não encontrada"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Filas"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 para %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 por <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Direitos reservados 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Direitos reservados 1996-2002 Jesse Vincent <jesse\\\\@bestpractical.com>\\\\n"
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "Adiministração do RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "Erro de autenticação no RT."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "Ricocheteio do RT: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "Erro de configuração do RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "Erro crítico no RT. A mensagem não foi registrada!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "Erro no RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "O RT recebeu email (%1) dele próprio."
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "O RT recebeu email (%1) de si próprio."
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "Auto-serviço do RT / Tíquetes Fechados"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "RT por alto"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "O RT não pôde autenticá-lo"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "O RT não pôde encontrar o requisitante através de consulta ao banco de dados externo"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "O RT não pôde encontrar a fila: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "O RT não pôde validar esta assinatura PGP. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT para %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "RT para %1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "O RT processou seus comandos"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT tem &copy; Direitos Reservados 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. Ele é distribuído sob a <a href=\"http://www.gnu.org/copyleft/gpl.html\">Versão 2 da Licença Pública Geral GNU (GPL).</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT tem &copy; Direitos Reservados 1996-%1 por Jesse Vincent &lt;jesse@bestpractical.com&gt;. Ele é distribuído sob a <a href=\\\"http://www.gnu.org/copyleft/gpl.html\\\">Versão 2 da Licença Pública Geral GNU (GPL).</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "O RT crê que esta mensagem seja um ricochete"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "O RT vai processar esta mensagem como se não fosse assinada.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "O modo de comandos por email do RT requer autenticação PGP. Ou você não assinou sua mensagem ou sua assinatura não pôde ser verificada."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "Nome real"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "Nome real"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Referenciado por"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "Faz referência a"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "RefersTo"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Refinar"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Refinar a Busca"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "Recarregar esta página a cada %1 minutos."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "Relacionamentos"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Remover AdminCc"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Remover Cc"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Remover Requisitante"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Responder"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr "Responder aos tíquetes"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr "ReplyToTicket"
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "Requisitante"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Endereço eletrônico do requisitante"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "Requisitante(s)"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "RequestorAddresses"
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "Requisitantes"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "A requisições vencem em"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "Restaurar"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Residência"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Resolver"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "Resolver tíquete #%1 (%2)"
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Resolvido"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Resposta aos requisitantes"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Resultados"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Resultados por página"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Confirmar a Senha"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Direito de acesso %1 não encontrado para %2 %3 referente a %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Direito de Acesso Delegado"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Direito de Acesso Outorgado"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Direito de Acesso Carregado"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Direito de acesso não pôde ser revogado"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Direito de acesso não encontrado"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Direito de acesso não carregado."
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Direito de acesso revogado"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Direitos de Acesso"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "Direitos de acesso não puderam ser outorgados a %1"
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "Direitos de acesso não puderam ser revogados de %1"
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "Papéis"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "RootApproval"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Sáb."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Salvar as Alterações"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Salvar as alterações"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "Scrip #%1"
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Scrip Criado"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Scrip removido"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Scrips"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Scrips para %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Scrips aplicáveis a todas as filas"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "Buscar"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Critérios de Busca"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "Buscar por aprovações"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "Segurança:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr "SeeQueue"
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Selecionar um grupo"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Selecionar uma fila"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Selecionar um usuário"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr "Selecionar um campo personalizado"
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Selecionar um grupo"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr "Selecionar múltiplos valores"
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr "Selecionar um valor"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Selecionar uma fila"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Selecionar um scrip"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr "Selecionar um esquema"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Selecionar um usuário"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "SelectMultiple"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "SelectSingle"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "Auto-serviço"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Enviar mensagem a todos os observadores"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Enviar mensagem a todos os observadores como um \"comentário\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Enviar mensagem aos requisitantes e Ccs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "Enviar mensagem aos requisitantes e Ccs como um comentário"
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr "Envia uma mensagem aos requisitantes"
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "Envia uma mensagem aos Ccs e Bccs explicitamente listados"
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr "Envia uma mensagem aos Ccs administrativos"
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "Envia uma mensagem aos Ccs administrativos como um comentário"
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr "Envia uma mensagem ao proprietário"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Set."
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "Setembro"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "Mostrar os Resultados"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Mostrar requisições aprovadas"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Mostrar o sumário"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Mostrar requisições negadas"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Mostrar os detalhes"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Mostrar requisições pendentes"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Mostrar requisições aguardando outras aprovações"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Mostrar comentário privado do tíquete"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Mostrar sumários do tíquete"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr "ShowACL"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr "ShowScrips"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr "ShowTemplate"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr "ShowTicket"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr "ShowTicketComments"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "Cadastrar como um Requisitante de tíquete ou um Cc de tíquete ou fila"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "Cadastrar como um AdminCC de tíquete ou fila"
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "Assinatura"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr "Assinado como %1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Único"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr "Saltar Menu"
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Chave de ordenação"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Ordenar os resultados por"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "Ordenação"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Pendente"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "Página inicial"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "Iniciado"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "A data de iníciado '%1' não pôde ser compreendida"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "Inicia"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "Inicia Por"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "A data de início '%1' não pôde ser compreendida"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "Estado"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "Estado"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr "Mudança de Estado"
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Estado alterado de %1 para %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "StatusChange"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Roubar"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Roubado de %1 "
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Assunto"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Assunto modou para %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Enviar"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "Enviar Workflow"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr "Deu certo"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Dom."
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "SuperUser"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "Sistema"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Erro do Sistema"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "Erro de sistema. Direito não outorgado."
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "Erro de sistema. direito não outorgado"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Erro do sistema. Direito de acesso não delegado."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Erro do sistema. Direito de acesso não outorgado."
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "Erro de sistema. Não posso outorgar direitos de acesso."
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "Grupos do sistema"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "SystemRolegroup para uso interno"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "Tomar"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "Tomado"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Modelo"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Esquema #%1"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Esquema removido"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Modelo não encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Modelo não encontrado\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Modelo processado"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Modelos"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Modelos de %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Este já é o valor atual"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Este não é um valor para este campo personalizado"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Este é o mesmo valor"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Este principal já é um %1 para esta fila"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Este principal já é um %1 para este tíquete"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Este principal não é um %1 para esta fila"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Este principal não é um %1 para este tíquete"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Esta fila não existe"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Este tíquete tem dependências não resolvidas"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Este usuário já tem este direito de acesso"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Este usuário já possui este tíquete"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Este usuário não existe"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Este usuário já tem privilégios"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Este usuário já não tem privilégios"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Este usuário agora tem privilégios"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Este usuário agora não tem privilégios"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "Este usuário agora é não privilegiado"
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Este usuário não pode possuir tíquetes nesta fila"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Este não é um identificador numérico"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Sumário"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr "O CC de um tíquete"
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr "O CC administrativo de um tíquete"
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "O comentário foi registrado"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "O seguinte comando procurará por todos os tíquetes ativos na fila 'geral' e alterar sua prioridade para 99 se eles não tiverem sido alterados em 4 horas:"
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Os seguintes comandos não foram processados:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "O novo valor foi atribuído."
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "O proprietário de um tíquete"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "O requisitante de um tíquete"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Estes comandos geralmente não estão visíveis para o usuário"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Este tíquete %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Esta ferramenta permite o usuário invocar módulos Perl arbitrários de dentro do RT."
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Parece que esta transação não tem conteúdo"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "Os %1 tíquetes mais prioritários deste usuário"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "Os 25 tíquetes de mais alta prioridade deste usuário"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Qui."
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket"
+msgstr "Tíquete"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "Tíquete # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "Tíquete # %1 atualização jumbo: %2"
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Tíquete #%1 Atualização jumbo: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "Tíquete #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Tíquete %1 criado na fila '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Tíquete %1 carregado\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Tíquete %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "Histórico do Tíquete # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Identificador do tíquete"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr "Tíquete Resolvido"
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Arquivo anexo do tíquete"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "Conteúdo do tíquete"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Tipo do conteúdo do tíquete"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "O tíquete não pôde ser criado devido a um erro interno"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Tíquete criado"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "A criação do tíquete falhou"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Tíquete removido"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Id de tíquete não encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "Tíquete destruído"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Tíquete não encontrado"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "O estado do tíquete mudou"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Observadores do tíquete"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr "Tíquetes"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "Tíquetes %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "Tíquetes %1 por %2"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Tíquetes de %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "Tíquetes dependentes desta aprovação:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "Tempo Restante"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "Tempo Trabalhado"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "Tempo restante"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Tempo de apresentação"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "Tempo trabalhado"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "TimeLeft"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "TimeWorked"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Para gerar as diferenças desta transação"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Para gerar as diferenças desta transação:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Última atualização"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "Transação"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Transação %1 removida"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Transação Criada"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "Transaction->Create não pôde, já que você não especificou um id de tíquete"
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Transações são imutáveis"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "Tentando remover um direito de acesso: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Ter."
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Tipo"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Não implementado"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Usuário Unix"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Usuário Unix"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "Codificação de conteúdo desconhecida %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Ilimitado"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Não privilegiado"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Não tomado"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Atualizar"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Identificador de atualização"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Tipo de atualização"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Atualizar todos estes tíquetes de uma vez"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Atualizar email"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Atualizar nome"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Atualização não registrada."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Atualizar os tíquetes selecionados"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Atualizar assinatura"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Atualizar o tíquete"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Atualizar o tíquete # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Atualizar o tíquete #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Atualizar tíquete #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "O tipo da atualização não foi nem correspondência e nem comentário."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Atualizado"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Usuário %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Usuário %1 Senha: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Usuário '%1' não encontrado"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Usuário '%1' não encontrado\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr "Definido pelo Usuário"
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "Identificador de usuário"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Identificador do usuário"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Direitos de Acesso de Usuário"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "O usuário não pôde ser criado: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Usuário criado"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Grupos definidos pelo usuário"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Usuário notificado"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "Visualização de usuário"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Nome de usuário"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Usuários"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Usuários que satisfazem o critério de busca"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "Valor da fila"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "Valores"
+
+#: NOT FOUND IN SOURCE
+msgid "VrijevormEnkele"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr "Observar"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr "WatchAsAdminCc"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "Observador carregado"
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Observadores"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "Codificação de Web"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Qua."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "Quando um tíquete for aprovado por todos os aprovadores, adicione uma correspondência ao tíquete original"
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "Quando um tíquete for aprovado por qualquer aprovador, adicione uma correspondência ao tíquete original"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "Quando um tíquete é criado"
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "Quando um tíquete de aprovação é criado, notificar o Proprietário e o AdminCc do item aguardando sua aprovação"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "Quando acontecer qualquer coisa"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "Sempre que um tíquete for resolvido"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "Sempre que mudar o proprietário de um tíquete"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "Sempre que um tíquete mudar de fila"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "Sempre que o estado de um tíquete mudar"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "Sempre que ocorrer uma condição definida por usuário"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "Sempre que um novo comentário é adicionado"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "Sempre que uma nova correspondência é adicionada"
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Trabalho"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Telefone de trabalho"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "Trabalhado"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Você já é proprietário deste tíquete"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Você não é um usuário autorizado"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Você só pode reatribuir seus próprios tíquetes ou aqueles que não têm dono"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "Você não tem permissão para ver este tíquete.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "Você encontrou %1 tíquetes na fila %2"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Você foi desconectado do RT."
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "Você não tem permissão para criar tíquetes nesta fila."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "Você não pode criar requisições nesta fila."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Volte sempre"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Suas %1 requisições"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "Seu administrador do RT configurou erradamente os endereços eletrônicos que invocam o RT"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Sua requisição foi aprovada por %1. Outras aprovações ainda podem estar pendentes."
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Sua requisição foi aprovada."
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "Sua requisição foi rejeitada"
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Sua requisição foi rejeitada."
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Nome de usuário ou senha incorretos"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "CEP"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[sem assunto]"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "como outorgado a %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "contém"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "content"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "content-type"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "correspondência (provavelmente) não enviada"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "correspondência enviada"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "dias"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "morto"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "remover"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "removido"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "não satisfaz"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "não contém"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "igual a"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "falso"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "filename"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "maior que"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "grupo '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "horas"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "identificador"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "é"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "não é"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "menor que"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "satisfaz"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "min"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "minutos"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "modificações\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "meses"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "novo"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr "sem valor"
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "nenhum"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "diferente de"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "diferente"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "aberto"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "grupo pessoal '%1' para o usuário '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "fila %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "rejeitado"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "resolvido"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "seg"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "pendente"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "sistema %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "grupo do sistema '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "o componente chamador não especificou por que"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "tíquete #%1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "verdadeiro"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "grupo %1 não descrito"
+
+#: NOT FOUND IN SOURCE
+msgid "undescripbed group %1"
+msgstr "grupo sem descrição %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "usuário %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "semanas"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "com modelo %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "anos"
+
+#: NOT FOUND IN SOURCE
+msgid "ニックãƒãƒ¼ãƒ "
+msgstr ""
+
diff --git a/rt/lib/RT/I18N/ru.po b/rt/lib/RT/I18N/ru.po
new file mode 100644
index 0000000..d5ef7fd
--- /dev/null
+++ b/rt/lib/RT/I18N/ru.po
@@ -0,0 +1,4540 @@
+msgid ""
+msgstr ""
+"Last-Translator: Kirill Pushkin <kirill@mns.ru>\n"
+"PO-Revision-Date: 2002-10-04 19:28+0400\n"
+"Language-Team: Russian <ru@li.org>\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 0.9.6\n"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28
+msgid "#"
+msgstr "&#8470;"
+
+#: html/Admin/Queues/Scrip.html:55
+#. ($QueueObj->id)
+msgid "#%1"
+msgstr ""
+
+#: html/Approvals/Elements/ShowDependency:50 html/Ticket/Display.html:26 html/Ticket/Display.html:30
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr ""
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:771
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr ""
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%1 %2 %3 %4:%5:%6 %7"
+
+#: lib/RT/Ticket_Overlay.pm:3438 lib/RT/Transaction_Overlay.pm:559 lib/RT/Transaction_Overlay.pm:601
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 added"
+msgstr ""
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 назад"
+
+#: lib/RT/Ticket_Overlay.pm:3444 lib/RT/Transaction_Overlay.pm:566
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+msgid "%1 %2 changed to %3"
+msgstr "%1 %2 изменено на %3"
+
+#: lib/RT/Ticket_Overlay.pm:3441 lib/RT/Transaction_Overlay.pm:562 lib/RT/Transaction_Overlay.pm:607
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+msgid "%1 %2 deleted"
+msgstr ""
+
+#: html/Admin/Elements/EditScrips:44 html/Admin/Elements/ListGlobalScrips:28
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 Ñтот тикет\\n"
+
+#: html/Search/Listing.html:57
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr ""
+
+#: bin/rt-crontool:169 bin/rt-crontool:176 bin/rt-crontool:182
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr ""
+
+#: bin/rt-crontool:185
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr ""
+
+#: bin/rt-crontool:179
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:173
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr ""
+
+#: bin/rt-crontool:166
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "%1 Ñкрипт загружен"
+
+#: lib/RT/Ticket_Overlay.pm:3471
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "%1 добавлено как значение Ð´Ð»Ñ %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "%1 алиаÑÑ‹ требуют идентификатор тикета Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "%1 алиаÑÑ‹ требуют идентификатор тикета Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "%1 алиаÑÑ‹ требуют идентификатор тикета Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ над (от %2) %3"
+
+#: lib/RT/Link_Overlay.pm:117 lib/RT/Link_Overlay.pm:124
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr ""
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:483
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 пользователем %2"
+
+#: lib/RT/Transaction_Overlay.pm:537 lib/RT/Transaction_Overlay.pm:626 lib/RT/Transaction_Overlay.pm:635 lib/RT/Transaction_Overlay.pm:638
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 изменилоÑÑŒ Ñ %2 на %3"
+
+#: lib/RT/Interface/Web.pm:857
+msgid "%1 could not be set to %2."
+msgstr "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÑтановить %1 в %2."
+
+#: lib/RT/Ticket_Overlay.pm:2813
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 не могу закрыть тикет. Возможно, база данных RT иÑпорчена."
+
+#: html/Elements/MyTickets:25
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "%1 Ñамых приоритетных моих тикетов..."
+
+#: html/Elements/MyRequests:25
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "%1 Ñамых приоритетных тикетов, запрошенных мной..."
+
+#: bin/rt-crontool:161
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 больше не ÑвлÑетÑÑ %2 Ð´Ð»Ñ Ñтой очереди."
+
+#: lib/RT/Ticket_Overlay.pm:1570
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 больше не ÑвлÑетÑÑ %2 Ð´Ð»Ñ Ñтого тикета."
+
+#: lib/RT/Ticket_Overlay.pm:3527
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 больше не ÑвлÑетÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸ÐµÐ¼ Ð´Ð»Ñ Ð½ÐµÑтандартного Ð¿Ð¾Ð»Ñ %2"
+
+#: html/Ticket/Elements/ShowBasics:36
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 мин"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "%1 не отображаетÑÑ"
+
+#: html/User/Elements/DelegateRights:76
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1 права"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 уÑпешно произведено\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "%1 тип не извеÑтен Ð´Ð»Ñ $MessageId"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "%1 тип не извеÑтен Ð´Ð»Ñ %2"
+
+#: lib/RT/Action/ResolveMembers.pm:42
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 закроет вÑе тикеты, входÑщие в групповой запроÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "%1 отложит тикеты, которые завиÑÑÑ‚ запроÑа или включены в него"
+
+#: lib/RT/Transaction_Overlay.pm:435
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1: без вложений"
+
+#: html/Ticket/Elements/ShowTransaction:102
+#. ($size)
+msgid "%1b"
+msgstr ""
+
+#: html/Ticket/Elements/ShowTransaction:99
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:1140
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' ÑвлÑетÑÑ Ð½ÐµÐ²ÐµÑ€Ð½Ñ‹Ð¼ значением ÑтатуÑа"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "Что делать ? : '%1'"
+
+
+#: html/Admin/Elements/EditQueueWatchers:29 html/Admin/Elements/EditScrips:35 html/Admin/Elements/EditTemplates:36 html/Admin/Groups/Members.html:52 html/Ticket/Elements/EditLinks:33 html/Ticket/Elements/EditPeople:46 html/User/Groups/Members.html:55
+msgid "(Check box to delete)"
+msgstr "(Пометьте то, что хотите удалить)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(Введите номера или ÑÑылки на тикеты. ÐеÑколько тикетов разделÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ð±ÐµÐ»Ð°Ð¼Ð¸.)"
+
+#: html/Admin/Queues/Modify.html:54 html/Admin/Queues/Modify.html:60
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(ЕÑли пуÑтое, то по умолчанию равно %1"
+
+#: html/Admin/Elements/EditCustomFields:33 html/Admin/Elements/ListGlobalCustomFields:32
+msgid "(No custom fields)"
+msgstr "(Ðет дополнительных полей)"
+
+#: html/Admin/Groups/Members.html:50 html/User/Groups/Members.html:53
+msgid "(No members)"
+msgstr "(Ðет пользователей)"
+
+#: html/Admin/Elements/EditScrips:32 html/Admin/Elements/ListGlobalScrips:32
+msgid "(No scrips)"
+msgstr "(Ðет Ñкриптов)"
+
+#: html/Admin/Elements/EditTemplates:31
+msgid "(No templates)"
+msgstr "(Ðет шаблонов)"
+
+#: html/Ticket/Update.html:85
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. ÐдреÑа <b>не</b> ÑохранÑÑŽÑ‚ÑÑ Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. СпиÑок Ñтих адреÑатов в пиÑьме не виден. ÐдреÑа <b>не</b> ÑохранÑÑŽÑ‚ÑÑ Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: html/Ticket/Create.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. ÐдреÑа <b>ÑохранÑÑŽÑ‚ÑÑ</b> Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: html/Ticket/Update.html:81
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. ÐдреÑа <b>не</b> ÑохранÑÑŽÑ‚ÑÑ Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. ÐдреÑа не ÑохранÑÑŽÑ‚ÑÑ Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: html/Ticket/Create.html:69
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(Ðа Ñти адреÑа [разделенные запÑтой] отправлÑÑŽÑ‚ÑÑ ÐºÐ¾Ð¿Ð¸Ð¸ ÑообщениÑ. ÐдреÑа <b>ÑохранÑÑŽÑ‚ÑÑ</b> Ð´Ð»Ñ Ð¿Ð¾Ñледующих уведомлений.)"
+
+#: html/Admin/Groups/index.html:33 html/User/Groups/index.html:33
+msgid "(empty)"
+msgstr "(пуÑто)"
+
+#: html/Admin/Users/index.html:39
+msgid "(no name listed)"
+msgstr "(не указано имен)"
+
+#: html/Elements/MyRequests:43 html/Elements/MyTickets:45
+msgid "(no subject)"
+msgstr "(без темы)"
+
+#: html/Admin/Elements/SelectRights:48 html/Elements/SelectCustomFieldValue:30 html/Ticket/Elements/EditCustomField:59 html/Ticket/Elements/ShowCustomFields:36 lib/RT/Transaction_Overlay.pm:536
+msgid "(no value)"
+msgstr "(нет значениÑ)"
+
+#: html/Ticket/Elements/EditLinks:116
+msgid "(only one ticket)"
+msgstr "(только один тикет)"
+
+#: html/Elements/MyRequests:52 html/Elements/MyTickets:55
+msgid "(pending approval)"
+msgstr "(в ожидании визы)"
+
+#: html/Elements/MyRequests:54 html/Elements/MyTickets:57
+msgid "(pending other tickets)"
+msgstr "(в ожидании других тикетов)"
+
+#: html/Admin/Users/Modify.html:50
+msgid "(required)"
+msgstr "(требуетÑÑ)"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "(untitled)"
+msgstr "(без названиÑ)"
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I own..."
+msgstr "25 важнейших моих тикетов..."
+
+#: NOT FOUND IN SOURCE
+msgid "25 highest priority tickets I requested..."
+msgstr "25 Ñамых важных моих запроÑов..."
+
+#: html/Ticket/Elements/ShowBasics:32
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:27
+msgid "<% $_ %>"
+msgstr ""
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:26
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"Создать тикет в очереди\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "ПуÑтой шаблон"
+
+#: lib/RT/ACE_Overlay.pm:157 lib/RT/Principal_Overlay.pm:181
+msgid "ACE not found"
+msgstr "ACE не найден"
+
+#: lib/RT/ACE_Overlay.pm:831
+msgid "ACEs can only be created and deleted."
+msgstr "ACEÑ‹ можно только Ñоздавать и удалÑÑ‚ÑŒ"
+
+#: bin/rt-commit-handler:755
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "Прекращаем работу во избежание нежелательного Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ð¸ÐºÐµÑ‚Ð°.\\n"
+
+#: html/User/Elements/Tabs:32
+msgid "About me"
+msgstr "Обо мне"
+
+#: html/Admin/Users/Modify.html:80
+msgid "Access control"
+msgstr "Права доÑтупа"
+
+#: html/Admin/Elements/EditScrip:57
+msgid "Action"
+msgstr "ДейÑтвие"
+
+#: lib/RT/Scrip_Overlay.pm:147
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "дейÑтвие %1 не найдено"
+
+#: bin/rt-crontool:123
+msgid "Action committed."
+msgstr "ДейÑтвие принÑто."
+
+#: bin/rt-crontool:119
+msgid "Action prepared..."
+msgstr "ДейÑтвие подготовлено..."
+
+#: html/Search/Bulk.html:92
+msgid "Add AdminCc"
+msgstr "Добавить админиÑтративную копию"
+
+#: html/Search/Bulk.html:90
+msgid "Add Cc"
+msgstr "Добавить копию"
+
+#: html/Ticket/Create.html:114 html/Ticket/Update.html:100
+msgid "Add More Files"
+msgstr "Добавить еще файлы"
+
+#: html/Search/Bulk.html:88
+msgid "Add Requestor"
+msgstr "Добавить проÑителÑ"
+
+
+#: html/Admin/Global/Scrip.html:55
+msgid "Add a scrip which will apply to all queues"
+msgstr "Добавить Ñкрипт, который будет дейÑтвовать на вÑе очереди"
+
+#: html/Search/Bulk.html:118
+msgid "Add comments or replies to selected tickets"
+msgstr "Добавить комментарии или ответы на выбранные тикеты"
+
+#: html/Admin/Groups/Members.html:42 html/User/Groups/Members.html:39
+msgid "Add members"
+msgstr "Добавить пользователей"
+
+#: html/Admin/Queues/People.html:66 html/Ticket/Elements/AddWatchers:28
+msgid "Add new watchers"
+msgstr "Добавить наблюдателей"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "Добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 Ð´Ð»Ñ Ñтой очереди"
+
+#: lib/RT/Ticket_Overlay.pm:1454
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "Добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 Ð´Ð»Ñ Ñтого тикета"
+
+#: html/Admin/Elements/ModifyUser:76 html/Admin/Users/Modify.html:122 html/User/Prefs.html:88
+msgid "Address1"
+msgstr "ÐдреÑ1"
+
+#: html/Admin/Elements/ModifyUser:78 html/Admin/Users/Modify.html:127 html/User/Prefs.html:90
+msgid "Address2"
+msgstr "ÐдреÑ2"
+
+#: html/Ticket/Create.html:74
+msgid "Admin Cc"
+msgstr "ÐдминиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ"
+
+#: etc/initialdata:274
+msgid "Admin Comment"
+msgstr "Комментарий админа"
+
+#: etc/initialdata:256
+msgid "Admin Correspondence"
+msgstr ""
+
+#: html/Admin/Queues/index.html:25 html/Admin/Queues/index.html:28
+msgid "Admin queues"
+msgstr "Управление очередÑми"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "Управление пользователÑми"
+
+#: html/Admin/Global/index.html:26 html/Admin/Global/index.html:28
+msgid "Admin/Global configuration"
+msgstr "Общие наÑтройки"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "Группы"
+
+#: html/Admin/Queues/Modify.html:25 html/Admin/Queues/Modify.html:29
+msgid "Admin/Queue/Basics"
+msgstr "Параметры очереди"
+
+#: etc/initialdata:56 html/Ticket/Elements/ShowPeople:39 html/Ticket/Update.html:50 lib/RT/ACE_Overlay.pm:89
+msgid "AdminCc"
+msgstr "ÐдминиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "AdminCustomFields"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "AdminGroup"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "AdminGroupMembership"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "AdminOwnPersonalGroups"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "AdminQueue"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "AdminUsers"
+msgstr ""
+
+#: html/Admin/Queues/People.html:48 html/Ticket/Elements/EditPeople:54
+msgid "Administrative Cc"
+msgstr "ÐдминиÑÑ‚Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ"
+
+#: html/Elements/SelectDateRelation:36
+msgid "After"
+msgstr "ПоÑле"
+
+#: html/Admin/Elements/EditCustomFields:96
+msgid "All Custom Fields"
+msgstr "Ð’Ñе дополнительные полÑ"
+
+#: html/Admin/Queues/index.html:53
+msgid "All Queues"
+msgstr "Ð’Ñе очереди"
+
+#: html/Elements/Tabs:58
+msgid "Approval"
+msgstr "Виза"
+
+#: html/Approvals/Display.html:46 html/Approvals/Elements/Approve:27 html/Approvals/Elements/ShowDependency:42 html/Approvals/index.html:65
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "Виза #%1: %2"
+
+#: html/Approvals/index.html:54
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "Виза #%1: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð½Ðµ Ñохранены из-за ошибки ÑиÑтемы"
+
+#: html/Approvals/index.html:52
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "Виза #%1: ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñаны"
+
+#: html/Approvals/Elements/Approve:45
+msgid "Approve"
+msgstr "Завизировать"
+
+#: etc/initialdata:431 etc/upgrade/2.1.71:148
+msgid "Approver's notes: %1"
+msgstr ""
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "Ðпр."
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Ascending"
+msgstr "Ð’ порÑдке возраÑтаниÑ"
+
+#: html/Search/Bulk.html:127 html/SelfService/Update.html:36 html/Ticket/ModifyAll.html:83 html/Ticket/Update.html:100
+msgid "Attach"
+msgstr "Вложение"
+
+#: html/SelfService/Create.html:67 html/Ticket/Create.html:110
+msgid "Attach file"
+msgstr "Вложить файл"
+
+#: html/Ticket/Create.html:98 html/Ticket/Update.html:89
+msgid "Attached file"
+msgstr "Вложенный файл"
+
+#: html/SelfService/Attachment/dhandler:36
+msgid "Attachment '%1' could not be loaded"
+msgstr "Вложение '%1' не может быть загружено"
+
+#: lib/RT/Transaction_Overlay.pm:443
+msgid "Attachment created"
+msgstr "Создано вложение"
+
+#: lib/RT/Tickets_Overlay.pm:1189
+msgid "Attachment filename"
+msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°"
+
+#: html/Ticket/Elements/ShowAttachments:26
+msgid "Attachments"
+msgstr "ВложениÑ"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "Ðвг."
+
+#: html/Admin/Elements/ModifyUser:66
+msgid "AuthSystem"
+msgstr "Тип региÑтрации"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "Ðвтоответ"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "Ðвтоответ инициатору запроÑа"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ PGP: %1\\n"
+
+#: html/SelfService/Attachment/dhandler:40
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "Ðеверный идентификатор вложениÑ. ОтÑутÑтвует вложение '%1'\\n"
+
+#: bin/rt-commit-handler:827
+#. ($val)
+msgid "Bad data in %1"
+msgstr "ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° в %1"
+
+#: html/SelfService/Attachment/dhandler:43
+#. ($trans, $AttachmentObj->TransactionId())
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "Ðеправильный номер транзакции Ð´Ð»Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ. %1 должен быть %2\\n"
+
+#: html/Admin/Elements/GroupTabs:39 html/Admin/Elements/QueueTabs:39 html/Admin/Elements/UserTabs:38 html/Ticket/Elements/Tabs:90 html/User/Elements/GroupTabs:38
+msgid "Basics"
+msgstr "Главное"
+
+#: html/Ticket/Update.html:83
+msgid "Bcc"
+msgstr "Ð¡ÐºÑ€Ñ‹Ñ‚Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ"
+
+#: html/Admin/Elements/EditScrip:88 html/Admin/Global/GroupRights.html:85 html/Admin/Global/Template.html:46 html/Admin/Global/UserRights.html:54 html/Admin/Groups/GroupRights.html:73 html/Admin/Groups/Members.html:81 html/Admin/Groups/Modify.html:56 html/Admin/Groups/UserRights.html:55 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:45 html/Admin/Queues/UserRights.html:54 html/User/Groups/Modify.html:56
+msgid "Be sure to save your changes"
+msgstr "Ðе забудьте Ñохранить наÑтройки"
+
+#: html/Elements/SelectDateRelation:34 lib/RT/CurrentUser.pm:322
+msgid "Before"
+msgstr "До"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin Approval"
+msgstr ""
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr ""
+
+#: html/Search/Listing.html:79
+msgid "Bookmarkable URL for this search"
+msgstr "Получить URL Ð´Ð»Ñ Ñтого поиÑка"
+
+#: html/Ticket/Elements/ShowHistory:39 html/Ticket/Elements/ShowHistory:45
+msgid "Brief headers"
+msgstr "Сокращенный"
+
+#: html/Search/Bulk.html:25 html/Search/Bulk.html:26
+msgid "Bulk ticket update"
+msgstr "Изменение одним махом"
+
+#: lib/RT/User_Overlay.pm:1331
+msgid "Can not modify system users"
+msgstr "Ðе могу изменÑÑ‚ÑŒ ÑиÑтемных пользователей"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Can this principal see this queue"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:144
+msgid "Can't add a custom field value without a name"
+msgstr "Ðе могу добавить значение Ð¿Ð¾Ð»Ñ Ð±ÐµÐ· имени"
+
+#: lib/RT/Link_Overlay.pm:132
+msgid "Can't link a ticket to itself"
+msgstr "Тикет не может быть ÑвÑзан Ñ Ñамим Ñобой"
+
+#: lib/RT/Ticket_Overlay.pm:2787
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "Ðе могу Ñоединить Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð½Ñ‹Ð¼ тикетом (Ñта ошибка никогда не должна проиÑходить)."
+
+#: lib/RT/Ticket_Overlay.pm:2605 lib/RT/Ticket_Overlay.pm:2674
+msgid "Can't specifiy both base and target"
+msgstr "Ðе могу указать одновременно и иÑточник, и Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ"
+
+#: html/autohandler:112
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "Ðе могу Ñоздать пользователÑ: %1"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:44 html/SelfService/Create.html:51 html/SelfService/Display.html:50 html/Ticket/Create.html:64 html/Ticket/Elements/EditPeople:51 html/Ticket/Elements/ShowPeople:35 html/Ticket/Update.html:45 html/Ticket/Update.html:78 lib/RT/ACE_Overlay.pm:88
+msgid "Cc"
+msgstr "КопиÑ"
+
+#: html/SelfService/Prefs.html:31
+msgid "Change password"
+msgstr "Сменить пароль"
+
+#: html/Ticket/Create.html:101 html/Ticket/Update.html:92
+msgid "Check box to delete"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:31
+msgid "Check box to revoke right"
+msgstr "Выберите права, которые хотите отозвать"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/EditLinks:131 html/Ticket/Elements/EditLinks:69 html/Ticket/Elements/ShowLinks:51
+msgid "Children"
+msgstr "Потомки"
+
+#: html/Admin/Elements/ModifyUser:80 html/Admin/Users/Modify.html:132 html/User/Prefs.html:92
+msgid "City"
+msgstr "Город"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr ""
+
+#: html/SelfService/Elements/Tabs:60
+msgid "Closed requests"
+msgstr "Закрытые запроÑÑ‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Code"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "Чего-чего?\\n"
+
+#: html/Ticket/Elements/ShowTransaction:179 html/Ticket/Elements/Tabs:153
+msgid "Comment"
+msgstr "Комментировать"
+
+#: html/Admin/Elements/ModifyQueue:45 html/Admin/Queues/Modify.html:58
+msgid "Comment Address"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸ÐµÐ²"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "Комментарий не запиÑан"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Comment on tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "CommentOnTicket"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:35
+msgid "Comments"
+msgstr "Комментарии"
+
+#: html/Ticket/ModifyAll.html:70 html/Ticket/Update.html:70
+msgid "Comments (Not sent to requestors)"
+msgstr "Комментарии (Ðе отправлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾ÑителÑм)"
+
+#: html/Search/Bulk.html:122
+msgid "Comments (not sent to requestors)"
+msgstr "Комментарии (не отправлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ñителю)"
+
+#: html/Elements/ViewUser:27
+#. ($name)
+msgid "Comments about %1"
+msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ %1"
+
+#: html/Admin/Users/Modify.html:185 html/Ticket/Elements/ShowRequestor:44
+msgid "Comments about this user"
+msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± Ñтом пользователе"
+
+#: lib/RT/Transaction_Overlay.pm:545
+msgid "Comments added"
+msgstr "Добавлены комментарии"
+
+#: lib/RT/Action/Generic.pm:140
+msgid "Commit Stubbed"
+msgstr "ДейÑтвие не реализовано"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "Применить ограничениÑ"
+
+#: html/Admin/Elements/EditScrip:41
+msgid "Condition"
+msgstr "УÑловие"
+
+#: bin/rt-crontool:109
+msgid "Condition matches..."
+msgstr "ПодходÑщее уÑловие..."
+
+#: lib/RT/Scrip_Overlay.pm:160
+msgid "Condition not found"
+msgstr "УÑловие не найдено"
+
+#: html/Elements/Tabs:52
+msgid "Configuration"
+msgstr "ÐаÑтройка"
+
+#: html/SelfService/Prefs.html:33
+msgid "Confirm"
+msgstr "Подтвердить"
+
+#: html/Admin/Elements/ModifyUser:60
+msgid "ContactInfoSystem"
+msgstr "ÐšÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "Ðе могу разобрать дату поÑледнего контакта '%1'"
+
+#: html/Admin/Elements/ModifyTemplate:44 html/Ticket/ModifyAll.html:87
+msgid "Content"
+msgstr "ТекÑÑ‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr ""
+
+#: etc/initialdata:266
+msgid "Correspondence"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:39 html/Admin/Queues/Modify.html:51
+msgid "Correspondence Address"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ñообщений"
+
+#: lib/RT/Transaction_Overlay.pm:541
+msgid "Correspondence added"
+msgstr "Добавлено Ñообщение"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "Сообщение не запиÑано"
+
+#: lib/RT/Ticket_Overlay.pm:3458
+msgid "Could not add new custom field value for ticket. "
+msgstr "Ðе могу добавить новое поле Ñ Ñ‚Ð°ÐºÐ¸Ð¼ значением."
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2963 lib/RT/Ticket_Overlay.pm:2971 lib/RT/Ticket_Overlay.pm:2987
+msgid "Could not change owner. "
+msgstr "Ðе могу Ñменить владельца. "
+
+#: html/Admin/Elements/EditCustomField:68 html/Admin/Elements/EditCustomFields:166
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "Ðе могу добавить поле"
+
+#: html/User/Groups/Modify.html:77 lib/RT/Group_Overlay.pm:474 lib/RT/Group_Overlay.pm:481
+msgid "Could not create group"
+msgstr "Ðе могу Ñоздать группу"
+
+#: html/Admin/Global/Template.html:75 html/Admin/Queues/Template.html:72
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "Ðе могу Ñоздать шаблон: %1"
+
+#: lib/RT/Ticket_Overlay.pm:1073 lib/RT/Ticket_Overlay.pm:333
+msgid "Could not create ticket. Queue not set"
+msgstr "Ðе могу Ñоздать тикет. Очередь не определена."
+
+#: lib/RT/User_Overlay.pm:208 lib/RT/User_Overlay.pm:220 lib/RT/User_Overlay.pm:238 lib/RT/User_Overlay.pm:414
+msgid "Could not create user"
+msgstr "Ðе могу Ñоздать пользователÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "Ðе могу Ñоздать Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ñ‚Ð¾Ñ€Ð° запроÑа"
+
+#: NOT FOUND IN SOURCÐе могу Ñоздать Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ñ‚Ð¾Ñ€Ð° запроÑаE
+msgid "Could not find a ticket with id %1"
+msgstr "Ðе могу найти тикет по идентификатору %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "Ðе найдена группа %1."
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1422
+msgid "Could not find or create that user"
+msgstr "Ðе могу найти или Ñоздать Ñтого пользователÑ"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1501
+msgid "Could not find that principal"
+msgstr "Ðе могу найти Ñтого пользователÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "Ðе найден пользователь %1."
+
+#: html/Admin/Groups/Members.html:88 html/User/Groups/Members.html:90 html/User/Groups/Modify.html:82
+msgid "Could not load group"
+msgstr "Ðе могу загрузить группу"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "Ðе могу назначить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1 Ð´Ð»Ñ Ñтой очереди"
+
+#: lib/RT/Ticket_Overlay.pm:1443
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "Ðе могу назначить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1 Ð´Ð»Ñ Ñтого тикета"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "Ðе могу отобрать функции у Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 в Ñтой очереди"
+
+#: lib/RT/Ticket_Overlay.pm:1559
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "Ðе могу отобрать функции у Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÐºÐ°Ðº %1 Ð´Ð»Ñ Ñтого тикета"
+
+#: lib/RT/Group_Overlay.pm:985
+msgid "Couldn't add member to group"
+msgstr "Ðе могу добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² группу"
+
+#: lib/RT/Ticket_Overlay.pm:3468 lib/RT/Ticket_Overlay.pm:3524
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "Ðе могу Ñоздать транзакцию: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "Ðе пойму что делать из ответа gpg\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "Ðе найти группу\\n"
+
+#: lib/RT/Interface/Web.pm:866
+msgid "Couldn't find row"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:959
+msgid "Couldn't find that principal"
+msgstr "Ðе найти Ñтого пользователÑ"
+
+#: lib/RT/CustomField_Overlay.pm:175
+msgid "Couldn't find that value"
+msgstr "Ðе найти Ñтого значениÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "Ðе могу найти данного наблюдателÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "Пользователь не найден\\n"
+
+#: lib/RT/CurrentUser.pm:112
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "Ðе загрузить %1 из базы пользователей.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "Ðе загрузить файл наÑтроек RT '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr ""
+
+#: html/Admin/Groups/GroupRights.html:88 html/Admin/Groups/UserRights.html:75
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "Ðе загрузить группу %1"
+
+#: lib/RT/Link_Overlay.pm:175 lib/RT/Link_Overlay.pm:184 lib/RT/Link_Overlay.pm:211
+msgid "Couldn't load link"
+msgstr "Ðе загрузить ÑÑылку"
+
+#: html/Admin/Elements/EditCustomFields:147 html/Admin/Queues/People.html:121
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "Ðе загрузить очередь"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:72
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "Ðе загрузить очередь %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "Ðе загрузить Ñкрипт"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "Ðе загрузить шаблон"
+
+#: html/Admin/Users/Prefs.html:79
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "Ðе загрузить Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ (%1)"
+
+#: html/SelfService/Display.html:166
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "Ðе загрузить тикет '%1'"
+
+#: html/Admin/Elements/ModifyUser:86 html/Admin/Users/Modify.html:149 html/User/Prefs.html:98
+msgid "Country"
+msgstr "Страна"
+
+#: html/Admin/Elements/CreateUserCalled:26 html/Ticket/Create.html:135 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "Создать"
+
+#: etc/initialdata:128
+msgid "Create Tickets"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomField:58
+msgid "Create a CustomField"
+msgstr "Добавить поле"
+
+#: html/Admin/Queues/CustomField.html:48
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "Создать дополнительное поле Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ 1"
+
+#: html/Admin/Global/CustomField.html:48
+msgid "Create a CustomField which applies to all queues"
+msgstr "Создать дополнительное поле Ð´Ð»Ñ Ð²Ñех очередей"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "Добавить новое поле"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr ""
+
+#: html/Admin/Groups/Modify.html:67 html/Admin/Groups/Modify.html:93
+msgid "Create a new group"
+msgstr "Добавить новую группу"
+
+#: html/User/Groups/Modify.html:67 html/User/Groups/Modify.html:92
+msgid "Create a new personal group"
+msgstr "Добавить новую личную группу"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "Добавить новую очередь"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "Добавить новый Ñкрипт"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "Добавить новый шаблон"
+
+#: html/SelfService/Create.html:30 html/Ticket/Create.html:25 html/Ticket/Create.html:28 html/Ticket/Create.html:36
+msgid "Create a new ticket"
+msgstr "Добавить новый тикет"
+
+#: html/Admin/Users/Modify.html:214 html/Admin/Users/Modify.html:241
+msgid "Create a new user"
+msgstr "Добавить нового пользователÑ"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "Создать очередь"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "Создать очередь Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼"
+
+#: html/SelfService/Create.html:25 html/SelfService/Create.html:27
+msgid "Create a request"
+msgstr "Создать запроÑ"
+
+#: html/Admin/Queues/Scrip.html:59
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "Создать Ñкрипт Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: html/Admin/Global/Template.html:69 html/Admin/Queues/Template.html:65
+msgid "Create a template"
+msgstr "Создать запроÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr ""
+
+#: etc/initialdata:130
+msgid "Create new tickets based on this scrip's template"
+msgstr ""
+
+#: html/SelfService/Create.html:81
+msgid "Create ticket"
+msgstr "Создать тикет"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Create tickets in this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Create, delete and modify custom fields"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Create, delete and modify queues"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify the members of personal groups"
+msgstr ""
+
+#: lib/RT/System.pm:60
+msgid "Create, delete and modify users"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "CreateTicket"
+msgstr "Создать тикет"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1167
+msgid "Created"
+msgstr "Создан"
+
+#: html/Admin/Elements/EditCustomField:71
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "Добавлено поле %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "Создан шаблон %1"
+
+#: html/Ticket/Elements/EditLinks:28
+msgid "Current Relationships"
+msgstr "Текущие ÑвÑзи"
+
+#: html/Admin/Elements/EditScrips:30
+msgid "Current Scrips"
+msgstr "Текущие Ñкрипты"
+
+#: html/Admin/Groups/Members.html:39 html/User/Groups/Members.html:42
+msgid "Current members"
+msgstr "Текущие пользователи"
+
+#: html/Admin/Elements/SelectRights:29
+msgid "Current rights"
+msgstr "Текущие права"
+
+#: html/Search/Listing.html:71
+msgid "Current search criteria"
+msgstr "Текущие критерии поиÑка"
+
+#: html/Admin/Queues/People.html:41 html/Ticket/Elements/EditPeople:45
+msgid "Current watchers"
+msgstr "Текущие наблюдатели"
+
+#: html/Admin/Global/CustomField.html:55
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "Дополнительное поле #%1"
+
+#: html/Admin/Elements/QueueTabs:53 html/Admin/Elements/SystemTabs:40 html/Admin/Global/index.html:50 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "Дополнительные полÑ"
+
+#: html/Admin/Elements/EditScrip:73
+msgid "Custom action cleanup code"
+msgstr "ПользовательÑкий код очиÑтки"
+
+#: html/Admin/Elements/EditScrip:65
+msgid "Custom action preparation code"
+msgstr "ПользовательÑкий подготовительный код"
+
+#: html/Admin/Elements/EditScrip:49
+msgid "Custom condition"
+msgstr "ПользовательÑкое уÑловие"
+
+#: lib/RT/Tickets_Overlay.pm:1618
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "Дополнительное поле %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1613
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "Дополнительное поле %1 имеет значение."
+
+#: lib/RT/Tickets_Overlay.pm:1610
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "Дополнительное поле %1 не имеет значениÑ."
+
+#: lib/RT/Ticket_Overlay.pm:3360
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "Дополнительное поле %1 не найдено"
+
+#: html/Admin/Elements/EditCustomFields:197
+msgid "Custom field deleted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3510
+msgid "Custom field not found"
+msgstr "Дополнительное поле не найдено"
+
+#: lib/RT/CustomField_Overlay.pm:283
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "Значение %1 не может быть найдено Ð´Ð»Ñ Ð¿Ð¾Ð»Ñ %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "Значение Ð¿Ð¾Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¾ Ñ %1 на %2"
+
+#: lib/RT/CustomField_Overlay.pm:185
+msgid "Custom field value could not be deleted"
+msgstr "Значение дополнительного Ð¿Ð¾Ð»Ñ Ð½Ðµ может быть удалено"
+
+#: lib/RT/CustomField_Overlay.pm:289
+msgid "Custom field value could not be found"
+msgstr "Значение дополнительного Ð¿Ð¾Ð»Ñ Ð½Ðµ найдено"
+
+#: lib/RT/CustomField_Overlay.pm:183 lib/RT/CustomField_Overlay.pm:291
+msgid "Custom field value deleted"
+msgstr "Значение дополнительного Ð¿Ð¾Ð»Ñ Ð±Ñ‹Ð»Ð¾ удалено"
+
+#: lib/RT/Transaction_Overlay.pm:550
+msgid "CustomField"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr ""
+
+#: html/Ticket/Create.html:161 html/Ticket/Elements/ShowSummary:53 html/Ticket/Elements/Tabs:93 html/Ticket/ModifyAll.html:44
+msgid "Dates"
+msgstr "Даты"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "Дек."
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "Шаблон автоответа по умолчанию"
+
+#: etc/initialdata:275
+msgid "Default admin comment template"
+msgstr "Шаблон ответа админа по умолчанию"
+
+#: etc/initialdata:257
+msgid "Default admin correspondence template"
+msgstr ""
+
+#: etc/initialdata:267
+msgid "Default correspondence template"
+msgstr ""
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "Шаблон транзакции по умолчанию"
+
+#: lib/RT/Transaction_Overlay.pm:645
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr ""
+
+#: html/User/Delegation.html:25 html/User/Delegation.html:28
+msgid "Delegate rights"
+msgstr "Передача прав"
+
+#: lib/RT/System.pm:63
+msgid "Delegate specific rights which have been granted to you."
+msgstr "Делегирование отдельных прав, которые вам даны."
+
+#: lib/RT/System.pm:63
+msgid "DelegateRights"
+msgstr ""
+
+#: html/User/Elements/Tabs:38
+msgid "Delegation"
+msgstr "Делегирование прав"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "Delete tickets"
+msgstr "Удаление тикетов"
+
+#: lib/RT/Queue_Overlay.pm:90
+msgid "DeleteTicket"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:187
+msgid "Deleting this object could break referential integrity"
+msgstr "Удаление Ñтого объекта может нарушить ÑÑылочную целоÑтноÑÑ‚ÑŒ"
+
+#: lib/RT/Queue_Overlay.pm:292
+msgid "Deleting this object would break referential integrity"
+msgstr "Удаление Ñтого объекта нарушит ÑÑылочную целоÑтноÑÑ‚ÑŒ"
+
+#: lib/RT/User_Overlay.pm:430
+msgid "Deleting this object would violate referential integrity"
+msgstr "Удаление Ñтого объекта нарушит ÑÑылочную целоÑтноÑÑ‚ÑŒ"
+
+#: html/Approvals/Elements/Approve:46
+msgid "Deny"
+msgstr "Отказать"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/EditLinks:123 html/Ticket/Elements/EditLinks:47 html/Ticket/Elements/ShowDependencies:32 html/Ticket/Elements/ShowLinks:35
+msgid "Depended on by"
+msgstr "От него завиÑÑÑ‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "ЗавиÑимоÑти: \\n"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:180 html/Ticket/Elements/EditLinks:119 html/Ticket/Elements/EditLinks:36 html/Ticket/Elements/ShowDependencies:25 html/Ticket/Elements/ShowLinks:27
+msgid "Depends on"
+msgstr "ЗавиÑит от"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr ""
+
+#: html/Elements/SelectSortOrder:35
+msgid "Descending"
+msgstr "Ð’ порÑдке убываниÑ"
+
+#: html/SelfService/Create.html:75 html/Ticket/Create.html:119
+msgid "Describe the issue below"
+msgstr "Опишите проблему"
+
+#: html/Admin/Elements/AddCustomFieldValue:27 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/EditScrip:34 html/Admin/Elements/ModifyQueue:36 html/Admin/Elements/ModifyTemplate:36 html/Admin/Groups/Modify.html:49 html/Admin/Queues/Modify.html:48 html/Elements/SelectGroups:27 html/User/Groups/Modify.html:49
+msgid "Description"
+msgstr "ОпиÑание"
+
+#: html/SelfService/Elements/MyRequests:44
+msgid "Details"
+msgstr "ПодробноÑти"
+
+#: html/Ticket/Elements/Tabs:85
+msgid "Display"
+msgstr "Показать"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Display Access Control List"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Display Scrip templates for this queue"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Display Scrips for this queue"
+msgstr ""
+
+#: html/Ticket/Elements/ShowHistory:35
+msgid "Display mode"
+msgstr "Режим показа"
+
+#: html/SelfService/Display.html:25 html/SelfService/Display.html:29
+#. ($Ticket->id)
+msgid "Display ticket #%1"
+msgstr "Показать тикет #%1"
+
+#: lib/RT/System.pm:54
+msgid "Do anything and everything"
+msgstr ""
+
+#: html/Elements/Refresh:30
+msgid "Don't refresh this page."
+msgstr "Ðе обновлÑÑ‚ÑŒ Ñту Ñтраницу"
+
+#: html/Search/Elements/PickRestriction:114
+msgid "Don't show search results"
+msgstr "Ðе показывать результаты поиÑка"
+
+#: html/Ticket/Elements/ShowTransaction:105
+msgid "Download"
+msgstr "Скачать"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Create.html:167 html/Ticket/Elements/EditDates:45 html/Ticket/Elements/ShowDates:43 lib/RT/Ticket_Overlay.pm:1171
+msgid "Due"
+msgstr "Дан Ñрок"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "Ðе могу прочеÑÑ‚ÑŒ Ñрок Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹ '%1'"
+
+#: bin/rt-commit-handler:754
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "ОШИБКÐ: Ðе могу загрузить тикет '%1': %2.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit"
+msgstr "Изменить"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Conditions"
+msgstr ""
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "Изменение дополнительных полей Ð´Ð»Ñ %1"
+
+#: html/Ticket/ModifyLinks.html:36
+msgid "Edit Relationships"
+msgstr "Изменение ÑвÑзей"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "Редактировать шаблоны Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr ""
+
+#: html/Admin/Global/index.html:46
+msgid "Edit system templates"
+msgstr "Изменение ÑиÑтемных шаблонов"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "Изменение шаблонов Ð´Ð»Ñ %1"
+
+#: html/Admin/Elements/ModifyQueue:25 html/Admin/Queues/Modify.html:117
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "Изменение наÑтроек очереди %1"
+
+#: html/Admin/Elements/ModifyUser:25
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "Изменение наÑтроек Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1"
+
+#: html/Admin/Elements/EditCustomField:74
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "Изменение Ð¿Ð¾Ð»Ñ %1"
+
+#: html/Admin/Groups/Members.html:32
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "Пользователи в группе %1"
+
+#: html/User/Groups/Members.html:129
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "Пользователи в личной группе %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "Изменение шаблона %1"
+
+#: lib/RT/Ticket_Overlay.pm:2615 lib/RT/Ticket_Overlay.pm:2683
+msgid "Either base or target must be specified"
+msgstr "Ðужно указать либо иÑточник, либо Ð°Ð´Ñ€ÐµÑ Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ"
+
+#: html/Admin/Users/Modify.html:53 html/Admin/Users/Prefs.html:46 html/Elements/SelectUsers:27 html/Ticket/Elements/AddWatchers:56 html/User/Prefs.html:42
+msgid "Email"
+msgstr "Email"
+
+#: lib/RT/User_Overlay.pm:188
+msgid "Email address in use"
+msgstr "Email уже занÑÑ‚"
+
+#: html/Admin/Elements/ModifyUser:42
+msgid "EmailAddress"
+msgstr "EmailAddress"
+
+#: html/Admin/Elements/ModifyUser:54
+msgid "EmailEncoding"
+msgstr "EmailEncoding"
+
+#: html/Admin/Elements/EditCustomField:36
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "Разрешено (ÑнÑтие отметки запрещает данное дополнительное поле)"
+
+#: html/Admin/Groups/Modify.html:53 html/User/Groups/Modify.html:53
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "Разрешено (ÑнÑтие отметки запрещает данную группу)"
+
+#: html/Admin/Queues/Modify.html:84
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "Включена (СнÑÑ‚Ð°Ñ Ð³Ð°Ð»Ð¾Ñ‡ÐºÐ° означает отключенную очередь)"
+
+#: html/Admin/Elements/EditCustomFields:99
+msgid "Enabled Custom Fields"
+msgstr "Разрешенные дополнительные полÑ"
+
+#: html/Admin/Queues/index.html:56
+msgid "Enabled Queues"
+msgstr "Включенные очереди"
+
+#: html/Admin/Elements/EditCustomField:90 html/Admin/Groups/Modify.html:117 html/Admin/Queues/Modify.html:138 html/Admin/Users/Modify.html:283 html/User/Groups/Modify.html:117
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "Включен ÑÑ‚Ð°Ñ‚ÑƒÑ %1"
+
+#: lib/RT/CustomField_Overlay.pm:361
+msgid "Enter multiple values"
+msgstr "Введите неÑколько значений"
+
+#: lib/RT/CustomField_Overlay.pm:358
+msgid "Enter one value"
+msgstr "Введите одно значение"
+
+#: html/Ticket/Elements/EditLinks:112
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "Введите номера или ÑÑылки на тикеты. ÐеÑколько тикетов разделÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ð±ÐµÐ»Ð°Ð¼Ð¸."
+
+#: html/Elements/Login:29 html/SelfService/Error.html:25 html/SelfService/Error.html:26
+msgid "Error"
+msgstr "Ошибка"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "Ошибка в параметрах Queue->AddWatcher"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "Ошибка в параметрах Queue->DelWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1356
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "Ошибка в параметрах Ticket->AddWatcher"
+
+#: lib/RT/Ticket_Overlay.pm:1532
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "Ошибка в параметрах Ticket->DelWatcher"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr ""
+
+#: bin/rt-crontool:194
+msgid "Example:"
+msgstr "Пример:"
+
+#: html/Admin/Elements/ModifyUser:64
+msgid "ExternalAuthId"
+msgstr "ExternalAuthId"
+
+#: html/Admin/Elements/ModifyUser:58
+msgid "ExternalContactInfoId"
+msgstr "ExternalContactInfoId"
+
+#: html/Admin/Users/Modify.html:73
+msgid "Extra info"
+msgstr "Доп. информациÑ"
+
+#: lib/RT/User_Overlay.pm:302
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "Ðе могу найти пÑевдо-группу 'Полномочных' пользователей"
+
+#: lib/RT/User_Overlay.pm:309
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "Ðе могу найти пÑевдо-группу 'Ðеполномочных' пользователей"
+
+#: bin/rt-crontool:138
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr ""
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "Фев."
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "Конец"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:59 lib/RT/Tickets_Overlay.pm:1091
+msgid "Final Priority"
+msgstr "Конечный приоритет"
+
+#: lib/RT/Ticket_Overlay.pm:1162
+msgid "FinalPriority"
+msgstr ""
+
+#: html/Admin/Queues/People.html:61 html/Ticket/Elements/EditPeople:34
+msgid "Find group whose"
+msgstr "Ðайти группы, у которых"
+
+#: html/Elements/Quicksearch:25
+msgid "Find new/open tickets"
+msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ тикетах"
+
+#: html/Admin/Queues/People.html:57 html/Admin/Users/index.html:46 html/Ticket/Elements/EditPeople:30
+msgid "Find people whose"
+msgstr "Ðайти людей, у которых"
+
+#: html/Search/Listing.html:108
+msgid "Find tickets"
+msgstr "ПоиÑк тикетов"
+
+#: html/Ticket/Elements/Tabs:58
+msgid "First"
+msgstr "Ðачало"
+
+#: html/Search/Listing.html:41
+msgid "First page"
+msgstr "ÐŸÐµÑ€Ð²Ð°Ñ Ñтраница"
+
+#: docs/design_docs/string-extraction-guide.txt:33
+msgid "Foo Bar Baz"
+msgstr "Foo Bar Baz"
+
+#: docs/design_docs/string-extraction-guide.txt:24
+msgid "Foo!"
+msgstr "Foo!"
+
+#: html/Search/Bulk.html:87
+msgid "Force change"
+msgstr "Изменить Ñилой"
+
+#: html/Search/Listing.html:106
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:868
+msgid "Found Object"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:44
+msgid "FreeformContactInfo"
+msgstr "FreeformContactInfo"
+
+#: lib/RT/CustomField_Overlay.pm:38
+msgid "FreeformMultiple"
+msgstr ""
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformSingle"
+msgstr ""
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "Птн."
+
+#: html/Ticket/Elements/ShowHistory:41 html/Ticket/Elements/ShowHistory:51
+msgid "Full headers"
+msgstr "Полный"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "Берем текущего Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð· pgp подпиÑи\\n"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($New->Name)
+msgid "Given to %1"
+msgstr ""
+
+#: html/Admin/Elements/Tabs:41 html/Admin/index.html:38
+msgid "Global"
+msgstr "Общие"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr ""
+
+#: html/Admin/Elements/SelectTemplate:38
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:75 html/Admin/Queues/People.html:59 html/Admin/Queues/People.html:63 html/Admin/Queues/index.html:44 html/Admin/Users/index.html:49 html/Ticket/Elements/EditPeople:32 html/Ticket/Elements/EditPeople:36 html/index.html:41
+msgid "Go!"
+msgstr "Поехали!"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "Ð¥Ð¾Ñ€Ð¾ÑˆÐ°Ñ pgp подпиÑÑŒ от %1\\n"
+
+#: html/Search/Listing.html:50
+msgid "Goto page"
+msgstr "Перейти на Ñтраницу"
+
+#: html/Elements/GotoTicket:25 html/SelfService/Elements/GotoTicket:25
+msgid "Goto ticket"
+msgstr "Показать тикет"
+
+#: NOT FOUND IN SOURCE
+msgid "Grand"
+msgstr ""
+
+#: html/Ticket/Elements/AddWatchers:46 html/User/Elements/DelegateRights:78
+msgid "Group"
+msgstr "Групповые"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "Группа %1 %2: %3"
+
+#: html/Admin/Elements/GroupTabs:45 html/Admin/Elements/QueueTabs:57 html/Admin/Elements/SystemTabs:44 html/Admin/Global/index.html:55
+msgid "Group Rights"
+msgstr "Права группы"
+
+#: lib/RT/Group_Overlay.pm:965
+msgid "Group already has member"
+msgstr "Пользователь уже входит в группу"
+
+#: html/Admin/Groups/Modify.html:77
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "Ðе могу Ñоздать группу: %1"
+
+#: lib/RT/Group_Overlay.pm:497
+msgid "Group created"
+msgstr "Создана группа"
+
+#: lib/RT/Group_Overlay.pm:1133
+msgid "Group has no such member"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:945 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1429 lib/RT/Ticket_Overlay.pm:1507
+msgid "Group not found"
+msgstr "Группа не найдена"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "Группа не найдена.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "Ðе задана группа.\\n"
+
+#: html/Admin/Elements/SelectNewGroupMembers:35 html/Admin/Elements/Tabs:35 html/Admin/Groups/Members.html:64 html/Admin/Queues/People.html:83 html/Admin/index.html:32 html/User/Groups/Members.html:67
+msgid "Groups"
+msgstr "Группы"
+
+#: lib/RT/Group_Overlay.pm:971
+msgid "Groups can't be members of their members"
+msgstr "Группы не могут быть членами входÑщих в них пользователей"
+
+#: lib/RT/Interface/CLI.pm:73 lib/RT/Interface/CLI.pm:73
+msgid "Hello!"
+msgstr "ЗдравÑтвуйте!"
+
+#: docs/design_docs/string-extraction-guide.txt:40
+#. ($name)
+msgid "Hello, %1"
+msgstr "Hello, %1"
+
+#: html/Ticket/Elements/ShowHistory:30 html/Ticket/Elements/Tabs:88
+msgid "History"
+msgstr "ИÑториÑ"
+
+#: html/Admin/Elements/ModifyUser:68
+msgid "HomePhone"
+msgstr "HomePhone"
+
+#: html/Elements/Tabs:46
+msgid "Homepage"
+msgstr "Домой"
+
+#: lib/RT/Base.pm:74
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "I have [quant,_1,concrete mixer]."
+msgstr "I have [quant,_1,concrete mixer]."
+
+#: html/Ticket/Elements/ShowBasics:27 lib/RT/Tickets_Overlay.pm:1018
+msgid "Id"
+msgstr "Тикет"
+
+#: html/Admin/Users/Modify.html:44 html/User/Prefs.html:39
+msgid "Identity"
+msgstr "ЛичноÑÑ‚ÑŒ"
+
+#: etc/upgrade/2.1.71:86
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr ""
+
+#: bin/rt-crontool:190
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "ЕÑли бы Ñта программа имела уÑтановленный бит setgid, то зловредный пользователь мог бы воÑпользоватьÑÑ Ñтим Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтративных полномочий в RT."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "If you've updated anything above, be sure to"
+msgstr "ЕÑли вы что-либо изменили, то удоÑтоверьтеÑÑŒ, что"
+
+#: lib/RT/Interface/Web.pm:860
+msgid "Illegal value for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:863
+msgid "Immutable field"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:74
+msgid "Include disabled custom fields in listing."
+msgstr "Включать отключенные дополнительные Ð¿Ð¾Ð»Ñ Ð² ÑпиÑок."
+
+#: html/Admin/Queues/index.html:43
+msgid "Include disabled queues in listing."
+msgstr "Показывать отключенные очереди."
+
+#: html/Admin/Users/index.html:47
+msgid "Include disabled users in search."
+msgstr "Показать отключенных пользователей."
+
+#: lib/RT/Tickets_Overlay.pm:1067
+msgid "Initial Priority"
+msgstr "Ðачальный приоритет"
+
+#: lib/RT/Ticket_Overlay.pm:1161 lib/RT/Ticket_Overlay.pm:1163
+msgid "InitialPriority"
+msgstr ""
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "Ошибка ввода"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:3729
+msgid "Internal Error"
+msgstr "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ°"
+
+#: lib/RT/Record.pm:143
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:644
+msgid "Invalid Group Type"
+msgstr "Ðеправильный тип группы"
+
+#: lib/RT/Principal_Overlay.pm:128
+msgid "Invalid Right"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:865
+msgid "Invalid data"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:438
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "Владелец отÑутÑтвует. ЗаменÑем его на 'nobody'."
+
+#: lib/RT/Scrip_Overlay.pm:134 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´ÑŒ"
+
+#: lib/RT/ACE_Overlay.pm:244 lib/RT/ACE_Overlay.pm:253 lib/RT/ACE_Overlay.pm:259 lib/RT/ACE_Overlay.pm:270 lib/RT/ACE_Overlay.pm:275
+msgid "Invalid right"
+msgstr "Ðеверные права"
+
+#: lib/RT/Record.pm:118
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "Ðеправильное значение Ð´Ð»Ñ %1"
+
+#: lib/RT/Ticket_Overlay.pm:3367
+msgid "Invalid value for custom field"
+msgstr "Ðеправильное значение Ð´Ð»Ñ Ñтого полÑ"
+
+#: lib/RT/Ticket_Overlay.pm:345
+msgid "Invalid value for status"
+msgstr "Такого ÑтатуÑа не бывает"
+
+#: bin/rt-crontool:191
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "Обратите внимание, что обычные пользователи не имеют права запуÑкать Ñту программу."
+
+#: bin/rt-crontool:192
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "ПредполагаетÑÑ, что Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка Ñтой программы вы должны Ñоздать учетную запиÑÑŒ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Unix Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼Ð¸ уÑтановками групп и доÑтупом к RT."
+
+#: bin/rt-crontool:163
+msgid "It takes several arguments:"
+msgstr "Она требует неÑколько параметров:"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "Тикеты, ожидающие моей визы"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "Янв."
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "Join or leave this group"
+msgstr ""
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "Июл."
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:99
+msgid "Jumbo"
+msgstr "Ð’Ñе вмеÑте"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "Июн."
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "Ключевое Ñлово"
+
+#: html/Admin/Elements/ModifyUser:52
+msgid "Lang"
+msgstr "Язык"
+
+#: html/Ticket/Elements/Tabs:73
+msgid "Last"
+msgstr "Конец"
+
+#: html/Ticket/Elements/EditDates:38 html/Ticket/Elements/ShowDates:39
+msgid "Last Contact"
+msgstr "Контакт"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Contacted"
+msgstr "Контакт"
+
+#: html/Search/Elements/TicketHeader:41
+msgid "Last Notified"
+msgstr ""
+
+#: html/Elements/SelectDateType:30
+msgid "Last Updated"
+msgstr "Обновлен"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "ОÑталоÑÑŒ"
+
+#: html/Admin/Users/Modify.html:83
+msgid "Let this user access RT"
+msgstr "Разрешить доÑтуп к RT"
+
+#: html/Admin/Users/Modify.html:87
+msgid "Let this user be granted rights"
+msgstr "Пользователь может иметь права"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "Ограничиваем владельца %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "Ограничиваем очередь Ð´Ð»Ñ %1 %2"
+
+#: lib/RT/Ticket_Overlay.pm:2697
+msgid "Link already exists"
+msgstr "СвÑзь уже ÑущеÑтвует"
+
+#: lib/RT/Ticket_Overlay.pm:2709
+msgid "Link could not be created"
+msgstr "Ðе могу ÑвÑзать тикеты"
+
+#: lib/RT/Ticket_Overlay.pm:2717 lib/RT/Ticket_Overlay.pm:2727
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2638
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "Удалена ÑвÑзь (%1)"
+
+#: lib/RT/Ticket_Overlay.pm:2644
+msgid "Link not found"
+msgstr "СвÑзь не найдена"
+
+#: html/Ticket/ModifyLinks.html:25 html/Ticket/ModifyLinks.html:29
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "СвÑзываем тикет #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr ""
+
+#: html/Ticket/Elements/Tabs:97
+msgid "Links"
+msgstr "СвÑзи"
+
+#: html/Admin/Users/Modify.html:114 html/User/Prefs.html:85
+msgid "Location"
+msgstr "МеÑтонахождение"
+
+#: lib/RT.pm:158
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "Ðе найден каталог Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ %1 или не доÑтупен на запиÑÑŒ.\\n RT не может продолжить работу."
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "ЗарегиÑтрирован как %1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:25 html/Elements/Login:34 html/Elements/Login:45
+msgid "Login"
+msgstr "Логин"
+
+#: html/Elements/Header:54
+msgid "Logout"
+msgstr "Выйти"
+
+#: html/Search/Bulk.html:86
+msgid "Make Owner"
+msgstr "Ðазначить владельцем"
+
+#: html/Search/Bulk.html:102
+msgid "Make Status"
+msgstr "Ðазначить ÑтатуÑ"
+
+#: html/Search/Bulk.html:109
+msgid "Make date Due"
+msgstr "Ðазначить Ñрок"
+
+#: html/Search/Bulk.html:110
+msgid "Make date Resolved"
+msgstr "Изменить дату решениÑ"
+
+#: html/Search/Bulk.html:107
+msgid "Make date Started"
+msgstr "Изменить дату 'ÐачалÑÑ'"
+
+#: html/Search/Bulk.html:106
+msgid "Make date Starts"
+msgstr "Изменить дату 'ÐачинаетÑÑ'"
+
+#: html/Search/Bulk.html:108
+msgid "Make date Told"
+msgstr "Изменить дату поÑледнего контакта"
+
+#: html/Search/Bulk.html:99
+msgid "Make priority"
+msgstr "Ðазначить приоритет"
+
+#: html/Search/Bulk.html:100
+msgid "Make queue"
+msgstr "Ðазначить очередь"
+
+#: html/Search/Bulk.html:98
+msgid "Make subject"
+msgstr "Изменить тему"
+
+#: html/Admin/index.html:33
+msgid "Manage groups and group membership"
+msgstr "ÐаÑтройка групп и их пользователей"
+
+#: html/Admin/index.html:39
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "ÐаÑтройки Ð´Ð»Ñ Ð²Ñех очередей"
+
+#: html/Admin/index.html:36
+msgid "Manage queues and queue-specific properties"
+msgstr "ÐаÑтройка очередей и их параметров"
+
+#: html/Admin/index.html:30
+msgid "Manage users and passwords"
+msgstr "ÐаÑтройка пользователей и их паролей"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "Мар."
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr ""
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "Май"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Member added"
+msgstr "Пользователь добавлен в группу"
+
+#: lib/RT/Group_Overlay.pm:1140
+msgid "Member deleted"
+msgstr "Пользователь удален из группы"
+
+#: lib/RT/Group_Overlay.pm:1144
+msgid "Member not deleted"
+msgstr "Пользователь не удален из группы"
+
+#: html/Elements/SelectLinkType:26
+msgid "Member of"
+msgstr "Входит в"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:42 html/User/Elements/GroupTabs:42
+msgid "Members"
+msgstr "Пользователи"
+
+#: lib/RT/Ticket_Overlay.pm:2843
+msgid "Merge Successful"
+msgstr "Тикеты уÑпешно Ñклеены"
+
+#: lib/RT/Ticket_Overlay.pm:2804
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "Склейка не удалаÑÑŒ. Ðе Ñмогла уÑтановить идентификатор тикета."
+
+#: html/Ticket/Elements/EditLinks:115
+msgid "Merge into"
+msgstr "Приклеить к"
+
+#: html/Ticket/Update.html:102
+msgid "Message"
+msgstr "ТекÑÑ‚"
+
+#: lib/RT/Interface/Web.pm:867
+msgid "Missing a primary key?: %1"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:169 html/User/Prefs.html:54
+msgid "Mobile"
+msgstr "Мобильник"
+
+#: html/Admin/Elements/ModifyUser:72
+msgid "MobilePhone"
+msgstr "MobilePhone"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify Access Control List"
+msgstr "Изменить ÑпиÑок ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð´Ð¾Ñтупа"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Custom Field %1"
+msgstr "Изменение дополнительного Ð¿Ð¾Ð»Ñ %1"
+
+#: html/Admin/Global/CustomFields.html:44 html/Admin/Global/index.html:51
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "Изменить дополнительные полÑ, применÑемые кл вÑем очередÑм"
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "Modify Scrip templates for this queue"
+msgstr "Изменить шаблоны Ñкриплетов Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ очереди"
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "Modify Scrips for this queue"
+msgstr "Изменить Ñкриплеты Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ очереди"
+
+#: html/Admin/Queues/CustomField.html:45
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr ""
+
+#: html/Admin/Global/CustomField.html:53
+msgid "Modify a CustomField which applies to all queues"
+msgstr ""
+
+#: html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "Изменить Ñкрипт Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: html/Admin/Global/Scrip.html:48
+msgid "Modify a scrip which applies to all queues"
+msgstr "Изменение Ñкрипта, который дейÑтвует Ð´Ð»Ñ Ð²Ñех очередей"
+
+#: html/Ticket/ModifyDates.html:25 html/Ticket/ModifyDates.html:29
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "Изменение дат в тикете #%1"
+
+#: html/Ticket/ModifyDates.html:35
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "Изменение дат в тикете #%1"
+
+#: html/Admin/Global/GroupRights.html:25 html/Admin/Global/GroupRights.html:28 html/Admin/Global/index.html:56
+msgid "Modify global group rights"
+msgstr "Изменение глобальных прав группы"
+
+#: html/Admin/Global/GroupRights.html:33
+msgid "Modify global group rights."
+msgstr "Изменение глобальных прав группы"
+
+
+#: html/Admin/Global/UserRights.html:25 html/Admin/Global/UserRights.html:28 html/Admin/Global/index.html:60
+msgid "Modify global user rights"
+msgstr "Изменение глобальных прав пользователÑ"
+
+#: html/Admin/Global/UserRights.html:33
+msgid "Modify global user rights."
+msgstr "Изменение глобальных прав пользователÑ."
+
+#: lib/RT/Group_Overlay.pm:146
+msgid "Modify group metadata or delete group"
+msgstr "Изменение метаданных группы или ее удаление"
+
+#: html/Admin/Groups/GroupRights.html:25 html/Admin/Groups/GroupRights.html:29 html/Admin/Groups/GroupRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "Изменение прав групп Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ðµ %1"
+
+#: html/Admin/Queues/GroupRights.html:25 html/Admin/Queues/GroupRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "Изменение прав групп Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Modify membership roster for this group"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "Modify one's own RT account"
+msgstr ""
+
+#: html/Admin/Queues/People.html:25 html/Admin/Queues/People.html:29
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "Изменение пользователей отноÑÑщихÑÑ Ðº очереди %1"
+
+#: html/Ticket/ModifyPeople.html:25 html/Ticket/ModifyPeople.html:29 html/Ticket/ModifyPeople.html:35
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "Изменение пользователей отноÑÑщихÑÑ Ðº тикету #%1"
+
+#: html/Admin/Queues/Scrips.html:44
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "Изменить Ñкрипты Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: html/Admin/Global/Scrips.html:44 html/Admin/Global/index.html:42
+msgid "Modify scrips which apply to all queues"
+msgstr "Изменение Ñкриптов, которые дейÑтвуют на вÑе очереди"
+
+#: html/Admin/Global/Template.html:25 html/Admin/Global/Template.html:30 html/Admin/Global/Template.html:81 html/Admin/Queues/Template.html:78
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "Изменение шаблона %1"
+
+#: html/Admin/Global/Templates.html:44
+msgid "Modify templates which apply to all queues"
+msgstr "Изменить шаблоны, которые применÑÑŽÑ‚ÑÑ ÐºÐ¾ вÑем очередÑм"
+
+#: html/Admin/Groups/Modify.html:87 html/User/Groups/Modify.html:86
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "ÐаÑтройки Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ %1"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Modify the queue watchers"
+msgstr "Изменить очередь наблюдателей"
+
+#: html/Admin/Users/Modify.html:236
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "ÐаÑтройки Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ %1"
+
+#: html/Ticket/ModifyAll.html:37
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "Изменение тикета # %1"
+
+#: html/Ticket/Modify.html:25 html/Ticket/Modify.html:28 html/Ticket/Modify.html:34
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "Изменение тикета # %1"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Modify tickets"
+msgstr "Изменить тикеты"
+
+#: html/Admin/Groups/UserRights.html:25 html/Admin/Groups/UserRights.html:29 html/Admin/Groups/UserRights.html:35
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "Изменение прав Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ %1"
+
+#: html/Admin/Queues/UserRights.html:25 html/Admin/Queues/UserRights.html:29
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "Изменение прав Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "Изменение наблюдателей Ð´Ð»Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸ '%1'"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyACL"
+msgstr ""
+
+#: lib/RT/Group_Overlay.pm:149
+msgid "ModifyOwnMembership"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "ModifyQueueWatchers"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:76
+msgid "ModifyScrips"
+msgstr ""
+
+#: lib/RT/System.pm:61
+msgid "ModifySelf"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:73
+msgid "ModifyTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "ModifyTicket"
+msgstr ""
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "Пнд."
+
+#: html/Ticket/Elements/ShowRequestor:42
+#. ($name)
+msgid "More about %1"
+msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ %1"
+
+#: html/Admin/Elements/EditCustomFields:61
+msgid "Move down"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFields:53
+msgid "Move up"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:27
+msgid "Multiple"
+msgstr "ÐеÑколько значений"
+
+#: lib/RT/User_Overlay.pm:179
+msgid "Must specify 'Name' attribute"
+msgstr "Ð’Ñ‹ должны указать ИмÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "My Approvals"
+msgstr "Мои визы"
+
+#: html/Approvals/index.html:25 html/Approvals/index.html:26
+msgid "My approvals"
+msgstr "Мои визы"
+
+#: html/Admin/Elements/AddCustomFieldValue:26 html/Admin/Elements/EditCustomField:32 html/Admin/Elements/ModifyTemplate:28 html/Admin/Elements/ModifyUser:30 html/Admin/Groups/Modify.html:44 html/Elements/SelectGroups:26 html/Elements/SelectUsers:28 html/User/Groups/Modify.html:44
+msgid "Name"
+msgstr "ИмÑ"
+
+#: lib/RT/User_Overlay.pm:186
+msgid "Name in use"
+msgstr "Ð˜Ð¼Ñ ÑƒÐ¶Ðµ иÑпользуетÑÑ"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr ""
+
+#: html/Elements/Quicksearch:30
+msgid "New"
+msgstr "Ðовых"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:93 html/User/Prefs.html:65
+msgid "New Password"
+msgstr "Ðовый пароль"
+
+#: etc/initialdata:311 etc/upgrade/2.1.71:16
+msgid "New Pending Approval"
+msgstr ""
+
+#: html/Ticket/Elements/EditLinks:111
+msgid "New Relationships"
+msgstr "Ðовые ÑвÑзи"
+
+#: html/Ticket/Elements/Tabs:36
+msgid "New Search"
+msgstr "Ðовый поиÑк"
+
+#: html/Admin/Global/CustomField.html:41 html/Admin/Global/CustomFields.html:39 html/Admin/Queues/CustomField.html:52 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "Ðовое дополнительное поле"
+
+#: html/Admin/Elements/GroupTabs:54 html/User/Elements/GroupTabs:52
+msgid "New group"
+msgstr "ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð°"
+
+#: html/SelfService/Prefs.html:32
+msgid "New password"
+msgstr "Ðовый пароль"
+
+#: lib/RT/User_Overlay.pm:639
+msgid "New password notification sent"
+msgstr "Отправлено Ñообщение Ñ Ð½Ð¾Ð²Ñ‹Ð¼ паролем"
+
+#: html/Admin/Elements/QueueTabs:70
+msgid "New queue"
+msgstr "ÐÐ¾Ð²Ð°Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´ÑŒ"
+
+#: html/SelfService/Elements/Tabs:63
+msgid "New request"
+msgstr "Ðовый запроÑ"
+
+#: html/Admin/Elements/SelectRights:42
+msgid "New rights"
+msgstr "Ðовые права"
+
+#: html/Admin/Global/Scrip.html:40 html/Admin/Global/Scrips.html:39 html/Admin/Queues/Scrip.html:43 html/Admin/Queues/Scrips.html:53
+msgid "New scrip"
+msgstr "Ðовый Ñкриплет"
+
+#: NOT FOUND IN SOURCE
+msgid "New search"
+msgstr "Ðовый поиÑк"
+
+#: html/Admin/Global/Template.html:60 html/Admin/Global/Templates.html:39 html/Admin/Queues/Template.html:58 html/Admin/Queues/Templates.html:46
+msgid "New template"
+msgstr "Ðовый шаблон"
+
+#: lib/RT/Ticket_Overlay.pm:2771
+msgid "New ticket doesn't exist"
+msgstr "Ðовый тикет не ÑущеÑтвует"
+
+#: html/Admin/Elements/UserTabs:52
+msgid "New user"
+msgstr "Ðовый пользователь"
+
+#: html/Admin/Elements/CreateUserCalled:26
+msgid "New user called"
+msgstr "Добавить Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼"
+
+#: html/Admin/Queues/People.html:55 html/Ticket/Elements/EditPeople:29
+msgid "New watchers"
+msgstr "Ðовые наблюдатели"
+
+#: html/Admin/Users/Prefs.html:42
+msgid "New window setting"
+msgstr "Ðовые наÑтройки окна"
+
+#: html/Ticket/Elements/Tabs:69
+msgid "Next"
+msgstr "Вперед"
+
+#: html/Search/Listing.html:48
+msgid "Next page"
+msgstr "Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтраница"
+
+#: html/Admin/Elements/ModifyUser:50
+msgid "NickName"
+msgstr "ПÑевдоним"
+
+#: html/Admin/Users/Modify.html:63 html/User/Prefs.html:46
+msgid "Nickname"
+msgstr "ПÑевдоним"
+
+#: html/Admin/Elements/EditCustomField:73 html/Admin/Elements/EditCustomFields:105
+msgid "No CustomField"
+msgstr "Ðет такого полÑ"
+
+#: html/Admin/Groups/GroupRights.html:84 html/Admin/Groups/UserRights.html:71
+msgid "No Group defined"
+msgstr "Ðет такой группы"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:68
+msgid "No Queue defined"
+msgstr "Ðет такой очереди"
+
+#: bin/rt-crontool:56
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "Пользователь RT не найден. ПожалуйÑта, обратитеÑÑŒ к вашему админиÑтратору RT.\\n"
+
+#: html/Admin/Global/Template.html:79 html/Admin/Queues/Template.html:76
+msgid "No Template"
+msgstr "Шаблон не определен"
+
+#: bin/rt-commit-handler:764
+msgid "No Ticket specified. Aborting ticket "
+msgstr "Тикет не задан. Ðичего не делаем."
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "Тикет не задан. ОтменÑем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ð¸ÐºÐµÑ‚Ð°\\n\\n"
+
+#: html/Approvals/Elements/Approve:47
+msgid "No action"
+msgstr "Ðет дейÑтвиÑ"
+
+#: lib/RT/Interface/Web.pm:862
+msgid "No column specified"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "Команда не найдена\\n"
+
+#: html/Elements/ViewUser:36 html/Ticket/Elements/ShowRequestor:45
+msgid "No comment entered about this user"
+msgstr "Без комментариев"
+
+#: lib/RT/Ticket_Overlay.pm:2189 lib/RT/Ticket_Overlay.pm:2257
+msgid "No correspondence attached"
+msgstr "ПуÑтое Ñообщение"
+
+#: lib/RT/Action/Generic.pm:150 lib/RT/Condition/Generic.pm:176 lib/RT/Search/ActiveTicketsInQueue.pm:56 lib/RT/Search/Generic.pm:113
+#. (ref $self)
+msgid "No description for %1"
+msgstr "Ðет опиÑÐ°Ð½Ð¸Ñ Ð´Ð»Ñ %1"
+
+#: lib/RT/Users_Overlay.pm:151
+msgid "No group specified"
+msgstr "Ðе указана группа"
+
+#: lib/RT/User_Overlay.pm:857
+msgid "No password set"
+msgstr "ОтÑутÑтвует пароль"
+
+#: lib/RT/Queue_Overlay.pm:259
+msgid "No permission to create queues"
+msgstr "У Ð²Ð°Ñ Ð½ÐµÑ‚ права на Ñоздание очереди"
+
+#: lib/RT/Ticket_Overlay.pm:341
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr ""
+
+#: lib/RT/User_Overlay.pm:151
+msgid "No permission to create users"
+msgstr "Ð’Ñ‹ не имеете права Ñоздавать пользователей"
+
+#: html/SelfService/Display.html:174
+msgid "No permission to display that ticket"
+msgstr "Показ Ñтого тикета запрещен"
+
+#: html/SelfService/Update.html:55
+msgid "No permission to view update ticket"
+msgstr "Запрещен показ изменений Ñтого тикета"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1488
+msgid "No principal specified"
+msgstr "Пользователь не указан"
+
+#: html/Admin/Queues/People.html:154 html/Admin/Queues/People.html:164
+msgid "No principals selected."
+msgstr "Пользователи не выбраны."
+
+#: html/Admin/Queues/index.html:35
+msgid "No queues matching search criteria found."
+msgstr "Ðичего подходÑщего не найдено."
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr ""
+
+#: html/Admin/Elements/SelectRights:33
+msgid "No rights granted."
+msgstr "Ðет прав."
+
+#: html/Search/Bulk.html:149
+msgid "No search to operate on."
+msgstr "Ðечего делать."
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "Ðе указан номер тикета"
+
+#: lib/RT/Transaction_Overlay.pm:480 lib/RT/Transaction_Overlay.pm:518
+msgid "No transaction type specified"
+msgstr "Ðе указан тип транзакции"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr ""
+
+#: html/Admin/Users/index.html:36
+msgid "No users matching search criteria found."
+msgstr "Ðи одного подходÑщего Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ найдено."
+
+#: bin/rt-commit-handler:644
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "Ðе найден пользователь RT. Обработчик CVS отключен. ОбратитеÑÑŒ к админиÑтратору RT.\\n"
+
+#: lib/RT/Interface/Web.pm:859
+msgid "No value sent to _Set!\\n"
+msgstr ""
+
+#: html/Search/Elements/TicketRow:37
+msgid "Nobody"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:864
+msgid "Nonexistant field?"
+msgstr ""
+
+#: html/Elements/Login:99
+msgid "Not logged in"
+msgstr ""
+
+#: html/Elements/Header:59 html/SelfService/Elements/Header:58
+msgid "Not logged in."
+msgstr "Ðе зарегиÑтрирован."
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "Ðе уÑтановлено"
+
+#: html/NoAuth/Reminder.html:27
+msgid "Not yet implemented."
+msgstr "Еще не реализовано."
+
+#: html/Admin/Groups/Rights.html:25
+msgid "Not yet implemented...."
+msgstr "Еще не реализовано..."
+
+#: html/Approvals/Elements/Approve:50
+msgid "Notes"
+msgstr "Примечание"
+
+#: lib/RT/User_Overlay.pm:642
+msgid "Notification could not be sent"
+msgstr "Ðе могу отоÑлать уведомление"
+
+#: etc/initialdata:94
+msgid "Notify AdminCcs"
+msgstr ""
+
+#: etc/initialdata:90
+msgid "Notify AdminCcs as Comment"
+msgstr ""
+
+#: etc/initialdata:121
+msgid "Notify Other Recipients"
+msgstr ""
+
+#: etc/initialdata:117
+msgid "Notify Other Recipients as Comment"
+msgstr ""
+
+#: etc/initialdata:86
+msgid "Notify Owner"
+msgstr ""
+
+#: etc/initialdata:82
+msgid "Notify Owner as Comment"
+msgstr ""
+
+#: etc/initialdata:313 etc/upgrade/2.1.71:17
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr ""
+
+#: etc/initialdata:78
+msgid "Notify Requestors"
+msgstr ""
+
+#: etc/initialdata:104
+msgid "Notify Requestors and Ccs"
+msgstr ""
+
+#: etc/initialdata:99
+msgid "Notify Requestors and Ccs as Comment"
+msgstr ""
+
+#: etc/initialdata:113
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr ""
+
+#: etc/initialdata:109
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr ""
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "ÐоÑ."
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr ""
+
+#: lib/RT/Record.pm:157
+msgid "Object could not be created"
+msgstr "Ðе могу Ñоздать объект"
+
+#: lib/RT/Record.pm:176
+msgid "Object created"
+msgstr "Создан объект"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "Окт."
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr ""
+
+#: html/Elements/SelectDateRelation:35
+msgid "On"
+msgstr "Ðа"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "Ðа комментарий"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr ""
+
+#: etc/initialdata:137
+msgid "On Create"
+msgstr "Ðа Ñоздание"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "Ðа изменение владельца"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "Ðа изменение очереди"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr ""
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "на изменение ÑтатуÑа"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "Ðа транзакцию"
+
+#: html/Approvals/Elements/PendingMyApproval:50
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "Показывать визы только Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов Ñозданных поÑле %1"
+
+#: html/Approvals/Elements/PendingMyApproval:48
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "Показывать визы только Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов Ñозданных до %1"
+
+#: html/Elements/Quicksearch:31
+msgid "Open"
+msgstr "Открытых"
+
+#: html/Ticket/Elements/Tabs:136
+msgid "Open it"
+msgstr "Открыть"
+
+#: html/SelfService/Elements/Tabs:57
+msgid "Open requests"
+msgstr "Открыть запроÑÑ‹"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "Open tickets (from listing) in a new window"
+msgstr "Открыть тикеты (из ÑпиÑка) в новом окне"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in another window"
+msgstr "Открыть тикеты (из ÑпиÑка) в другом окне"
+
+#: etc/initialdata:133
+msgid "Open tickets on correspondence"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:101
+msgid "Ordering and sorting"
+msgstr "ПорÑдок и Ñортировка"
+
+#: html/Admin/Elements/ModifyUser:46 html/Admin/Users/Modify.html:117 html/Elements/SelectUsers:29 html/User/Prefs.html:86
+msgid "Organization"
+msgstr "ОрганизациÑ"
+
+#: html/Approvals/Elements/Approve:34
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:55 html/Admin/Queues/Modify.html:69
+msgid "Over time, priority moves toward"
+msgstr "Со временем поднÑÑ‚ÑŒ приоритет до"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Own tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "OwnTicket"
+msgstr ""
+
+#: etc/initialdata:38 html/Elements/MyRequests:32 html/SelfService/Elements/MyRequests:30 html/Ticket/Create.html:48 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/EditPeople:44 html/Ticket/Elements/ShowPeople:27 html/Ticket/Update.html:63 lib/RT/ACE_Overlay.pm:86 lib/RT/Tickets_Overlay.pm:1244
+msgid "Owner"
+msgstr "Владелец"
+
+#: lib/RT/Ticket_Overlay.pm:3004
+#. ($OldOwnerObj->Name, $NewOwnerObj->Name)
+msgid "Owner changed from %1 to %2"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:584
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "Владелец Ñилой изменен Ñ %1 на %2"
+
+#: html/Search/Elements/PickRestriction:31
+msgid "Owner is"
+msgstr "Владелец"
+
+#: html/Admin/Users/Modify.html:174 html/User/Prefs.html:56
+msgid "Pager"
+msgstr "Пейджер"
+
+#: html/Admin/Elements/ModifyUser:74
+msgid "PagerPhone"
+msgstr "Телефон пейджера"
+
+#: NOT FOUND IN SOURCE
+msgid "Parent"
+msgstr ""
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/EditLinks:127 html/Ticket/Elements/EditLinks:58 html/Ticket/Elements/ShowLinks:43
+msgid "Parents"
+msgstr "Предки"
+
+#: html/Elements/Login:43 html/User/Prefs.html:61
+msgid "Password"
+msgstr "Пароль"
+
+#: html/NoAuth/Reminder.html:25
+msgid "Password Reminder"
+msgstr "ПодÑказка к паролю"
+
+#: lib/RT/User_Overlay.pm:168 lib/RT/User_Overlay.pm:860
+msgid "Password too short"
+msgstr "Пароль Ñлишком короткий"
+
+#: html/Admin/Users/Modify.html:291 html/User/Prefs.html:172
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "Пароль: %1"
+
+#: html/Ticket/Elements/ShowSummary:43 html/Ticket/Elements/Tabs:96 html/Ticket/ModifyAll.html:51
+msgid "People"
+msgstr "Люди"
+
+#: etc/initialdata:126
+msgid "Perform a user-defined action"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:231 lib/RT/ACE_Overlay.pm:237 lib/RT/ACE_Overlay.pm:563 lib/RT/ACE_Overlay.pm:573 lib/RT/ACE_Overlay.pm:583 lib/RT/ACE_Overlay.pm:648 lib/RT/CurrentUser.pm:83 lib/RT/CurrentUser.pm:92 lib/RT/CustomField_Overlay.pm:445 lib/RT/CustomField_Overlay.pm:451 lib/RT/Group_Overlay.pm:1095 lib/RT/Group_Overlay.pm:1099 lib/RT/Group_Overlay.pm:1108 lib/RT/Group_Overlay.pm:1159 lib/RT/Group_Overlay.pm:1163 lib/RT/Group_Overlay.pm:1169 lib/RT/Group_Overlay.pm:426 lib/RT/Group_Overlay.pm:518 lib/RT/Group_Overlay.pm:596 lib/RT/Group_Overlay.pm:604 lib/RT/Group_Overlay.pm:701 lib/RT/Group_Overlay.pm:705 lib/RT/Group_Overlay.pm:711 lib/RT/Group_Overlay.pm:904 lib/RT/Group_Overlay.pm:908 lib/RT/Group_Overlay.pm:921 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:931 lib/RT/Scrip_Overlay.pm:126 lib/RT/Scrip_Overlay.pm:137 lib/RT/Scrip_Overlay.pm:197 lib/RT/Scrip_Overlay.pm:430 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:88 lib/RT/Template_Overlay.pm:94 lib/RT/Ticket_Overlay.pm:1341 lib/RT/Ticket_Overlay.pm:1351 lib/RT/Ticket_Overlay.pm:1365 lib/RT/Ticket_Overlay.pm:1518 lib/RT/Ticket_Overlay.pm:1527 lib/RT/Ticket_Overlay.pm:1540 lib/RT/Ticket_Overlay.pm:1875 lib/RT/Ticket_Overlay.pm:2013 lib/RT/Ticket_Overlay.pm:2177 lib/RT/Ticket_Overlay.pm:2244 lib/RT/Ticket_Overlay.pm:2596 lib/RT/Ticket_Overlay.pm:2668 lib/RT/Ticket_Overlay.pm:2762 lib/RT/Ticket_Overlay.pm:2777 lib/RT/Ticket_Overlay.pm:2910 lib/RT/Ticket_Overlay.pm:3139 lib/RT/Ticket_Overlay.pm:3337 lib/RT/Ticket_Overlay.pm:3499 lib/RT/Ticket_Overlay.pm:3551 lib/RT/Ticket_Overlay.pm:3716 lib/RT/Transaction_Overlay.pm:468 lib/RT/Transaction_Overlay.pm:475 lib/RT/Transaction_Overlay.pm:504 lib/RT/Transaction_Overlay.pm:511 lib/RT/User_Overlay.pm:1334 lib/RT/User_Overlay.pm:562 lib/RT/User_Overlay.pm:597 lib/RT/User_Overlay.pm:853 lib/RT/User_Overlay.pm:941
+msgid "Permission Denied"
+msgstr "Ð’ доÑтупе отказано"
+
+#: html/User/Elements/Tabs:35
+msgid "Personal Groups"
+msgstr "Личные группы"
+
+#: html/User/Groups/index.html:30 html/User/Groups/index.html:40
+msgid "Personal groups"
+msgstr "Личные группы"
+
+#: html/User/Elements/DelegateRights:37
+msgid "Personal groups:"
+msgstr "Личные группы:"
+
+#: html/Admin/Users/Modify.html:156 html/User/Prefs.html:49
+msgid "Phone numbers"
+msgstr "Ðомера телефонов"
+
+#: html/Admin/Users/Rights.html:25
+msgid "Placeholder"
+msgstr "Заполнитель"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr ""
+
+#: html/Elements/Header:52 html/Elements/Tabs:55 html/SelfService/Prefs.html:25 html/User/Prefs.html:25 html/User/Prefs.html:28
+msgid "Preferences"
+msgstr "ПредпочтениÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "ПредпочтениÑ"
+
+#: lib/RT/Action/Generic.pm:160
+msgid "Prepare Stubbed"
+msgstr "Подготовка не реализована"
+
+#: html/Ticket/Elements/Tabs:61
+msgid "Prev"
+msgstr "Ðазад"
+
+#: html/Search/Listing.html:44
+msgid "Previous page"
+msgstr "ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ñтраница"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "Приоритет"
+
+#: lib/RT/ACE_Overlay.pm:133 lib/RT/ACE_Overlay.pm:208 lib/RT/ACE_Overlay.pm:552
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:54 html/SelfService/Display.html:76 html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:54 html/Ticket/Elements/ShowBasics:39 lib/RT/Tickets_Overlay.pm:1042
+msgid "Priority"
+msgstr "Приоритет"
+
+#: html/Admin/Elements/ModifyQueue:51 html/Admin/Queues/Modify.html:65
+msgid "Priority starts at"
+msgstr "Приоритет начинаетÑÑ Ñ"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "Привилегированные"
+
+#: html/Admin/Users/Modify.html:271 html/User/Prefs.html:163
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "СоÑтоÑние полномочий: %1"
+
+#: html/Admin/Users/index.html:62
+msgid "Privileged users"
+msgstr "Полномочные пользователи"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr ""
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Elements/Quicksearch:29 html/Search/Elements/PickRestriction:46 html/SelfService/Create.html:35 html/SelfService/Display.html:68 html/Ticket/Create.html:38 html/Ticket/Elements/EditBasics:64 html/Ticket/Elements/ShowBasics:43 html/User/Elements/DelegateRights:80 lib/RT/Tickets_Overlay.pm:883
+msgid "Queue"
+msgstr "Очередь"
+
+#: html/Admin/Queues/CustomField.html:42 html/Admin/Queues/Scrip.html:50 html/Admin/Queues/Scrips.html:46 html/Admin/Queues/Templates.html:43
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "Ðе найдена очередь %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "Ðе найдена очередь '%1'\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr ""
+
+#: html/Admin/Elements/ModifyQueue:31 html/Admin/Queues/Modify.html:43
+msgid "Queue Name"
+msgstr "Ð˜Ð¼Ñ Ð¾Ñ‡ÐµÑ€ÐµÐ´Ð¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:263
+msgid "Queue already exists"
+msgstr "Очередь уже ÑущеÑтвует"
+
+#: lib/RT/Queue_Overlay.pm:272 lib/RT/Queue_Overlay.pm:278
+msgid "Queue could not be created"
+msgstr "Ðе могу Ñоздать очередь"
+
+#: html/Ticket/Create.html:209
+msgid "Queue could not be loaded."
+msgstr "Ðе могу загрузить очередь"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:282
+msgid "Queue created"
+msgstr "Создана очередь"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue is not specified."
+msgstr ""
+
+#: html/SelfService/Display.html:129
+msgid "Queue not found"
+msgstr "Ðет такой очереди"
+
+#: html/Admin/Elements/Tabs:38 html/Admin/index.html:35
+msgid "Queues"
+msgstr "Очереди"
+
+#: html/Elements/Login:34
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "RT %1 Ð´Ð»Ñ %2"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 от <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr ""
+
+#: html/Admin/index.html:25 html/Admin/index.html:26
+msgid "RT Administration"
+msgstr "ÐаÑтройка RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "Ошибка региÑтрации в RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "ПиÑьмо возвращено RT: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "Ошибка конфигурации RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "КритичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ° RT: Сообщение не было Ñохранено!"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:41
+msgid "RT Error"
+msgstr "Ошибка RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT получил Ñвое ÑобÑтвенное Ñообщение (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr ""
+
+#: html/SelfService/Closed.html:25
+msgid "RT Self Service / Closed Tickets"
+msgstr "СамообÑлуживание RT / Закрытые тикеты"
+
+#: html/index.html:25 html/index.html:28
+msgid "RT at a glance"
+msgstr "Обзор RT"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT не может зарегиÑтрировать ваÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT не Ñмог найти проÑÐ¸Ñ‚ÐµÐ»Ñ Ð²Ð¾ внешней базе данных"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT не Ñмог найти очередь: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT не Ñмог проверить Ñту подпиÑÑŒ PGP. \\n"
+
+#: html/Elements/PageLayout:26
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "RT Ð´Ð»Ñ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT выполнил ваши команды"
+
+#: html/Elements/Login:83
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "Ð’Ñе права на RT защищены и охранÑÑŽÑ‚ÑÑ Ð·Ð°ÐºÐ¾Ð½Ð¾Ð¼. &copy; 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. ПО раÑпроÑтранÑетÑÑ Ð¿Ð¾Ð´ <a href=\"http://www.gnu.org/copyleft/gpl.html\">Стандартной ОбщеÑтвенной Лицензией GNU ВерÑии 2.</a>"
+
+#: NOT FOUND IN SOURCE
+msgid "RT is &copy; Copyright 1996-2002 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT Ñчитает, что Ñто Ñообщение может быть возвратом"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT будет обрабатывать Ñто Ñообщение как неподпиÑанное.\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "Командный режим RT требует иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи .PGP. Ð’Ñ‹ либо не подпиÑали Ñообщение, либо ваша подпиÑÑŒ не может быть проверена."
+
+#: html/Admin/Users/Modify.html:58 html/Admin/Users/Prefs.html:52 html/User/Prefs.html:44
+msgid "Real Name"
+msgstr "ИмÑ"
+
+#: html/Admin/Elements/ModifyUser:48
+msgid "RealName"
+msgstr "ИмÑ"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/EditLinks:139 html/Ticket/Elements/EditLinks:94 html/Ticket/Elements/ShowLinks:63
+msgid "Referred to by"
+msgstr "Ðа него ÑÑылаютÑÑ"
+
+#: html/Elements/SelectLinkType:28 html/Ticket/Create.html:184 html/Ticket/Elements/EditLinks:135 html/Ticket/Elements/EditLinks:80 html/Ticket/Elements/ShowLinks:55
+msgid "Refers to"
+msgstr "СÑылаетÑÑ Ð½Ð°"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "Улучшить"
+
+#: html/Search/Elements/PickRestriction:27
+msgid "Refine search"
+msgstr "Улучшить поиÑк"
+
+#: html/Elements/Refresh:36
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "ОбновлÑÑ‚ÑŒ Ñту Ñтраницу каждые %1 минут."
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:60 html/Ticket/ModifyAll.html:57
+msgid "Relationships"
+msgstr "СвÑзи"
+
+#: html/Search/Bulk.html:93
+msgid "Remove AdminCc"
+msgstr "Удалить админиÑтративную копию"
+
+#: html/Search/Bulk.html:91
+msgid "Remove Cc"
+msgstr "Удалить копию"
+
+#: html/Search/Bulk.html:89
+msgid "Remove Requestor"
+msgstr "Удалить проÑителÑ"
+
+#: html/Ticket/Elements/ShowTransaction:173 html/Ticket/Elements/Tabs:122
+msgid "Reply"
+msgstr "Ответить"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Reply to tickets"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "ReplyToTicket"
+msgstr ""
+
+#: etc/initialdata:44 html/Ticket/Update.html:40 lib/RT/ACE_Overlay.pm:87
+msgid "Requestor"
+msgstr "ПроÑитель"
+
+#: html/Search/Elements/PickRestriction:38
+msgid "Requestor email address"
+msgstr "Email проÑителÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr ""
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr ""
+
+#: html/SelfService/Create.html:43 html/SelfService/Display.html:42 html/Ticket/Create.html:56 html/Ticket/Elements/EditPeople:48 html/Ticket/Elements/ShowPeople:31
+msgid "Requestors"
+msgstr "ПроÑители"
+
+#: html/Admin/Elements/ModifyQueue:61 html/Admin/Queues/Modify.html:75
+msgid "Requests should be due in"
+msgstr "ЗапроÑÑ‹ должны быть обработаны за"
+
+#: html/Elements/Submit:62
+msgid "Reset"
+msgstr "ОчиÑтить"
+
+#: html/Admin/Users/Modify.html:159 html/User/Prefs.html:50
+msgid "Residence"
+msgstr "Домашний"
+
+#: html/Ticket/Elements/Tabs:132
+msgid "Resolve"
+msgstr "Закрыть"
+
+#: html/Ticket/Update.html:133
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr ""
+
+#: etc/initialdata:302 html/Elements/SelectDateType:28 lib/RT/Ticket_Overlay.pm:1170
+msgid "Resolved"
+msgstr "Закрыт"
+
+#: html/Search/Bulk.html:123 html/Ticket/ModifyAll.html:73 html/Ticket/Update.html:73
+msgid "Response to requestors"
+msgstr "Ответ проÑителÑм"
+
+#: html/Elements/ListActions:26
+msgid "Results"
+msgstr "Отчет"
+
+#: html/Search/Elements/PickRestriction:105
+msgid "Results per page"
+msgstr "Тикетов на Ñтраницу"
+
+#: html/Admin/Elements/ModifyUser:33 html/Admin/Users/Modify.html:100 html/User/Prefs.html:72
+msgid "Retype Password"
+msgstr "Повторите пароль"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "Право %1 не найдено Ð´Ð»Ñ %2 %3 в рамках %4 (%5)\\n"
+
+#: lib/RT/ACE_Overlay.pm:613
+msgid "Right Delegated"
+msgstr "Право делегировано"
+
+#: lib/RT/ACE_Overlay.pm:303
+msgid "Right Granted"
+msgstr "Право выдано"
+
+#: lib/RT/ACE_Overlay.pm:161
+msgid "Right Loaded"
+msgstr "Право загружено"
+
+#: lib/RT/ACE_Overlay.pm:678 lib/RT/ACE_Overlay.pm:693
+msgid "Right could not be revoked"
+msgstr "Право не может быть отобрано"
+
+#: html/User/Delegation.html:64
+msgid "Right not found"
+msgstr "Право не найдено"
+
+#: lib/RT/ACE_Overlay.pm:543 lib/RT/ACE_Overlay.pm:638
+msgid "Right not loaded."
+msgstr "Право не загружено"
+
+#: lib/RT/ACE_Overlay.pm:689
+msgid "Right revoked"
+msgstr "Право отобрано"
+
+#: html/Admin/Elements/UserTabs:41
+msgid "Rights"
+msgstr "Права"
+
+#: lib/RT/Interface/Web.pm:758
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr ""
+
+#: lib/RT/Interface/Web.pm:791
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr ""
+
+#: html/Admin/Global/GroupRights.html:51 html/Admin/Queues/GroupRights.html:52
+msgid "Roles"
+msgstr "ПÑевдо-группы"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr ""
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "Суб."
+
+#: html/Admin/Queues/People.html:105 html/Ticket/Modify.html:39 html/Ticket/ModifyAll.html:94 html/Ticket/ModifyPeople.html:38
+msgid "Save Changes"
+msgstr "Сохранить изменениÑ"
+
+#: html/Ticket/ModifyLinks.html:39
+msgid "Save changes"
+msgstr "Сохранить изменениÑ"
+
+#: html/Admin/Global/Scrip.html:49
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr ""
+
+#: lib/RT/Scrip_Overlay.pm:176
+msgid "Scrip Created"
+msgstr "Создан Ñкрипт"
+
+#: html/Admin/Elements/EditScrips:84
+msgid "Scrip deleted"
+msgstr "Удален Ñкрипт"
+
+#: html/Admin/Elements/QueueTabs:46 html/Admin/Elements/SystemTabs:33 html/Admin/Global/index.html:41
+msgid "Scrips"
+msgstr "Скрипты"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "Скрипты Ð´Ð»Ñ %1\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "Скрипты, которые дейÑтвуют Ð´Ð»Ñ Ð²Ñех очередей"
+
+#: html/Elements/SimpleSearch:27 html/Search/Elements/PickRestriction:126 html/Ticket/Elements/Tabs:159
+msgid "Search"
+msgstr "ПоиÑк"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "Параметры поиÑка"
+
+#: html/Approvals/Elements/PendingMyApproval:39
+msgid "Search for approvals"
+msgstr "ИÑкать визы"
+
+#: bin/rt-crontool:188
+msgid "Security:"
+msgstr "БезопаÑноÑÑ‚ÑŒ:"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "SeeQueue"
+msgstr ""
+
+#: html/Admin/Groups/index.html:40
+msgid "Select a group"
+msgstr "Выбор группы"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "Выбор очереди"
+
+#: html/Admin/Users/index.html:25 html/Admin/Users/index.html:28
+msgid "Select a user"
+msgstr "Выбор пользователÑ"
+
+#: html/Admin/Global/CustomField.html:38 html/Admin/Global/CustomFields.html:36
+msgid "Select custom field"
+msgstr ""
+
+#: html/Admin/Elements/GroupTabs:52 html/User/Elements/GroupTabs:50
+msgid "Select group"
+msgstr "Выбрать группу"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Select multiple values"
+msgstr "Выбрать неÑколько значений"
+
+#: lib/RT/CustomField_Overlay.pm:352
+msgid "Select one value"
+msgstr "Выбрать одно значение"
+
+#: html/Admin/Elements/QueueTabs:67
+msgid "Select queue"
+msgstr "Выбрать очередь"
+
+#: html/Admin/Global/Scrip.html:37 html/Admin/Global/Scrips.html:36 html/Admin/Queues/Scrip.html:40 html/Admin/Queues/Scrips.html:50
+msgid "Select scrip"
+msgstr "Выбрать Ñкриплет"
+
+#: html/Admin/Global/Template.html:57 html/Admin/Global/Templates.html:36 html/Admin/Queues/Template.html:55
+msgid "Select template"
+msgstr "Выбрать шаблон"
+
+#: html/Admin/Elements/UserTabs:49
+msgid "Select user"
+msgstr "Выбрать пользователÑ"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "SelectMultiple"
+msgstr "Выбрать неÑколько"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectSingle"
+msgstr "Выбрать одно"
+
+#: html/SelfService/index.html:25
+msgid "Self Service"
+msgstr "СамообÑлуживание"
+
+#: etc/initialdata:114
+msgid "Send mail to all watchers"
+msgstr "Отправить Ñообщение вÑем наблюдателÑм"
+
+#: etc/initialdata:110
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "Отправить Ñообщение вÑем наблюдателÑм как \"комментарий\""
+
+#: etc/initialdata:105
+msgid "Send mail to requestors and Ccs"
+msgstr "Отправить Ñообщение вÑем инициаторам запроÑа и CCs"
+
+#: etc/initialdata:100
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:79
+msgid "Sends a message to the requestors"
+msgstr ""
+
+#: etc/initialdata:118 etc/initialdata:122
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr ""
+
+#: etc/initialdata:95
+msgid "Sends mail to the administrative Ccs"
+msgstr ""
+
+#: etc/initialdata:91
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr ""
+
+#: etc/initialdata:83 etc/initialdata:87
+msgid "Sends mail to the owner"
+msgstr ""
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "Сен."
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show approved requests"
+msgstr "Показать завизированные запроÑÑ‹"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show basics"
+msgstr "Показать главное"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show denied requests"
+msgstr "Показать отвергнутые запроÑÑ‹"
+
+#: html/Ticket/Create.html:144 html/Ticket/Create.html:34
+msgid "Show details"
+msgstr "Показать вÑе"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show pending requests"
+msgstr "Показать ожидающие запроÑÑ‹"
+
+#: html/Approvals/Elements/PendingMyApproval:46
+msgid "Show requests awaiting other approvals"
+msgstr "Показать запроÑÑ‹, ждущие других виз"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Show ticket private commentary"
+msgstr "Показать приватные комментарии по тикету"
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "Show ticket summaries"
+msgstr "Показать общую информацию по запроÑу"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ShowACL"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowScrips"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ShowTemplate"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:79
+msgid "ShowTicket"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "ShowTicketComments"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr ""
+
+#: html/Admin/Elements/ModifyUser:39 html/Admin/Users/Modify.html:191 html/Admin/Users/Prefs.html:32 html/SelfService/Prefs.html:37 html/User/Prefs.html:112
+msgid "Signature"
+msgstr "ПодпиÑÑŒ"
+
+#: html/SelfService/Elements/Header:52
+#. ($session{'CurrentUser'}->Name)
+msgid "Signed in as %1"
+msgstr ""
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Single"
+msgstr "Одно значение"
+
+#: html/Elements/Header:51
+msgid "Skip Menu"
+msgstr ""
+
+#: html/Admin/Elements/EditCustomFieldValues:31
+msgid "Sort key"
+msgstr "Ключ Ð´Ð»Ñ Ñортировки"
+
+#: html/Search/Elements/PickRestriction:109
+msgid "Sort results by"
+msgstr "Сортировать по полю"
+
+#: html/Admin/Elements/AddCustomFieldValue:25
+msgid "SortOrder"
+msgstr "ПорÑдок Ñортировки"
+
+#: NOT FOUND IN SOURCE
+msgid "Stalled"
+msgstr "Отложенных"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ñтраница"
+
+#: html/Elements/SelectDateType:27 html/Ticket/Elements/EditDates:32 html/Ticket/Elements/ShowDates:35
+msgid "Started"
+msgstr "ÐачалÑÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "Ðе могу разобрать дату 'ÐачалÑÑ': '%1'"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:27 html/Ticket/Elements/ShowDates:31
+msgid "Starts"
+msgstr "ÐачнетÑÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "ЗапуÑки"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "Ðе могу разобрать дату 'ЗапуÑки': '%1'"
+
+#: html/Admin/Elements/ModifyUser:82 html/Admin/Users/Modify.html:138 html/User/Prefs.html:94
+msgid "State"
+msgstr "СоÑтоÑние"
+
+#: html/Elements/MyRequests:31 html/Elements/MyTickets:31 html/Search/Elements/PickRestriction:74 html/SelfService/Display.html:59 html/SelfService/Elements/MyRequests:29 html/SelfService/Update.html:31 html/Ticket/Create.html:42 html/Ticket/Elements/EditBasics:38 html/Ticket/Elements/ShowBasics:31 html/Ticket/Update.html:60 lib/RT/Ticket_Overlay.pm:1164 lib/RT/Tickets_Overlay.pm:908
+msgid "Status"
+msgstr "СтатуÑ"
+
+#: etc/initialdata:288
+msgid "Status Change"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:530
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½ Ñ %1 на %2"
+
+#: html/Ticket/Elements/Tabs:147
+msgid "Steal"
+msgstr "Отобрать"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "Отобран у %1"
+
+#: html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Search/Bulk.html:126 html/Search/Elements/PickRestriction:43 html/SelfService/Create.html:59 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:35 html/Ticket/Create.html:84 html/Ticket/Elements/EditBasics:28 html/Ticket/ModifyAll.html:79 html/Ticket/Update.html:77 lib/RT/Ticket_Overlay.pm:1160 lib/RT/Tickets_Overlay.pm:987
+msgid "Subject"
+msgstr "Тема"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/Transaction_Overlay.pm:611
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "Тема изменена на %1"
+
+#: html/Elements/Submit:59
+msgid "Submit"
+msgstr "Готово"
+
+#: lib/RT/Group_Overlay.pm:749
+msgid "Succeeded"
+msgstr ""
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "Ð’Ñк."
+
+#: lib/RT/System.pm:54
+msgid "SuperUser"
+msgstr "ÐдминиÑтратор"
+
+#: html/User/Elements/DelegateRights:77
+msgid "System"
+msgstr "СиÑтемные"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:567 lib/RT/Interface/Web.pm:757 lib/RT/Interface/Web.pm:790
+msgid "System Error"
+msgstr "Ошибка ÑиÑтемы"
+
+#: lib/RT/ACE_Overlay.pm:616
+msgid "System error. Right not delegated."
+msgstr "Ошибка ÑиÑтемы. Право не было делегировано."
+
+#: lib/RT/ACE_Overlay.pm:146 lib/RT/ACE_Overlay.pm:223 lib/RT/ACE_Overlay.pm:306 lib/RT/ACE_Overlay.pm:898
+msgid "System error. Right not granted."
+msgstr "Ошибка ÑиÑтемы. Право не было выдано."
+
+#: html/Admin/Global/GroupRights.html:35 html/Admin/Groups/GroupRights.html:37 html/Admin/Queues/GroupRights.html:36
+msgid "System groups"
+msgstr "СиÑтемные группы"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° Ð´Ð»Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ³Ð¾ иÑпользованиÑ"
+
+#: lib/RT/CurrentUser.pm:320
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: html/Ticket/Elements/Tabs:143
+msgid "Take"
+msgstr "ВзÑÑ‚ÑŒ"
+
+#: lib/RT/Transaction_Overlay.pm:575
+msgid "Taken"
+msgstr "ВзÑÑ‚"
+
+#: html/Admin/Elements/EditScrip:81
+msgid "Template"
+msgstr "Шаблон"
+
+#: html/Admin/Global/Template.html:91 html/Admin/Queues/Template.html:90
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "Шаблон #%1"
+
+#: html/Admin/Elements/EditTemplates:89
+msgid "Template deleted"
+msgstr "Шаблон удален"
+
+#: lib/RT/Scrip_Overlay.pm:153
+msgid "Template not found"
+msgstr "Шаблон не найден"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "Шаблон не найден\\n"
+
+#: lib/RT/Template_Overlay.pm:347
+msgid "Template parsed"
+msgstr "Шаблон обработан"
+
+#: html/Admin/Elements/QueueTabs:49 html/Admin/Elements/SystemTabs:36 html/Admin/Global/index.html:45
+msgid "Templates"
+msgstr "Шаблоны"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "Шаблоны Ð´Ð»Ñ %1\\n"
+
+#: lib/RT/Interface/Web.pm:858
+msgid "That is already the current value"
+msgstr "Это уже текущее значение"
+
+#: lib/RT/CustomField_Overlay.pm:178
+msgid "That is not a value for this custom field"
+msgstr "Это поле не может иметь такого значениÑ"
+
+#: lib/RT/Ticket_Overlay.pm:1886
+msgid "That is the same value"
+msgstr "Значение не изменилоÑÑŒ"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "Этот пользователь уже %1 Ð´Ð»Ñ Ñтой очереди"
+
+#: lib/RT/Ticket_Overlay.pm:1434
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "Этот пользователь уже %1 Ð´Ð»Ñ Ñтого тикета"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "Этот пользователь не %1 Ñтой очереди"
+
+#: lib/RT/Ticket_Overlay.pm:1551
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "Этот пользователь не %1 Ñтому тикету"
+
+#: lib/RT/Ticket_Overlay.pm:1882
+msgid "That queue does not exist"
+msgstr "Этой очереди не ÑущеÑтвует"
+
+#: lib/RT/Ticket_Overlay.pm:3143
+msgid "That ticket has unresolved dependencies"
+msgstr "Этот тикет имеет неразрешенные завиÑимоÑти"
+
+#: lib/RT/ACE_Overlay.pm:288 lib/RT/ACE_Overlay.pm:597
+msgid "That user already has that right"
+msgstr "Пользователь уже имеет Ñто право"
+
+#: lib/RT/Ticket_Overlay.pm:2952
+msgid "That user already owns that ticket"
+msgstr "Пользователь уже владеет Ñтим тикетом"
+
+#: lib/RT/Ticket_Overlay.pm:2918
+msgid "That user does not exist"
+msgstr "Пользователь не ÑущеÑтвует"
+
+#: lib/RT/User_Overlay.pm:315
+msgid "That user is already privileged"
+msgstr "Этот пользователь уже имеет вÑе полномочиÑ"
+
+#: lib/RT/User_Overlay.pm:332
+msgid "That user is already unprivileged"
+msgstr "Этот пользователь уже не имеет полномочий"
+
+#: lib/RT/User_Overlay.pm:327
+msgid "That user is now privileged"
+msgstr "Этот пользователь теперь имеет вÑе полномочиÑ"
+
+#: lib/RT/User_Overlay.pm:344
+msgid "That user is now unprivileged"
+msgstr "Этот пользователь теперь не имеет полномочий"
+
+#: lib/RT/Ticket_Overlay.pm:2944
+msgid "That user may not own tickets in that queue"
+msgstr "Этот пользователь не может владеть тикетами из Ñтой очереди"
+
+#: lib/RT/Link_Overlay.pm:206
+msgid "That's not a numerical id"
+msgstr "Это не чиÑловой идентификатор"
+
+#: html/Ticket/Create.html:150 html/Ticket/Elements/ShowSummary:28
+msgid "The Basics"
+msgstr "Главное"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The CC of a ticket"
+msgstr ""
+
+#: lib/RT/ACE_Overlay.pm:89
+msgid "The administrative CC of a ticket"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:2213
+msgid "The comment has been recorded"
+msgstr "ЗапиÑан комментарий"
+
+#: bin/rt-crontool:198
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr ""
+
+#: bin/rt-commit-handler:756 bin/rt-commit-handler:766
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "Эти команды не были иÑполнены:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:861
+msgid "The new value has been set."
+msgstr "Ðовое значение уÑтановлено"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The owner of a ticket"
+msgstr "Владелец тикета"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The requestor of a ticket"
+msgstr "Кто отправил тикет"
+
+#: html/Admin/Elements/EditUserComments:26
+msgid "These comments aren't generally visible to the user"
+msgstr "Эти комментарии не показываютÑÑ Ð¾Ð±Ñ‹ÐºÐ½Ð¾Ð²ÐµÐ½Ð½Ð¾Ð¼Ñƒ пользователю"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "Этот тикет %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:189
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "Этот инÑтрумент позволÑет пользователю запуÑкать некоторые модули Perl из RT."
+
+#: lib/RT/Transaction_Overlay.pm:253
+msgid "This transaction appears to have no content"
+msgstr "Похоже, что Ñта Ñ‚Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ Ð½Ðµ имеет информации"
+
+#: html/Ticket/Elements/ShowRequestor:47
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "%1 тикетов макÑимального приоритета Ñтого пользователÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "25 важнейших тикетов пользователÑ..."
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "Чтв."
+
+#: html/Ticket/ModifyAll.html:25 html/Ticket/ModifyAll.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "Тикет #%1 Обновление вÑего: %2"
+
+#: html/Approvals/Elements/ShowDependency:46
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr ""
+
+#: lib/RT/Ticket_Overlay.pm:608
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "Тикет %1 Ñоздан в очереди '%2'"
+
+#: bin/rt-commit-handler:760
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "Загружен тикет %1\\n"
+
+#: html/Search/Bulk.html:181
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "Тикет %1: %2"
+
+#: html/Ticket/History.html:25 html/Ticket/History.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñ‚Ð¸ÐºÐµÑ‚Ð° # %1 %2"
+
+#: html/SelfService/Display.html:34
+msgid "Ticket Id"
+msgstr "Тикет #"
+
+#: etc/initialdata:303
+msgid "Ticket Resolved"
+msgstr ""
+
+#: html/Search/Elements/PickRestriction:63
+msgid "Ticket attachment"
+msgstr "Ð”Ð»Ñ Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹"
+
+#: lib/RT/Tickets_Overlay.pm:1166
+msgid "Ticket content"
+msgstr "ТекÑÑ‚ тикета"
+
+#: lib/RT/Tickets_Overlay.pm:1212
+msgid "Ticket content type"
+msgstr "Тип данных тикета"
+
+#: lib/RT/Ticket_Overlay.pm:495 lib/RT/Ticket_Overlay.pm:597
+msgid "Ticket could not be created due to an internal error"
+msgstr "Тикет не может быть Ñоздан из-за внутренней ошибки"
+
+#: lib/RT/Transaction_Overlay.pm:522
+msgid "Ticket created"
+msgstr "Создан тикет"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "Ðе удалоÑÑŒ Ñоздать тикет"
+
+#: lib/RT/Transaction_Overlay.pm:527
+msgid "Ticket deleted"
+msgstr "Тикет удален"
+
+#: html/REST/1.0/modify:29 html/REST/1.0/update:34
+msgid "Ticket id not found"
+msgstr "Идентификатор тикета не найден"
+
+#: html/REST/1.0/modify:36 html/REST/1.0/update:41
+msgid "Ticket not found"
+msgstr "Тикет не найден"
+
+#: etc/initialdata:289
+msgid "Ticket status changed"
+msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ñ‚Ð¸ÐºÐµÑ‚Ð° изменен"
+
+#: html/Ticket/Update.html:39
+msgid "Ticket watchers"
+msgstr "Ðаблюдатели Ð´Ð»Ñ Ñ‚Ð¸ÐºÐµÑ‚Ð°"
+
+#: html/Elements/Tabs:49
+msgid "Tickets"
+msgstr "Тикеты"
+
+#: lib/RT/Tickets_Overlay.pm:1383
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr ""
+
+#: lib/RT/Tickets_Overlay.pm:1348
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr ""
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Tickets from %1"
+msgstr "Тикеты от %1"
+
+#: html/Approvals/Elements/ShowDependency:27
+msgid "Tickets which depend on this approval:"
+msgstr "От Ñтой визы завиÑÑÑ‚ Ñледующие тикеты:"
+
+#: html/Ticket/Create.html:157 html/Ticket/Elements/EditBasics:48
+msgid "Time Left"
+msgstr "ОÑталоÑÑŒ"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:43
+msgid "Time Worked"
+msgstr "В работе"
+
+#: lib/RT/Tickets_Overlay.pm:1139
+msgid "Time left"
+msgstr "ОÑталоÑÑŒ"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°"
+
+#: lib/RT/Tickets_Overlay.pm:1115
+msgid "Time worked"
+msgstr "В работе"
+
+#: lib/RT/Ticket_Overlay.pm:1165
+msgid "TimeWorked"
+msgstr "В работе"
+
+#: bin/rt-commit-handler:402
+msgid "To generate a diff of this commit:"
+msgstr "Ð”Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ изменений Ñтого коммита:"
+
+#: bin/rt-commit-handler:391
+msgid "To generate a diff of this commit:\\n"
+msgstr "Ð”Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ изменений Ñтого коммита:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1168
+msgid "Told"
+msgstr "Контакт"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "ТранзакциÑ"
+
+#: lib/RT/Transaction_Overlay.pm:642
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ %1 удалена"
+
+#: lib/RT/Transaction_Overlay.pm:177
+msgid "Transaction Created"
+msgstr "Создана транзакциÑ"
+
+#: lib/RT/Transaction_Overlay.pm:89
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr ""
+
+#: lib/RT/Transaction_Overlay.pm:701
+msgid "Transactions are immutable"
+msgstr "Транзакции не изменены"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "ПытаемÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ право: %1"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "Ð’Ñ‚Ñ€."
+
+#: html/Admin/Elements/EditCustomField:34 html/Ticket/Elements/AddWatchers:33 html/Ticket/Elements/AddWatchers:44 html/Ticket/Elements/AddWatchers:54 lib/RT/Ticket_Overlay.pm:1166 lib/RT/Tickets_Overlay.pm:959
+msgid "Type"
+msgstr "Тип"
+
+#: lib/RT/ScripCondition_Overlay.pm:104
+msgid "Unimplemented"
+msgstr "Ðе реализовано"
+
+#: html/Admin/Users/Modify.html:68
+msgid "Unix login"
+msgstr "Логин UNIX"
+
+#: html/Admin/Elements/ModifyUser:62
+msgid "UnixUsername"
+msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ UNIX"
+
+#: lib/RT/Attachment_Overlay.pm:265
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° %1"
+
+#: html/Elements/SelectResultsPerPage:37
+msgid "Unlimited"
+msgstr "Ðе ограничено"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "Ðепривилегированный"
+
+#: lib/RT/Transaction_Overlay.pm:571
+msgid "Untaken"
+msgstr "Ðичей"
+
+#: html/Elements/MyTickets:64 html/Search/Bulk.html:33
+msgid "Update"
+msgstr "Обновить"
+
+#: html/Admin/Users/Prefs.html:62
+msgid "Update ID"
+msgstr "Обновить идентификатор"
+
+#: html/Search/Bulk.html:120 html/Ticket/ModifyAll.html:66 html/Ticket/Update.html:67
+msgid "Update Type"
+msgstr "Обновить тип"
+
+#: html/Search/Listing.html:61
+msgid "Update all these tickets at once"
+msgstr "Изменить одним махом"
+
+#: html/Admin/Users/Prefs.html:49
+msgid "Update email"
+msgstr "Обновить e-mail"
+
+#: html/Admin/Users/Prefs.html:55
+msgid "Update name"
+msgstr "Обновить имÑ"
+
+#: lib/RT/Interface/Web.pm:375
+msgid "Update not recorded."
+msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ Ñохранены."
+
+#: html/Search/Bulk.html:81
+msgid "Update selected tickets"
+msgstr "Изменить выбранные тикеты"
+
+#: html/Admin/Users/Prefs.html:36
+msgid "Update signature"
+msgstr "Обновить подпиÑÑŒ"
+
+#: html/Ticket/ModifyAll.html:63
+msgid "Update ticket"
+msgstr "Обновить тикет"
+
+#: html/SelfService/Update.html:25 html/SelfService/Update.html:27
+#. ($Ticket->id)
+msgid "Update ticket # %1"
+msgstr "Обновить тикет # %1"
+
+#: html/SelfService/Update.html:50
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "Обновить тикет #%1"
+
+#: html/Ticket/Update.html:135
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "Обновить тикет #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:373
+msgid "Update type was neither correspondence nor comment."
+msgstr "Обновление не было ни Ñообщением, ни комментарием."
+
+#: html/Elements/SelectDateType:33 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1169
+msgid "Updated"
+msgstr "Обновлен"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "Пользователь %1 %2: %3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "Пользователь %1 Пароль: %2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "Пользователь '%1' не найден"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "Пользователь '%1' не найден\\n"
+
+#: etc/initialdata:125 etc/initialdata:191
+msgid "User Defined"
+msgstr ""
+
+#: html/Admin/Users/Prefs.html:59
+msgid "User ID"
+msgstr "Логин"
+
+#: html/Elements/SelectUsers:26
+msgid "User Id"
+msgstr "Логин"
+
+#: html/Admin/Elements/GroupTabs:47 html/Admin/Elements/QueueTabs:60 html/Admin/Elements/SystemTabs:47 html/Admin/Global/index.html:59
+msgid "User Rights"
+msgstr "Права пользователÑ"
+
+#: html/Admin/Users/Modify.html:226
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "Ðе могу Ñоздать пользователÑ: %1"
+
+#: lib/RT/User_Overlay.pm:262
+msgid "User created"
+msgstr "Создан пользователь"
+
+#: html/Admin/Global/GroupRights.html:67 html/Admin/Groups/GroupRights.html:54 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "Группы, определенные пользователем"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "Пользователю отоÑлано напоминание"
+
+#: html/Admin/Users/Prefs.html:25 html/Admin/Users/Prefs.html:29
+msgid "User view"
+msgstr "ПользовательÑкие наÑтройки"
+
+#: html/Admin/Users/Modify.html:48 html/Elements/Login:42 html/Ticket/Elements/AddWatchers:35
+msgid "Username"
+msgstr "Логин"
+
+#: html/Admin/Elements/SelectNewGroupMembers:26 html/Admin/Elements/Tabs:32 html/Admin/Groups/Members.html:55 html/Admin/Queues/People.html:68 html/Admin/index.html:29 html/User/Groups/Members.html:58
+msgid "Users"
+msgstr "Пользователи"
+
+#: html/Admin/Users/index.html:65
+msgid "Users matching search criteria"
+msgstr "Ðайдены пользователи"
+
+#: html/Search/Elements/PickRestriction:51
+msgid "ValueOfQueue"
+msgstr "ValueOfQueue"
+
+#: html/Admin/Elements/EditCustomField:40
+msgid "Values"
+msgstr "ЗначениÑ"
+
+#: NOT FOUND IN SOURCE
+msgid "VrijevormEnkele"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Watch"
+msgstr ""
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "WatchAsAdminCc"
+msgstr ""
+
+#: html/Admin/Elements/QueueTabs:42
+msgid "Watchers"
+msgstr "Ðаблюдатели"
+
+#: html/Admin/Elements/ModifyUser:56
+msgid "WebEncoding"
+msgstr "WebEncoding"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "Срд."
+
+#: etc/upgrade/2.1.71:161
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/upgrade/2.1.71:135
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr ""
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr ""
+
+#: etc/upgrade/2.1.71:79
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr ""
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr ""
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr ""
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr ""
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr ""
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr ""
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr ""
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr ""
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr ""
+
+#: html/Admin/Users/Modify.html:164 html/User/Prefs.html:52
+msgid "Work"
+msgstr "Рабочий"
+
+#: html/Admin/Elements/ModifyUser:70
+msgid "WorkPhone"
+msgstr "Рабочий"
+
+#: html/SelfService/Display.html:86 html/Ticket/Elements/ShowBasics:35 html/Ticket/Update.html:65
+msgid "Worked"
+msgstr "В работе"
+
+#: lib/RT/Ticket_Overlay.pm:3056
+msgid "You already own this ticket"
+msgstr "Ð’Ñ‹ уже владеете Ñтим тикетом"
+
+#: html/autohandler:121
+msgid "You are not an authorized user"
+msgstr "Вам Ñюда запрещено"
+
+#: lib/RT/Ticket_Overlay.pm:2930
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "Ð’Ñ‹ можете назначать владельца только Ð´Ð»Ñ Ñвоих или ничьих тикетов."
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "У Ð²Ð°Ñ Ð½ÐµÑ‚ права на проÑмотр Ñтого тикета.\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "You found %1 tickets in queue %2"
+
+#: html/NoAuth/Logout.html:31 html/REST/1.0/logout:25
+msgid "You have been logged out of RT."
+msgstr "Вы вышли из RT."
+
+#: html/SelfService/Display.html:134
+msgid "You have no permission to create tickets in that queue."
+msgstr "У Ð²Ð°Ñ Ð½ÐµÑ‚ права Ñоздавать тикеты в Ñтой очереди."
+
+#: lib/RT/Ticket_Overlay.pm:1895
+msgid "You may not create requests in that queue."
+msgstr "Ð’Ñ‹ не можете Ñоздавать запроÑÑ‹ в Ñтой очереди."
+
+#: html/NoAuth/Logout.html:36
+msgid "You're welcome to login again"
+msgstr "Заходите еще"
+
+#: html/SelfService/Elements/MyRequests:25
+#. ($friendly_status)
+msgid "Your %1 requests"
+msgstr "Ваши запроÑÑ‹: %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "ÐдминиÑтратор RT неправильно наÑтроил почтовые алиаÑÑ‹"
+
+#: etc/initialdata:429 etc/upgrade/2.1.71:146
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "Ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¸Ð» %1. Другие Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ продолжать находитьÑÑ Ð² ожидании."
+
+#: etc/initialdata:463 etc/upgrade/2.1.71:180
+msgid "Your request has been approved."
+msgstr "Ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½."
+
+#: etc/initialdata:384 etc/upgrade/2.1.71:101
+msgid "Your request was rejected."
+msgstr "Ваш Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð±Ñ‹Ð» отвергнут."
+
+#: html/autohandler:136 html/autohandler:142
+msgid "Your username or password is incorrect"
+msgstr "Ð’Ñ‹ ввели неверное Ð¸Ð¼Ñ Ð¸Ð»Ð¸ пароль"
+
+#: html/Admin/Elements/ModifyUser:84 html/Admin/Users/Modify.html:144 html/User/Prefs.html:96
+msgid "Zip"
+msgstr "ИндекÑ"
+
+#: html/User/Elements/DelegateRights:59
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "Ñ Ð¿Ñ€Ð°Ð²Ð°Ð¼Ð¸ %1"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:34
+msgid "contains"
+msgstr "Ñодержит"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content"
+msgstr "данные"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "content-type"
+msgstr "тип данных"
+
+#: lib/RT/Ticket_Overlay.pm:2282
+msgid "correspondence (probably) not sent"
+msgstr "Ñообщение (возможно) не отправлено"
+
+#: lib/RT/Ticket_Overlay.pm:2292
+msgid "correspondence sent"
+msgstr "отправлено Ñообщение"
+
+#: html/Admin/Elements/ModifyQueue:63 html/Admin/Queues/Modify.html:77 lib/RT/Date.pm:319
+msgid "days"
+msgstr "дней"
+
+#: html/Search/Listing.html:75
+msgid "delete"
+msgstr "удалить"
+
+#: lib/RT/Queue_Overlay.pm:63
+msgid "deleted"
+msgstr "удален"
+
+#: html/Search/Elements/PickRestriction:68
+msgid "does not match"
+msgstr "не Ñовпадает"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:35
+msgid "doesn't contain"
+msgstr "не Ñодержит"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "equal to"
+msgstr "равнÑетÑÑ"
+
+#: html/Elements/SelectAttachmentField:28
+msgid "filename"
+msgstr "Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "greater than"
+msgstr "больше чем"
+
+#: lib/RT/Group_Overlay.pm:194
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "группа '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "чаÑов"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "идентификатор"
+
+#: html/Elements/SelectBoolean:32 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88
+msgid "is"
+msgstr "ÑвлÑетÑÑ"
+
+#: html/Elements/SelectBoolean:36 html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectMatch:37 html/Search/Elements/PickRestriction:48 html/Search/Elements/PickRestriction:77 html/Search/Elements/PickRestriction:89
+msgid "isn't"
+msgstr "не ÑвлÑетÑÑ"
+
+#: html/Elements/SelectCustomFieldOperator:38 html/Elements/SelectEqualityOperator:38
+msgid "less than"
+msgstr "меньше чем"
+
+#: html/Search/Elements/PickRestriction:67
+msgid "matches"
+msgstr "Ñовпадает"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "мин"
+
+#: html/Ticket/Update.html:66
+msgid "minutes"
+msgstr "минут"
+
+#: bin/rt-commit-handler:765
+msgid "modifications\\n\\n"
+msgstr "изменениÑ\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "меÑÑцев"
+
+#: lib/RT/Queue_Overlay.pm:58
+msgid "new"
+msgstr "новый"
+
+#: html/Admin/Elements/EditScrips:43
+msgid "no value"
+msgstr ""
+
+#: html/Ticket/Elements/EditWatchers:28
+msgid "none"
+msgstr "нет"
+
+#: html/Elements/SelectEqualityOperator:38
+msgid "not equal to"
+msgstr "не равен"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "open"
+msgstr "открыт"
+
+#: lib/RT/Group_Overlay.pm:199
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "Ð»Ð¸Ñ‡Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° '%1' Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ '%2'"
+
+#: lib/RT/Group_Overlay.pm:207
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "очередь %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "rejected"
+msgstr "отклонен"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "resolved"
+msgstr "решен"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "Ñек"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "stalled"
+msgstr "отложен"
+
+#: lib/RT/Group_Overlay.pm:202
+#. ($self->Type)
+msgid "system %1"
+msgstr "ÑиÑтема %1"
+
+#: lib/RT/Group_Overlay.pm:213
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:42
+msgid "the calling component did not specify why"
+msgstr "вызывающий компонент не указал причину"
+
+#: lib/RT/Group_Overlay.pm:210
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "тикет #%1 %2"
+
+#: lib/RT/Group_Overlay.pm:216
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "неопиÑÐ°Ð½Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° %1"
+
+#: lib/RT/Group_Overlay.pm:191
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "пользователь %1"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "недель"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ð¾Ð¼ %1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "лет"
diff --git a/rt/lib/RT/I18N/zh_cn.po b/rt/lib/RT/I18N/zh_cn.po
new file mode 100644
index 0000000..0f0b8da
--- /dev/null
+++ b/rt/lib/RT/I18N/zh_cn.po
@@ -0,0 +1,6677 @@
+# Chinese localization catalog for Request Tracker (RT)
+msgid ""
+msgstr ""
+"Last-Translator: Autrijus Tang <autrijus@autrijus.org>\n"
+"Language-Team: Chinese <members@ourinet.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:27 html/Elements/MyTickets:27 html/Work/Elements/MyApprovals:8 html/Work/Elements/MyRequests:15 html/Work/Elements/MyTickets:15
+msgid "#"
+msgstr "#"
+
+#: NOT FOUND IN SOURCE
+msgid "#%1"
+msgstr "#%1"
+
+#: html/Approvals/Elements/Approve:26 html/Approvals/Elements/ShowDependency:49 html/SelfService/Display.html:24 html/Ticket/Display.html:25 html/Ticket/Display.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,group ticket)"
+msgstr "%*(%1) 件å‚与的申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,ticket) due"
+msgstr "%*(%1) 件é™æœŸå®Œæˆçš„申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,unresolved ticket)"
+msgstr "%*(%1) 件尚未解决的申请å•"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:790
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%7-%2-%3 %4:%5:%6 %1"
+
+#: lib/RT/Ticket_Overlay.pm:3588 lib/RT/Transaction_Overlay.pm:514 lib/RT/Transaction_Overlay.pm:557 lib/RT/Transaction_Vendor.pm:19
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+#. ($field, $new_value)
+msgid "%1 %2 added"
+msgstr "%2 已新增为 %1"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 之å‰"
+
+#: lib/RT/Ticket_Overlay.pm:3594 lib/RT/Transaction_Overlay.pm:521 lib/RT/Transaction_Vendor.pm:25
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+#. ($field, $old_value, $new_value)
+msgid "%1 %2 changed to %3"
+msgstr "%1 已从 %2 改为 %3"
+
+#: lib/RT/Ticket_Overlay.pm:3591 lib/RT/Transaction_Overlay.pm:517 lib/RT/Transaction_Overlay.pm:563 lib/RT/Transaction_Vendor.pm:22
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+#. ($field, $old_value)
+msgid "%1 %2 deleted"
+msgstr "%2 已自 %1 删除"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:157
+#. ($depth_str, $role_str, $group_str)
+msgid "%1 %2 of group %3"
+msgstr "%3 群组的 %1 %2"
+
+#: html/Admin/Elements/EditScrips:43 html/Admin/Elements/ListGlobalScrips:27
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "æ¡ä»¶ï¼š%1 | 动作:%2 | 模æ¿ï¼š%3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 这份申请å•\\n"
+
+#: html/Search/Listing.html:56 html/Work/Search/index.html:28
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "显示第 %1 - %2 笔"
+
+#: bin/rt-crontool:168 bin/rt-crontool:175 bin/rt-crontool:181
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - 传递给 %2 的一个å‚æ•°"
+
+#: bin/rt-crontool:184
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - 将更新状æ€è¾“出到 STDOUT"
+
+#: bin/rt-crontool:178
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - 指定欲使用的动作模å—"
+
+#: bin/rt-crontool:172
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - 指定欲使用的æ¡ä»¶æ¨¡å—"
+
+#: bin/rt-crontool:165
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - 指定欲使用的查询模å—"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "加载手续 %1"
+
+#: html/Edit/Elements/Page:49
+#. (scalar $count)
+msgid "%1 Total"
+msgstr "共 %1 笔"
+
+#: lib/RT/Ticket_Overlay.pm:3621
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "新增 %1 作为 %2 的值"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "别å %1 需è¦å¯ç”¨çš„申请å•ç¼–å·"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "别å %1 需è¦å¯ç”¨çš„申请å•ç¼–å· "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "别å %1 需è¦å¯ç”¨çš„申请å•ç¼–å·ä»¥å¤„ç† %3(出自 %2)"
+
+#: lib/RT/Link_Overlay.pm:116 lib/RT/Link_Overlay.pm:123
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 看æ¥æ˜¯ä¸ªæœ¬åœ°å¯¹è±¡ï¼Œå´ä¸åœ¨æ•°æ®åº“里"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:430
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 (%2)"
+
+#: lib/RT/Transaction_Overlay.pm:484 lib/RT/Transaction_Overlay.pm:649 lib/RT/Transaction_Overlay.pm:658 lib/RT/Transaction_Overlay.pm:661
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 的值从 %2 改为 %3"
+
+#: lib/RT/Interface/Web.pm:953
+msgid "%1 could not be set to %2."
+msgstr "无法将 %1 设定为 %2。"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 无法åˆå§‹æ›´æ–° (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2880
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 无法将现况设æˆå·²è§£å†³ã€‚RT æ•°æ®åº“内容å¯èƒ½ä¸ä¸€è‡´ã€‚"
+
+#: html/Elements/MyTickets:24 html/Work/Elements/MyTickets:9
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "å‰ %1 份待处ç†ç”³è¯·å•..."
+
+#: html/Elements/MyRequests:24 html/Work/Elements/MyRequests:9
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "å‰ %1 份é€å‡ºçš„申请å•..."
+
+#: html/Work/Elements/MyApprovals:5
+#. ($rows)
+msgid "%1 highest priority tickets pending my approval..."
+msgstr "å‰ %1 份待签核申请å•..."
+
+#: bin/rt-crontool:160
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 是从外部排程程åº(如 cron)æ¥å¯¹ç”³è¯·å•è¿›è¡Œæ“作的工具。"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 å·²ä¸å†æ˜¯æ­¤è¡¨å•çš„ %2。"
+
+#: lib/RT/Ticket_Overlay.pm:1596
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 å·²ä¸å†æ˜¯æ­¤ç”³è¯·å•çš„ %2。"
+
+#: lib/RT/Ticket_Overlay.pm:3677
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 å·²ä¸å†æ˜¯è‡ªè®¢å­—段 %2 的值。"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 ä¸æ˜¯ä¸€ä¸ªåˆæ³•çš„表å•ç¼–å·ã€‚"
+
+#: html/Ticket/Elements/ShowBasics:35
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 分钟"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "没有显示 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 result(s) found"
+msgstr "找到 %1 项结果"
+
+#: html/User/Elements/DelegateRights:75
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 完æˆ\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "ä¸çŸ¥é“ $MessageID çš„ %1 类别"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "ä¸çŸ¥é“ %2 çš„ %1 类别"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 新增时未指定现行使用者"
+
+#: lib/RT/Action/ResolveMembers.pm:41
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 会解决在已解决群组里æˆå‘˜çš„申请å•ã€‚"
+
+#: lib/RT/Action/StallDependent.pm:40
+#. (ref $self)
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "如果 %1 起始申请å•ä¾èµ–于æŸä¸ªé“¾æŽ¥ï¼Œæˆ–是æŸä¸ªé“¾æŽ¥çš„æˆå‘˜ï¼Œå®ƒå°†ä¼šè¢«å»¶å®•ã€‚"
+
+#: lib/RT/Transaction_Overlay.pm:382 lib/RT/Transaction_Vendor.pm:37
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1:未指定附件"
+
+#: html/Ticket/Elements/ShowTransaction:100 html/Work/Tickets/Elements/ShowTransaction:158
+#. ($size)
+msgid "%1b"
+msgstr "%1 字节"
+
+#: html/Ticket/Elements/ShowTransaction:97 html/Work/Tickets/Elements/ShowTransaction:155
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k 字节"
+
+#: NOT FOUND IN SOURCE
+msgid "%quant(%1,result) found"
+msgstr "找到 %1 项结果"
+
+#: lib/RT/Ticket_Overlay.pm:1185
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' ä¸æ˜¯ä¸€ä¸ªåˆæ³•çš„状æ€å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1'为无法辨识的动作。 "
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(点选欲删除的æˆå‘˜)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(点选欲删除的手续)"
+
+#: html/Admin/Elements/EditCustomFieldValues:24 html/Admin/Elements/EditQueueWatchers:28 html/Admin/Elements/EditScrips:34 html/Admin/Elements/EditTemplates:35 html/Admin/Elements/EditWorkflows:36 html/Admin/Groups/Members.html:51 html/Ticket/Elements/EditLinks:32 html/Ticket/Elements/EditPeople:45 html/User/Groups/Members.html:54 html/Work/Tickets/Elements/EditLinks:20 html/Work/Tickets/Elements/EditPeople:36
+msgid "(Check box to delete)"
+msgstr "(点选欲删除的项目)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(点选欲删除的项目)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(键入申请å•ç¼–å·æˆ–网å€ï¼Œä»¥ç©ºç™½åˆ†éš”)"
+
+#: html/Admin/Queues/Modify.html:53 html/Admin/Queues/Modify.html:59
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(如果留白, 则预设为 %1)"
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(没有值)"
+
+#: html/Admin/Elements/EditCustomFields:32 html/Admin/Elements/ListGlobalCustomFields:31
+msgid "(No custom fields)"
+msgstr "(没有自订字段)"
+
+#: html/Admin/Groups/Members.html:49 html/User/Groups/Members.html:52
+msgid "(No members)"
+msgstr "(没有æˆå‘˜)"
+
+#: html/Admin/Elements/EditScrips:31 html/Admin/Elements/ListGlobalScrips:31
+msgid "(No scrips)"
+msgstr "(没有手续)"
+
+#: html/Admin/Elements/EditTemplates:30
+msgid "(No templates)"
+msgstr "没有模æ¿"
+
+#: html/Admin/Elements/EditWorkflows:31
+msgid "(No workflows)"
+msgstr "没有æµç¨‹"
+
+#: html/Ticket/Update.html:83 html/Work/Tickets/Update.html:56
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„密件副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<b>ä¸ä¼š</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„密件副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<b>ä¸ä¼š</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: html/Ticket/Create.html:78
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的管ç†å‘˜ç”µå­é‚®ä»¶åœ°å€ã€‚è¿™<b>将会</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: html/Ticket/Update.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<b>ä¸ä¼š</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<b>ä¸ä¼š</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: html/Ticket/Create.html:68
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本给åå•ä¸Šä»¥é€—å·éš”开的电å­é‚®ä»¶åœ°å€ã€‚è¿™<b>将会</b>更改åŽç»­çš„收件者åå•ã€‚)"
+
+#: html/Ticket/Elements/EditCustomFieldEntries:35 html/Work/Tickets/Elements/EditCustomFieldEntries:43 html/Work/Tickets/Elements/ShowCustomFieldEntries:14
+msgid "(delete)"
+msgstr "(删除)"
+
+#: html/Admin/Groups/index.html:32 html/User/Groups/index.html:32
+msgid "(empty)"
+msgstr "(空白)"
+
+#: html/Edit/Elements/Index:87 html/Edit/Global/CustomField/index.html:116 html/Edit/Global/Scrip/index.html:111 html/Edit/Global/Template/index.html:106
+msgid "(new)"
+msgstr "(新增)"
+
+#: html/Admin/Users/index.html:38
+msgid "(no name listed)"
+msgstr "(没有列出姓å)"
+
+#: html/Elements/MyRequests:42 html/Elements/MyTickets:44 html/Work/Elements/MyApprovals:37 html/Work/Elements/MyRequests:43 html/Work/Elements/MyTickets:52
+msgid "(no subject)"
+msgstr "(没有主题)"
+
+#: html/Admin/Elements/SelectRights:47 html/Elements/SelectCustomFieldValue:29 html/Ticket/Elements/EditCustomField:64 html/Ticket/Elements/EditCustomFieldValues:52 html/Ticket/Elements/ShowCustomFields:35 html/Work/Elements/EditCustomFieldValues:50 html/Work/Elements/EditCustomFields:32 html/Work/Tickets/Elements/EditCustomFieldValues:33 lib/RT/Transaction_Overlay.pm:483
+msgid "(no value)"
+msgstr "(æ— )"
+
+#: html/Ticket/Elements/BulkLinks:27 html/Ticket/Elements/EditLinks:98 html/Work/Search/BulkLinks:3 html/Work/Tickets/Elements/EditLinks:102
+msgid "(only one ticket)"
+msgstr "(仅能指定一份申请å•)"
+
+#: html/Elements/MyRequests:51 html/Elements/MyTickets:54 html/Work/Elements/List:17 html/Work/Elements/MyRequests:53 html/Work/Elements/MyTickets:67 html/Work/Tickets/Elements/ShowBasics:52
+msgid "(pending approval)"
+msgstr "(等待签核)"
+
+#: html/Elements/MyRequests:53 html/Elements/MyTickets:56 html/Work/Elements/MyRequests:55 html/Work/Elements/MyTickets:69
+msgid "(pending other tickets)"
+msgstr "(等待其它申请å•)"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:246
+msgid "(requestor's group)"
+msgstr "(申请人所属)"
+
+#: html/Admin/Users/Modify.html:49 html/Edit/Users/Info:26
+msgid "(required)"
+msgstr "(å¿…å¡«)"
+
+#: html/Ticket/Elements/ShowTransaction:103 html/Work/Tickets/Elements/ShowTransaction:44
+msgid "(untitled)"
+msgstr "(未命å)"
+
+#: NOT FOUND IN SOURCE
+msgid ":"
+msgstr ":"
+
+#: html/Ticket/Elements/ShowBasics:31
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:26
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:25 html/Work/Elements/104Header:43 lib/RT/StyleGuide.pod:767
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"æ出申请å•\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "空白模æ¿"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE 已删除"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE 已加载"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "无法删除 ACE"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "找ä¸åˆ° ACE"
+
+#: lib/RT/ACE_Overlay.pm:156 lib/RT/Principal_Overlay.pm:180
+msgid "ACE not found"
+msgstr "找ä¸åˆ° ACE 设定"
+
+#: lib/RT/ACE_Overlay.pm:830
+msgid "ACEs can only be created and deleted."
+msgstr "祇能新增或删除 ACE 设定。"
+
+#: NOT FOUND IN SOURCE
+msgid "ACLEquivalence"
+msgstr "ACLEquivalence"
+
+#: bin/rt-commit-handler:754
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "离开以å…ä¸å°å¿ƒæ›´æ”¹åˆ°ç”³è¯·å•ã€‚\\n"
+
+#: html/User/Elements/Tabs:31
+msgid "About me"
+msgstr "个人信æ¯"
+
+#: html/Edit/Users/System:12
+msgid "Access Right"
+msgstr "系统使用登录æƒé™"
+
+#: html/Admin/Users/Modify.html:79
+msgid "Access control"
+msgstr "å­˜å–æƒé™"
+
+#: html/Admin/Elements/EditScrip:56 html/Work/Tickets/Elements/ShowTransaction:21
+msgid "Action"
+msgstr "动作"
+
+#: lib/RT/Scrip_Overlay.pm:148
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "动作 %1 找ä¸åˆ°"
+
+#: bin/rt-crontool:122
+msgid "Action committed."
+msgstr "动作执行完毕"
+
+#: bin/rt-crontool:118
+msgid "Action prepared..."
+msgstr "动作准备完毕..."
+
+#: html/Work/Elements/List:13 html/Work/Elements/SelectSearch:25 html/Work/Tickets/Create.html:27 html/Work/Tickets/Elements/ShowBasics:12
+msgid "Activated Date"
+msgstr "申请激活时间"
+
+#: html/Edit/Elements/104Buttons:82 html/Edit/Elements/ListButtons:7
+msgid "Add"
+msgstr "新增"
+
+#: html/Search/Bulk.html:95 html/Work/Search/Bulk.html:74
+msgid "Add AdminCc"
+msgstr "新增管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Search/Bulk.html:91 html/Work/Search/Bulk.html:68
+msgid "Add Cc"
+msgstr "新增副本收件人"
+
+#: html/Ticket/Elements/EditCustomFieldEntries:71 html/Work/Tickets/Elements/ShowCustomFieldEntries:50
+msgid "Add Entry"
+msgstr "新增列"
+
+#: html/Ticket/Create.html:113 html/Ticket/Update.html:98 html/Work/Tickets/Elements/AddAttachments:23
+msgid "Add More Files"
+msgstr "新增更多附件"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:112 html/Admin/Elements/ModifyTemplateAsWorkflow:45
+msgid "Add Next State"
+msgstr "新增下一项关å¡"
+
+#: html/Search/Bulk.html:87 html/Work/Search/Bulk.html:62
+msgid "Add Requestor"
+msgstr "新增申请人"
+
+#: html/Admin/Elements/AddCustomFieldValue:24
+msgid "Add Value"
+msgstr "新增字段值"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr "新增此表å•çš„手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr "新增适用于所有表å•çš„手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "新增此表å•çš„关键è¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "新增全域手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "新增一é“手续到此表å•"
+
+#: html/Admin/Global/Scrip.html:54
+msgid "Add a scrip which will apply to all queues"
+msgstr "新增一é“用于所有表å•çš„手续"
+
+#: html/Search/Bulk.html:127 html/Work/Search/Bulk.html:80
+msgid "Add comments or replies to selected tickets"
+msgstr "新增评论或回å¤åˆ°æŒ‡å®šçš„申请å•"
+
+#: html/Admin/Groups/Members.html:41 html/User/Groups/Members.html:38
+msgid "Add members"
+msgstr "新增æˆå‘˜"
+
+#: html/Admin/Queues/People.html:65 html/Ticket/Elements/AddWatchers:27
+msgid "Add new watchers"
+msgstr "新增视察员"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "新增下一项关å¡"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "å•ä½å·²æ–°å¢žä¸ºæ­¤è¡¨å•çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1480
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "å•ä½å·²æ–°å¢žä¸ºæ­¤ç”³è¯·å•çš„ %1"
+
+#: html/Edit/Global/CustomField/Top:52
+msgid "Additional Hints"
+msgstr "é¢å¤–æ示"
+
+#: html/Admin/Elements/ModifyUser:75 html/Admin/Users/Modify.html:121 html/User/Prefs.html:114 html/Work/Preferences/Info:79
+msgid "Address1"
+msgstr "ä½å€"
+
+#: html/Admin/Elements/ModifyUser:77 html/Admin/Users/Modify.html:126 html/User/Prefs.html:118 html/Work/Preferences/Info:81
+msgid "Address2"
+msgstr "ä½å€(ç»­)"
+
+#: NOT FOUND IN SOURCE
+msgid "Adjust Blinking Rate"
+msgstr "调整闪çƒé€Ÿåº¦å¿«æ…¢"
+
+#: html/Edit/Queues/List:12
+msgid "Admin"
+msgstr "管ç†å‘˜"
+
+#: html/Ticket/Create.html:73
+msgid "Admin Cc"
+msgstr "管ç†å‘˜å‰¯æœ¬"
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr "管ç†å‘˜è¯„论"
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr "管ç†å‘˜å›žå¤"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin Rights"
+msgstr "管ç†å‘˜æƒé™"
+
+#: html/Admin/Queues/index.html:24 html/Admin/Queues/index.html:27
+msgid "Admin queues"
+msgstr "表å•ç®¡ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "使用者管ç†"
+
+#: html/Admin/Global/index.html:25 html/Admin/Global/index.html:27
+msgid "Admin/Global configuration"
+msgstr "管ç†/全域设定"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "管ç†/群组"
+
+#: html/Admin/Queues/Modify.html:24 html/Admin/Queues/Modify.html:28
+msgid "Admin/Queue/Basics"
+msgstr "管ç†/表å•/基本信æ¯"
+
+#: html/Edit/Global/Basic/Top:65
+msgid "AdminAddress"
+msgstr "管ç†å‘˜ Email"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "管ç†æ‰€æœ‰ä»£ç†äººç¾¤ç»„"
+
+#: etc/initialdata:56 html/Admin/Elements/ModifyTemplateAsWorkflow:155 html/Ticket/Elements/ShowPeople:38 html/Ticket/Update.html:49 html/Work/Tickets/Elements/ShowLinks:11 lib/RT/ACE_Overlay.pm:88
+msgid "AdminCc"
+msgstr "管ç†å‘˜å‰¯æœ¬"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "管ç†å‘˜è¯„论"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "管ç†å‘˜å›žå¤"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "AdminCustomFields"
+msgstr "管ç†è‡ªè®¢å­—段"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "AdminGroup"
+msgstr "管ç†ç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupDescription"
+msgstr "管ç†ç¾¤ç»„æè¿°"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "AdminGroupMembership"
+msgstr "管ç†ç¾¤ç»„æˆå‘˜"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupName"
+msgstr "管ç†ç¾¤ç»„å称"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupPermission"
+msgstr "管ç†ç¾¤ç»„æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupStatus"
+msgstr "管ç†ç¾¤ç»„状æ€"
+
+#: lib/RT/System.pm:58
+msgid "AdminOwnPersonalGroups"
+msgstr "管ç†ä»£ç†äººç¾¤ç»„"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "AdminQueue"
+msgstr "管ç†è¡¨å•"
+
+#: lib/RT/System.pm:59
+msgid "AdminUsers"
+msgstr "管ç†ä½¿ç”¨è€…"
+
+#: NOT FOUND IN SOURCE
+msgid "Administrative"
+msgstr "行政类"
+
+#: html/Admin/Queues/People.html:47 html/Ticket/Elements/EditPeople:53 html/Work/Tickets/Elements/EditPeople:44
+msgid "Administrative Cc"
+msgstr "管ç†å‘˜å‰¯æœ¬"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:233
+msgid "Admins"
+msgstr "主管"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "进阶查询"
+
+#: html/Elements/SelectDateRelation:35
+msgid "After"
+msgstr "晚于"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "ç»åŽ†æ—¶é—´"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:172 html/Edit/Global/Workflow/Action:35
+msgid "Alias"
+msgstr "执行其它æµç¨‹"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:175
+msgid "Alias for"
+msgstr "相当于"
+
+#: html/Work/Delegates/index.html:13 html/Work/Elements/SelectSearch:11 html/Work/Queues/Select.html:14 html/Work/Queues/index.html:13
+msgid "All"
+msgstr "全部"
+
+#: etc/initialdata:348
+msgid "All Approvals Passed"
+msgstr "完æˆå…¨éƒ¨ç­¾æ ¸"
+
+#: html/Edit/Global/Workflow/Condition:16
+msgid "All Condition"
+msgstr "所有æ¡ä»¶"
+
+#: html/Admin/Elements/EditCustomFields:94
+msgid "All Custom Fields"
+msgstr "所有自订字段"
+
+#: html/Admin/Queues/index.html:52
+msgid "All Queues"
+msgstr "所有表å•"
+
+#: NOT FOUND IN SOURCE
+msgid "All Users"
+msgstr "全体员工"
+
+#: NOT FOUND IN SOURCE
+msgid "All done! Now you can proceed to %1."
+msgstr "处ç†å®Œæ¯•ï¼æ‚¨çŽ°åœ¨å¯ä»¥ç»§ç»­è¿›è¡Œ %1。"
+
+#: NOT FOUND IN SOURCE
+msgid "Allowance Request"
+msgstr "ç¦åˆ©è¡¥åŠ©ç”³è¯·"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "无论寄件æ¥æºä¸ºä½•ï¼Œä¸€å¾‹å¯„信给申请人"
+
+#: NOT FOUND IN SOURCE
+msgid "Amount"
+msgstr "æ•°é¢"
+
+#: html/Edit/Global/Workflow/Condition:13
+msgid "Any Condition"
+msgstr "ä»»æ„æ¡ä»¶"
+
+#: html/Edit/Global/Scrip/List:10 html/Edit/Global/Scrip/Top:86
+msgid "Apply Template"
+msgstr "引用模æ¿"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:138 html/Elements/Tabs:55 html/Work/Approvals/Elements/Approve:6
+msgid "Approval"
+msgstr "签核"
+
+#: html/Approvals/Display.html:45 html/Approvals/Elements/ShowDependency:41 html/Approvals/index.html:64
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "ç­¾æ ¸å• #%1:%2"
+
+#: html/Approvals/index.html:53
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "ç­¾æ ¸å• #%1:系统错误,记录失败"
+
+#: html/Approvals/index.html:51
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "ç­¾æ ¸å• #%1:记录完毕"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:123
+msgid "Approval Details"
+msgstr "签核细节"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Due"
+msgstr "签核时é™"
+
+#: html/Work/Approvals/Elements/Approve:37
+msgid "Approval Notes"
+msgstr "签核æ„è§"
+
+#: etc/initialdata:336
+msgid "Approval Passed"
+msgstr "完æˆæŸé¡¹ç­¾æ ¸"
+
+#: etc/initialdata:359
+msgid "Approval Rejected"
+msgstr "驳回æŸé¡¹ç­¾æ ¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Result"
+msgstr "签核结果"
+
+#: html/Work/Approvals/Elements/Approve:25
+msgid "Approval Status"
+msgstr "核准结果"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Type"
+msgstr "签核ç§ç±»"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:25
+msgid "Approval diagram"
+msgstr "签核æµç¨‹"
+
+#: html/Approvals/Elements/Approve:43 html/Work/Approvals/Elements/Approve:29
+msgid "Approve"
+msgstr "核准"
+
+#: html/Work/Approvals/Elements/Approve:21 html/Work/Elements/List:9
+msgid "Approver"
+msgstr "签核人"
+
+#: html/Edit/Global/Workflow/Action:25 html/Edit/Global/Workflow/Owner.html:10
+msgid "Approver Setting"
+msgstr "执行签核人设定"
+
+#: etc/initialdata:486 etc/upgrade/2.1.71:148 html/Edit/Elements/CreateApprovalsQueue:122
+msgid "Approver's notes: %1"
+msgstr "签核备注:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Apr"
+msgstr "四月"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "04"
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "四月"
+
+#: html/Edit/Elements/104Buttons:24
+msgid "Are you sure to delete checked items?"
+msgstr "您确定è¦åˆ é™¤ï¼Ÿ"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Ascending"
+msgstr "递增"
+
+#: html/Search/Bulk.html:136 html/SelfService/Update.html:47 html/Ticket/ModifyAll.html:82 html/Ticket/Update.html:98 html/Work/Search/Bulk.html:88
+msgid "Attach"
+msgstr "附件"
+
+#: html/SelfService/Create.html:64 html/Ticket/Create.html:109 html/Work/Tickets/Elements/AddAttachments:19
+msgid "Attach file"
+msgstr "附加档案"
+
+#: html/SelfService/Update.html:36 html/Ticket/Create.html:97 html/Ticket/Update.html:87 html/Work/Tickets/Elements/AddAttachments:7 html/Work/Tickets/Elements/ShowAttachments:9
+msgid "Attached file"
+msgstr "现有附件"
+
+#: NOT FOUND IN SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr "无法加载附件 '%1'"
+
+#: lib/RT/Transaction_Overlay.pm:390 lib/RT/Transaction_Vendor.pm:50
+msgid "Attachment created"
+msgstr "附件新增完毕"
+
+#: lib/RT/Tickets_Overlay.pm:1208
+msgid "Attachment filename"
+msgstr "附件档å"
+
+#: html/Ticket/Elements/ShowAttachments:25 html/Work/Tickets/Elements/ShowTransaction:37
+msgid "Attachments"
+msgstr "附件"
+
+#: NOT FOUND IN SOURCE
+msgid "Aug"
+msgstr "八月"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "08"
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "八月"
+
+#: html/Admin/Elements/ModifyUser:65
+msgid "AuthSystem"
+msgstr "认è¯æ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoReject"
+msgstr "自动驳回表å•"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoResolve"
+msgstr "自动完æˆè¡¨å•å¤„ç†"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "自动回å¤"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "自动对申请人回å¤"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "自动对申请人回å¤"
+
+#: html/Edit/Rights/index.html:17
+msgid "Available Rights:"
+msgstr "æƒé™é¡¹ç›®åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Back to Homepage"
+msgstr "回到首页"
+
+#: html/Work/Elements/BackButton:2 html/Work/Search/Bulk.html:101
+msgid "Back to Previous"
+msgstr "回上页"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "错误的 PGP 签章:%1\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "错误的附件编å·ã€‚无法找到附件 '%1'\\n"
+
+#: bin/rt-commit-handler:826
+#. ($val)
+msgid "Bad data in %1"
+msgstr "%1 çš„æ•°æ®é”™è¯¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "附件的处ç†å·ç é”™è¯¯ã€‚%1 应为 %2\\n"
+
+#: html/Admin/Elements/GroupTabs:38 html/Admin/Elements/QueueTabs:38 html/Admin/Elements/UserTabs:37 html/Edit/Global/autohandler:6 html/Edit/Queues/autohandler:21 html/Edit/Users/index.html:94 html/Ticket/Elements/Tabs:89 html/User/Elements/GroupTabs:37
+msgid "Basics"
+msgstr "基本信æ¯"
+
+#: html/Ticket/Update.html:81 html/Work/Tickets/Update.html:53
+msgid "Bcc"
+msgstr "密件副本"
+
+#: html/Admin/Elements/EditScrip:95 html/Admin/Global/GroupRights.html:84 html/Admin/Global/Template.html:45 html/Admin/Global/UserRights.html:53 html/Admin/Global/Workflow.html:46 html/Admin/Groups/GroupRights.html:72 html/Admin/Groups/Members.html:80 html/Admin/Groups/Modify.html:55 html/Admin/Groups/UserRights.html:54 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:44 html/Admin/Queues/UserRights.html:53 html/Admin/Queues/Workflow.html:44 html/User/Groups/Modify.html:55
+msgid "Be sure to save your changes"
+msgstr "请别忘了储存修改。"
+
+#: html/Elements/SelectDateRelation:33 lib/RT/CurrentUser.pm:320
+msgid "Before"
+msgstr "早于"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:44
+msgid "Begin Approval"
+msgstr "开始签核"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin From "
+msgstr "起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Birthday"
+msgstr "生日"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "空白模æ¿"
+
+#: html/Search/Listing.html:78 html/Work/Search/index.html:53
+msgid "Bookmarkable URL for this search"
+msgstr "将查询结果转为å¯æ”¾å…¥ä¹¦ç­¾çš„网å€"
+
+#: html/Ticket/Elements/ShowHistory:38 html/Ticket/Elements/ShowHistory:44
+msgid "Brief headers"
+msgstr "精简标头档"
+
+#: html/Search/Bulk.html:24 html/Search/Bulk.html:25
+msgid "Bulk ticket update"
+msgstr "更新整批申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Business Unit"
+msgstr "事业部"
+
+#: NOT FOUND IN SOURCE
+msgid "Business Unit:"
+msgstr "事业部:"
+
+#: lib/RT/User_Overlay.pm:1529
+msgid "Can not modify system users"
+msgstr "无法更改系统使用者"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "Can this principal see this queue"
+msgstr "该å•ä½æ˜¯å¦èƒ½æŸ¥é˜…此表å•"
+
+#: lib/RT/CustomField_Overlay.pm:205
+msgid "Can't add a custom field value without a name"
+msgstr "ä¸èƒ½æ–°å¢žæ²¡æœ‰å称的自订字段值"
+
+#: lib/RT/Link_Overlay.pm:131
+msgid "Can't link a ticket to itself"
+msgstr "申请å•ä¸èƒ½é“¾æŽ¥è‡ªå·±ã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:2857
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "ä¸èƒ½æ•´åˆè¿›å·²æ•´åˆè¿‡çš„申请å•ã€‚这个错误ä¸è¯¥å‘生。"
+
+#: lib/RT/Ticket_Overlay.pm:2659 lib/RT/Ticket_Overlay.pm:2738
+msgid "Can't specifiy both base and target"
+msgstr "ä¸èƒ½åŒæ—¶æŒ‡å®šèµ·å§‹ç”³è¯·å•ä¸Žç›®çš„申请å•"
+
+#: html/Edit/Elements/PopFooter:8
+msgid "Cancel"
+msgstr "å–消"
+
+#: html/autohandler:126
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "无法新增使用者:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Card No."
+msgstr "å¡å·"
+
+#: NOT FOUND IN SOURCE
+msgid "Categories"
+msgstr "分类管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Category"
+msgstr "分类"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:43 html/SelfService/Create.html:48 html/Ticket/Create.html:63 html/Ticket/Elements/EditPeople:50 html/Ticket/Elements/ShowPeople:34 html/Ticket/Update.html:44 html/Ticket/Update.html:76 html/Work/Tickets/Elements/EditPeople:41 html/Work/Tickets/Elements/ShowLinks:6 html/Work/Tickets/Update.html:43 lib/RT/ACE_Overlay.pm:87
+msgid "Cc"
+msgstr "副本"
+
+#: NOT FOUND IN SOURCE
+msgid "Cc Type"
+msgstr "副本类别"
+
+#: NOT FOUND IN SOURCE
+msgid "Chairperson's Office"
+msgstr "董事长室"
+
+#: NOT FOUND IN SOURCE
+msgid "Change Ticket"
+msgstr "修改申请å•"
+
+#: html/SelfService/Prefs.html:30
+msgid "Change password"
+msgstr "更改å£ä»¤"
+
+#: html/Edit/Global/Basic/Top:79
+msgid "ChangeOwnerUI"
+msgstr "å¯å¦é€‰æ‹©è¡¨å•æ‰¿åŠžäºº"
+
+#: html/SelfService/Update.html:39 html/Ticket/Create.html:100 html/Ticket/Elements/EditCustomFieldEntries:35 html/Ticket/Update.html:90 html/Work/Tickets/Elements/ShowCustomFieldEntries:14
+msgid "Check box to delete"
+msgstr "选择欲删除的项目"
+
+#: html/Admin/Elements/SelectRights:30
+msgid "Check box to revoke right"
+msgstr "选择欲撤消的æƒåˆ©"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/BulkLinks:42 html/Ticket/Elements/EditLinks:113 html/Ticket/Elements/EditLinks:63 html/Ticket/Elements/ShowLinks:56 html/Work/Search/BulkLinks:18 html/Work/Tickets/Elements/EditLinks:117 html/Work/Tickets/Elements/EditLinks:56 html/Work/Tickets/Elements/ShowMembers:4
+msgid "Children"
+msgstr "å­ç”³è¯·å•"
+
+#: html/Edit/Elements/PickUsers:21 html/Edit/Global/UserRight/List:8 html/Edit/Global/UserRight/Top:19
+msgid "Chinese Name"
+msgstr "中文姓å"
+
+#: NOT FOUND IN SOURCE
+msgid "Chinese/English"
+msgstr "中英文"
+
+#: html/Admin/Elements/ModifyUser:79 html/Admin/Users/Modify.html:131 html/User/Prefs.html:122 html/Work/Preferences/Info:83
+msgid "City"
+msgstr "所在城市"
+
+#: html/Edit/Elements/104Top:30
+msgid "ClassicUI"
+msgstr "传统接å£"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "已解决"
+
+#: html/SelfService/Closed.html:24
+msgid "Closed Tickets"
+msgstr "已解决的申请å•"
+
+#: html/SelfService/Elements/Tabs:44
+msgid "Closed tickets"
+msgstr "已解决的申请å•"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:181 html/Edit/Global/Workflow/Action:54 html/Edit/Global/Workflow/Condition:52
+msgid "Code"
+msgstr "执行程åºç "
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "指令无法辨识ï¼\\n"
+
+#: html/Ticket/Elements/ShowTransaction:178 html/Ticket/Elements/Tabs:152 html/Work/Search/Bulk.html:89 html/Work/Tickets/Display.html:60 html/Work/Tickets/Elements/ShowTransaction:118 html/Work/Tickets/Elements/ShowTransaction:32
+msgid "Comment"
+msgstr "评论"
+
+#: html/Admin/Elements/ModifyQueue:44 html/Admin/Queues/Modify.html:57
+msgid "Comment Address"
+msgstr "评论电å­é‚®ä»¶åœ°å€"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "评论未被纪录"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Comment on tickets"
+msgstr "对申请å•æ出评论"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "CommentOnTicket"
+msgstr "评论申请å•"
+
+#: html/Admin/Elements/ModifyUser:34 html/Work/Tickets/Elements/AddContent:7
+msgid "Comments"
+msgstr "评论"
+
+#: html/Ticket/ModifyAll.html:69 html/Ticket/Update.html:68 html/Work/Tickets/Update.html:35
+msgid "Comments (Not sent to requestors)"
+msgstr "评论(ä¸é€ç»™ç”³è¯·äºº)"
+
+#: html/Search/Bulk.html:131 html/Work/Search/Bulk.html:83
+msgid "Comments (not sent to requestors)"
+msgstr "评论(ä¸é€ç»™ç”³è¯·äºº)"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Comments about %1"
+msgstr "对 %1 的评论"
+
+#: html/Admin/Users/Modify.html:184 html/Edit/Users/Info:46 html/Ticket/Elements/ShowRequestor:43
+msgid "Comments about this user"
+msgstr "使用者æè¿°"
+
+#: lib/RT/Transaction_Overlay.pm:501
+msgid "Comments added"
+msgstr "新增评论完毕"
+
+#: html/Edit/Elements/PopFooter:4 html/Edit/Elements/PopFooter:6
+msgid "Commit"
+msgstr "确认"
+
+#: lib/RT/Action/Generic.pm:139
+msgid "Commit Stubbed"
+msgstr "消除更动完毕"
+
+#: NOT FOUND IN SOURCE
+msgid "Company Name"
+msgstr "å…¬å¸å称"
+
+#: html/Edit/Global/Basic/Top:85
+msgid "CompanySpecific"
+msgstr "å„å…¬å¸ç‹¬ç«‹æ˜¾ç¤º"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "设定查询æ¡ä»¶"
+
+#: html/Admin/Elements/EditScrip:40 html/Admin/Elements/ModifyTemplateAsWorkflow:127
+msgid "Condition"
+msgstr "æ¡ä»¶"
+
+#: bin/rt-crontool:108
+msgid "Condition matches..."
+msgstr "符åˆæ¡ä»¶..."
+
+#: lib/RT/Scrip_Overlay.pm:164
+msgid "Condition not found"
+msgstr "未找到符åˆçš„现况"
+
+#: html/Edit/Global/GroupRight/Top:26 html/Edit/Global/UserRight/Top:45 html/Edit/Groups/Member:56 html/Elements/Tabs:49
+msgid "Configuration"
+msgstr "设定"
+
+#: html/SelfService/Prefs.html:32
+msgid "Confirm"
+msgstr "确认å£ä»¤"
+
+#: NOT FOUND IN SOURCE
+msgid "Confirm Password"
+msgstr "å£ä»¤ç¡®è®¤"
+
+#: html/Work/Approvals/Display.html:25 html/Work/Tickets/Create.html:154 html/Work/Tickets/Create.html:168 html/Work/Tickets/Update.html:77
+msgid "Confirm Submit"
+msgstr "确定é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "Contact System Administrator"
+msgstr "连络系统管ç†å‘˜"
+
+#: html/Admin/Elements/ModifyUser:59
+msgid "ContactInfoSystem"
+msgstr "连络信æ¯ç³»ç»Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "无法解读è”络日期 '%1'"
+
+#: html/Admin/Elements/ModifyTemplate:43 html/Admin/Elements/ModifyTemplateAsWorkflow:200 html/Ticket/ModifyAll.html:86
+msgid "Content"
+msgstr "内容"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "无法新增群组"
+
+#: html/Edit/Elements/104Buttons:85
+msgid "Copy"
+msgstr "å¤åˆ¶"
+
+#: NOT FOUND IN SOURCE
+msgid "Copy Field From:"
+msgstr "欲å¤åˆ¶å­—段:"
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr "回å¤"
+
+#: html/Admin/Elements/ModifyQueue:38 html/Admin/Queues/Modify.html:50
+msgid "Correspondence Address"
+msgstr "申请å•å›žå¤åœ°å€"
+
+#: lib/RT/Transaction_Overlay.pm:497
+msgid "Correspondence added"
+msgstr "新增申请å•å›žå¤"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "未纪录申请å•å›žå¤"
+
+#: lib/RT/Ticket_Overlay.pm:3608
+msgid "Could not add new custom field value for ticket. "
+msgstr "ä¸èƒ½æ–°å¢žè‡ªè®¢å­—段的值 "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "ä¸èƒ½æ–°å¢žè‡ªè®¢å­—段的值。%1 "
+
+#: lib/RT/Ticket_Overlay.pm:3108 lib/RT/Ticket_Overlay.pm:3116 lib/RT/Ticket_Overlay.pm:3133
+msgid "Could not change owner. "
+msgstr "ä¸èƒ½æ›´æ”¹æ‰¿åŠžäººã€‚ "
+
+#: html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditCustomFields:164 html/Edit/Global/CustomField/index.html:120
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "无法新增自订字段"
+
+#: html/Edit/Global/Workflow/index.html:126
+#. ($msg)
+msgid "Could not create Scrip"
+msgstr "无法建立讯æ¯é€šçŸ¥"
+
+#: html/Edit/Global/Template/index.html:110
+#. ($msg)
+msgid "Could not create Template"
+msgstr "无法建立通知模æ¿"
+
+#: html/User/Groups/Modify.html:76 lib/RT/Group_Overlay.pm:471 lib/RT/Group_Overlay.pm:478
+msgid "Could not create group"
+msgstr "无法新增群组"
+
+#: html/Edit/Elements/Index:89
+#. ($msg)
+msgid "Could not create item"
+msgstr "无法新增项目"
+
+#: html/Admin/Global/Template.html:74 html/Admin/Queues/Template.html:71
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "无法新增模æ¿ï¼š%1"
+
+#: lib/RT/Ticket_Overlay.pm:1118 lib/RT/Ticket_Overlay.pm:353
+msgid "Could not create ticket. Queue not set"
+msgstr "无法新增申请å•ã€‚尚未指定表å•ã€‚"
+
+#: lib/RT/User_Overlay.pm:271 lib/RT/User_Overlay.pm:284 lib/RT/User_Overlay.pm:302 lib/RT/User_Overlay.pm:488
+msgid "Could not create user"
+msgstr "无法新增使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "无法为申请人新增视察员"
+
+#: html/Admin/Elements/ModifyWorkflow:219 html/Admin/Global/Workflow.html:75 html/Admin/Queues/Workflow.html:71
+#. ($msg)
+msgid "Could not create workflow: %1"
+msgstr "无法新增æµç¨‹ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "找ä¸åˆ°ç¼–å· %1 的申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "找ä¸åˆ°ç¾¤ç»„ %1。"
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1448
+msgid "Could not find or create that user"
+msgstr "找ä¸åˆ°æˆ–无法新增该å使用者"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1527
+msgid "Could not find that principal"
+msgstr "找ä¸åˆ°è¯¥å•ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… %1。"
+
+#: html/Admin/Groups/Members.html:87 html/User/Groups/Members.html:89 html/User/Groups/Modify.html:81
+msgid "Could not load group"
+msgstr "无法加载群组"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "无法将该å•ä½è®¾ä¸ºæ­¤è¡¨å•çš„ %1。"
+
+#: lib/RT/Ticket_Overlay.pm:1469
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "无法将该å•ä½è®¾ä¸ºæ­¤ç”³è¯·å•çš„ %1。"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "无法将å•ä½ %1 从表å•ç§»é™¤ã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:1585
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "无法将å•ä½ %1 从申请å•ç§»é™¤ã€‚"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Couldn't add member to group"
+msgstr "无法新增æˆå‘˜è‡³ç¾¤ç»„"
+
+#: lib/RT/Ticket_Overlay.pm:3618 lib/RT/Ticket_Overlay.pm:3674
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "无法新增更动报告"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "无法从 gpg 回函辨识出该采å–的行动\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "找ä¸åˆ°ç¾¤ç»„\\n"
+
+#: lib/RT/Interface/Web.pm:962
+msgid "Couldn't find row"
+msgstr "找ä¸åˆ°æ­¤åˆ—æ•°æ®"
+
+#: lib/RT/Group_Overlay.pm:956
+msgid "Couldn't find that principal"
+msgstr "找ä¸åˆ°è¯¥å•ä½"
+
+#: lib/RT/CustomField_Overlay.pm:239
+msgid "Couldn't find that value"
+msgstr "找ä¸åˆ°è¯¥å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "找ä¸åˆ°è¯¥è§†å¯Ÿå‘˜"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€…\\n"
+
+#: lib/RT/CurrentUser.pm:111
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "无法从使用者数æ®åº“加载 %1。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "无法加载 KeywordSelects。"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "无法加载 RT 设定档 '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "无法加载手续。"
+
+#: html/Admin/Groups/GroupRights.html:87 html/Admin/Groups/UserRights.html:74 html/Edit/Global/GroupRight/Add.html:55 html/Edit/Global/GroupRight/Add.html:60 html/Edit/Global/UserRight/Add.html:25 html/Edit/Global/UserRight/Add.html:30 html/Edit/Groups/Member:120 html/Edit/Groups/Members/Add.html:43 html/Edit/Rights/index.html:58 html/Edit/Rights/index.html:63
+#. ($ObjectGroup)
+#. ($Report)
+#. ($Group)
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "无法加载手续 %1"
+
+#: lib/RT/Link_Overlay.pm:174 lib/RT/Link_Overlay.pm:183 lib/RT/Link_Overlay.pm:210
+msgid "Couldn't load link"
+msgstr "无法加载链接。"
+
+#: html/Admin/Elements/EditCustomFields:145 html/Admin/Queues/CustomFields.html:35 html/Admin/Queues/People.html:120
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "无法加载表å•"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:71 html/Edit/Global/GroupRight/Add.html:51 html/Edit/Global/GroupRight/index.html:82 html/Edit/Global/GroupRight/index.html:87 html/Edit/Global/UserRight/Add.html:21 html/Edit/Global/UserRight/index.html:83 html/Edit/Global/UserRight/index.html:88 html/Edit/Rights/index.html:54
+#. ($Queue)
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "æ— æ³•åŠ è½½è¡¨å• %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "无法加载手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "无法加载模æ¿"
+
+#: html/Admin/Users/Prefs.html:78
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "无法加载该å使用者(%1)"
+
+#: html/SelfService/Display.html:114
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "æ— æ³•åŠ è½½ç”³è¯·å• '%1'"
+
+#: html/Admin/Elements/ModifyUser:85 html/Admin/Users/Modify.html:148 html/User/Prefs.html:134 html/Work/Preferences/Info:89
+msgid "Country"
+msgstr "国家"
+
+#: html/Admin/Elements/CreateUserCalled:25 html/Edit/Elements/PopHeader:33 html/Edit/Global/GroupRight/Add.html:19 html/Ticket/Create.html:134 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "新增"
+
+#: html/Edit/Groups/MemberGroups/Add.html:17
+msgid "Create Subgroup:"
+msgstr "新增å­ç¾¤ç»„:"
+
+#: etc/initialdata:127
+msgid "Create Tickets"
+msgstr "新增申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Create User:"
+msgstr "新增æˆå‘˜ï¼š"
+
+#: html/Admin/Elements/EditCustomField:74
+msgid "Create a CustomField"
+msgstr "新增自订字段"
+
+#: html/Admin/Queues/CustomField.html:47
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "为 %1 表å•æ–°å¢žè‡ªè®¢å­—段"
+
+#: html/Admin/Global/CustomField.html:47
+msgid "Create a CustomField which applies to all queues"
+msgstr "为 %1 表å•æ–°å¢žè‡ªè®¢å­—段"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "新增自订字段"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr "新增全域手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "新增全域手续"
+
+#: html/Admin/Groups/Modify.html:66 html/Admin/Groups/Modify.html:92
+msgid "Create a new group"
+msgstr "新增群组"
+
+#: html/User/Groups/Modify.html:66 html/User/Groups/Modify.html:91
+msgid "Create a new personal group"
+msgstr "新增代ç†äººç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "新增表å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "新增手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "新增模æ¿"
+
+#: html/Ticket/Create.html:24 html/Ticket/Create.html:27 html/Ticket/Create.html:35
+msgid "Create a new ticket"
+msgstr "新增申请å•"
+
+#: html/Admin/Users/Modify.html:213 html/Admin/Users/Modify.html:242
+msgid "Create a new user"
+msgstr "新增使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new workflow"
+msgstr "新增æµç¨‹"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "新增表å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "新增表å•å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a request"
+msgstr "æ出申请"
+
+#: html/Admin/Queues/Scrip.html:58
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "为 %1 表å•æ–°å¢žæ‰‹ç»­"
+
+#: html/Admin/Global/Template.html:68 html/Admin/Queues/Template.html:64
+msgid "Create a template"
+msgstr "新增模æ¿"
+
+#: html/SelfService/Create.html:24
+msgid "Create a ticket"
+msgstr "æ出申请å•"
+
+#: html/Admin/Elements/ModifyWorkflow:206 html/Admin/Global/Workflow.html:69 html/Admin/Queues/Workflow.html:64
+msgid "Create a workflow"
+msgstr "新增æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "新增失败:%1 / %2 / %3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "新增失败:%1/%2/%3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create new item"
+msgstr "建立新项目"
+
+#: etc/initialdata:129
+msgid "Create new tickets based on this scrip's template"
+msgstr "ä¾æ®æ­¤é¡¹æ‰‹ç»­å†…的模版,新增申请å•"
+
+#: html/SelfService/Create.html:77
+msgid "Create ticket"
+msgstr "新增申请å•"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Create tickets in this queue"
+msgstr "在此表å•ä¸­æ–°å¢žç”³è¯·å•"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Create, delete and modify custom fields"
+msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹è‡ªè®¢å­—段"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Create, delete and modify queues"
+msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹è¡¨å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹ä»»ä½•ä½¿ç”¨è€…的代ç†äººç¾¤ç»„"
+
+#: lib/RT/System.pm:58
+msgid "Create, delete and modify the members of personal groups"
+msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹ä»£ç†äººç¾¤ç»„"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify users"
+msgstr "新增ã€åˆ é™¤åŠæ›´æ”¹ä½¿ç”¨è€…"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "CreateTicket"
+msgstr "新增申请å•"
+
+#: html/Elements/SelectDateType:25 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1212
+msgid "Created"
+msgstr "新增日"
+
+#: html/Admin/Elements/EditCustomField:87
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "自订字段 %1 新增æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "æ¨¡æ¿ %1 新增æˆåŠŸ"
+
+#: html/Admin/Elements/ModifyWorkflow:221
+#. (loc( $WorkflowObj->Name() ))
+msgid "Created workflow %1"
+msgstr "æµç¨‹ %1 新增æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Currency"
+msgstr "å¸åˆ«"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Approval Info"
+msgstr "截至目å‰ç­¾æ ¸ä¿¡æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Custom Fields"
+msgstr "现有自订字段"
+
+#: html/Edit/Groups/MemberGroups/Add.html:14
+msgid "Current Groups:"
+msgstr "现有群组列表:"
+
+#: html/Ticket/Elements/EditLinks:27 html/Work/Tickets/Elements/EditLinks:10
+msgid "Current Relationships"
+msgstr "现有关系"
+
+#: html/Edit/Rights/index.html:20
+msgid "Current Rights:"
+msgstr "现有æƒé™ï¼š"
+
+#: html/Admin/Elements/EditScrips:29
+msgid "Current Scrips"
+msgstr "现有手续"
+
+#: html/Work/Tickets/Create.html:49 html/Work/Tickets/Elements/ShowBasics:47
+msgid "Current Status"
+msgstr "ç›®å‰çŠ¶æ€"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Templates"
+msgstr "现有模æ¿"
+
+#: html/Work/Tickets/Elements/EditPeople:9
+msgid "Current Watchers"
+msgstr "现有视察员"
+
+#: html/Admin/Groups/Members.html:38 html/User/Groups/Members.html:41
+msgid "Current members"
+msgstr "现有æˆå‘˜"
+
+#: html/Admin/Elements/SelectRights:28
+msgid "Current rights"
+msgstr "现有æƒé™"
+
+#: html/Search/Listing.html:70 html/Work/Search/index.html:42
+msgid "Current search criteria"
+msgstr "现有查询æ¡ä»¶"
+
+#: html/Admin/Queues/People.html:40 html/Ticket/Elements/EditPeople:44 html/Work/Tickets/Elements/EditPeople:32
+msgid "Current watchers"
+msgstr "现有视察员"
+
+#: html/Admin/Global/CustomField.html:54
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "自订字段 #%1"
+
+#: html/Admin/Elements/QueueTabs:52 html/Admin/Elements/SystemTabs:39 html/Admin/Global/index.html:49 html/Edit/Global/autohandler:7 html/Edit/Queues/autohandler:22 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "自订字段"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom Fields which apply to all queues"
+msgstr "适用于所有表å•çš„自订字段"
+
+#: html/Admin/Elements/EditScrip:72 html/Edit/Global/Scrip/Top:69
+msgid "Custom action cleanup code"
+msgstr "动作åŽæ‰§è¡Œç¨‹åº"
+
+#: html/Admin/Elements/EditScrip:64 html/Edit/Global/Scrip/Top:62
+msgid "Custom action preparation code"
+msgstr "动作å‰æ‰§è¡Œç¨‹åº"
+
+#: html/Admin/Elements/EditScrip:48 html/Edit/Global/Scrip/Top:35 html/Edit/Global/Scrip/Top:61
+msgid "Custom condition"
+msgstr "自订æ¡ä»¶"
+
+#: lib/RT/Tickets_Overlay.pm:1637
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "自订字段 %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1632
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "自订字段 %1 已有值"
+
+#: lib/RT/Tickets_Overlay.pm:1629
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "自订字段 %1 没有值"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "找ä¸åˆ°è‡ªè®¢å­—段 %1"
+
+#: html/Admin/Elements/EditCustomFields:195
+msgid "Custom field deleted"
+msgstr "自订字段已删除"
+
+#: lib/RT/Ticket_Overlay.pm:3660
+msgid "Custom field not found"
+msgstr "找ä¸åˆ°è‡ªè®¢å­—段"
+
+#: lib/RT/CustomField_Overlay.pm:349
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "无法从自订字段 %2 中找到 %1 这个字段值"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "自订字段值从 %1 改为 %2"
+
+#: lib/RT/CustomField_Overlay.pm:249
+msgid "Custom field value could not be deleted"
+msgstr "无法删除自订字段值"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Custom field value could not be found"
+msgstr "找ä¸åˆ°è‡ªè®¢å­—段值"
+
+#: lib/RT/CustomField_Overlay.pm:247 lib/RT/CustomField_Overlay.pm:357
+msgid "Custom field value deleted"
+msgstr "自订字段值删除æˆåŠŸ"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:145 html/Edit/Global/Workflow/Owner.html:90 lib/RT/Transaction_Overlay.pm:505 lib/RT/Transaction_Vendor.pm:5
+msgid "CustomField"
+msgstr "自订字段"
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "æ•°æ®é”™è¯¯"
+
+#: html/Edit/Global/Basic/Top:77
+msgid "DatabaseBindRemote"
+msgstr "容许外部è”机"
+
+#: html/Edit/Global/Basic/Top:75
+msgid "DatabaseName"
+msgstr "MySQLæ•°æ®åº“"
+
+#: NOT FOUND IN SOURCE
+msgid "Date of Departure"
+msgstr "出å‘日期"
+
+#: html/SelfService/Display.html:38 html/Ticket/Create.html:160 html/Ticket/Elements/ShowSummary:54 html/Ticket/Elements/Tabs:92 html/Ticket/ModifyAll.html:43 html/Work/Tickets/Elements/ShowTransaction:17
+msgid "Dates"
+msgstr "日期"
+
+#: NOT FOUND IN SOURCE
+msgid "Dec"
+msgstr "å二月"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "12"
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "å二月"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Approval"
+msgstr "预设签核"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "预设自动å“应模æ¿"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "预设自动å“应模æ¿"
+
+#: html/Edit/Global/CustomField/Top:46
+msgid "Default Value"
+msgstr "预设值"
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr "预设管ç†å‘˜è¯„论模æ¿"
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr "预设管ç†å‘˜å›žå¤æ¨¡æ¿"
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr "预设回å¤æ¨¡æ¿"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "预设更动模æ¿"
+
+#: lib/RT/Transaction_Overlay.pm:491
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "预设:%1/%2 已自 %3 改为 %4"
+
+#: NOT FOUND IN SOURCE
+msgid "DefaultApproval"
+msgstr "预设签核"
+
+#: html/User/Delegation.html:24 html/User/Delegation.html:27
+msgid "Delegate rights"
+msgstr "代表团æƒé™"
+
+#: lib/RT/System.pm:62
+msgid "Delegate specific rights which have been granted to you."
+msgstr "将拥有的æƒé™å§”托他人代ç†"
+
+#: lib/RT/System.pm:62
+msgid "DelegateRights"
+msgstr "设定代ç†äºº"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Approval"
+msgstr "代ç†ç­¾æ ¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Queue"
+msgstr "代ç†è¡¨å•å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Queue:"
+msgstr "代ç†è¡¨å•ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Type"
+msgstr "代ç†è¡¨å•ç§ç±»"
+
+#: html/Edit/Users/index.html:98 html/Work/Delegates/Info:31 html/Work/Delegates/List:8 html/Work/Elements/Tab:41 html/Work/Overview/Info:28
+msgid "Delegates"
+msgstr "代ç†äºº"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Enabled Status"
+msgstr "代ç†æ¿€æ´»çŠ¶æ€"
+
+#: html/Work/Delegates/Info:18 html/Work/Overview/Info:18
+msgid "Delegates Info"
+msgstr "代ç†äººä¿¡æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Period"
+msgstr "代ç†æœŸé—´"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Permission Setting"
+msgstr "代ç†æƒé™è®¾å®š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Permission:"
+msgstr "代ç†æƒé™ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Setting"
+msgstr "代ç†äººè®¾å®š"
+
+#: html/Work/Delegates/Info:46 html/Work/Delegates/List:11 html/Work/Overview/Info:39
+msgid "Delegates Status"
+msgstr "代ç†çŠ¶æ€"
+
+#: html/User/Elements/Tabs:37
+msgid "Delegation"
+msgstr "代ç†äººæƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegation Groups"
+msgstr "代ç†äººç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegation Rights"
+msgstr "代ç†äººæƒé™"
+
+#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ModifyTemplateAsWorkflow:113 html/Edit/Elements/104Buttons:84 html/Work/Search/index.html:48 html/Work/Search/index.html:48
+msgid "Delete"
+msgstr "删除"
+
+#: html/Admin/Elements/EditScrips:52
+msgid "Delete selected scrips"
+msgstr "删除指定的手续"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Delete tickets"
+msgstr "删除申请å•"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "DeleteTicket"
+msgstr "删除申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:136
+msgid "Deleting this object could break referential integrity"
+msgstr "删除此对象å¯èƒ½ç ´åå‚考完整性"
+
+#: lib/RT/Queue_Overlay.pm:293
+msgid "Deleting this object would break referential integrity"
+msgstr "删除此对象å¯èƒ½ç ´åå‚考完整性"
+
+#: lib/RT/User_Overlay.pm:504
+msgid "Deleting this object would violate referential integrity"
+msgstr "删除此对象会è¿åå‚考完整性"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "删除此对象会è¿åå‚考完整性"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "删除此对象会è¿åå‚考完整性"
+
+#: html/Approvals/Elements/Approve:44 html/Work/Approvals/Elements/Approve:32
+msgid "Deny"
+msgstr "驳回"
+
+#: NOT FOUND IN SOURCE
+msgid "Department"
+msgstr "部门"
+
+#: html/Edit/Global/UserRight/List:12 html/Edit/Global/UserRight/Top:13
+msgid "Department ID"
+msgstr "部门代ç "
+
+#: html/Edit/Global/UserRight/List:11 html/Edit/Global/UserRight/Top:49 html/Work/Delegates/Info:78 html/Work/Overview/Info:60
+msgid "Department Name"
+msgstr "部门å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Department's"
+msgstr "部门之"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Details"
+msgstr "差旅明细"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure From"
+msgstr "差旅起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Request"
+msgstr "请å‡å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Until"
+msgstr "差旅截止日"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/BulkLinks:34 html/Ticket/Elements/EditLinks:105 html/Ticket/Elements/EditLinks:44 html/Ticket/Elements/ShowDependencies:31 html/Ticket/Elements/ShowLinks:36 html/Work/Search/BulkLinks:10 html/Work/Tickets/Elements/EditLinks:109 html/Work/Tickets/Elements/EditLinks:34 html/Work/Tickets/Elements/ShowLinks:21
+msgid "Depended on by"
+msgstr "å¯æŽ¥ç»­å¤„ç†çš„申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "附属性:\\n"
+
+#: lib/RT/Transaction_Overlay.pm:585
+#. ($value)
+msgid "Dependency by %1 added"
+msgstr "已加入å¯æŽ¥ç»­å¤„ç†çš„ç”³è¯·å• %1"
+
+#: lib/RT/Transaction_Overlay.pm:622
+#. ($value)
+msgid "Dependency by %1 deleted"
+msgstr "已移除å¯æŽ¥ç»­å¤„ç†çš„ç”³è¯·å• %1"
+
+#: lib/RT/Transaction_Overlay.pm:582
+#. ($value)
+msgid "Dependency on %1 added"
+msgstr "已加入需先处ç†çš„ç”³è¯·å• %1"
+
+#: lib/RT/Transaction_Overlay.pm:619
+#. ($value)
+msgid "Dependency on %1 deleted"
+msgstr "已移除需先处ç†çš„ç”³è¯·å• %1"
+
+#: html/Elements/SelectLinkType:26 html/Ticket/Create.html:180 html/Ticket/Elements/BulkLinks:30 html/Ticket/Elements/EditLinks:101 html/Ticket/Elements/EditLinks:35 html/Ticket/Elements/ShowDependencies:24 html/Ticket/Elements/ShowLinks:26 html/Work/Search/BulkLinks:6 html/Work/Tickets/Elements/EditLinks:105 html/Work/Tickets/Elements/EditLinks:23 html/Work/Tickets/Elements/ShowLinks:16
+msgid "Depends on"
+msgstr "需先处ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "需先处ç†"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Descending"
+msgstr "递å‡"
+
+#: html/SelfService/Create.html:72 html/Ticket/Create.html:118
+msgid "Describe the issue below"
+msgstr "在以下字段æ述主题"
+
+#: html/Admin/Elements/AddCustomFieldValue:35 html/Admin/Elements/EditCustomField:38 html/Admin/Elements/EditScrip:33 html/Admin/Elements/ModifyQueue:35 html/Admin/Elements/ModifyTemplate:35 html/Admin/Elements/ModifyTemplateAsWorkflow:192 html/Admin/Groups/Modify.html:48 html/Admin/Queues/Modify.html:47 html/Edit/Elements/SelectQueues:4 html/Edit/Global/Workflow/Action:13 html/Elements/SelectGroups:26 html/User/Groups/Modify.html:48
+msgid "Description"
+msgstr "æè¿°"
+
+#: NOT FOUND IN SOURCE
+msgid "Description of Responsibility"
+msgstr "ç»åŠžä¸šåŠ¡è¯´æ˜Ž"
+
+#: NOT FOUND IN SOURCE
+msgid "Description:"
+msgstr "æ述:"
+
+#: html/Work/Tickets/Create.html:132 html/Work/Tickets/Create.html:84 html/Work/Tickets/Elements/EditCustomFields:13 html/Work/Tickets/Elements/EditCustomFields:61 html/Work/Tickets/Elements/ShowCustomFields:14 html/Work/Tickets/Elements/ShowCustomFields:53
+msgid "Details"
+msgstr "细节"
+
+#: NOT FOUND IN SOURCE
+msgid "Direct"
+msgstr "直接"
+
+#: NOT FOUND IN SOURCE
+msgid "Disability"
+msgstr "残障身分"
+
+#: NOT FOUND IN SOURCE
+msgid "Disability Type"
+msgstr "残障类别"
+
+#: html/Edit/Global/GroupRight/List:9 html/Edit/Global/GroupRight/Top:16 html/Edit/Groups/List:11 html/Edit/Groups/Top:19 html/Edit/Queues/Basic/Top:69 html/Edit/Queues/List:15 html/Edit/Queues/List:27 html/Work/Delegates/Info:48 html/Work/Delegates/Info:53 html/Work/Delegates/List:12 html/Work/Overview/Info:42
+msgid "Disabled"
+msgstr "åœç”¨"
+
+#: html/Ticket/Elements/Tabs:84
+msgid "Display"
+msgstr "显示内容"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Display Access Control List"
+msgstr "显示æƒé™æŽ§åˆ¶æ¸…å•"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "Display Scrip templates for this queue"
+msgstr "显示此表å•çš„模æ¿"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "Display Scrips for this queue"
+msgstr "显示此表å•çš„手续"
+
+#: html/Ticket/Elements/ShowHistory:34
+msgid "Display mode"
+msgstr "显示模å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Display ticket #%1"
+msgstr "显示第%1å·ç”³è¯·å•"
+
+#: lib/RT/System.pm:53
+msgid "Do anything and everything"
+msgstr "å…许一切æ“作"
+
+#: html/Elements/Refresh:29
+msgid "Don't refresh this page."
+msgstr "ä¸æ›´æ–°æ­¤é¡µé¢ã€‚"
+
+#: html/Search/Elements/PickRestriction:113 html/Work/Search/PickRestriction:101
+msgid "Don't show search results"
+msgstr "ä¸æ˜¾ç¤ºæŸ¥è¯¢ç»“æžœ"
+
+#: html/Edit/Elements/Page:19 html/Edit/Elements/Page:21
+msgid "Down"
+msgstr "下一页"
+
+#: html/Ticket/Elements/ShowTransaction:103
+msgid "Download"
+msgstr "下载"
+
+#: NOT FOUND IN SOURCE
+msgid "Dr."
+msgstr "åšå£«"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:44 html/Ticket/Elements/ShowDates:43 html/Work/Tickets/Elements/EditBasics:54 lib/RT/Ticket_Overlay.pm:1216
+msgid "Due"
+msgstr "到期日"
+
+#: NOT FOUND IN SOURCE
+msgid "Due Date"
+msgstr "截止日"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "无法解读日期 '%1'"
+
+#: bin/rt-commit-handler:753
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "æ— æ³•åŠ è½½ç”³è¯·å• '%1':%2.\\n"
+
+#: html/Work/Tickets/Update.html:47
+msgid "Edit"
+msgstr "编辑"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:132
+msgid "Edit Conditions"
+msgstr "编辑å‰ç½®æ¡ä»¶"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "编辑 %1 的自订字段"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Custom Fields for queue %1"
+msgstr "ç¼–è¾‘è¡¨å• %1 的自订字段"
+
+#: html/Search/Bulk.html:143 html/Ticket/ModifyLinks.html:35 html/Work/Search/Bulk.html:93
+msgid "Edit Relationships"
+msgstr "编辑申请å•å…³ç³»"
+
+#: html/Edit/Groups/MemberGroups/Add.html:3 html/Edit/Groups/MemberGroups/index.html:22
+msgid "Edit Subgroups"
+msgstr "新增/维护å­ç¾¤ç»„"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "ç¼–è¾‘è¡¨å• %1 的模æ¿"
+
+#: html/Admin/Queues/Workflows.html:42
+#. ($QueueObj->Name)
+msgid "Edit Workflows for queue %1"
+msgstr "ç¼–è¾‘è¡¨å• %1 çš„æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "编辑关键è¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "编辑手续"
+
+#: html/Admin/Global/index.html:45
+msgid "Edit system templates"
+msgstr "编辑全域模æ¿"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit system workflows"
+msgstr "编辑全域æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "编辑 %1 的模æ¿"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit workflows for %1"
+msgstr "编辑 %1 çš„æµç¨‹"
+
+#: html/Admin/Elements/ModifyQueue:24 html/Admin/Queues/Modify.html:118
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "ç¼–è¾‘è¡¨å• %1 的设定"
+
+#: html/Admin/Elements/ModifyUser:24
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "编辑使用者 %1 的设定"
+
+#: html/Admin/Elements/EditCustomField:90
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "编辑自订字段 %1"
+
+#: html/Admin/Groups/Members.html:31
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "编辑群组 %1 çš„æˆå‘˜ä¿¡æ¯"
+
+#: html/User/Groups/Members.html:128
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "编辑代ç†äººç¾¤ç»„ %1 çš„æˆå‘˜ä¿¡æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "ç¼–è¾‘æ¨¡æ¿ %1"
+
+#: html/Admin/Elements/ModifyWorkflow:238
+#. (loc( $WorkflowObj->Name() ))
+msgid "Editing workflow %1"
+msgstr "编辑æµç¨‹ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Education"
+msgstr "最高学历"
+
+#: NOT FOUND IN SOURCE
+msgid "EffectiveId"
+msgstr "有效编å·"
+
+#: lib/RT/Ticket_Overlay.pm:2673 lib/RT/Ticket_Overlay.pm:2751
+msgid "Either base or target must be specified"
+msgstr "需è¦æŒ‡å®šèµ·å§‹ç”³è¯·å•æˆ–目的申请å•"
+
+#: html/Admin/Users/Modify.html:52 html/Admin/Users/Prefs.html:45 html/Edit/Elements/SelectUsers:4 html/Edit/Users/List:7 html/Elements/SelectUsers:26 html/Ticket/Elements/AddWatchers:55 html/User/Prefs.html:43 html/Work/Delegates/Info:96 html/Work/Overview/Info:78
+msgid "Email"
+msgstr "电å­é‚®ä»¶ä¿¡ç®±"
+
+#: html/Work/Preferences/Info:16
+msgid "Email Address"
+msgstr "电å­é‚®ä»¶ä¿¡ç®±"
+
+#: lib/RT/User_Overlay.pm:251
+msgid "Email address in use"
+msgstr "此电å­é‚®ä»¶ä¿¡ç®±å·²è¢«ä½¿ç”¨"
+
+#: html/Admin/Elements/ModifyUser:41
+msgid "EmailAddress"
+msgstr "电å­é‚®ä»¶ä¿¡ç®±åœ°å€"
+
+#: html/Admin/Elements/ModifyUser:53
+msgid "EmailEncoding"
+msgstr "电å­é‚®ä»¶æ–‡å­—ç¼–ç æ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Embark Date"
+msgstr "外ç±å‘˜å·¥å…¥å¢ƒæ—¥"
+
+#: NOT FOUND IN SOURCE
+msgid "Embarked Date"
+msgstr "抵达日期"
+
+#: NOT FOUND IN SOURCE
+msgid "Embarked Location"
+msgstr "抵达地点"
+
+#: NOT FOUND IN SOURCE
+msgid "Enable Delegates"
+msgstr "代ç†æ¿€æ´»"
+
+#: html/Admin/Elements/EditCustomField:50
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤è‡ªè®¢å­—段)"
+
+#: html/Admin/Groups/Modify.html:52 html/User/Groups/Modify.html:52
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤ç¾¤ç»„)"
+
+#: html/Admin/Queues/Modify.html:83
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "å¯ç”¨(å–消勾选将åœç”¨æ­¤è¡¨å•)"
+
+#: html/Admin/Elements/EditCustomFields:97
+msgid "Enabled Custom Fields"
+msgstr "å·²å¯ç”¨çš„自订字段"
+
+#: html/Edit/Queues/Basic/Top:74 html/Edit/Queues/List:17 html/Edit/Queues/List:29
+msgid "Enabled Date"
+msgstr "å¯ç”¨æ—¥æœŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Enabled Date:"
+msgstr "激活日期:"
+
+#: html/Admin/Queues/index.html:55
+msgid "Enabled Queues"
+msgstr "å·²å¯ç”¨çš„表å•"
+
+#: html/Edit/Queues/Basic/Top:65 html/Edit/Queues/List:13 html/Edit/Queues/List:25
+msgid "Enabled Status"
+msgstr "å¯ç”¨çŠ¶æ€"
+
+#: html/Admin/Elements/EditCustomField:106 html/Admin/Groups/Modify.html:116 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:284 html/User/Groups/Modify.html:116
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "å¯ç”¨çŠ¶æ€ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "End of Trial"
+msgstr "试用期满日"
+
+#: NOT FOUND IN SOURCE
+msgid "English Name"
+msgstr "英文姓å"
+
+#: lib/RT/CustomField_Overlay.pm:427
+msgid "Enter multiple values"
+msgstr "键入多é‡é¡¹ç›®"
+
+#: html/Edit/Users/Search.html:15
+msgid "Enter one or more conditions below to search for users"
+msgstr "输入下列å•ä¸€æˆ–å¤å¼æ¡ä»¶ï¼ŒæŸ¥è¯¢ç”¨æˆ·æ•°æ®"
+
+#: lib/RT/CustomField_Overlay.pm:424
+msgid "Enter one value"
+msgstr "键入å•ä¸€é¡¹ç›®"
+
+#: html/Search/Bulk.html:144 html/Ticket/Elements/EditLinks:94 html/Work/Search/Bulk.html:95 html/Work/Tickets/Elements/EditLinks:98
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "输入申请å•å¯é“¾æŽ¥åˆ°çš„申请å•ç¼–å·æˆ–网å€ã€‚以空白隔开。"
+
+#: lib/RT/CustomField_Vendor.pm:20
+msgid "EntryBoolean"
+msgstr "是éžå¡«è¡¨"
+
+#: lib/RT/CustomField_Vendor.pm:17
+msgid "EntryDate"
+msgstr "日期填表"
+
+#: NOT FOUND IN SOURCE
+msgid "EntryExternal"
+msgstr "系统填表"
+
+#: lib/RT/CustomField_Vendor.pm:16
+msgid "EntryFreeform"
+msgstr "输入填表"
+
+#: NOT FOUND IN SOURCE
+msgid "EntryMultiple"
+msgstr "多选填表"
+
+#: lib/RT/CustomField_Vendor.pm:19
+msgid "EntryNumber"
+msgstr "数值填表"
+
+#: lib/RT/CustomField_Vendor.pm:15
+msgid "EntrySelect"
+msgstr "å•é€‰å¡«è¡¨"
+
+#: lib/RT/CustomField_Vendor.pm:18
+msgid "EntryTime"
+msgstr "时间填表"
+
+#: html/Elements/Login:39 html/SelfService/Error.html:24 html/SelfService/Error.html:25
+msgid "Error"
+msgstr "错误"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "新增视察员失败"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "表å•->新增视察员的å‚数有误"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "表å•->删除视察员的å‚数有误"
+
+#: lib/RT/Ticket_Overlay.pm:1401
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "申请å•->新增视察员的å‚数有误"
+
+#: lib/RT/Ticket_Overlay.pm:1558
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "申请å•->删除视察员的å‚数有误"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "所有人"
+
+#: bin/rt-crontool:193
+msgid "Example:"
+msgstr "范例:"
+
+#: NOT FOUND IN SOURCE
+msgid "Existing user renamed from %1 to %2"
+msgstr "现有使用者 %1 已改å为 %2"
+
+#: html/Edit/Elements/104Buttons:88
+msgid "Export"
+msgstr "汇出"
+
+#: html/Admin/Elements/ModifyUser:63
+msgid "ExternalAuthId"
+msgstr "外部认è¯å¸å·"
+
+#: html/Admin/Elements/ModifyUser:57
+msgid "ExternalContactInfoId"
+msgstr "外部è”络方å¼å¸å·"
+
+#: html/Edit/Global/Basic/Top:69
+msgid "ExternalDatabaseDSN"
+msgstr "外部数æ®åº“连结字符串"
+
+#: html/Edit/Global/Basic/Top:73
+msgid "ExternalDatabasePass"
+msgstr "外部数æ®åº“å£ä»¤"
+
+#: html/Edit/Global/Basic/Top:71
+msgid "ExternalDatabaseUser"
+msgstr "外部数æ®åº“用户"
+
+#: html/Edit/Global/Basic/Top:67
+msgid "ExternalURL"
+msgstr "外部接å£ç½‘å€"
+
+#: html/Admin/Users/Modify.html:72 html/Edit/Users/Info:41
+msgid "Extra info"
+msgstr "备注"
+
+#: lib/RT/User_Overlay.pm:368
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "找ä¸åˆ°ã€Œå†…部æˆå‘˜ã€è™šæ‹Ÿç¾¤ç»„的使用者。"
+
+#: lib/RT/User_Overlay.pm:375
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "找ä¸åˆ°ã€Œéžå†…部æˆå‘˜ã€è™šæ‹Ÿç¾¤ç»„的使用者。"
+
+#: bin/rt-crontool:137
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "æ— æ³•åŠ è½½æ¨¡å— %1. (%2)"
+
+#: NOT FOUND IN SOURCE
+msgid "Feb"
+msgstr "二月"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "02"
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "二月"
+
+#: NOT FOUND IN SOURCE
+msgid "Female"
+msgstr "女"
+
+#: html/Edit/Global/CustomField/Info:14
+msgid "Field Content:"
+msgstr "字段内容:"
+
+#: html/Edit/Global/CustomField/List:7 html/Edit/Global/CustomField/Top:20
+msgid "Field Description"
+msgstr "字段æè¿°"
+
+#: html/Edit/Global/CustomField/List:6 html/Edit/Global/CustomField/Top:14
+msgid "Field Name"
+msgstr "字段å称"
+
+#: html/Edit/Global/CustomField/List:5 html/Edit/Global/CustomField/Top:9
+msgid "Field Type"
+msgstr "字段属性"
+
+#: html/Edit/Elements/PickUsers:52 html/Edit/Users/Add.html:47
+msgid "Filter"
+msgstr "筛选"
+
+#: html/Edit/Elements/PickUsers:6 html/Edit/Users/Add.html:7 html/Work/Tickets/Cc:4
+msgid "Filter people"
+msgstr "对象筛选"
+
+#: html/Edit/Elements/PickUsers:68 html/Edit/Users/Add.html:63 html/Work/Tickets/Cc:42
+msgid "Filtered list:"
+msgstr "筛选列表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "最终"
+
+#: html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:58 html/Work/Tickets/Elements/EditBasics:52 lib/RT/Tickets_Overlay.pm:1110
+msgid "Final Priority"
+msgstr "最终顺ä½"
+
+#: lib/RT/Ticket_Overlay.pm:1207
+msgid "FinalPriority"
+msgstr "最终顺ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Financial Department:"
+msgstr "财务部:"
+
+#: html/Admin/Queues/People.html:60 html/Ticket/Elements/EditPeople:33 html/Work/Tickets/Elements/EditPeople:18
+msgid "Find group whose"
+msgstr "寻找群组的"
+
+#: NOT FOUND IN SOURCE
+msgid "Find new/open tickets"
+msgstr "寻找/å¼€å¯ç”³è¯·å•"
+
+#: html/Admin/Queues/People.html:56 html/Admin/Users/index.html:45 html/Edit/Users/Top:6 html/Ticket/Elements/EditPeople:29 html/Work/Tickets/Elements/EditPeople:14
+msgid "Find people whose"
+msgstr "寻找人员的"
+
+#: html/Edit/Queues/Top:6
+msgid "Find queues whose"
+msgstr "寻找表å•çš„"
+
+#: html/Search/Listing.html:107 html/Work/Search/index.html:88
+msgid "Find tickets"
+msgstr "寻找申请å•"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:118
+msgid "Finish Approval"
+msgstr "签核完毕"
+
+#: html/Ticket/Elements/Tabs:57
+msgid "First"
+msgstr "第一项"
+
+#: html/Search/Listing.html:40 html/Work/Search/index.html:17
+msgid "First page"
+msgstr "第一页"
+
+#: html/Edit/Global/Workflow/Owner.html:30
+msgid "First-"
+msgstr "一"
+
+#: NOT FOUND IN SOURCE
+msgid "First-level Admins"
+msgstr "一阶主管"
+
+#: NOT FOUND IN SOURCE
+msgid "First-level Users"
+msgstr "一阶主管员工"
+
+#: NOT FOUND IN SOURCE
+msgid "Fixed shift"
+msgstr "固定ç­"
+
+#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:746
+msgid "Foo Bar Baz"
+msgstr "甲 乙 丙"
+
+#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:737
+msgid "Foo!"
+msgstr "甲ï¼"
+
+#: html/Search/Bulk.html:86 html/Work/Search/Bulk.html:55
+msgid "Force change"
+msgstr "强制更æ¢"
+
+#: html/Work/Elements/104Header:89
+msgid "Form Processing"
+msgstr "电å­è¡¨å•ä½œä¸šåŒº"
+
+#: html/Search/Listing.html:105 html/Work/Search/index.html:86
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "找到 %1 张申请å•"
+
+#: lib/RT/Interface/Web.pm:964
+msgid "Found Object"
+msgstr "已找到对象"
+
+#: html/Edit/Global/Workflow/Owner.html:33
+msgid "Fourth-"
+msgstr "å››"
+
+#: html/Admin/Elements/ModifyUser:43
+msgid "FreeformContactInfo"
+msgstr "è”络方å¼"
+
+#: lib/RT/CustomField_Vendor.pm:11
+msgid "FreeformDate"
+msgstr "日期输入"
+
+#: NOT FOUND IN SOURCE
+msgid "FreeformExternal"
+msgstr "系统字段"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformMultiple"
+msgstr "多é‡è¾“å…¥"
+
+#: lib/RT/CustomField_Vendor.pm:13
+msgid "FreeformNumber"
+msgstr "数值输入"
+
+#: lib/RT/CustomField_Vendor.pm:14
+msgid "FreeformPassword"
+msgstr "å£ä»¤è¾“å…¥"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "FreeformSingle"
+msgstr "å•ä¸€è¾“å…¥"
+
+#: lib/RT/CustomField_Vendor.pm:12
+msgid "FreeformTime"
+msgstr "时间输入"
+
+#: NOT FOUND IN SOURCE
+msgid "Fri"
+msgstr "星期五"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "星期五"
+
+#: html/Ticket/Elements/ShowHistory:40 html/Ticket/Elements/ShowHistory:50
+msgid "Full headers"
+msgstr "完整标头档"
+
+#: NOT FOUND IN SOURCE
+msgid "Gecos"
+msgstr "登入å¸å·"
+
+#: NOT FOUND IN SOURCE
+msgid "Gender"
+msgstr "性别"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "å–å¾—ç›®å‰ä½¿ç”¨è€…çš„ pgp 签章\\n"
+
+#: lib/RT/Transaction_Overlay.pm:551
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "交予 %1"
+
+#: html/Admin/Elements/Tabs:40 html/Admin/index.html:37
+msgid "Global"
+msgstr "全域设定"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Approval"
+msgstr "全域签核"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "全域关键è¯é€‰å–"
+
+#: html/Edit/Users/System:24
+msgid "Global Rights:"
+msgstr "拥有全域æƒé™åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "全域手续"
+
+#: html/Edit/Elements/Tab:40
+msgid "Global Setup"
+msgstr "全域设定"
+
+#: html/Admin/Elements/SelectTemplate:37 html/Edit/Elements/SelectTemplate:11
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "全域模æ¿ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "GlobalApproval"
+msgstr "全域签核"
+
+#: html/Admin/Elements/EditCustomFields:73 html/Admin/Queues/People.html:58 html/Admin/Queues/People.html:62 html/Admin/Queues/index.html:43 html/Admin/Users/index.html:48 html/Ticket/Elements/EditPeople:31 html/Ticket/Elements/EditPeople:35 html/Work/Tickets/Elements/EditPeople:16 html/Work/Tickets/Elements/EditPeople:20 html/index.html:40
+msgid "Go!"
+msgstr "执行"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "%1 的 pgp 签章是正确的\\n"
+
+#: html/Search/Listing.html:49
+msgid "Goto page"
+msgstr "到页é¢"
+
+#: html/Elements/GotoTicket:24 html/SelfService/Elements/GotoTicket:24 html/Work/Elements/104Header:49
+msgid "Goto ticket"
+msgstr "跳到申请å•"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:224
+msgid "Grand"
+msgstr "上"
+
+#: html/Ticket/Elements/AddWatchers:45 html/User/Elements/DelegateRights:77
+msgid "Group"
+msgstr "群组"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "群组 %1 %2:%3"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Admin"
+msgstr "群组管ç†å‘˜"
+
+#: html/Edit/Global/GroupRight/List:5 html/Edit/Global/GroupRight/Top:20 html/Edit/Groups/List:7
+msgid "Group Description"
+msgstr "群组æè¿°"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Management"
+msgstr "群组管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Members"
+msgstr "群组æˆå‘˜"
+
+#: html/Edit/Elements/PickUsers:28 html/Edit/Global/GroupRight/List:4 html/Edit/Global/GroupRight/Top:10 html/Edit/Groups/List:6 html/Edit/Groups/Top:7 html/Edit/Queues/Basic/Add.html:15 html/Edit/Users/Add.html:29 html/Edit/Users/Group:10 html/Edit/Users/Search.html:43 html/Work/Delegates/Add.html:15 html/Work/Tickets/Cc:24
+msgid "Group Name"
+msgstr "群组å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Name:"
+msgstr "群组å称:"
+
+#: html/Admin/Elements/GroupTabs:44 html/Admin/Elements/QueueTabs:56 html/Admin/Elements/SystemTabs:43 html/Admin/Global/index.html:54 html/Edit/Global/autohandler:12 html/Edit/Queues/autohandler:27 html/Edit/Users/Group:11 html/Edit/Users/index.html:96
+msgid "Group Rights"
+msgstr "群组æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Rights:"
+msgstr "拥有群组æƒé™åˆ—表:"
+
+#: html/Edit/Elements/Tab:36
+msgid "Group Setup"
+msgstr "群组设定"
+
+#: html/Edit/Global/GroupRight/List:8 html/Edit/Global/GroupRight/Top:14 html/Edit/Groups/List:10 html/Edit/Groups/Top:15
+msgid "Group Status"
+msgstr "群组状æ€"
+
+#: lib/RT/Group_Overlay.pm:962
+msgid "Group already has member"
+msgstr "群组内已有此æˆå‘˜"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "无法新增群组"
+
+#: html/Admin/Groups/Modify.html:76
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "无法新增群组:%1"
+
+#: lib/RT/Group_Overlay.pm:494
+msgid "Group created"
+msgstr "群组新增完毕"
+
+#: NOT FOUND IN SOURCE
+msgid "Group created: %1"
+msgstr "群组 %1 新增完毕"
+
+#: lib/RT/Group_Overlay.pm:1134
+msgid "Group has no such member"
+msgstr "群组没有这个æˆå‘˜"
+
+#: lib/RT/Group_Overlay.pm:942 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1455 lib/RT/Ticket_Overlay.pm:1533
+msgid "Group not found"
+msgstr "找ä¸åˆ°ç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "找ä¸åˆ°ç¾¤ç»„。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "未指定群组。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group redescribed from %1 to %2"
+msgstr "群组æè¿° %1 已改为 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Group renamed from %1 to %2"
+msgstr "群组 %1 已改å为 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Group with Queue Rights"
+msgstr "拥有表å•æƒé™ç¾¤ç»„"
+
+#: html/Edit/Global/Workflow/Owner.html:70
+msgid "Group's"
+msgstr "群组之"
+
+#: NOT FOUND IN SOURCE
+msgid "Group:"
+msgstr "群组:"
+
+#: html/Admin/Elements/SelectNewGroupMembers:34 html/Admin/Elements/Tabs:34 html/Admin/Groups/Members.html:63 html/Admin/Queues/People.html:82 html/Admin/index.html:31 html/Edit/Global/GroupRight/Add.html:16 html/Edit/Groups/Admin:12 html/User/Groups/Members.html:66
+msgid "Groups"
+msgstr "群组"
+
+#: lib/RT/Group_Overlay.pm:968
+msgid "Groups can't be members of their members"
+msgstr "ä¸èƒ½å°†ç¾¤ç»„设为群组内æˆå‘˜"
+
+#: NOT FOUND IN SOURCE
+msgid "Groups with Global Rights"
+msgstr "拥有全域æƒé™ç¾¤ç»„"
+
+#: html/Edit/Global/GroupRight/List:6 html/Edit/Global/GroupRight/Top:22 html/Edit/Groups/List:8
+msgid "HRMSDefined"
+msgstr "组织架构"
+
+#: NOT FOUND IN SOURCE
+msgid "Health Insurance"
+msgstr "å¥ä¿è¡¥åŠ©èº«ä»½"
+
+#: lib/RT/Interface/CLI.pm:72 lib/RT/Interface/CLI.pm:72
+msgid "Hello!"
+msgstr "å—¨ï¼"
+
+#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:753
+#. ($name)
+msgid "Hello, %1"
+msgstr "嗨,%1"
+
+#: html/Edit/Elements/104Top:28
+msgid "Help"
+msgstr "说明"
+
+#: NOT FOUND IN SOURCE
+msgid "Help Desks"
+msgstr "å„项业务窗å£"
+
+#: html/Edit/Global/CustomField/SelectWritable:9 html/Edit/Queues/Basic/Top:80
+msgid "Hidden"
+msgstr "éšè—"
+
+#: html/Ticket/Elements/ShowHistory:29 html/Ticket/Elements/Tabs:87 html/Work/Tickets/Elements/ShowHistory:8
+msgid "History"
+msgstr "纪录"
+
+#: html/Admin/Elements/ModifyUser:67
+msgid "HomePhone"
+msgstr "ä½å¤„电è¯"
+
+#: html/Edit/Elements/104Top:15 html/Edit/Elements/104Top:24 html/Edit/Elements/EDOMHeader:9 html/Elements/Tabs:43
+msgid "Homepage"
+msgstr "主页"
+
+#: NOT FOUND IN SOURCE
+msgid "Hotel Expense"
+msgstr "ä½å®¿è´¹"
+
+#: lib/RT/Base.pm:75
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "我有 %quant(%1,份固体æ…拌器)。"
+
+#: NOT FOUND IN SOURCE
+msgid "ID Number"
+msgstr "身分è¯å·"
+
+#: NOT FOUND IN SOURCE
+msgid "ID Type"
+msgstr "身分类别"
+
+#: html/Ticket/Elements/ShowBasics:26 lib/RT/Tickets_Overlay.pm:1037
+msgid "Id"
+msgstr "ç¼–å·"
+
+#: html/Admin/Users/Modify.html:43 html/User/Prefs.html:38 html/Work/Preferences/Info:14
+msgid "Identity"
+msgstr "身份"
+
+#: etc/initialdata:411 etc/upgrade/2.1.71:86 html/Edit/Elements/CreateApprovalsQueue:58
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "若签核å•é­åˆ°é©³å›žï¼Œåˆ™è¿žå¸¦é©³å›žåŽŸç”³è¯·å•ï¼Œå¹¶åˆ é™¤å…¶å®ƒç›¸å…³çš„待签核事项"
+
+#: bin/rt-crontool:189
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "如果此工具程åºä¸º setgid,æ¶æ„的本地端用户å³èƒ½ç”±æ­¤å–å¾— RT 的管ç†å‘˜æƒé™ã€‚"
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyPeople.html:37
+msgid "If you've updated anything above, be sure to"
+msgstr "若您已更新以上数æ®ï¼Œè¯·è®°å¾—按一下"
+
+#: lib/RT/Interface/Web.pm:956
+msgid "Illegal value for %1"
+msgstr "%1 的值错误"
+
+#: lib/RT/Interface/Web.pm:959
+msgid "Immutable field"
+msgstr "此字段值ä¸å¯æ›´åŠ¨"
+
+#: html/Edit/Elements/104Buttons:87 html/Edit/Global/Workflow/Import.html:2
+msgid "Import"
+msgstr "汇入"
+
+#: html/Admin/Elements/EditCustomFields:72
+msgid "Include disabled custom fields in listing."
+msgstr "列出åœç”¨çš„自订字段"
+
+#: html/Admin/Queues/index.html:42 html/Edit/Queues/Top:9
+msgid "Include disabled queues in listing."
+msgstr "列出åœç”¨çš„表å•"
+
+#: html/Admin/Users/index.html:46 html/Edit/Users/Search.html:62 html/Edit/Users/Top:9
+msgid "Include disabled users in search."
+msgstr "列出åœç”¨çš„使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Indirect Employee"
+msgstr "直接/间接员工"
+
+#: lib/RT/Tickets_Overlay.pm:1086
+msgid "Initial Priority"
+msgstr "åˆå§‹ä¼˜å…ˆé¡ºä½"
+
+#: lib/RT/Ticket_Overlay.pm:1206 lib/RT/Ticket_Overlay.pm:1208
+msgid "InitialPriority"
+msgstr "åˆå§‹ä¼˜å…ˆé¡ºä½"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "输入错误"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "登记æˆåŠŸ"
+
+#: lib/RT/Ticket_Overlay.pm:3913
+msgid "Internal Error"
+msgstr "内部错误"
+
+#: lib/RT/Record.pm:142
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "内部错误:%1"
+
+#: lib/RT/Group_Overlay.pm:641
+msgid "Invalid Group Type"
+msgstr "错误的群组类别"
+
+#: lib/RT/Principal_Overlay.pm:127
+msgid "Invalid Right"
+msgstr "错误的æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "错误的类型"
+
+#: lib/RT/Interface/Web.pm:961
+msgid "Invalid data"
+msgstr "错误的数æ®"
+
+#: lib/RT/Ticket_Overlay.pm:463
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "错误的承办人。改为预设承办人「nobodyã€ã€‚"
+
+#: lib/RT/Scrip_Overlay.pm:133 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "错误的表å•"
+
+#: lib/RT/ACE_Overlay.pm:243 lib/RT/ACE_Overlay.pm:252 lib/RT/ACE_Overlay.pm:258 lib/RT/ACE_Overlay.pm:269 lib/RT/ACE_Overlay.pm:274
+msgid "Invalid right"
+msgstr "错误的æƒé™"
+
+#: lib/RT/Record.pm:117
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "%1 的值错误"
+
+#: lib/RT/Ticket_Overlay.pm:3517
+msgid "Invalid value for custom field"
+msgstr "错误的自订字段值"
+
+#: lib/RT/Ticket_Overlay.pm:365
+msgid "Invalid value for status"
+msgstr "错误的状æ€å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "IssueStatement"
+msgstr "é€å‡ºé™ˆè¿°"
+
+#: bin/rt-crontool:190
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "请ç»å¯¹ä¸è¦è®©æœªå…·æƒé™çš„使用者执行此工具程åºã€‚"
+
+#: bin/rt-crontool:191
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "建议您新增一个隶属于正确群组的低æƒé™ç³»ç»Ÿä½¿ç”¨è€…,并以该身份执行此工具程åºã€‚"
+
+#: bin/rt-crontool:162
+msgid "It takes several arguments:"
+msgstr "它接å—下列å‚数:"
+
+#: NOT FOUND IN SOURCE
+msgid "Item Name"
+msgstr "å“å"
+
+#: NOT FOUND IN SOURCE
+msgid "Items"
+msgstr "笔"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "待签核项目"
+
+#: NOT FOUND IN SOURCE
+msgid "Jan"
+msgstr "一月"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "01"
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "一月"
+
+#: NOT FOUND IN SOURCE
+msgid "Job"
+msgstr "èŒç§°"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Join or leave this group"
+msgstr "加入或离开此群组"
+
+#: NOT FOUND IN SOURCE
+msgid "Jul"
+msgstr "七月"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "07"
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "七月"
+
+#: html/Ticket/Elements/Tabs:98
+msgid "Jumbo"
+msgstr "全部信æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Jun"
+msgstr "六月"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "06"
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "六月"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "关键è¯"
+
+#: lib/RT/CustomField_Vendor.pm:23
+msgid "LabelAttachments"
+msgstr "附件å·æ ‡"
+
+#: lib/RT/CustomField_Vendor.pm:24
+msgid "LabelContent"
+msgstr "内容å·æ ‡"
+
+#: lib/RT/CustomField_Vendor.pm:22
+msgid "LabelSubject"
+msgstr "主题å·æ ‡"
+
+#: lib/RT/CustomField_Vendor.pm:21
+msgid "LabelURL"
+msgstr "链接å·æ ‡"
+
+#: html/Admin/Elements/ModifyUser:51
+msgid "Lang"
+msgstr "使用语言"
+
+#: html/User/Prefs.html:54 html/Work/Preferences/Info:29
+msgid "Language"
+msgstr "语言"
+
+#: html/Ticket/Elements/Tabs:72
+msgid "Last"
+msgstr "上次更新"
+
+#: html/Ticket/Elements/EditDates:37 html/Ticket/Elements/ShowDates:39 html/Work/Tickets/Elements/EditBasics:44
+msgid "Last Contact"
+msgstr "上次è”络"
+
+#: html/Elements/SelectDateType:28
+msgid "Last Contacted"
+msgstr "上次è”络日期"
+
+#: html/Search/Elements/TicketHeader:40 html/Work/Search/TicketHeader:19
+msgid "Last Notified"
+msgstr "上次通知"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Updated"
+msgstr "上次更新"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "上次更新"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "剩馀时间"
+
+#: html/Admin/Users/Modify.html:82 html/Edit/Users/Info:62
+msgid "Let this user access RT"
+msgstr "å…许这å使用者登入"
+
+#: html/Admin/Users/Modify.html:86 html/Edit/Users/Info:68
+msgid "Let this user be granted rights"
+msgstr "内部æˆå‘˜ï¼ˆå…·æœ‰ä¸ªäººæƒé™ï¼‰"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "é™åˆ¶æ‰¿åŠžäººä¸º %1 到%2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "é™åˆ¶è¡¨å•ä¸º %1 到 %2"
+
+#: html/Work/Queues/Select.html:4
+msgid "Link a Queue"
+msgstr "申请表å•è¿žç»“"
+
+#: lib/RT/Ticket_Overlay.pm:2765
+msgid "Link already exists"
+msgstr "此链接已存在"
+
+#: lib/RT/Ticket_Overlay.pm:2777
+msgid "Link could not be created"
+msgstr "无法新增链接"
+
+#: lib/RT/Ticket_Overlay.pm:2785 lib/RT/Ticket_Overlay.pm:2797
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "链接(%1)新增完毕"
+
+#: lib/RT/Ticket_Overlay.pm:2698
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "链接(%1)删除完毕"
+
+#: lib/RT/Ticket_Overlay.pm:2704
+msgid "Link not found"
+msgstr "找ä¸åˆ°é“¾æŽ¥"
+
+#: html/Ticket/ModifyLinks.html:24 html/Ticket/ModifyLinks.html:28
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "é“¾æŽ¥ç”³è¯·å• #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "é“¾æŽ¥ç”³è¯·å• %1"
+
+#: html/Ticket/Elements/Tabs:96
+msgid "Links"
+msgstr "链接"
+
+#: html/Edit/Users/Search.html:11
+msgid "List All Users"
+msgstr "列出所有用户数æ®"
+
+#: html/Admin/Users/Modify.html:113 html/User/Prefs.html:107 html/Work/Preferences/Info:75
+msgid "Location"
+msgstr "ä½ç½®"
+
+#: lib/RT.pm:174
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "登入目录 %1 找ä¸åˆ°æˆ–无法写入\\n。无法执行 RT。"
+
+#: html/Edit/Global/Basic/Top:57
+msgid "LogToFile"
+msgstr "纪录等级"
+
+#: html/Edit/Global/Basic/Top:59
+msgid "LogToFileNamed"
+msgstr "纪录档å"
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "使用者:%1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:35 html/Elements/Login:44 html/Elements/Login:54 lib/RT/StyleGuide.pod:777
+msgid "Login"
+msgstr "登入"
+
+#: html/Edit/Elements/104Top:17 html/Edit/Elements/104Top:17 html/Edit/Elements/104Top:32 html/Elements/Header:54
+msgid "Logout"
+msgstr "注销"
+
+#: NOT FOUND IN SOURCE
+msgid "Long-term contractor"
+msgstr "长期契约员工"
+
+#: html/Search/Bulk.html:85 html/Work/Search/Bulk.html:54
+msgid "Make Owner"
+msgstr "新增承办人"
+
+#: html/Search/Bulk.html:109 html/Work/Search/Bulk.html:63
+msgid "Make Status"
+msgstr "新增现况"
+
+#: html/Search/Bulk.html:117 html/Work/Search/Bulk.html:75
+msgid "Make date Due"
+msgstr "新增到期日"
+
+#: html/Search/Bulk.html:119 html/Work/Search/Bulk.html:78
+msgid "Make date Resolved"
+msgstr "新增解决日期"
+
+#: html/Search/Bulk.html:113 html/Work/Search/Bulk.html:69
+msgid "Make date Started"
+msgstr "新增实际起始日期"
+
+#: html/Search/Bulk.html:111 html/Work/Search/Bulk.html:66
+msgid "Make date Starts"
+msgstr "新增应起始日期"
+
+#: html/Search/Bulk.html:115 html/Work/Search/Bulk.html:72
+msgid "Make date Told"
+msgstr "新增报告日期"
+
+#: html/Search/Bulk.html:105 html/Work/Search/Bulk.html:57
+msgid "Make priority"
+msgstr "新增优先顺ä½"
+
+#: html/Search/Bulk.html:107 html/Work/Search/Bulk.html:60
+msgid "Make queue"
+msgstr "新增表å•"
+
+#: html/Search/Bulk.html:103 html/Work/Search/Bulk.html:59
+msgid "Make subject"
+msgstr "新增主题"
+
+#: NOT FOUND IN SOURCE
+msgid "Male"
+msgstr "ç”·"
+
+#: html/Admin/index.html:32
+msgid "Manage groups and group membership"
+msgstr "管ç†ç¾¤ç»„åŠæ‰€å±žæˆå‘˜"
+
+#: html/Admin/index.html:38
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "管ç†é€‚用于所有表å•çš„属性与设定"
+
+#: html/Admin/index.html:35
+msgid "Manage queues and queue-specific properties"
+msgstr "管ç†å„表å•åŠç›¸å…³å±žæ€§"
+
+#: html/Admin/index.html:29
+msgid "Manage users and passwords"
+msgstr "管ç†ä½¿ç”¨è€…与å£ä»¤"
+
+#: NOT FOUND IN SOURCE
+msgid "Manager"
+msgstr "ç»ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Mar"
+msgstr "三月"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "03"
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "三月"
+
+#: NOT FOUND IN SOURCE
+msgid "Marketing Department"
+msgstr "行销部"
+
+#: html/Edit/Global/CustomField/Top:63
+msgid "Match Pattern"
+msgstr "符åˆæ ·å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "五月"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "05"
+
+#: lib/RT/Transaction_Overlay.pm:598
+#. ($value)
+msgid "Member %1 added"
+msgstr "æˆå‘˜ %1 新增完毕"
+
+#: lib/RT/Transaction_Overlay.pm:635
+#. ($value)
+msgid "Member %1 deleted"
+msgstr "æˆå‘˜ %1 删除完毕"
+
+#: lib/RT/Group_Overlay.pm:979
+msgid "Member added"
+msgstr "新增æˆå‘˜å®Œæ¯•"
+
+#: lib/RT/Group_Overlay.pm:1141
+msgid "Member deleted"
+msgstr "æˆå‘˜å·²åˆ é™¤"
+
+#: lib/RT/Group_Overlay.pm:1145
+msgid "Member not deleted"
+msgstr "æˆå‘˜æœªåˆ é™¤"
+
+#: html/Elements/SelectLinkType:25
+msgid "Member of"
+msgstr "隶属于"
+
+#: html/Work/Preferences/index.html:19
+msgid "Member since"
+msgstr "注册日期"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "隶属于"
+
+#: html/Admin/Elements/GroupTabs:41 html/Admin/Elements/ModifyTemplateAsWorkflow:232 html/User/Elements/GroupTabs:41
+msgid "Members"
+msgstr "æˆå‘˜"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($value)
+msgid "Membership in %1 added"
+msgstr "所属群组 %1 加入完毕"
+
+#: lib/RT/Transaction_Overlay.pm:632
+#. ($value)
+msgid "Membership in %1 deleted"
+msgstr "所属群组 %1 移除完毕"
+
+#: lib/RT/Ticket_Overlay.pm:2954
+msgid "Merge Successful"
+msgstr "æ•´åˆå®Œæ¯•"
+
+#: lib/RT/Ticket_Overlay.pm:2874
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "æ•´åˆå¤±è´¥ã€‚无法设定 EffectiveId"
+
+#: html/Ticket/Elements/BulkLinks:26 html/Ticket/Elements/EditLinks:97 html/Work/Search/BulkLinks:2 html/Work/Tickets/Elements/EditLinks:101
+msgid "Merge into"
+msgstr "æ•´åˆè¿›"
+
+#: html/Search/Bulk.html:137 html/Ticket/Update.html:100
+msgid "Message"
+msgstr "讯æ¯"
+
+#: html/Ticket/Elements/ShowTransaction:80
+msgid "Message body not shown because it is too large or is not plain text."
+msgstr "信件内文ä¸æ˜¯çº¯æ–‡å­—,因此无法显示。"
+
+#: NOT FOUND IN SOURCE
+msgid "Misc. Expense"
+msgstr "æ‚è´¹"
+
+#: lib/RT/Interface/Web.pm:963
+msgid "Missing a primary key?: %1"
+msgstr "缺少主键值?(%1)"
+
+#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:71 html/Work/Preferences/Info:38
+msgid "Mobile"
+msgstr "行动电è¯"
+
+#: html/Admin/Elements/ModifyUser:71
+msgid "MobilePhone"
+msgstr "行动电è¯"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Modify Access Control List"
+msgstr "更改æƒé™æŽ§åˆ¶æ¸…å•"
+
+#: html/Admin/Global/CustomFields.html:43 html/Admin/Global/index.html:50
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "更改适用于所有表å•çš„自订字段"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Modify Scrip templates for this queue"
+msgstr "更改此表å•çš„模æ¿"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Modify Scrips for this queue"
+msgstr "更改此表å•çš„手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "更改系统æƒé™æ¸…å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "æ›´æ”¹æ¨¡æ¿ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Workflow"
+msgstr "更改æµç¨‹"
+
+#: html/Admin/Queues/CustomField.html:44
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "更改 %1 表å•å†…的自订字段"
+
+#: html/Admin/Global/CustomField.html:52
+msgid "Modify a CustomField which applies to all queues"
+msgstr "更改适用于所有表å•çš„自订字段"
+
+#: html/Admin/Queues/Scrip.html:53
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "更改 %1 表å•å†…的手续"
+
+#: html/Admin/Global/Scrip.html:47
+msgid "Modify a scrip which applies to all queues"
+msgstr "更改适用于所有表å•çš„手续"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "更改 # %1 的日期"
+
+#: html/Ticket/ModifyDates.html:24 html/Ticket/ModifyDates.html:28
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "更改 #%1 的日期"
+
+#: html/Ticket/ModifyDates.html:34
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "æ›´æ”¹ç”³è¯·å• # %1 的日期"
+
+#: html/Admin/Global/GroupRights.html:24 html/Admin/Global/GroupRights.html:27 html/Admin/Global/index.html:55
+msgid "Modify global group rights"
+msgstr "更改全域设定的群组æƒé™"
+
+#: html/Admin/Global/GroupRights.html:32
+msgid "Modify global group rights."
+msgstr "更改全域设定的群组æƒé™ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "更改全域设定的群组æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "更改全域设定的使用者æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "更改全域手续"
+
+#: html/Admin/Global/UserRights.html:24 html/Admin/Global/UserRights.html:27 html/Admin/Global/index.html:59
+msgid "Modify global user rights"
+msgstr "更改全域设定的使用者æƒé™"
+
+#: html/Admin/Global/UserRights.html:32
+msgid "Modify global user rights."
+msgstr "更改全域设定的使用者æƒé™ã€‚"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "Modify group metadata or delete group"
+msgstr "更改群组数æ®åŠåˆ é™¤ç¾¤ç»„"
+
+#: html/Admin/Groups/GroupRights.html:24 html/Admin/Groups/GroupRights.html:28 html/Admin/Groups/GroupRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "更改 %1 的群组æƒé™"
+
+#: html/Admin/Queues/GroupRights.html:24 html/Admin/Queues/GroupRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "æ›´æ”¹è¡¨å• %1 的群组æƒé™"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "Modify membership roster for this group"
+msgstr "更改此群组的æˆå‘˜åå•"
+
+#: lib/RT/System.pm:60
+msgid "Modify one's own RT account"
+msgstr "更改个人的å¸å·ä¿¡æ¯"
+
+#: html/Admin/Queues/People.html:24 html/Admin/Queues/People.html:28
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "æ›´æ”¹é“¾æŽ¥åˆ°è¡¨å• %1 的人员"
+
+#: html/Ticket/ModifyPeople.html:24 html/Ticket/ModifyPeople.html:28 html/Ticket/ModifyPeople.html:34
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "æ›´æ”¹ç”³è¯·å• #%1 链接到的人员"
+
+#: html/Admin/Queues/Scrips.html:45
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "æ›´æ”¹è¡¨å• %1 的手续"
+
+#: html/Admin/Global/Scrips.html:43 html/Admin/Global/index.html:41
+msgid "Modify scrips which apply to all queues"
+msgstr "更改适用于所有表å•çš„手续"
+
+#: html/Admin/Global/Template.html:24 html/Admin/Global/Template.html:29 html/Admin/Global/Template.html:80 html/Admin/Queues/Template.html:77
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "æ›´æ”¹æ¨¡æ¿ %1"
+
+#: html/Admin/Global/Templates.html:43
+msgid "Modify templates which apply to all queues"
+msgstr "更改适用于所有表å•çš„模æ¿"
+
+#: html/Admin/Groups/Modify.html:86 html/User/Groups/Modify.html:85
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "更改群组 %1"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify the queue watchers"
+msgstr "更改表å•è§†å¯Ÿå‘˜"
+
+#: html/Admin/Users/Modify.html:237
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "更改使用者 %1"
+
+#: html/Ticket/ModifyAll.html:36
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "æ›´æ”¹ç”³è¯·å• # %1"
+
+#: html/Ticket/Modify.html:24 html/Ticket/Modify.html:27 html/Ticket/Modify.html:33
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "æ›´æ”¹ç”³è¯·å• # %1"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Modify tickets"
+msgstr "更改申请å•"
+
+#: html/Admin/Groups/UserRights.html:24 html/Admin/Groups/UserRights.html:28 html/Admin/Groups/UserRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "更改群组 %1 的使用者æƒé™"
+
+#: html/Admin/Queues/UserRights.html:24 html/Admin/Queues/UserRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "æ›´æ”¹è¡¨å• %1 的使用者æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "更改 '%1' 的视察员"
+
+#: html/Admin/Global/Workflow.html:25 html/Admin/Global/Workflow.html:30 html/Admin/Global/Workflow.html:81 html/Admin/Queues/Workflow.html:77
+#. (loc($WorkflowObj->Name()))
+#. ($WorkflowObj->id)
+msgid "Modify workflow %1"
+msgstr "更改æµç¨‹ %1"
+
+#: html/Admin/Global/Workflows.html:44
+msgid "Modify workflows which apply to all queues"
+msgstr "更改适用于所有表å•çš„æµç¨‹"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ModifyACL"
+msgstr "更改æƒé™æ¸…å•"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "ModifyOwnMembership"
+msgstr "更改自己是å¦å±žäºŽæŸç¾¤ç»„"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyQueueWatchers"
+msgstr "更改表å•è§†å¯Ÿå‘˜"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ModifyScrips"
+msgstr "更改手续"
+
+#: lib/RT/System.pm:60
+msgid "ModifySelf"
+msgstr "更改个人å¸å·"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "ModifyTemplate"
+msgstr "更改模æ¿"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "ModifyTicket"
+msgstr "更改申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Mon"
+msgstr "星期一"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "星期一"
+
+#: html/Work/Elements/MyRequests:11 html/Work/Elements/MyTickets:11
+msgid "More"
+msgstr "更多"
+
+#: html/Ticket/Elements/ShowRequestor:41
+#. ($name)
+msgid "More about %1"
+msgstr "关于 %1 的进一步信æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Morning Shift"
+msgstr "æ—©ç­"
+
+#: html/Edit/Elements/ListButtons:16
+msgid "Move All"
+msgstr "全移"
+
+#: html/Admin/Elements/EditCustomFields:60
+msgid "Move down"
+msgstr "下移"
+
+#: html/Admin/Elements/EditCustomFields:52
+msgid "Move up"
+msgstr "上移"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Multiple"
+msgstr "多é‡"
+
+#: lib/RT/User_Overlay.pm:242
+msgid "Must specify 'Name' attribute"
+msgstr "必须指定 'Name' 的属性"
+
+#: html/SelfService/Elements/MyRequests:48
+#. ($friendly_status)
+msgid "My %1 tickets"
+msgstr "我的 %1 申请å•"
+
+#: html/Work/Elements/Tab:37
+msgid "My Approvals"
+msgstr "表å•ç­¾æ ¸"
+
+#: html/Work/Elements/Tab:35
+msgid "My Requests"
+msgstr "表å•ç”³è¯·è¿½è¸ª"
+
+#: html/Work/Elements/Tab:39
+msgid "My Tickets"
+msgstr "表å•å¤„ç†"
+
+#: html/Approvals/index.html:24 html/Approvals/index.html:25
+msgid "My approvals"
+msgstr "表å•ç­¾æ ¸"
+
+#: html/Admin/Elements/AddCustomFieldValue:31 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/ModifyTemplate:27 html/Admin/Elements/ModifyTemplateAsWorkflow:185 html/Admin/Elements/ModifyUser:29 html/Admin/Groups/Modify.html:43 html/Edit/Elements/SelectQueues:3 html/Edit/Queues/List:8 html/Edit/Users/Add.html:22 html/Edit/Users/List:5 html/Edit/Users/Search.html:31 html/Elements/SelectGroups:25 html/Elements/SelectUsers:27 html/User/Groups/Modify.html:43 html/Work/Tickets/Cc:18
+msgid "Name"
+msgstr "å称"
+
+#: lib/RT/User_Overlay.pm:249
+msgid "Name in use"
+msgstr "å¸å·å·²æœ‰äººä½¿ç”¨"
+
+#: NOT FOUND IN SOURCE
+msgid "Nationality"
+msgstr "国ç±"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "需先由系统管ç†å‘˜è¿›è¡Œæ‰¹å‡†"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "从未更动"
+
+#: html/Elements/Quicksearch:29 html/Work/Elements/Quicksearch:15 html/Work/Tickets/Create.html:53
+msgid "New"
+msgstr "新建立"
+
+#: html/Admin/Elements/ModifyUser:31 html/Admin/Users/Modify.html:92 html/Edit/Users/Info:33 html/User/Prefs.html:87 html/Work/Preferences/Info:49
+msgid "New Password"
+msgstr "æ–°çš„å£ä»¤"
+
+#: etc/initialdata:317 etc/upgrade/2.1.71:16 html/Edit/Elements/CreateApprovalsQueue:21
+msgid "New Pending Approval"
+msgstr "新的待签核事项"
+
+#: html/Ticket/Elements/EditLinks:93 html/Work/Tickets/Elements/EditLinks:12
+msgid "New Relationships"
+msgstr "新增关系"
+
+#: html/Work/Elements/Tab:33
+msgid "New Request"
+msgstr "表å•ç”³è¯·"
+
+#: html/Ticket/Elements/Tabs:35
+msgid "New Search"
+msgstr "新增查询"
+
+#: html/Work/Tickets/Elements/EditPeople:7
+msgid "New Watchers"
+msgstr "新增视察员"
+
+#: html/Admin/Global/CustomField.html:40 html/Admin/Global/CustomFields.html:38 html/Admin/Queues/CustomField.html:51 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "新增自订字段"
+
+#: html/Admin/Elements/GroupTabs:53 html/User/Elements/GroupTabs:51
+msgid "New group"
+msgstr "新增群组"
+
+#: html/SelfService/Prefs.html:31
+msgid "New password"
+msgstr "æ–°çš„å£ä»¤"
+
+#: lib/RT/User_Overlay.pm:769
+msgid "New password notification sent"
+msgstr "é€å‡ºæ–°å£ä»¤é€šçŸ¥"
+
+#: html/Admin/Elements/QueueTabs:69
+msgid "New queue"
+msgstr "新增表å•"
+
+#: NOT FOUND IN SOURCE
+msgid "New request"
+msgstr "æ出申请å•"
+
+#: html/Admin/Elements/SelectRights:41
+msgid "New rights"
+msgstr "新增æƒé™"
+
+#: html/Admin/Global/Scrip.html:39 html/Admin/Global/Scrips.html:38 html/Admin/Queues/Scrip.html:42 html/Admin/Queues/Scrips.html:54
+msgid "New scrip"
+msgstr "新增手续"
+
+#: html/Work/Search/index.html:62
+msgid "New search"
+msgstr "é‡æ–°æŸ¥è¯¢"
+
+#: html/Admin/Global/Template.html:59 html/Admin/Global/Templates.html:38 html/Admin/Queues/Template.html:57 html/Admin/Queues/Templates.html:49
+msgid "New template"
+msgstr "新增模æ¿"
+
+#: html/SelfService/Elements/Tabs:47
+msgid "New ticket"
+msgstr "æ出申请å•"
+
+#: lib/RT/Ticket_Overlay.pm:2841
+msgid "New ticket doesn't exist"
+msgstr "没有新申请å•"
+
+#: html/Admin/Elements/UserTabs:51
+msgid "New user"
+msgstr "新增使用者"
+
+#: html/Admin/Elements/CreateUserCalled:25
+msgid "New user called"
+msgstr "新使用者åå­—"
+
+#: html/Admin/Queues/People.html:54 html/Ticket/Elements/EditPeople:28
+msgid "New watchers"
+msgstr "新视察员"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "New window setting"
+msgstr "更新窗å£è®¾å®š"
+
+#: html/Admin/Global/Workflow.html:60 html/Admin/Global/Workflows.html:39 html/Admin/Queues/Workflow.html:57 html/Admin/Queues/Workflows.html:50
+msgid "New workflow"
+msgstr "新增æµç¨‹"
+
+#: html/Ticket/Elements/Tabs:68
+msgid "Next"
+msgstr "下一项"
+
+#: html/Search/Listing.html:47 html/Work/Search/index.html:24
+msgid "Next page"
+msgstr "下一页"
+
+#: html/Admin/Elements/ModifyUser:49
+msgid "NickName"
+msgstr "昵称"
+
+#: html/Admin/Users/Modify.html:62 html/User/Prefs.html:50 html/Work/Preferences/Info:26
+msgid "Nickname"
+msgstr "昵称"
+
+#: NOT FOUND IN SOURCE
+msgid "Night Shift"
+msgstr "å°å¤œç­"
+
+#: html/Edit/Global/Basic/Top:27 html/Edit/Queues/Basic/Top:83
+msgid "No"
+msgstr "å¦"
+
+#: html/Admin/Elements/EditCustomField:89 html/Admin/Elements/EditCustomFields:103
+msgid "No CustomField"
+msgstr "无自订字段"
+
+#: html/Admin/Groups/GroupRights.html:83 html/Admin/Groups/UserRights.html:70
+msgid "No Group defined"
+msgstr "尚未定义群组"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:67
+msgid "No Queue defined"
+msgstr "没有定义好的表å•"
+
+#: bin/rt-crontool:55
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "找ä¸åˆ° RT ä½¿ç”¨è€…ã€‚è¯·å‘ RT 管ç†å‘˜æŸ¥è¯¢ã€‚\\n"
+
+#: html/Admin/Global/Template.html:78 html/Admin/Queues/Template.html:75
+msgid "No Template"
+msgstr "没有模æ¿"
+
+#: bin/rt-commit-handler:763
+msgid "No Ticket specified. Aborting ticket "
+msgstr "未指定申请å•ã€‚é€€å‡ºç”³è¯·å• "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "未指定申请å•ã€‚退出申请å•æ›´æ”¹\\n\\n"
+
+#: html/Admin/Elements/ModifyWorkflow:237 html/Admin/Global/Workflow.html:79 html/Admin/Queues/Workflow.html:75
+msgid "No Workflow"
+msgstr "没有æµç¨‹"
+
+#: html/Approvals/Elements/Approve:45 html/Work/Approvals/Elements/Approve:35
+msgid "No action"
+msgstr "æš‚ä¸å¤„ç†"
+
+#: lib/RT/Interface/Web.pm:958
+msgid "No column specified"
+msgstr "未指定字段"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "找ä¸åˆ°å‘½ä»¤"
+
+#: html/Elements/ViewUser:35 html/Ticket/Elements/ShowRequestor:44
+msgid "No comment entered about this user"
+msgstr "没有对这å使用者的评论"
+
+#: lib/RT/Ticket_Overlay.pm:2229 lib/RT/Ticket_Overlay.pm:2299
+msgid "No correspondence attached"
+msgstr "没有附上申请å•å›žå¤"
+
+#: lib/RT/Action/Generic.pm:149 lib/RT/Condition/Generic.pm:175 lib/RT/Search/ActiveTicketsInQueue.pm:55 lib/RT/Search/Generic.pm:112
+#. (ref $self)
+msgid "No description for %1"
+msgstr "没有对 %1 çš„æè¿°"
+
+#: lib/RT/Users_Overlay.pm:149
+msgid "No group specified"
+msgstr "未指定群组"
+
+#: lib/RT/User_Overlay.pm:987
+msgid "No password set"
+msgstr "没有设定å£ä»¤"
+
+#: lib/RT/Queue_Overlay.pm:260
+msgid "No permission to create queues"
+msgstr "没有新增表å•çš„æƒé™"
+
+#: lib/RT/Ticket_Overlay.pm:361
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "æ²¡æœ‰åœ¨è¡¨å• '%1' 新增申请å•çš„æƒé™"
+
+#: lib/RT/User_Overlay.pm:208
+msgid "No permission to create users"
+msgstr "没有新增使用者的æƒé™"
+
+#: html/SelfService/Display.html:123
+msgid "No permission to display that ticket"
+msgstr "没有显示该申请å•çš„æƒé™"
+
+#: html/SelfService/Update.html:68
+msgid "No permission to view update ticket"
+msgstr "没有检视申请å•æ›´æ–°çš„æƒé™"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1514
+msgid "No principal specified"
+msgstr "未指定å•ä½"
+
+#: html/Admin/Queues/People.html:153 html/Admin/Queues/People.html:163
+msgid "No principals selected."
+msgstr "未指定å•ä½ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "No protocol specified in %1"
+msgstr "%1 内未指定åè®®"
+
+#: html/Admin/Queues/index.html:34
+msgid "No queues matching search criteria found."
+msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è¯¢æ¡ä»¶çš„表å•ã€‚"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "找ä¸åˆ°æƒé™"
+
+#: html/Admin/Elements/SelectRights:32
+msgid "No rights granted."
+msgstr "没有选定æƒé™"
+
+#: html/Search/Bulk.html:160 html/Work/Search/Bulk.html:117
+msgid "No search to operate on."
+msgstr "没有è¦è¿›è¡Œçš„查询"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "未指定申请å•ç¼–å·"
+
+#: lib/RT/Transaction_Overlay.pm:427 lib/RT/Transaction_Overlay.pm:465
+msgid "No transaction type specified"
+msgstr "未指定更动报告类别"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "未指定使用者或电å­é‚®ä»¶åœ°å€"
+
+#: html/Admin/Users/index.html:35
+msgid "No users matching search criteria found."
+msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è¯¢æ¡ä»¶çš„使用者。"
+
+#: bin/rt-commit-handler:643
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "找ä¸åˆ°åˆæ ¼çš„ RT 使用者。RT cvs 处ç†å™¨å·²åœç”¨ã€‚è¯·å‘ RT 管ç†è€…询问。\\n"
+
+#: lib/RT/Interface/Web.pm:955
+msgid "No value sent to _Set!\\n"
+msgstr "_Set 没有收到任何值!\\n"
+
+#: html/Search/Elements/TicketRow:36 html/Work/Search/TicketRow:9
+msgid "Nobody"
+msgstr "没有人"
+
+#: lib/RT/Interface/Web.pm:960
+msgid "Nonexistant field?"
+msgstr "字段ä¸å­˜åœ¨ï¼Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Normal Users"
+msgstr "一般用户群组"
+
+#: NOT FOUND IN SOURCE
+msgid "Not configured to fetch the content from a %1 in %2"
+msgstr "未设定æˆä»Ž %2 å†…æ’·å– %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Not logged in"
+msgstr "尚未登入"
+
+#: html/Elements/Header:59
+msgid "Not logged in."
+msgstr "尚未登入"
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "尚未设定"
+
+#: html/NoAuth/Reminder.html:26
+msgid "Not yet implemented."
+msgstr "尚未完工。"
+
+#: NOT FOUND IN SOURCE
+msgid "Not yet implemented...."
+msgstr "尚未完工..."
+
+#: html/Approvals/Elements/Approve:48 html/Work/Tickets/Elements/AddContent:9
+msgid "Notes"
+msgstr "备注"
+
+#: NOT FOUND IN SOURCE
+msgid "Notes:"
+msgstr "备注:"
+
+#: lib/RT/User_Overlay.pm:772
+msgid "Notification could not be sent"
+msgstr "无法é€å‡ºé€šçŸ¥"
+
+#: etc/initialdata:93
+msgid "Notify AdminCcs"
+msgstr "通知管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:89
+msgid "Notify AdminCcs as Comment"
+msgstr "以评论方å¼é€šçŸ¥ç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:120
+msgid "Notify Other Recipients"
+msgstr "通知其它收件人"
+
+#: etc/initialdata:116
+msgid "Notify Other Recipients as Comment"
+msgstr "以评论方å¼é€šçŸ¥å…¶å®ƒæ”¶ä»¶äºº"
+
+#: etc/initialdata:85
+msgid "Notify Owner"
+msgstr "通知承办人"
+
+#: etc/initialdata:81
+msgid "Notify Owner as Comment"
+msgstr "以评论方å¼é€šçŸ¥æ‰¿åŠžäºº"
+
+#: etc/initialdata:361
+msgid "Notify Owner of their rejected ticket"
+msgstr "通知承办人申请å•å·²é©³å›ž"
+
+#: etc/initialdata:350
+msgid "Notify Owner of their ticket has been approved by all approvers"
+msgstr "通知承办人申请å•å·²å®Œæˆå…¨éƒ¨ç­¾æ ¸"
+
+#: etc/initialdata:338
+msgid "Notify Owner of their ticket has been approved by some approver"
+msgstr "通知承办人申请å•å·²å®ŒæˆæŸé¡¹ç­¾æ ¸"
+
+#: etc/initialdata:319 etc/upgrade/2.1.71:17 html/Edit/Elements/CreateApprovalsQueue:22
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "æ•´ç†å¾…签核事项,通知承办人åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:77
+msgid "Notify Requestors"
+msgstr "通知申请人"
+
+#: etc/initialdata:103
+msgid "Notify Requestors and Ccs"
+msgstr "通知申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:98
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "以评论方å¼é€šçŸ¥ç”³è¯·äººåŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:112
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "通知申请人ã€å‰¯æœ¬åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:108
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "以评论方å¼é€šçŸ¥ç”³è¯·äººã€å‰¯æœ¬åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Work/Tickets/Cc:55
+msgid "Notify people:"
+msgstr "通知对象"
+
+#: NOT FOUND IN SOURCE
+msgid "Nov"
+msgstr "å一月"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "11"
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "å一月"
+
+#: html/Edit/Global/Basic/Top:83
+msgid "OIN104"
+msgstr "104eHRMS 接å£"
+
+#: html/Edit/Global/Workflow/Export.html:30 html/Work/Copyright.html:23
+msgid "OK"
+msgstr "确定"
+
+#: lib/RT/Record.pm:156
+msgid "Object could not be created"
+msgstr "无法新增对象"
+
+#: lib/RT/Record.pm:175
+msgid "Object created"
+msgstr "对象新增完毕"
+
+#: NOT FOUND IN SOURCE
+msgid "Occupation Status"
+msgstr "在èŒçŠ¶æ€"
+
+#: NOT FOUND IN SOURCE
+msgid "Oct"
+msgstr "å月"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "10"
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "å月"
+
+#: NOT FOUND IN SOURCE
+msgid "Office Phone"
+msgstr "办公室电è¯"
+
+#: html/Elements/SelectDateRelation:34
+msgid "On"
+msgstr "等于"
+
+#: html/Edit/Global/CustomField/Top:68
+msgid "On Change"
+msgstr "更改申请å•æ—¶"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "评论时"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "回å¤ç”³è¯·å•æ—¶"
+
+#: etc/initialdata:137 html/Edit/Global/CustomField/Top:57
+msgid "On Create"
+msgstr "新增申请å•æ—¶"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "承办人改å˜æ—¶"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "表å•æ”¹å˜æ—¶"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "解决申请å•æ—¶"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "现况改å˜æ—¶"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "å‘生更动时"
+
+#: html/Approvals/Elements/PendingMyApproval:49
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "仅显示 %1 之åŽæ–°å¢žçš„申请å•"
+
+#: html/Approvals/Elements/PendingMyApproval:47
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "仅显示 %1 之å‰æ–°å¢žçš„申请å•"
+
+#: html/Edit/Global/GroupRight/List:9 html/Edit/Global/GroupRight/Top:16 html/Edit/Groups/List:11 html/Edit/Groups/Top:18 html/Edit/Queues/Basic/Top:68 html/Edit/Queues/List:15 html/Edit/Queues/List:27 html/Elements/Quicksearch:30 html/Work/Delegates/Info:48 html/Work/Delegates/Info:51 html/Work/Delegates/List:12 html/Work/Elements/Quicksearch:16 html/Work/Overview/Info:41 html/Work/Tickets/Display.html:51
+msgid "Open"
+msgstr "å¼€å¯"
+
+#: html/Ticket/Elements/Tabs:135
+msgid "Open it"
+msgstr "å¼€å¯"
+
+#: html/SelfService/Elements/Tabs:41
+msgid "Open tickets"
+msgstr "å¼€å¯çš„申请å•"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in a new window"
+msgstr "在新窗å£å¼€å¯(列表的)申请å•"
+
+#: html/Admin/Users/Prefs.html:39
+msgid "Open tickets (from listing) in another window"
+msgstr "在å¦ä¸€ä¸ªçª—å£å¼€å¯(列表的)申请å•"
+
+#: etc/initialdata:132
+msgid "Open tickets on correspondence"
+msgstr "收到回å¤æ—¶å³å¼€å¯ç”³è¯·å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Opened Tickets"
+msgstr "已申请è¿è¡Œä¸­è¡¨å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Opinion"
+msgstr "æ„è§"
+
+#: html/Edit/Global/CustomField/Info:35
+msgid "Option Description"
+msgstr "选项æè¿°"
+
+#: html/Edit/Global/CustomField/Info:29
+msgid "Option Name"
+msgstr "选项å称"
+
+#: html/Search/Elements/PickRestriction:100 html/Work/Search/PickRestriction:87
+msgid "Ordering and sorting"
+msgstr "顺åºä¸ŽæŽ’åºæ–¹å¼"
+
+#: html/Admin/Elements/ModifyUser:45 html/Admin/Users/Modify.html:116 html/Edit/Elements/SelectUsers:7 html/Edit/Global/Basic/Top:55 html/Elements/SelectUsers:28 html/User/Prefs.html:110 html/Work/Preferences/Info:77
+msgid "Organization"
+msgstr "组织å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Organization:"
+msgstr "组织:"
+
+#: html/Approvals/Elements/Approve:32
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "原申请å•ï¼š#%1"
+
+#: html/Edit/Elements/PickUsers:111 html/Edit/Users/Add.html:106 html/Work/Tickets/Cc:80
+msgid "Other comma-delimited email addresses"
+msgstr "其它e-mailå¸å· (ä»…e-mail通知;多笔å¸å·è¯·ç”¨é€—å·','区隔)"
+
+#: html/Admin/Elements/ModifyQueue:54 html/Admin/Queues/Modify.html:68 html/Edit/Queues/Basic/Top:44
+msgid "Over time, priority moves toward"
+msgstr "优先顺ä½éšæ—¶é—´å¢žåŠ è°ƒæ•´ä¸º"
+
+#: NOT FOUND IN SOURCE
+msgid "Override current custom fields with fields from %1"
+msgstr "以 %1 表å•çš„自订字段å–代现有字段"
+
+#: html/Admin/Elements/CheckOverrideGlobalACL:25
+msgid "Override global rights"
+msgstr "å–代全域æƒé™"
+
+#: html/Admin/Elements/CheckOverrideGlobalACL:36
+#. (loc_fuzzy($msg))
+msgid "OverrideGlobalACL status %1"
+msgstr "å–代全域æƒé™ %1"
+
+#: html/Work/Elements/Tab:31
+msgid "Overview"
+msgstr "总览"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Own tickets"
+msgstr "承办申请å•"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "OwnTicket"
+msgstr "承办申请å•"
+
+#: etc/initialdata:38 html/Admin/Elements/ModifyTemplateAsWorkflow:141 html/Edit/Global/Workflow/Owner.html:19 html/Edit/Queues/Basic/Top:51 html/Edit/Queues/Basic/Top:59 html/Elements/MyRequests:31 html/SelfService/Elements/MyRequests:29 html/Ticket/Create.html:47 html/Ticket/Elements/EditPeople:42 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/ShowPeople:26 html/Ticket/Update.html:62 html/Work/Elements/MyRequests:19 html/Work/Elements/Quicksearch:18 html/Work/Tickets/Elements/EditPeople:28 html/Work/Tickets/Elements/ShowBasics:21 html/Work/Tickets/Update.html:27 lib/RT/ACE_Overlay.pm:85 lib/RT/Tickets_Overlay.pm:1263
+msgid "Owner"
+msgstr "承办人"
+
+#: NOT FOUND IN SOURCE
+msgid "Owner changed from %1 to %2"
+msgstr "承办人已从 %1 改为 %2"
+
+#: lib/RT/Transaction_Overlay.pm:539
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "强制将承办人从 %1 改为 %2"
+
+#: html/Search/Elements/PickRestriction:30 html/Work/Search/PickRestriction:10
+msgid "Owner is"
+msgstr "承办人"
+
+#: html/Work/Elements/List:27 html/Work/Queues/List:8 html/Work/Tickets/Create.html:55 html/Work/Tickets/Elements/ShowBasics:60
+msgid "Owner's Phone"
+msgstr "承办人电è¯"
+
+#: html/Edit/Elements/Page:40
+msgid "Page #"
+msgstr " "
+
+#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:75 html/Work/Preferences/Info:40
+msgid "Pager"
+msgstr "呼å«å™¨"
+
+#: html/Admin/Elements/ModifyUser:73
+msgid "PagerPhone"
+msgstr "呼å«å™¨å·ç "
+
+#: html/Edit/Global/Workflow/Action:75 html/Edit/Global/Workflow/Condition:65
+msgid "Parameter"
+msgstr "呼å«å‚æ•°"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:225
+msgid "Parent"
+msgstr "上级"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/BulkLinks:38 html/Ticket/Elements/EditLinks:109 html/Ticket/Elements/EditLinks:54 html/Ticket/Elements/ShowLinks:46 html/Work/Search/BulkLinks:14 html/Work/Tickets/Elements/EditLinks:113 html/Work/Tickets/Elements/EditLinks:45 html/Work/Tickets/Elements/ShowLinks:26
+msgid "Parents"
+msgstr "æ¯ç”³è¯·å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Park Space"
+msgstr "åœè½¦ä½ç”³è¯·"
+
+#: html/Elements/Login:52 html/User/Prefs.html:83 html/Work/Preferences/Info:46
+msgid "Password"
+msgstr "å£ä»¤"
+
+#: html/NoAuth/Reminder.html:24
+msgid "Password Reminder"
+msgstr "å£ä»¤æ示"
+
+#: lib/RT/User_Overlay.pm:230 lib/RT/User_Overlay.pm:990
+msgid "Password too short"
+msgstr "å£ä»¤å¤ªçŸ­"
+
+#: html/Admin/Users/Modify.html:292 html/User/Prefs.html:209 html/Work/Preferences/Info:173
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "å£ä»¤ï¼š%1"
+
+#: html/Admin/Users/Modify.html:294
+msgid "Passwords do not match."
+msgstr "å£ä»¤ç¡®è®¤å¤±è´¥ã€‚"
+
+#: html/User/Prefs.html:211 html/Work/Preferences/Info:175
+msgid "Passwords do not match. Your password has not been changed"
+msgstr "å£ä»¤ç¡®è®¤å¤±è´¥ã€‚您的å£ä»¤å¹¶æœªæ”¹å˜ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Pelase select a queue"
+msgstr "请选择表å•å称"
+
+#: NOT FOUND IN SOURCE
+msgid "Pending Approval"
+msgstr "等待签核"
+
+#: html/Ticket/Elements/ShowSummary:44 html/Ticket/Elements/Tabs:95 html/Ticket/ModifyAll.html:50
+msgid "People"
+msgstr "人员"
+
+#: NOT FOUND IN SOURCE
+msgid "People with Queue Rights"
+msgstr "拥有表å•æƒé™äººå‘˜"
+
+#: etc/initialdata:125
+msgid "Perform a user-defined action"
+msgstr "执行使用者自订的动作"
+
+#: lib/RT/ACE_Overlay.pm:230 lib/RT/ACE_Overlay.pm:236 lib/RT/ACE_Overlay.pm:562 lib/RT/ACE_Overlay.pm:572 lib/RT/ACE_Overlay.pm:582 lib/RT/ACE_Overlay.pm:647 lib/RT/CurrentUser.pm:82 lib/RT/CurrentUser.pm:91 lib/RT/CustomField_Overlay.pm:100 lib/RT/CustomField_Overlay.pm:201 lib/RT/CustomField_Overlay.pm:233 lib/RT/CustomField_Overlay.pm:511 lib/RT/CustomField_Overlay.pm:90 lib/RT/Group_Overlay.pm:1096 lib/RT/Group_Overlay.pm:1100 lib/RT/Group_Overlay.pm:1109 lib/RT/Group_Overlay.pm:1160 lib/RT/Group_Overlay.pm:1164 lib/RT/Group_Overlay.pm:1170 lib/RT/Group_Overlay.pm:423 lib/RT/Group_Overlay.pm:515 lib/RT/Group_Overlay.pm:593 lib/RT/Group_Overlay.pm:601 lib/RT/Group_Overlay.pm:698 lib/RT/Group_Overlay.pm:702 lib/RT/Group_Overlay.pm:708 lib/RT/Group_Overlay.pm:901 lib/RT/Group_Overlay.pm:905 lib/RT/Group_Overlay.pm:918 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:934 lib/RT/Scrip_Overlay.pm:125 lib/RT/Scrip_Overlay.pm:136 lib/RT/Scrip_Overlay.pm:201 lib/RT/Scrip_Overlay.pm:441 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:87 lib/RT/Template_Overlay.pm:93 lib/RT/Ticket_Overlay.pm:1386 lib/RT/Ticket_Overlay.pm:1396 lib/RT/Ticket_Overlay.pm:1410 lib/RT/Ticket_Overlay.pm:1544 lib/RT/Ticket_Overlay.pm:1553 lib/RT/Ticket_Overlay.pm:1566 lib/RT/Ticket_Overlay.pm:1915 lib/RT/Ticket_Overlay.pm:2053 lib/RT/Ticket_Overlay.pm:2217 lib/RT/Ticket_Overlay.pm:2286 lib/RT/Ticket_Overlay.pm:2647 lib/RT/Ticket_Overlay.pm:2728 lib/RT/Ticket_Overlay.pm:2832 lib/RT/Ticket_Overlay.pm:2847 lib/RT/Ticket_Overlay.pm:3046 lib/RT/Ticket_Overlay.pm:3056 lib/RT/Ticket_Overlay.pm:3061 lib/RT/Ticket_Overlay.pm:3284 lib/RT/Ticket_Overlay.pm:3288 lib/RT/Ticket_Overlay.pm:3487 lib/RT/Ticket_Overlay.pm:3649 lib/RT/Ticket_Overlay.pm:3701 lib/RT/Ticket_Overlay.pm:3907 lib/RT/Transaction_Overlay.pm:415 lib/RT/Transaction_Overlay.pm:422 lib/RT/Transaction_Overlay.pm:451 lib/RT/Transaction_Overlay.pm:458 lib/RT/User_Overlay.pm:1084 lib/RT/User_Overlay.pm:1532 lib/RT/User_Overlay.pm:692 lib/RT/User_Overlay.pm:727 lib/RT/User_Overlay.pm:983
+msgid "Permission Denied"
+msgstr "æƒé™ä¸è¶³"
+
+#: html/Edit/Rights/index.html:3
+msgid "Permission Settings"
+msgstr "æƒé™è®¾å®š"
+
+#: NOT FOUND IN SOURCE
+msgid "Permitted Queues:"
+msgstr "拥有æƒé™è¡¨å•åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Personal"
+msgstr "代ç†äººç¾¤ç»„"
+
+#: html/User/Elements/Tabs:34
+msgid "Personal Groups"
+msgstr "代ç†äººç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "Personal Todo"
+msgstr "ç§äººå¾…办事项"
+
+#: html/User/Groups/index.html:29 html/User/Groups/index.html:39
+msgid "Personal groups"
+msgstr "代ç†äººç¾¤ç»„"
+
+#: html/User/Elements/DelegateRights:36
+msgid "Personal groups:"
+msgstr "代ç†äººç¾¤ç»„:"
+
+#: html/Work/Preferences/Info:24
+msgid "PersonalHomepage"
+msgstr "个人首页"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 1: Create/Rename Groups (%1)"
+msgstr "第一阶段:群组建立åŠæ”¹å (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 2: Disable/Enable Groups (%1)"
+msgstr "第二阶段:群组åœç”¨åŠå¯ç”¨ (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 3: Create/Rename Users (%1)"
+msgstr "第三阶段:使用者建立åŠæ”¹å (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 4: Disable/Enable Users (%1)"
+msgstr "第四阶段:使用者åœç”¨åŠå¯ç”¨ (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phone"
+msgstr "电è¯"
+
+#: html/Work/Delegates/Info:90 html/Work/Overview/Info:72
+msgid "Phone number"
+msgstr "电è¯å·ç "
+
+#: html/Admin/Users/Modify.html:155 html/User/Prefs.html:60 html/Work/Preferences/Info:32
+msgid "Phone numbers"
+msgstr "电è¯å·ç "
+
+#: html/Edit/Queues/Basic/Add.html:3 html/Edit/Queues/Basic/Top:55 html/Edit/Users/Add.html:3 html/Work/Delegates/Add.html:3 html/Work/Delegates/Info:34 html/Work/Tickets/ModifyPeople.html:2
+msgid "Pick"
+msgstr "挑选"
+
+#: NOT FOUND IN SOURCE
+msgid "Place of Departure"
+msgstr "出å‘地点"
+
+#: NOT FOUND IN SOURCE
+msgid "Placeholder"
+msgstr "尚未完工"
+
+#: html/Edit/Elements/PickUsers:31 html/Edit/Elements/PickUsers:44 html/Edit/Elements/SelectCustomFieldType:3 html/Work/Elements/SelectOwner:3 html/Work/Tickets/Elements/EditCustomField:187 html/Work/Tickets/Elements/EditCustomFieldEntry:24 html/Work/Tickets/Elements/EditCustomFieldEntry:35
+msgid "Please Select"
+msgstr "请选择"
+
+#: html/Edit/Elements/104Buttons:30
+msgid "Please check items to be deleted first."
+msgstr "请先选中è¦åˆ é™¤çš„对象"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select a group"
+msgstr "请选择群组"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select a queue's workflow"
+msgstr "请选择表å•æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select one of the category types above."
+msgstr "请从上é¢é€‰æ‹©ä¸€é¡¹åˆ†ç±»ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select role"
+msgstr "请选择角色"
+
+#: NOT FOUND IN SOURCE
+msgid "Policy"
+msgstr "ç»è¥è§„ç« "
+
+#: NOT FOUND IN SOURCE
+msgid "Position"
+msgstr "èŒåŠ¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Position Level"
+msgstr "èŒç­‰"
+
+#: html/Edit/Elements/PickUsers:41 html/Edit/Global/UserRight/List:13 html/Edit/Global/UserRight/Top:23 html/Edit/Queues/Basic/Add.html:26 html/Edit/Users/Add.html:41 html/Work/Delegates/Add.html:26 html/Work/Delegates/Info:84 html/Work/Overview/Info:66
+msgid "Position Name"
+msgstr "èŒåŠ¡å称"
+
+#: html/Edit/Global/UserRight/List:14 html/Edit/Global/UserRight/Top:33
+msgid "Position Number"
+msgstr "èŒåŠ¡ä»£ç "
+
+#: NOT FOUND IN SOURCE
+msgid "Position Rank"
+msgstr "èŒçº§"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr "å好"
+
+#: html/Edit/Elements/104Top:26 html/Elements/Header:51 html/Elements/Tabs:52 html/SelfService/Elements/Tabs:50 html/SelfService/Prefs.html:24 html/User/Prefs.html:24 html/User/Prefs.html:27 html/Work/Elements/Tab:43
+msgid "Preferences"
+msgstr "å好"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "个人信æ¯"
+
+#: lib/RT/Action/Generic.pm:159
+msgid "Prepare Stubbed"
+msgstr "预备动作完毕"
+
+#: html/Ticket/Elements/Tabs:60
+msgid "Prev"
+msgstr "上一项"
+
+#: html/Search/Listing.html:43 html/Work/Search/index.html:20
+msgid "Previous page"
+msgstr "å‰ä¸€é¡µ"
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "优先顺ä½"
+
+#: lib/RT/ACE_Overlay.pm:132 lib/RT/ACE_Overlay.pm:207 lib/RT/ACE_Overlay.pm:551
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "找ä¸åˆ°å•ä½ %1。"
+
+#: html/Search/Elements/PickRestriction:53 html/Ticket/Create.html:153 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:38 html/Work/Search/PickRestriction:34 html/Work/Tickets/Elements/EditBasics:41 lib/RT/Tickets_Overlay.pm:1061
+msgid "Priority"
+msgstr "优先顺ä½"
+
+#: html/Admin/Elements/ModifyQueue:50 html/Admin/Queues/Modify.html:64
+msgid "Priority starts at"
+msgstr "优先顺ä½èµ·å§‹å€¼"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "内部æˆå‘˜"
+
+#: html/Admin/Users/Modify.html:272 html/User/Prefs.html:200 html/Work/Preferences/Info:164
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "内部æˆå‘˜çŠ¶æ€ï¼š%1"
+
+#: html/Admin/Users/index.html:61
+msgid "Privileged users"
+msgstr "内部æˆå‘˜"
+
+#: html/Work/Elements/SelectSearch:16
+msgid "Process Status"
+msgstr "处ç†çŠ¶æ€"
+
+#: html/Edit/Queues/List:10
+msgid "Project Name"
+msgstr "项目å称"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "内部用的虚拟群组"
+
+#: html/Edit/Queues/List:11
+msgid "Public Description"
+msgstr "公开说明"
+
+#: html/Work/Preferences/Info:70
+msgid "Public Info"
+msgstr "公开信æ¯"
+
+#: html/Work/Elements/104Header:88
+msgid "Public Service"
+msgstr "公共事务区"
+
+#: NOT FOUND IN SOURCE
+msgid "Purging stale data: %1"
+msgstr "移除过期数æ®: %1"
+
+#: html/Edit/Users/Search.html:4
+msgid "Query"
+msgstr "查询"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:166 html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Elements/Quicksearch:28 html/Search/Elements/PickRestriction:45 html/SelfService/Create.html:32 html/Ticket/Create.html:37 html/Ticket/Elements/EditBasics:63 html/Ticket/Elements/ShowBasics:42 html/User/Elements/DelegateRights:79 html/Work/Elements/MyApprovals:10 html/Work/Elements/MyRequests:17 html/Work/Elements/MyTickets:17 html/Work/Elements/Quicksearch:14 html/Work/Search/PickRestriction:26 html/Work/Tickets/Elements/EditBasics:16 lib/RT/Tickets_Overlay.pm:902
+msgid "Queue"
+msgstr "表å•"
+
+#: html/Admin/Queues/CustomField.html:41 html/Admin/Queues/Scrip.html:49 html/Admin/Queues/Scrips.html:47 html/Admin/Queues/Templates.html:43 html/Admin/Queues/Workflows.html:44
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "找ä¸åˆ°è¡¨å• %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "找ä¸åˆ°è¡¨å• '%1'\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "表å•å…³é”®è¯é€‰å–"
+
+#: html/Admin/Elements/ModifyQueue:30 html/Admin/Queues/Modify.html:42 html/Edit/Queues/Basic/Top:13 html/Edit/Queues/Basic/index.html:36 html/Edit/Queues/Global:21 html/Edit/Queues/List:20 html/Edit/Users/Queue:10 html/Work/Delegates/List:6 html/Work/Elements/List:11 html/Work/Queues/List:5 html/Work/Tickets/Create.html:21 html/Work/Tickets/Elements/ShowBasics:6
+msgid "Queue Name"
+msgstr "表å•å称"
+
+#: html/Edit/Queues/List:22 html/Work/Elements/List:25 html/Work/Queues/List:7 html/Work/Tickets/Create.html:34 html/Work/Tickets/Elements/ShowBasics:19
+msgid "Queue Owner"
+msgstr "业务承办人"
+
+#: html/Edit/Queues/Basic/Top:38
+msgid "Queue Priority"
+msgstr "优先等级"
+
+#: html/Edit/Global/GroupRight/Top:24 html/Edit/Global/UserRight/Top:43 html/Edit/Users/Queue:11 html/Edit/Users/index.html:97
+msgid "Queue Rights"
+msgstr "表å•æƒé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "表å•æ‰‹ç»­"
+
+#: html/Edit/Elements/Tab:38
+msgid "Queue Setup"
+msgstr "表å•è®¾å®š"
+
+#: lib/RT/Queue_Overlay.pm:264
+msgid "Queue already exists"
+msgstr "表å•å·²å­˜åœ¨"
+
+#: lib/RT/Queue_Overlay.pm:273 lib/RT/Queue_Overlay.pm:279
+msgid "Queue could not be created"
+msgstr "无法新增表å•"
+
+#: html/Edit/Queues/autohandler:8 html/Ticket/Create.html:208 html/Work/Tickets/Create.html:180
+msgid "Queue could not be loaded."
+msgstr "无法加载表å•"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:283 lib/RT/StyleGuide.pod:789
+msgid "Queue created"
+msgstr "表å•æ–°å¢žå®Œæ¯•"
+
+#: html/Admin/Elements/ModifyWorkflow:32
+msgid "Queue is not specified."
+msgstr "未指定表å•ã€‚"
+
+#: html/SelfService/Display.html:70 lib/RT/CustomField_Overlay.pm:97
+msgid "Queue not found"
+msgstr "找ä¸åˆ°è¡¨å•"
+
+#: html/Admin/Elements/Tabs:37 html/Admin/index.html:34
+msgid "Queues"
+msgstr "表å•"
+
+#: html/Work/Elements/Quicksearch:10
+msgid "Quick Search"
+msgstr "表å•çŽ°å†µ"
+
+#: html/Elements/Quicksearch:24
+msgid "Quick search"
+msgstr "表å•ä¸€è§ˆ"
+
+#: html/Elements/Login:44
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:776
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "%2:RT %1 版"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 版,<a href=\"http://bestpractical.com\">Best Practical Solutions å…¬å¸</a>出å“。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1。版æƒæ‰€æœ‰ 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1。版æƒæ‰€æœ‰ 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:24 html/Admin/index.html:25
+msgid "RT Administration"
+msgstr "RT 管ç†é¡µé¢"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RT 认è¯é”™è¯¯ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT 退信:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT 设定错误"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RT 致命错误。讯æ¯æœªè¢«çºªå½•ã€‚"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:40
+msgid "RT Error"
+msgstr "RT 错误"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT 收到从自己寄出的邮件 (%1)。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT 收到从自己寄出的邮件 (%1)。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT 自助æœåŠ¡/已解决的申请å•"
+
+#: html/index.html:24 html/index.html:27
+msgid "RT at a glance"
+msgstr "RT 一览"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT 无法认è¯ä½ "
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT 无法从外部数æ®åº“查询找到申请人信æ¯"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT 找ä¸åˆ°è¡¨å•ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT 无法确认这个 PGP 签章。\\n"
+
+#: html/Edit/Elements/104Header:7 html/Edit/Elements/104Top:20 html/Elements/PageLayout:85 html/Work/Elements/104Header:7
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "%1 专用æµç¨‹ç³»ç»Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "%1 专用 RT 系统:%2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT 已执行您的命令"
+
+#: html/Elements/Login:94
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT 版æƒæ‰€æœ‰ 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;。<br>æœ¬è½¯ä½“ä¾ <a href=\"http://www.gnu.org/copyleft/gpl.html\">GNU 通用公共授æƒç¬¬äºŒç‰ˆ</a> 散布。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT 认为这å¯èƒ½æ˜¯é€€ä¿¡"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT 以未签章方å¼å¤„ç†è¿™å°é‚®ä»¶ã€‚\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT 的电å­é‚®ä»¶å‘½ä»¤æ¨¡å¼é¡»è¦ PGP 认è¯ã€‚您å¯èƒ½æ²¡æœ‰ç­¾ç« ï¼Œæˆ–是您的签章无法辨识。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::Queue-Role"
+msgstr "表å•è¿è¡Œè§’色"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::System-Role"
+msgstr "系统è¿è¡Œè§’色"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::Ticket-Role"
+msgstr "申请å•è¿è¡Œè§’色"
+
+#: html/Work/Tickets/Elements/ShowTransaction:14
+msgid "RT_System"
+msgstr "系统讯æ¯"
+
+#: html/Edit/Global/CustomField/SelectWritable:7
+msgid "Read Only"
+msgstr "åªè¯»"
+
+#: html/Admin/Users/Modify.html:57 html/Admin/Users/Prefs.html:51 html/Edit/Elements/SelectUsers:5 html/Edit/Users/List:6 html/User/Prefs.html:47 html/Work/Preferences/Info:18
+msgid "Real Name"
+msgstr "真实姓å"
+
+#: html/Admin/Elements/ModifyUser:47
+msgid "RealName"
+msgstr "真实姓å"
+
+#: html/Work/Approvals/Display.html:30 html/Work/Tickets/Update.html:81
+msgid "Really reject this ticket?"
+msgstr "您确定è¦é©³å›žè¿™å¼ ç”³è¯·å•å—?"
+
+#: lib/RT/Transaction_Overlay.pm:592
+#. ($value)
+msgid "Reference by %1 added"
+msgstr "已加入 %1 为å‚考本申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:629
+#. ($value)
+msgid "Reference by %1 deleted"
+msgstr "已移除 %1 为å‚考本申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($value)
+msgid "Reference to %1 added"
+msgstr "已加入å‚è€ƒç”³è¯·å• %1"
+
+#: lib/RT/Transaction_Overlay.pm:626
+#. ($value)
+msgid "Reference to %1 deleted"
+msgstr "已移除å‚è€ƒç”³è¯·å• %1"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/BulkLinks:50 html/Ticket/Elements/EditLinks:121 html/Ticket/Elements/EditLinks:81 html/Ticket/Elements/ShowLinks:70 html/Work/Search/BulkLinks:26 html/Work/Tickets/Elements/EditLinks:125 html/Work/Tickets/Elements/EditLinks:81 html/Work/Tickets/Elements/ShowLinks:38
+msgid "Referred to by"
+msgstr "被å‚考"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:184 html/Ticket/Elements/BulkLinks:46 html/Ticket/Elements/EditLinks:117 html/Ticket/Elements/EditLinks:72 html/Ticket/Elements/ShowLinks:60 html/Work/Search/BulkLinks:22 html/Work/Tickets/Elements/EditLinks:121 html/Work/Tickets/Elements/EditLinks:67 html/Work/Tickets/Elements/ShowLinks:33
+msgid "Refers to"
+msgstr "å‚考"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "å‚考"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "在结果范围内查询"
+
+#: html/Search/Elements/PickRestriction:26 html/Work/Search/PickRestriction:7
+msgid "Refine search"
+msgstr "调整查询æ¡ä»¶"
+
+#: html/Work/Overview/index.html:12
+msgid "Refresh"
+msgstr "æ›´æ–°"
+
+#: html/Elements/Refresh:35
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "æ¯ %1 分钟更新页é¢"
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:61 html/Ticket/ModifyAll.html:56
+msgid "Relationships"
+msgstr "关系"
+
+#: html/Edit/Elements/ListButtons:13
+msgid "Remove"
+msgstr "移除"
+
+#: html/Search/Bulk.html:97 html/Work/Search/Bulk.html:77
+msgid "Remove AdminCc"
+msgstr "移除管ç†å‘˜å‰¯æœ¬"
+
+#: html/Search/Bulk.html:93 html/Work/Search/Bulk.html:71
+msgid "Remove Cc"
+msgstr "移除副本"
+
+#: html/Search/Bulk.html:89 html/Work/Search/Bulk.html:65
+msgid "Remove Requestor"
+msgstr "移除申请人"
+
+#: html/Ticket/Elements/ShowTransaction:172 html/Ticket/Elements/Tabs:121 html/Work/Tickets/Display.html:54 html/Work/Tickets/Elements/ShowTransaction:115
+msgid "Reply"
+msgstr "回å¤"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Reply to tickets"
+msgstr "对申请å•è¿›è¡Œå›žå¤"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "ReplyToTicket"
+msgstr "回å¤ç”³è¯·å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Report to Duty"
+msgstr "上下ç­åˆ·å¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Reported on"
+msgstr "到èŒæ—¥æœŸ"
+
+#: etc/initialdata:44 html/Ticket/Update.html:39 html/Work/Elements/List:21 html/Work/Elements/MyApprovals:12 html/Work/Elements/MyTickets:20 html/Work/Elements/SelectSearch:31 html/Work/Tickets/Elements/ShowBasics:62 lib/RT/ACE_Overlay.pm:86
+msgid "Requestor"
+msgstr "申请人"
+
+#: html/Edit/Global/Workflow/Owner.html:44
+msgid "Requestor Group's"
+msgstr "申请人所属群组之"
+
+#: html/Search/Elements/PickRestriction:37 html/Work/Search/PickRestriction:17
+msgid "Requestor email address"
+msgstr "申请人电å­é‚®ä»¶ä¿¡ç®±åœ°å€"
+
+#: html/Edit/Global/Workflow/Owner.html:28
+msgid "Requestor's"
+msgstr "申请人所属之第上"
+
+#: html/Work/Elements/List:23
+msgid "Requestor's Phone"
+msgstr "申请人电è¯"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "申请人"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "申请人地å€"
+
+#: html/SelfService/Create.html:40 html/Ticket/Create.html:55 html/Ticket/Elements/EditPeople:47 html/Ticket/Elements/ShowPeople:30 html/Work/Tickets/Elements/EditPeople:38
+msgid "Requestors"
+msgstr "申请人"
+
+#: html/Admin/Elements/ModifyQueue:60 html/Admin/Queues/Modify.html:74
+msgid "Requests should be due in"
+msgstr "申请å•å¤„ç†æœŸé™"
+
+#: html/Elements/Submit:61
+msgid "Reset"
+msgstr "é‡è®¾"
+
+#: html/Admin/Users/Modify.html:158 html/User/Prefs.html:63 html/Work/Preferences/Info:34
+msgid "Residence"
+msgstr "ä½å¤„"
+
+#: NOT FOUND IN SOURCE
+msgid "Resolution"
+msgstr "解决状æ€"
+
+#: html/Ticket/Elements/Tabs:131 html/Work/Tickets/Display.html:57
+msgid "Resolve"
+msgstr "解决"
+
+#: html/Ticket/Update.html:137
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "è§£å†³ç”³è¯·å• #%1 (%2)"
+
+#: etc/initialdata:308 html/Elements/SelectDateType:27 lib/RT/Ticket_Overlay.pm:1215
+msgid "Resolved"
+msgstr "已解决"
+
+#: html/Search/Bulk.html:132 html/Ticket/ModifyAll.html:72 html/Ticket/Update.html:71 html/Work/Search/Bulk.html:84 html/Work/Tickets/Update.html:38
+msgid "Response to requestors"
+msgstr "回å¤ç”³è¯·äºº"
+
+#: NOT FOUND IN SOURCE
+msgid "Responsibility Type"
+msgstr "责任区分"
+
+#: html/Elements/ListActions:25
+msgid "Results"
+msgstr "结果"
+
+#: html/Search/Elements/PickRestriction:104 html/Work/Search/PickRestriction:90
+msgid "Results per page"
+msgstr "æ¯é¡µåˆ—出几笔结果"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:99 html/User/Prefs.html:94 html/Work/Preferences/Info:56
+msgid "Retype Password"
+msgstr "å†æ¬¡è¾“å…¥å£ä»¤"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "在 %4 (%5) 的范围内找ä¸åˆ° %2 %3 çš„ %1 æƒé™\\n"
+
+#: lib/RT/ACE_Overlay.pm:612
+msgid "Right Delegated"
+msgstr "æƒé™ä»£ç†å®Œæ¯•"
+
+#: lib/RT/ACE_Overlay.pm:302
+msgid "Right Granted"
+msgstr "æƒé™è®¾å®šå®Œæ¯•"
+
+#: lib/RT/ACE_Overlay.pm:160
+msgid "Right Loaded"
+msgstr "æƒé™åŠ è½½å®Œæ¯•"
+
+#: lib/RT/ACE_Overlay.pm:677 lib/RT/ACE_Overlay.pm:692
+msgid "Right could not be revoked"
+msgstr "无法撤消æƒé™"
+
+#: html/User/Delegation.html:63
+msgid "Right not found"
+msgstr "找ä¸åˆ°æƒé™"
+
+#: lib/RT/ACE_Overlay.pm:542 lib/RT/ACE_Overlay.pm:637
+msgid "Right not loaded."
+msgstr "æƒé™å¹¶æœªåŠ è½½ã€‚"
+
+#: lib/RT/ACE_Overlay.pm:688
+msgid "Right revoked"
+msgstr "æƒé™æ’¤æ¶ˆå®Œæ¯•"
+
+#: html/Admin/Elements/UserTabs:40
+msgid "Rights"
+msgstr "æƒé™åŠä»£ç†äºº"
+
+#: lib/RT/Interface/Web.pm:857
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "无法将æƒé™èµ‹äºˆ %1"
+
+#: lib/RT/Interface/Web.pm:887
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "无法撤消 %1 çš„æƒé™"
+
+#: html/Edit/Groups/Member:54 html/Edit/Groups/Members/List:10
+msgid "Role Members"
+msgstr "角色æˆå‘˜"
+
+#: html/Edit/Groups/Member:37 html/Edit/Groups/Members/Add.html:13 html/Edit/Groups/Members/List:7 html/Edit/Groups/Roles/List:4 html/Edit/Groups/Roles/Top:7
+msgid "Role Name"
+msgstr "角色å称"
+
+#: html/Admin/Global/GroupRights.html:50 html/Admin/Queues/GroupRights.html:52 html/Edit/Global/Workflow/Owner.html:55 html/Edit/Global/Workflow/Owner.html:81 html/Edit/Groups/Member:24
+msgid "Roles"
+msgstr "角色"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "交由系统管ç†å‘˜ç­¾æ ¸"
+
+#: html/Edit/Global/Workflow/Action:23
+msgid "Run Approval"
+msgstr "签核执行"
+
+#: html/Edit/Global/Basic/Top:81
+msgid "SMTPDebug"
+msgstr "SMTP 侦错纪录"
+
+#: html/Edit/Global/Basic/Top:63
+msgid "SMTPFrom"
+msgstr "SMTP 寄件地å€"
+
+#: html/Edit/Global/Basic/Top:61
+msgid "SMTPServer"
+msgstr "SMTP æœåŠ¡å™¨"
+
+#: NOT FOUND IN SOURCE
+msgid "Sat"
+msgstr "星期六"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "星期六"
+
+#: html/Edit/Elements/104Buttons:83 html/Work/Preferences/index.html:33 html/Work/Tickets/Elements/EditBasics:63 html/Work/Tickets/Elements/EditLinks:133 html/Work/Tickets/Elements/EditPeople:51
+msgid "Save"
+msgstr "储存"
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyLinks.html:38 html/Ticket/ModifyPeople.html:37
+msgid "Save Changes"
+msgstr "储存更改"
+
+#: NOT FOUND IN SOURCE
+msgid "Save changes"
+msgstr "储存更改"
+
+#: html/Admin/Global/Scrip.html:48 html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->id)
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "手续 #%1"
+
+#: html/Edit/Global/Scrip/List:9 html/Edit/Global/Scrip/Top:41
+msgid "Scrip Action"
+msgstr "讯æ¯é€šçŸ¥åŠ¨ä½œ"
+
+#: html/Edit/Global/Scrip/List:8 html/Edit/Global/Scrip/Top:15
+msgid "Scrip Condition"
+msgstr "讯æ¯é€šçŸ¥æ¡ä»¶"
+
+#: lib/RT/Scrip_Overlay.pm:180
+msgid "Scrip Created"
+msgstr "手续新增完毕"
+
+#: html/Edit/Global/Scrip/List:7 html/Edit/Global/Scrip/Top:9
+msgid "Scrip Name"
+msgstr "讯æ¯å称"
+
+#: html/Admin/Elements/EditScrips:85
+msgid "Scrip deleted"
+msgstr "手续删除完毕"
+
+#: html/Admin/Elements/QueueTabs:45 html/Admin/Elements/SystemTabs:32 html/Admin/Global/index.html:40
+msgid "Scrips"
+msgstr "手续"
+
+#: html/Edit/Global/autohandler:9 html/Edit/Queues/autohandler:24
+msgid "Scrips "
+msgstr "讯æ¯é€šçŸ¥"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "%1 的手续\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "适用于所有表å•çš„手续"
+
+#: html/Edit/Elements/104Buttons:86 html/Elements/SimpleSearch:26 html/Search/Elements/PickRestriction:125 html/Ticket/Elements/Tabs:158 html/Work/Elements/Tab:45 html/Work/Search/PickRestriction:108
+msgid "Search"
+msgstr "查询"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "查询æ¡ä»¶"
+
+#: html/Approvals/Elements/PendingMyApproval:38
+msgid "Search for approvals"
+msgstr "签核å•æŸ¥è¯¢"
+
+#: html/Edit/Global/Workflow/Owner.html:31
+msgid "Second-"
+msgstr "二"
+
+#: NOT FOUND IN SOURCE
+msgid "Second-level Users"
+msgstr "二阶主管员工"
+
+#: bin/rt-crontool:187
+msgid "Security:"
+msgstr "安全性:"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "SeeQueue"
+msgstr "查阅表å•"
+
+#: html/Edit/Elements/ListButtons:10
+msgid "Select All"
+msgstr "全选"
+
+#: html/Admin/Groups/index.html:39
+msgid "Select a group"
+msgstr "选择群组"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "选择表å•"
+
+#: html/Work/Queues/Select.html:8
+msgid "Select a queue to link to"
+msgstr "请选择欲连结表å•"
+
+#: html/Admin/Users/index.html:24 html/Admin/Users/index.html:27
+msgid "Select a user"
+msgstr "选择使用者"
+
+#: html/Admin/Global/CustomField.html:37 html/Admin/Global/CustomFields.html:35
+msgid "Select custom field"
+msgstr "选择自订字段"
+
+#: html/Admin/Elements/GroupTabs:51 html/User/Elements/GroupTabs:49
+msgid "Select group"
+msgstr "选择群组"
+
+#: lib/RT/CustomField_Overlay.pm:421
+msgid "Select multiple values"
+msgstr "选择多é‡é¡¹ç›®"
+
+#: lib/RT/CustomField_Overlay.pm:418
+msgid "Select one value"
+msgstr "选择å•ä¸€é¡¹ç›®"
+
+#: html/Admin/Elements/QueueTabs:66
+msgid "Select queue"
+msgstr "选择表å•"
+
+#: html/Admin/Global/Scrip.html:36 html/Admin/Global/Scrips.html:35 html/Admin/Queues/Scrip.html:39 html/Admin/Queues/Scrips.html:51
+msgid "Select scrip"
+msgstr "选择手续"
+
+#: html/Admin/Global/Template.html:56 html/Admin/Global/Templates.html:35 html/Admin/Queues/Template.html:54 html/Admin/Queues/Templates.html:46
+msgid "Select template"
+msgstr "选择模æ¿"
+
+#: html/Admin/Elements/UserTabs:48
+msgid "Select user"
+msgstr "选择使用者"
+
+#: html/Admin/Global/Workflow.html:57 html/Admin/Global/Workflows.html:36 html/Admin/Queues/Workflow.html:54 html/Admin/Queues/Workflows.html:47
+msgid "Select workflow"
+msgstr "选择æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "SelectExternal"
+msgstr "系统选项"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectMultiple"
+msgstr "多é‡é€‰é¡¹"
+
+#: lib/RT/CustomField_Overlay.pm:34
+msgid "SelectSingle"
+msgstr "å•ä¸€é€‰é¡¹"
+
+#: html/Edit/Elements/PickUsers:87 html/Edit/Users/Add.html:78
+msgid "Selected users:"
+msgstr "新增对象:"
+
+#: NOT FOUND IN SOURCE
+msgid "Self Service"
+msgstr "自助æœåŠ¡"
+
+#: etc/initialdata:113
+msgid "Send mail to all watchers"
+msgstr "寄信给所有视察员"
+
+#: etc/initialdata:109
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "以评论方å¼å¯„信给所有视察员"
+
+#: etc/initialdata:104
+msgid "Send mail to requestors and Ccs"
+msgstr "寄信给申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:99
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "以评论方å¼å¯„信给申请人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:78
+msgid "Sends a message to the requestors"
+msgstr "寄信给申请人"
+
+#: etc/initialdata:117 etc/initialdata:121
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "寄信给特定的副本åŠå¯†ä»¶å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:94
+msgid "Sends mail to the administrative Ccs"
+msgstr "寄信给管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:90
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "以评论寄信给管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:82 etc/initialdata:86
+msgid "Sends mail to the owner"
+msgstr "寄信给申请人"
+
+#: NOT FOUND IN SOURCE
+msgid "Sep"
+msgstr "ä¹æœˆ"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "09"
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "ä¹æœˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Setting %1's 'Disabled' property to %2"
+msgstr "%1 的「åœç”¨ã€å±žæ€§å·²è®¾ä¸º %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Shift Type"
+msgstr "ç­åˆ«å±žæ€§"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "显示结果"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show approved requests"
+msgstr "显示已批准的签核å•"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show basics"
+msgstr "显示基本信æ¯"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show denied requests"
+msgstr "显示已驳回的签核å•"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show details"
+msgstr "显示细节"
+
+#: html/Approvals/Elements/PendingMyApproval:42
+msgid "Show pending requests"
+msgstr "显示待处ç†çš„签核å•"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show requests awaiting other approvals"
+msgstr "显示尚待他人批准的签核å•"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "Show ticket private commentary"
+msgstr "显示申请å•å†…çš„ç§äººè¯„论"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Show ticket summaries"
+msgstr "显示申请å•æ‘˜è¦"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "ShowACL"
+msgstr "显示æƒé™æ¸…å•"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "ShowScrips"
+msgstr "显示手续"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "ShowTemplate"
+msgstr "显示模æ¿"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowTicket"
+msgstr "显示申请å•"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "ShowTicketComments"
+msgstr "显示申请å•çš„评论"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "登记æˆä¸ºç”³è¯·äººæˆ–副本收件人"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "登记æˆä¸ºç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Admin/Elements/ModifyUser:38 html/Admin/Users/Modify.html:190 html/Admin/Users/Prefs.html:31 html/Edit/Users/Info:52 html/User/Prefs.html:148 html/Work/Preferences/Info:113
+msgid "Signature"
+msgstr "ç­¾åæ¡£"
+
+#: NOT FOUND IN SOURCE
+msgid "Signed in as %1"
+msgstr "使用者:%1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:25
+msgid "Single"
+msgstr "å•ä¸€"
+
+#: html/Edit/Elements/104Top:21 html/Elements/Header:50
+msgid "Skip Menu"
+msgstr "略过选å•"
+
+#: html/Admin/Elements/AddCustomFieldValue:27
+msgid "Sort"
+msgstr "顺åº"
+
+#: NOT FOUND IN SOURCE
+msgid "Sort key"
+msgstr "排åºæ–¹å¼"
+
+#: html/Search/Elements/PickRestriction:108 html/Work/Search/PickRestriction:95
+msgid "Sort results by"
+msgstr "结果排åºæ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "SortOrder"
+msgstr "排åºé¡ºåº"
+
+#: html/Admin/Elements/EditScrip:80 html/Edit/Global/Scrip/Top:75 html/Work/Elements/List:8 html/Work/Elements/MyApprovals:11
+msgid "Stage"
+msgstr "å…³å¡"
+
+#: html/Edit/Global/Workflow/Top:8
+msgid "Stage Action"
+msgstr "å…³å¡è¿è¡ŒåŠ¨ä½œ"
+
+#: html/Edit/Global/Workflow/Top:5
+msgid "Stage Condition"
+msgstr "å…³å¡è¿è¡Œæ¡ä»¶"
+
+#: html/Work/Elements/Quicksearch:17
+msgid "Stalled"
+msgstr "延宕"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "首页"
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/EditDates:31 html/Ticket/Elements/ShowDates:35 html/Work/Tickets/Elements/EditBasics:35
+msgid "Started"
+msgstr "实际起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "无法解读起始日期 '%1"
+
+#: html/Elements/SelectDateType:30 html/Ticket/Create.html:165 html/Ticket/Elements/EditDates:26 html/Ticket/Elements/ShowDates:31 html/Work/Tickets/Elements/EditBasics:26
+msgid "Starts"
+msgstr "应起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "应起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "无法解读起始日期 '%1"
+
+#: html/Admin/Elements/ModifyUser:81 html/Admin/Users/Modify.html:137 html/User/Prefs.html:126 html/Work/Preferences/Info:85
+msgid "State"
+msgstr "å·ž"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Search/Elements/PickRestriction:73 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:30 html/Ticket/Create.html:41 html/Ticket/Elements/EditBasics:37 html/Ticket/Elements/ShowBasics:30 html/Ticket/Update.html:59 html/Work/Elements/List:15 html/Work/Elements/MyRequests:18 html/Work/Elements/MyTickets:18 html/Work/Search/PickRestriction:54 html/Work/Tickets/Elements/EditBasics:19 html/Work/Tickets/Update.html:22 lib/RT/Ticket_Overlay.pm:1209 lib/RT/Tickets_Overlay.pm:927
+msgid "Status"
+msgstr "现况"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr "现况改å˜æ—¶"
+
+#: lib/RT/Transaction_Overlay.pm:477
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "现况从 %1 改为 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "现况改å˜æ—¶"
+
+#: html/Ticket/Elements/Tabs:146
+msgid "Steal"
+msgstr "强制更æ¢æ‰¿åŠžäºº"
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "Steal tickets"
+msgstr "强制承办申请å•"
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "StealTicket"
+msgstr "强制承办申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:545
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "承办人从 %1 强制更æ¢"
+
+#: html/Edit/Groups/Member:68
+msgid "Subgroup"
+msgstr "å­ç¾¤ç»„"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28 html/Search/Bulk.html:135 html/Search/Elements/PickRestriction:42 html/SelfService/Create.html:56 html/SelfService/Elements/MyRequests:27 html/SelfService/Update.html:31 html/Ticket/Create.html:83 html/Ticket/Elements/EditBasics:27 html/Ticket/ModifyAll.html:78 html/Ticket/Update.html:75 html/Work/Elements/MyApprovals:9 html/Work/Elements/MyRequests:16 html/Work/Elements/MyTickets:16 html/Work/Search/Bulk.html:87 html/Work/Search/PickRestriction:22 html/Work/Tickets/Elements/AddSubject:8 html/Work/Tickets/Elements/EditBasics:8 html/Work/Tickets/Elements/ShowBasics:36 html/Work/Tickets/Elements/ShowSubject:8 lib/RT/Ticket_Overlay.pm:1205 lib/RT/Tickets_Overlay.pm:1006
+msgid "Subject"
+msgstr "主题"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:795 lib/RT/Transaction_Overlay.pm:567
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "标题已改为 %1"
+
+#: html/Edit/Users/Info:71 html/Elements/Submit:58 html/Work/Search/Bulk.html:103
+msgid "Submit"
+msgstr "é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "é€å‡ºæµç¨‹"
+
+#: lib/RT/Group_Overlay.pm:746
+msgid "Succeeded"
+msgstr "设定æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Sun"
+msgstr "星期日"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "星期日"
+
+#: html/Edit/Users/System:17 lib/RT/System.pm:53
+msgid "SuperUser"
+msgstr "系统管ç†å‘˜"
+
+#: html/Edit/Global/Basic/Top:29
+msgid "Sync now"
+msgstr "执行åŒæ­¥"
+
+#: html/Edit/Global/Basic/Top:87
+msgid "Sync104HRMS"
+msgstr "自动åŒæ­¥104HRMS"
+
+#: NOT FOUND IN SOURCE
+msgid "Synchronizing HRMS data. This may take a while..."
+msgstr "正在åŒæ­¥åŒ– HRMS 人事系统数æ®ã€‚请ç¨å¾…..."
+
+#: html/User/Elements/DelegateRights:76
+msgid "System"
+msgstr "系统"
+
+#: html/Edit/Global/Scrip/Top:18 html/Edit/Global/Scrip/Top:44
+msgid "System Defined"
+msgstr "系统定义"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:566 lib/RT/Interface/Web.pm:856 lib/RT/Interface/Web.pm:886
+msgid "System Error"
+msgstr "系统错误"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "系统错误。设定æƒé™å¤±è´¥ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "系统错误。设定æƒé™å¤±è´¥ã€‚"
+
+#: html/Edit/Users/index.html:95
+msgid "System Rights"
+msgstr "系统æƒé™"
+
+#: lib/RT/ACE_Overlay.pm:615
+msgid "System error. Right not delegated."
+msgstr "系统错误。æƒé™ä»£ç†å¤±è´¥ã€‚"
+
+#: lib/RT/ACE_Overlay.pm:145 lib/RT/ACE_Overlay.pm:222 lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:897
+msgid "System error. Right not granted."
+msgstr "系统错误。设定æƒé™å¤±è´¥ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "系统错误。无法设定æƒé™ã€‚"
+
+#: html/Admin/Global/GroupRights.html:34 html/Admin/Groups/GroupRights.html:36 html/Admin/Queues/GroupRights.html:35
+msgid "System groups"
+msgstr "系统群组"
+
+#: NOT FOUND IN SOURCE
+msgid "SystemInternal"
+msgstr "系统内部用"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "内部使用的系统角色群组"
+
+#: lib/RT/CurrentUser.pm:318
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: NOT FOUND IN SOURCE
+msgid "TabbedUI"
+msgstr "页签接å£"
+
+#: html/Ticket/Elements/Tabs:142
+msgid "Take"
+msgstr "å—ç†"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "Take tickets"
+msgstr "自行承办申请å•"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "TakeTicket"
+msgstr "自行承办申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:530
+msgid "Taken"
+msgstr "å·²å—ç†"
+
+#: html/Admin/Elements/EditScrip:88
+msgid "Template"
+msgstr "模æ¿"
+
+#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:89
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "æ¨¡æ¿ #%1"
+
+#: html/Edit/Global/Template/List:9 html/Edit/Global/Template/Top:17
+msgid "Template Content"
+msgstr "通知模æ¿å†…容"
+
+#: html/Edit/Global/Template/List:8 html/Edit/Global/Template/Top:13
+msgid "Template Description"
+msgstr "通知模æ¿æè¿°"
+
+#: html/Edit/Global/Template/List:7 html/Edit/Global/Template/Top:9
+msgid "Template Name"
+msgstr "通知模æ¿å称"
+
+#: html/Admin/Elements/EditTemplates:88
+msgid "Template deleted"
+msgstr "模æ¿å·²åˆ é™¤"
+
+#: lib/RT/Scrip_Overlay.pm:156
+msgid "Template not found"
+msgstr "找ä¸åˆ°æ¨¡æ¿"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "找ä¸åˆ°æ¨¡æ¿\\n"
+
+#: lib/RT/Template_Overlay.pm:359
+msgid "Template parsed"
+msgstr "模æ¿å‰–æžå®Œæ¯•"
+
+#: html/Admin/Elements/QueueTabs:48 html/Admin/Elements/SystemTabs:35 html/Admin/Global/index.html:44
+msgid "Templates"
+msgstr "模æ¿"
+
+#: html/Edit/Global/autohandler:8 html/Edit/Queues/autohandler:23
+msgid "Templates "
+msgstr "通知模æ¿"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "找ä¸åˆ° %1 的模æ¿\\n"
+
+#: lib/RT/Interface/Web.pm:954
+msgid "That is already the current value"
+msgstr "å·²ç»æ˜¯ç›®å‰å­—段的值"
+
+#: lib/RT/CustomField_Overlay.pm:242
+msgid "That is not a value for this custom field"
+msgstr "è¿™ä¸æ˜¯è¯¥è‡ªè®¢å­—段的值"
+
+#: lib/RT/Ticket_Overlay.pm:1926
+msgid "That is the same value"
+msgstr "åŒæ ·çš„值"
+
+#: lib/RT/ACE_Overlay.pm:287 lib/RT/ACE_Overlay.pm:596
+msgid "That principal already has that right"
+msgstr "这项å•ä½å·²ç»æ‹¥æœ‰è¯¥æƒé™"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "这项å•ä½å·²ç»æ˜¯è¿™ä¸ªè¡¨å•çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1460
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "这项å•ä½å·²ç»æ˜¯è¿™ä»½ç”³è¯·å•çš„ %1"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "这项å•ä½ä¸æ˜¯è¿™ä¸ªè¡¨å•çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1577
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "这项å•ä½ä¸æ˜¯è¿™ä»½ç”³è¯·å•çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1922
+msgid "That queue does not exist"
+msgstr "此表å•ä¸å­˜åœ¨"
+
+#: lib/RT/Ticket_Overlay.pm:3293
+msgid "That ticket has unresolved dependencies"
+msgstr "这份申请å•æœ‰å°šæœªè§£å†³çš„附属申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "That user already has that right"
+msgstr "使用者已具有该项æƒé™"
+
+#: lib/RT/Ticket_Overlay.pm:3097
+msgid "That user already owns that ticket"
+msgstr "该使用者已ç»æ‰¿åŠžè¿™ä»½ç”³è¯·å•"
+
+#: lib/RT/Ticket_Overlay.pm:3069
+msgid "That user does not exist"
+msgstr "使用者ä¸å­˜åœ¨"
+
+#: lib/RT/User_Overlay.pm:381
+msgid "That user is already privileged"
+msgstr "è¿™å使用者已ç»æ˜¯å†…部æˆå‘˜"
+
+#: lib/RT/User_Overlay.pm:402
+msgid "That user is already unprivileged"
+msgstr "è¿™å使用者属于éžå†…部æˆå‘˜ç¾¤ç»„"
+
+#: lib/RT/User_Overlay.pm:394
+msgid "That user is now privileged"
+msgstr "使用者加入内部æˆå‘˜ç¾¤ç»„完毕"
+
+#: lib/RT/User_Overlay.pm:415
+msgid "That user is now unprivileged"
+msgstr "è¿™å使用者已加入éžå†…部æˆå‘˜ç¾¤ç»„"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "è¿™å使用者已加入éžå†…部æˆå‘˜ç¾¤ç»„"
+
+#: lib/RT/Ticket_Overlay.pm:3090
+msgid "That user may not own tickets in that queue"
+msgstr "使用者å¯èƒ½æ²¡æœ‰æ‰¿åŠžè¡¨å•é‡Œçš„申请å•"
+
+#: lib/RT/Link_Overlay.pm:205
+msgid "That's not a numerical id"
+msgstr "è¿™ä¸æ˜¯ä¸€ä¸ªæ•°å­—ç¼–å·"
+
+#: html/SelfService/Display.html:31 html/Ticket/Create.html:149 html/Ticket/Elements/ShowSummary:27
+msgid "The Basics"
+msgstr "基本信æ¯"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The CC of a ticket"
+msgstr "申请å•çš„副本收件人"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The administrative CC of a ticket"
+msgstr "申请å•çš„管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: lib/RT/Ticket_Overlay.pm:2255
+msgid "The comment has been recorded"
+msgstr "评论已被纪录"
+
+#: bin/rt-crontool:197
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "下列命令会找到 'general' 表å•å†…所有è¿ä½œä¸­çš„申请å•ï¼Œå¹¶å°†å…¶ä¸­ 4 å°æ—¶å†…未处ç†çš„申请å•ä¼˜å…ˆç¨‹åº¦è®¾ä¸º 99:"
+
+#: bin/rt-commit-handler:755 bin/rt-commit-handler:765
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "以下命令未被执行:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:957
+msgid "The new value has been set."
+msgstr "新的字段值设定完æˆã€‚"
+
+#: lib/RT/ACE_Overlay.pm:85
+msgid "The owner of a ticket"
+msgstr "申请å•çš„承办人"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The requestor of a ticket"
+msgstr "申请å•çš„申请人"
+
+#: html/Admin/Elements/EditUserComments:25
+msgid "These comments aren't generally visible to the user"
+msgstr "该使用者ä¸ä¼šçœ‹è§è¿™äº›è¯„论"
+
+#: html/Edit/Global/Workflow/Owner.html:32
+msgid "Third-"
+msgstr "三"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "ç”³è¯·å• %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:188
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "此工具程åºä¼šè®©ä½¿ç”¨è€…ç»ç”± RT 执行任æ„命令。"
+
+#: lib/RT/Transaction_Overlay.pm:200
+msgid "This transaction appears to have no content"
+msgstr "此项更动报告没有内容"
+
+#: html/Ticket/Elements/ShowRequestor:46
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "使用者é€å‡ºçš„å‰ %1 份优先处ç†ç”³è¯·å•"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "使用者é€å‡ºçš„å‰ 25 份优先处ç†ç”³è¯·å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Thu"
+msgstr "星期四"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "星期四"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:163 html/Edit/Global/Workflow/Condition:24
+msgid "Ticket"
+msgstr "申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "ç”³è¯·å• # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "æ›´æ–°ç”³è¯·å• # %1 的全部信æ¯ï¼š%2"
+
+#: html/Ticket/ModifyAll.html:24 html/Ticket/ModifyAll.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "æ›´æ–°ç”³è¯·å• #%1 的全部信æ¯ï¼š%2"
+
+#: html/Approvals/Elements/ShowDependency:45
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "ç”³è¯·å• #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:632 lib/RT/Ticket_Overlay.pm:653
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "ç”³è¯·å• #%1 æˆåŠŸæ–°å¢žäºŽ '%2' 表å•"
+
+#: bin/rt-commit-handler:759
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "åŠ è½½ç”³è¯·å• %1\\n"
+
+#: html/Search/Bulk.html:213 html/Work/Search/Bulk.html:169
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "ç”³è¯·å• %1:%2"
+
+#: html/Edit/Queues/Basic/Top:30 html/Edit/Queues/List:30 html/Work/Queues/List:9
+msgid "Ticket Due"
+msgstr "表å•å¤„ç†æœŸé™"
+
+#: html/Ticket/History.html:24 html/Ticket/History.html:27
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "申请å•å¤„ç†çºªå½• # %1 %2"
+
+#: html/Work/Elements/List:6
+msgid "Ticket ID"
+msgstr "å•å·"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Id"
+msgstr "申请å•ç¼–å·"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Processing Due"
+msgstr "表å•è¿è¡ŒæœŸé™"
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr "申请å•å·²è§£å†³"
+
+#: html/Edit/Queues/Basic/Top:20 html/Edit/Queues/Category/List:6 html/Edit/Queues/Category/Top:7 html/Edit/Queues/List:21 html/Work/Delegates/List:7 html/Work/Delegates/index.html:11 html/Work/Elements/List:12 html/Work/Elements/SelectSearch:9 html/Work/Queues/List:6 html/Work/Queues/Select.html:12 html/Work/Queues/index.html:11 html/Work/Tickets/Create.html:43 html/Work/Tickets/Elements/ShowBasics:34
+msgid "Ticket Type"
+msgstr "表å•ç§ç±»"
+
+#: html/Search/Elements/PickRestriction:62 html/Work/Search/PickRestriction:43
+msgid "Ticket attachment"
+msgstr "申请å•é™„件"
+
+#: lib/RT/Tickets_Overlay.pm:1185
+msgid "Ticket content"
+msgstr "申请å•å†…容"
+
+#: lib/RT/Tickets_Overlay.pm:1231
+msgid "Ticket content type"
+msgstr "申请å•å†…容类别"
+
+#: lib/RT/Ticket_Overlay.pm:520 lib/RT/Ticket_Overlay.pm:529 lib/RT/Ticket_Overlay.pm:539 lib/RT/Ticket_Overlay.pm:642
+msgid "Ticket could not be created due to an internal error"
+msgstr "内部错误,无法新增申请å•"
+
+#: lib/RT/Transaction_Overlay.pm:469
+msgid "Ticket created"
+msgstr "申请å•æ–°å¢žå®Œæ¯•"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "申请å•æ–°å¢žå¤±è´¥"
+
+#: lib/RT/Transaction_Overlay.pm:474
+msgid "Ticket deleted"
+msgstr "申请å•åˆ é™¤å®Œæ¯•"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket id not found"
+msgstr "找ä¸åˆ°ç”³è¯·å•ç¼–å·"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "申请å•åˆ é™¤å®Œæ¯•"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket not found"
+msgstr "找ä¸åˆ°ç”³è¯·å•"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "申请å•çŽ°å†µå·²æ”¹å˜"
+
+#: html/Ticket/Update.html:38
+msgid "Ticket watchers"
+msgstr "申请å•è§†å¯Ÿå‘˜"
+
+#: html/Elements/Tabs:46
+msgid "Tickets"
+msgstr "申请å•"
+
+#: lib/RT/Tickets_Overlay.pm:1402
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "ç”³è¯·å• %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1367
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "ç”³è¯·å• %1 (%2)"
+
+#: NOT FOUND IN SOURCE
+msgid "Tickets I own"
+msgstr "待处ç†çš„申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Tickets I requested"
+msgstr "é€å‡ºçš„申请å•"
+
+#: html/Elements/ViewUser:25
+#. ($name)
+msgid "Tickets from %1"
+msgstr "%1 的申请å•"
+
+#: html/Approvals/Elements/ShowDependency:26
+msgid "Tickets which depend on this approval:"
+msgstr "批准之åŽï¼Œå¯æŽ¥ç»­å¤„ç†ï¼š"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:47 html/Work/Tickets/Elements/EditBasics:32
+msgid "Time Left"
+msgstr "剩馀时间"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:42 html/Work/Tickets/Elements/EditBasics:24
+msgid "Time Worked"
+msgstr "处ç†æ—¶é—´"
+
+#: lib/RT/Tickets_Overlay.pm:1158
+msgid "Time left"
+msgstr "剩馀时间"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "显示时间"
+
+#: lib/RT/Tickets_Overlay.pm:1134
+msgid "Time worked"
+msgstr "已处ç†æ—¶é—´"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "剩馀时间"
+
+#: lib/RT/Ticket_Overlay.pm:1210
+msgid "TimeWorked"
+msgstr "已处ç†æ—¶é—´"
+
+#: bin/rt-commit-handler:401
+msgid "To generate a diff of this commit:"
+msgstr "产生这次更动的差异档:"
+
+#: bin/rt-commit-handler:390
+msgid "To generate a diff of this commit:\\n"
+msgstr "产生这次更动的差异档:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1213
+msgid "Told"
+msgstr "告知日期"
+
+#: html/Edit/Elements/Page:47
+msgid "Total"
+msgstr "页"
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "更动"
+
+#: lib/RT/Transaction_Overlay.pm:666
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "清除更动报告 %1"
+
+#: lib/RT/Transaction_Overlay.pm:126
+msgid "Transaction Created"
+msgstr "更动报告已新增"
+
+#: lib/RT/Transaction_Overlay.pm:90
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "未指定申请å•ç¼–å·ï¼Œæ— æ³•æ–°å¢žæ›´åŠ¨"
+
+#: NOT FOUND IN SOURCE
+msgid "TransactionBatch"
+msgstr "批次更动时"
+
+#: NOT FOUND IN SOURCE
+msgid "TransactionCreate"
+msgstr "新增更动时"
+
+#: lib/RT/Transaction_Overlay.pm:721
+msgid "Transactions are immutable"
+msgstr "ä¸å¯æ›´æ”¹æ›´åŠ¨æŠ¥å‘Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "试图删除æŸé¡¹æƒé™ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Tue"
+msgstr "星期二"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "星期二"
+
+#: html/Admin/Elements/EditCustomField:43 html/Admin/Elements/ModifyTemplateAsWorkflow:135 html/Ticket/Elements/AddWatchers:32 html/Ticket/Elements/AddWatchers:43 html/Ticket/Elements/AddWatchers:53 lib/RT/Ticket_Overlay.pm:1211 lib/RT/Tickets_Overlay.pm:978
+msgid "Type"
+msgstr "类别"
+
+#: lib/RT/ScripCondition_Overlay.pm:103
+msgid "Unimplemented"
+msgstr "尚无实作"
+
+#: html/Admin/Users/Modify.html:67
+msgid "Unix login"
+msgstr "外部系统登入å¸å·"
+
+#: html/Admin/Elements/ModifyUser:61
+msgid "UnixUsername"
+msgstr "外部系统登入å¸å·"
+
+#: lib/RT/Attachment_Overlay.pm:281 lib/RT/Attachment_Overlay.pm:313
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "ä¸å¯è§£çš„内容文字编ç æ–¹å¼ %1"
+
+#: html/Elements/SelectResultsPerPage:36
+msgid "Unlimited"
+msgstr "全数显示"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "éžå†…部æˆå‘˜"
+
+#: lib/RT/Transaction_Overlay.pm:526
+msgid "Untaken"
+msgstr "未被å—ç†"
+
+#: html/Edit/Elements/Page:13 html/Edit/Elements/Page:15
+msgid "Up"
+msgstr "上一页"
+
+#: html/Elements/MyTickets:63 html/Search/Bulk.html:32 html/Work/Elements/MyTickets:83 html/Work/Search/Bulk.html:10 html/Work/Tickets/Elements/EditCustomFieldEntries:63
+msgid "Update"
+msgstr "处ç†"
+
+#: html/Admin/Users/Prefs.html:61
+msgid "Update ID"
+msgstr "æ›´æ–°ç¼–å·"
+
+#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:65 html/Ticket/Update.html:65 html/Work/Search/Bulk.html:81 html/Work/Tickets/Update.html:32
+msgid "Update Type"
+msgstr "更新类别"
+
+#: html/Search/Listing.html:60 html/Work/Search/index.html:32
+msgid "Update all these tickets at once"
+msgstr "整批更新申请å•"
+
+#: html/Admin/Users/Prefs.html:48
+msgid "Update email"
+msgstr "更新电å­é‚®ä»¶ä¿¡ç®±"
+
+#: html/Admin/Users/Prefs.html:54
+msgid "Update name"
+msgstr "æ›´æ–°å¸å·"
+
+#: lib/RT/Interface/Web.pm:467
+msgid "Update not recorded."
+msgstr "更新未被记录"
+
+#: html/Search/Bulk.html:80 html/Work/Search/Bulk.html:52
+msgid "Update selected tickets"
+msgstr "更新选择的申请å•"
+
+#: html/Admin/Users/Prefs.html:35
+msgid "Update signature"
+msgstr "更新签章"
+
+#: html/Ticket/ModifyAll.html:62
+msgid "Update ticket"
+msgstr "更新申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Update ticket # %1"
+msgstr "æ›´æ–°ç”³è¯·å• # %1"
+
+#: html/SelfService/Update.html:24 html/SelfService/Update.html:63
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "æ›´æ–°ç”³è¯·å• #%1"
+
+#: html/Ticket/Update.html:139
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "æ›´æ–°ç”³è¯·å• #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:465
+msgid "Update type was neither correspondence nor comment."
+msgstr "更新的内容并éžç”³è¯·å•å›žå¤ä¹Ÿä¸æ˜¯è¯„论"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1214
+msgid "Updated"
+msgstr "å‰æ¬¡æ›´æ–°"
+
+#: html/Work/Preferences/index.html:15
+msgid "User"
+msgstr "使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "使用者 %1 %2:%3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "使用者 %1 å£ä»¤ï¼š%2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'\\n"
+
+#: etc/initialdata:124 etc/initialdata:191
+msgid "User Defined"
+msgstr "使用者自订"
+
+#: html/Admin/Users/Prefs.html:58
+msgid "User ID"
+msgstr "使用者 ID"
+
+#: html/Edit/Elements/SelectUsers:3 html/Elements/SelectUsers:25
+msgid "User Id"
+msgstr "使用者 ID"
+
+#: html/Edit/Elements/PickUsers:12 html/Edit/Global/UserRight/List:7 html/Edit/Global/UserRight/Top:9 html/Edit/Users/Add.html:13 html/Edit/Users/Search.html:23 html/Work/Delegates/Info:60 html/Work/Tickets/Cc:10
+msgid "User Number"
+msgstr "员工编å·"
+
+#: html/Admin/Elements/GroupTabs:46 html/Admin/Elements/QueueTabs:59 html/Admin/Elements/SystemTabs:46 html/Admin/Global/index.html:58 html/Edit/Global/autohandler:11 html/Edit/Queues/autohandler:26
+msgid "User Rights"
+msgstr "使用者æƒé™"
+
+#: html/Edit/Elements/Tab:34
+msgid "User Setup"
+msgstr "使用者设定"
+
+#: NOT FOUND IN SOURCE
+msgid "User Shift"
+msgstr "员工ç­åˆ«"
+
+#: html/Admin/Users/Modify.html:225
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "无法新增使用者:%1"
+
+#: lib/RT/User_Overlay.pm:326
+msgid "User created"
+msgstr "使用者新增完毕"
+
+#: NOT FOUND IN SOURCE
+msgid "User created: %1"
+msgstr "使用者 %1 新增完毕"
+
+#: NOT FOUND IN SOURCE
+msgid "User created: %1 (%2)"
+msgstr "使用者 %1 (%2) 新增完毕"
+
+#: html/Admin/Global/GroupRights.html:66 html/Admin/Groups/GroupRights.html:53 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "使用者定义的群组"
+
+#: lib/RT/User_Overlay.pm:580 lib/RT/User_Overlay.pm:597
+msgid "User loaded"
+msgstr "已加载使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "已通知使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User renamed from %1 to %2"
+msgstr "使用者 %1 已改å为 %2"
+
+#: html/Admin/Users/Prefs.html:24 html/Admin/Users/Prefs.html:28
+msgid "User view"
+msgstr "使用者ç§äººæ•°æ®"
+
+#: NOT FOUND IN SOURCE
+msgid "UserDefined"
+msgstr "使用者自定"
+
+#: html/Admin/Users/Modify.html:47 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:34
+msgid "Username"
+msgstr "å¸å·"
+
+#: html/Admin/Elements/SelectNewGroupMembers:25 html/Admin/Elements/Tabs:31 html/Admin/Groups/Members.html:54 html/Admin/Queues/People.html:67 html/Admin/index.html:28 html/Edit/Groups/Admin:9 html/User/Groups/Members.html:57 html/Work/Tickets/Elements/ShowTransaction:11
+msgid "Users"
+msgstr "使用者"
+
+#: html/Admin/Users/index.html:64
+msgid "Users matching search criteria"
+msgstr "符åˆæŸ¥è¯¢æ¡ä»¶çš„使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "ValueOfQueue"
+msgstr "选择表å•"
+
+#: html/Admin/Elements/EditCustomField:56
+msgid "Values"
+msgstr "字段值"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Watch"
+msgstr "视察"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "WatchAsAdminCc"
+msgstr "以管ç†å‘˜å‰¯æœ¬æ”¶ä»¶äººèº«ä»½è§†å¯Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "æˆåŠŸåŠ è½½è§†å¯Ÿå‘˜ä¿¡æ¯"
+
+#: html/Admin/Elements/QueueTabs:41 html/Edit/Elements/SelectQueues:5
+msgid "Watchers"
+msgstr "视察员"
+
+#: html/Admin/Elements/ModifyUser:55
+msgid "WebEncoding"
+msgstr "网页文字编ç æ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Wed"
+msgstr "星期三"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "星期三"
+
+#: etc/initialdata:503 etc/upgrade/2.1.71:161 html/Edit/Elements/CreateApprovalsQueue:135
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "当申请å•é€šè¿‡æ‰€æœ‰ç­¾æ ¸åŽï¼Œå°†æ­¤è®¯æ¯å›žå¤åˆ°åŽŸç”³è¯·å•"
+
+#: etc/initialdata:467 etc/upgrade/2.1.71:135 html/Edit/Elements/CreateApprovalsQueue:107
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "当申请å•é€šè¿‡æŸé¡¹ç­¾æ ¸åŽï¼Œå°†æ­¤è®¯æ¯å›žå¤åˆ°åŽŸç”³è¯·å•"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "新增申请å•æ—¶"
+
+#: etc/initialdata:400 etc/upgrade/2.1.71:79 html/Edit/Elements/CreateApprovalsQueue:51
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "签核å•æ–°å¢žä¹‹åŽï¼Œé€šçŸ¥åº”å—ç†çš„承办人åŠç®¡ç†å‘˜å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "当任何事情å‘生时"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "当申请å•è§£å†³æ—¶"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "当申请å•æ›´æ¢æ‰¿åŠžäººæ—¶"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "当申请å•æ›´æ¢è¡¨å•æ—¶"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "当申请å•æ›´æ–°çŽ°å†µæ—¶"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "当使用者自订的情况å‘生时"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "当评论é€è¾¾æ—¶"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "当回å¤é€è¾¾æ—¶"
+
+#: html/Admin/Users/Modify.html:163 html/User/Prefs.html:67 html/Work/Preferences/Info:36
+msgid "Work"
+msgstr "å…¬å¸"
+
+#: html/Admin/Elements/ModifyUser:69
+msgid "WorkPhone"
+msgstr "å…¬å¸ç”µè¯"
+
+#: html/Ticket/Elements/ShowBasics:34 html/Ticket/Update.html:64
+msgid "Worked"
+msgstr "处ç†æ—¶é—´"
+
+#: html/Admin/Global/Workflow.html:91 html/Admin/Queues/Workflow.html:89
+#. ($WorkflowObj->Id())
+msgid "Workflow #%1"
+msgstr "æµç¨‹ #%1"
+
+#: html/Edit/Global/Workflow/List:15
+msgid "Workflow Begin"
+msgstr "æµç¨‹å¼€å§‹"
+
+#: html/Edit/Global/Workflow/List:20
+msgid "Workflow End"
+msgstr "æµç¨‹ç»“æŸ"
+
+#: html/Admin/Elements/EditWorkflows:90
+msgid "Workflow deleted"
+msgstr "æµç¨‹å·²åˆ é™¤"
+
+#: html/Edit/Global/autohandler:10 html/Edit/Queues/autohandler:25
+msgid "Workflows"
+msgstr "æµç¨‹"
+
+#: html/Edit/Global/CustomField/SelectWritable:5
+msgid "Writable"
+msgstr "å¯è¯»å†™"
+
+#: html/autohandler:144
+msgid "XXX CHANGEME You are not an authorized user"
+msgstr "XXX CHANGEME 您是未ç»æŽˆæƒçš„使用者"
+
+#: html/Edit/Global/Basic/Top:25 html/Edit/Queues/Basic/Top:82
+msgid "Yes"
+msgstr "是"
+
+#: lib/RT/Ticket_Overlay.pm:3200
+msgid "You already own this ticket"
+msgstr "您已是这份申请å•çš„承办人"
+
+#: html/autohandler:136
+msgid "You are not an authorized user"
+msgstr "您ä¸æ˜¯è¢«æŽˆæƒçš„使用者"
+
+#: html/Ticket/Elements/ShowTransaction:81
+msgid "You can access it with the Download button on the right."
+msgstr "您å¯ä»¥æŒ‰å³æ–¹çš„「下载ã€é”®æ¥å–得。"
+
+#: lib/RT/Ticket_Overlay.pm:3082
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "祇能é‡æ–°æŒ‡æ´¾æ‚¨æ‰€æ‰¿åŠžæˆ–是没有承办人的申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "您没有看那份申请å•çš„æƒé™ã€‚\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47 lib/RT/StyleGuide.pod:760
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "æ‚¨ä¼šåœ¨è¡¨å• %2 找到 %1 的申请å•"
+
+#: html/NoAuth/Logout.html:30
+msgid "You have been logged out of RT."
+msgstr "您已注销 RT。"
+
+#: html/SelfService/Display.html:77
+msgid "You have no permission to create tickets in that queue."
+msgstr "您没有在该表å•æ–°å¢žç”³è¯·å•çš„æƒé™ã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:1935
+msgid "You may not create requests in that queue."
+msgstr "您ä¸èƒ½åœ¨è¯¥è¡¨å•ä¸­æ出申请。"
+
+#: html/Edit/Global/Basic/Top:42
+msgid "You need to restart the Request Tracker service for saved changes to take effect."
+msgstr "您必须é‡æ–°æ¿€æ´» Request Tracker æœåŠ¡ï¼Œå‚¨å­˜çš„更动æ‰ä¼šç”Ÿæ•ˆã€‚"
+
+#: html/NoAuth/Logout.html:34
+msgid "You're welcome to login again"
+msgstr "欢迎下次å†æ¥"
+
+#: NOT FOUND IN SOURCE
+msgid "Your %1 requests"
+msgstr "您æ出的 %1 申请å•"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "RT 管ç†å‘˜å¯èƒ½è®¾é”™äº†ç”± RT 寄出的邮件收件人标头档"
+
+#: etc/initialdata:484 etc/upgrade/2.1.71:146 html/Edit/Elements/CreateApprovalsQueue:119
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "申请å•å·²ç”± %1 批准。å¯èƒ½è¿˜æœ‰å…¶å®ƒå¾…签核的步骤。"
+
+#: etc/initialdata:522 etc/upgrade/2.1.71:180 html/Edit/Elements/CreateApprovalsQueue:154
+msgid "Your request has been approved."
+msgstr "您的申请å•å·²å®Œæˆç­¾æ ¸ç¨‹åºã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "您的申请å•å·²è¢«é©³å›ž"
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected by %1."
+msgstr "您的申请å•å·²è¢« %1 驳回。"
+
+#: etc/initialdata:427 etc/upgrade/2.1.71:101 html/Edit/Elements/CreateApprovalsQueue:73
+msgid "Your request was rejected."
+msgstr "您的申请å•å·²è¢«é©³å›žã€‚"
+
+#: html/autohandler:170
+msgid "Your username or password is incorrect"
+msgstr "您的å¸å·æˆ–å£ä»¤æœ‰è¯¯"
+
+#: html/Admin/Elements/ModifyUser:83 html/Admin/Users/Modify.html:143 html/User/Prefs.html:130 html/Work/Preferences/Info:87
+msgid "Zip"
+msgstr "邮政编ç "
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[没有标题]"
+
+#: NOT FOUND IN SOURCE
+msgid "ago"
+msgstr "过期"
+
+#: NOT FOUND IN SOURCE
+msgid "alert"
+msgstr "急讯"
+
+#: NOT FOUND IN SOURCE
+msgid "approving"
+msgstr "待签核"
+
+#: html/User/Elements/DelegateRights:58
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "æƒé™åŒ %1"
+
+#: html/SelfService/Closed.html:27
+msgid "closed"
+msgstr "已解决"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:33
+msgid "contains"
+msgstr "包å«"
+
+#: html/Elements/SelectAttachmentField:25
+msgid "content"
+msgstr "内容"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content-type"
+msgstr "类型"
+
+#: lib/RT/Ticket_Overlay.pm:2326
+msgid "correspondence (probably) not sent"
+msgstr "申请å•å›žå¤(å¯èƒ½)未é€å‡º"
+
+#: lib/RT/Ticket_Overlay.pm:2336
+msgid "correspondence sent"
+msgstr "申请å•å›žå¤å·²é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "critical"
+msgstr "严é‡"
+
+#: html/Admin/Elements/ModifyQueue:62 html/Admin/Queues/Modify.html:76 html/Edit/Queues/Basic/Top:34 html/Edit/Queues/List:32 html/Work/Queues/List:11 lib/RT/Date.pm:319
+msgid "days"
+msgstr "天"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "æ‹’ç»å¤„ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "debug"
+msgstr "侦错"
+
+#: html/Search/Listing.html:74
+msgid "delete"
+msgstr "删除"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "deleted"
+msgstr "已删除"
+
+#: html/Search/Elements/PickRestriction:67 html/Work/Search/PickRestriction:47
+msgid "does not match"
+msgstr "ä¸ç¬¦åˆ"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:34
+msgid "doesn't contain"
+msgstr "ä¸åŒ…å«"
+
+#: NOT FOUND IN SOURCE
+msgid "emergency"
+msgstr "å±éš¾"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "equal to"
+msgstr "等于"
+
+#: NOT FOUND IN SOURCE
+msgid "error"
+msgstr "错误"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "å‡"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "filename"
+msgstr "æ¡£å"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "greater than"
+msgstr "大于"
+
+#: lib/RT/Group_Overlay.pm:193
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "群组 '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "å°æ—¶"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "ç¼–å·"
+
+#: NOT FOUND IN SOURCE
+msgid "info"
+msgstr "ä¿¡æ¯"
+
+#: html/Elements/SelectBoolean:31 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:35 html/Search/Elements/PickRestriction:46 html/Search/Elements/PickRestriction:75 html/Search/Elements/PickRestriction:87 html/Work/Search/PickRestriction:27 html/Work/Search/PickRestriction:56 html/Work/Search/PickRestriction:75
+msgid "is"
+msgstr "是"
+
+#: html/Elements/SelectBoolean:35 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88 html/Work/Search/PickRestriction:28 html/Work/Search/PickRestriction:57 html/Work/Search/PickRestriction:76
+msgid "isn't"
+msgstr "ä¸æ˜¯"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "less than"
+msgstr "å°äºŽ"
+
+#: html/Edit/Global/Workflow/Owner.html:35
+msgid "level Admins"
+msgstr "层主管"
+
+#: html/Search/Elements/PickRestriction:66 html/Work/Search/PickRestriction:46
+msgid "matches"
+msgstr "符åˆ"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "分"
+
+#: html/Ticket/Update.html:64
+msgid "minutes"
+msgstr "分钟"
+
+#: bin/rt-commit-handler:764
+msgid "modifications\\n\\n"
+msgstr "更改\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "月"
+
+#: lib/RT/Queue_Overlay.pm:57
+msgid "new"
+msgstr "新建立"
+
+#: html/Admin/Elements/EditCustomFields:42
+msgid "no name"
+msgstr "没有å称"
+
+#: html/Admin/Elements/EditScrips:42
+msgid "no value"
+msgstr "没有值"
+
+#: html/Admin/Elements/EditQueueWatchers:26 html/Edit/Groups/Member:40 html/Edit/Groups/Members/Add.html:17 html/Edit/Groups/Members/List:8 html/Edit/Queues/List:32 html/Ticket/Elements/EditWatchers:27 html/Work/Delegates/Info:37 html/Work/Delegates/Info:48 html/Work/Overview/Info:31 html/Work/Queues/List:11 html/Work/Tickets/Elements/EditWatchers:5 html/Work/Tickets/Elements/ShowAttachments:30 html/Work/Tickets/Elements/ShowBasics:27
+msgid "none"
+msgstr "æ— "
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "not equal to"
+msgstr "ä¸ç­‰äºŽ"
+
+#: NOT FOUND IN SOURCE
+msgid "notice"
+msgstr "æ示"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "ä¸ç¬¦åˆ"
+
+#: html/Edit/Elements/PickUsers:17 html/Edit/Users/Add.html:18 html/Edit/Users/Search.html:28 html/Work/Tickets/Cc:15
+msgid "number"
+msgstr "å·"
+
+#: html/SelfService/Elements/MyRequests:60 lib/RT/Queue_Overlay.pm:58
+msgid "open"
+msgstr "å¼€å¯"
+
+#: NOT FOUND IN SOURCE
+msgid "opened"
+msgstr "已开å¯"
+
+#: lib/RT/Group_Overlay.pm:198
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "使用者「%2ã€çš„「%1ã€ä»£ç†äººç¾¤ç»„"
+
+#: lib/RT/Group_Overlay.pm:206
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "è¡¨å• %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "rejected"
+msgstr "已驳回"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "resolved"
+msgstr "已处ç†"
+
+#: html/Edit/Global/Basic/Top:53
+msgid "rtname"
+msgstr "æœåŠ¡å™¨å称"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "秒"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "stalled"
+msgstr "延宕"
+
+#: lib/RT/Group_Overlay.pm:201
+#. ($self->Type)
+msgid "system %1"
+msgstr "系统 %1"
+
+#: lib/RT/Group_Overlay.pm:212
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "系统群组 '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:41
+msgid "the calling component did not specify why"
+msgstr "呼å«ç»„件未指明原因"
+
+#: lib/RT/URI/fsck_com_rt.pm:234
+#. ($self->Object->Id)
+msgid "ticket #%1"
+msgstr "ç”³è¯·å• #%1"
+
+#: lib/RT/Group_Overlay.pm:209
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "ç”³è¯·å• #%1 %2"
+
+#: html/Work/Elements/SelectSearch:28
+msgid "till"
+msgstr "至"
+
+#: html/Edit/Elements/PickUsers:15 html/Edit/Global/Workflow/Condition:31 html/Edit/Users/Add.html:16 html/Edit/Users/Search.html:26 html/Work/Tickets/Cc:13
+msgid "to"
+msgstr "到"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "真"
+
+#: lib/RT/Group_Overlay.pm:215
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "没有æ述的群组 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "unresolved"
+msgstr "未处ç†"
+
+#: lib/RT/Group_Overlay.pm:190
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "使用者 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "warning"
+msgstr "警告"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "周"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "模æ¿ï¼š%1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "å¹´"
+
diff --git a/rt/lib/RT/I18N/zh_tw.po b/rt/lib/RT/I18N/zh_tw.po
new file mode 100644
index 0000000..6688b6a
--- /dev/null
+++ b/rt/lib/RT/I18N/zh_tw.po
@@ -0,0 +1,6677 @@
+# Chinese localization catalog for Request Tracker (RT)
+msgid ""
+msgstr ""
+"Last-Translator: Autrijus Tang <autrijus@autrijus.org>\n"
+"Language-Team: Chinese <members@ourinet.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: html/Elements/MyRequests:27 html/Elements/MyTickets:27 html/Work/Elements/MyApprovals:8 html/Work/Elements/MyRequests:15 html/Work/Elements/MyTickets:15
+msgid "#"
+msgstr "#"
+
+#: NOT FOUND IN SOURCE
+msgid "#%1"
+msgstr "#%1"
+
+#: html/Approvals/Elements/Approve:26 html/Approvals/Elements/ShowDependency:49 html/SelfService/Display.html:24 html/Ticket/Display.html:25 html/Ticket/Display.html:29
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($Ticket->id, $Ticket->Subject)
+#. ($ticket->Id, $ticket->Subject)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "#%1: %2"
+msgstr "#%1: %2"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,group ticket)"
+msgstr "%*(%1) 件åƒèˆ‡çš„申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,ticket) due"
+msgstr "%*(%1) 件é™æœŸå®Œæˆçš„申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "%*(%1,unresolved ticket)"
+msgstr "%*(%1) 件尚未解決的申請單"
+
+#: lib/RT/Date.pm:337
+#. ($s, $time_unit)
+msgid "%1 %2"
+msgstr "%1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:790
+#. ($args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'})
+msgid "%1 %2 %3"
+msgstr "%1 %2 %3"
+
+#: lib/RT/Date.pm:373
+#. ($self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900))
+msgid "%1 %2 %3 %4:%5:%6 %7"
+msgstr "%7-%2-%3 %4:%5:%6 %1"
+
+#: lib/RT/Ticket_Overlay.pm:3588 lib/RT/Transaction_Overlay.pm:514 lib/RT/Transaction_Overlay.pm:557 lib/RT/Transaction_Vendor.pm:19
+#. ($cf->Name, $new_value->Content)
+#. ($field, $self->NewValue)
+#. ($self->Field, $principal->Object->Name)
+#. ($field, $new_value)
+msgid "%1 %2 added"
+msgstr "%2 已新增為 %1"
+
+#: lib/RT/Date.pm:334
+#. ($s, $time_unit)
+msgid "%1 %2 ago"
+msgstr "%1 %2 之å‰"
+
+#: lib/RT/Ticket_Overlay.pm:3594 lib/RT/Transaction_Overlay.pm:521 lib/RT/Transaction_Vendor.pm:25
+#. ($cf->Name, $old_value, $new_value->Content)
+#. ($field, $self->OldValue, $self->NewValue)
+#. ($field, $old_value, $new_value)
+msgid "%1 %2 changed to %3"
+msgstr "%1 已從 %2 改為 %3"
+
+#: lib/RT/Ticket_Overlay.pm:3591 lib/RT/Transaction_Overlay.pm:517 lib/RT/Transaction_Overlay.pm:563 lib/RT/Transaction_Vendor.pm:22
+#. ($cf->Name, $old_value)
+#. ($field, $self->OldValue)
+#. ($self->Field, $principal->Object->Name)
+#. ($field, $old_value)
+msgid "%1 %2 deleted"
+msgstr "%2 已自 %1 刪除"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:157
+#. ($depth_str, $role_str, $group_str)
+msgid "%1 %2 of group %3"
+msgstr "%3 群組的 %1 %2"
+
+#: html/Admin/Elements/EditScrips:43 html/Admin/Elements/ListGlobalScrips:27
+#. (loc($scrip->ConditionObj->Name), loc($scrip->ActionObj->Name), loc($scrip->TemplateObj->Name))
+msgid "%1 %2 with template %3"
+msgstr "æ¢ä»¶ï¼š%1 | 動作:%2 | 範本:%3"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 (%2) %3 this ticket\\n"
+msgstr "%1 (%2) %3 這份申請單\\n"
+
+#: html/Search/Listing.html:56 html/Work/Search/index.html:28
+#. (($session{'tickets'}->FirstRow+1), ($session{'tickets'}->FirstRow() + $session{'tickets'}->RowsPerPage() ))
+msgid "%1 - %2 shown"
+msgstr "顯示第 %1 - %2 筆"
+
+#: bin/rt-crontool:168 bin/rt-crontool:175 bin/rt-crontool:181
+#. ("--search-argument", "--search")
+#. ("--condition-argument", "--condition")
+#. ("--action-argument", "--action")
+msgid "%1 - An argument to pass to %2"
+msgstr "%1 - 傳éžçµ¦ %2 的一個åƒæ•¸"
+
+#: bin/rt-crontool:184
+#. ("--verbose")
+msgid "%1 - Output status updates to STDOUT"
+msgstr "%1 - 將更新狀態輸出到 STDOUT"
+
+#: bin/rt-crontool:178
+#. ("--action")
+msgid "%1 - Specify the action module you want to use"
+msgstr "%1 - 指定欲使用的動作模組"
+
+#: bin/rt-crontool:172
+#. ("--condition")
+msgid "%1 - Specify the condition module you want to use"
+msgstr "%1 - 指定欲使用的æ¢ä»¶æ¨¡çµ„"
+
+#: bin/rt-crontool:165
+#. ("--search")
+msgid "%1 - Specify the search module you want to use"
+msgstr "%1 - 指定欲使用的查詢模組"
+
+#: lib/RT/ScripAction_Overlay.pm:122
+#. ($self->Id)
+msgid "%1 ScripAction loaded"
+msgstr "載入手續 %1"
+
+#: html/Edit/Elements/Page:49
+#. (scalar $count)
+msgid "%1 Total"
+msgstr "å…± %1 ç­†"
+
+#: lib/RT/Ticket_Overlay.pm:3621
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 added as a value for %2"
+msgstr "新增 %1 作為 %2 的值"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on"
+msgstr "別å %1 需è¦å¯ç”¨çš„申請單編號"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on "
+msgstr "別å %1 需è¦å¯ç”¨çš„申請單編號 "
+
+#: NOT FOUND IN SOURCE
+msgid "%1 aliases require a TicketId to work on (from %2) %3"
+msgstr "別å %1 需è¦å¯ç”¨çš„ç”³è«‹å–®ç·¨è™Ÿä»¥è™•ç† %3(出自 %2)"
+
+#: lib/RT/Link_Overlay.pm:116 lib/RT/Link_Overlay.pm:123
+#. ($args{'Base'})
+#. ($args{'Target'})
+msgid "%1 appears to be a local object, but can't be found in the database"
+msgstr "%1 看來是個本地物件,å»ä¸åœ¨è³‡æ–™åº«è£¡"
+
+#: html/Ticket/Elements/ShowDates:52 lib/RT/Transaction_Overlay.pm:430
+#. ($self->BriefDescription , $self->CreatorObj->Name)
+#. ($Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name)
+msgid "%1 by %2"
+msgstr "%1 (%2)"
+
+#: lib/RT/Transaction_Overlay.pm:484 lib/RT/Transaction_Overlay.pm:649 lib/RT/Transaction_Overlay.pm:658 lib/RT/Transaction_Overlay.pm:661
+#. ($self->Field , ( $self->OldValue || $no_value ) , $self->NewValue)
+#. ($self->Field , $q1->Name , $q2->Name)
+#. ($self->Field, $t2->AsString, $t1->AsString)
+#. ($self->Field, $self->OldValue, $self->NewValue)
+msgid "%1 changed from %2 to %3"
+msgstr "%1 的值從 %2 改為 %3"
+
+#: lib/RT/Interface/Web.pm:953
+msgid "%1 could not be set to %2."
+msgstr "無法將 %1 設定為 %2。"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 couldn't init a transaction (%2)\\n"
+msgstr "%1 無法åˆå§‹æ›´æ–° (%2)\\n"
+
+#: lib/RT/Ticket_Overlay.pm:2880
+#. ($self)
+msgid "%1 couldn't set status to resolved. RT's Database may be inconsistent."
+msgstr "%1 無法將ç¾æ³è¨­æˆå·²è§£æ±ºã€‚RT 資料庫內容å¯èƒ½ä¸ä¸€è‡´ã€‚"
+
+#: html/Elements/MyTickets:24 html/Work/Elements/MyTickets:9
+#. ($rows)
+msgid "%1 highest priority tickets I own..."
+msgstr "å‰ %1 份待處ç†ç”³è«‹å–®..."
+
+#: html/Elements/MyRequests:24 html/Work/Elements/MyRequests:9
+#. ($rows)
+msgid "%1 highest priority tickets I requested..."
+msgstr "å‰ %1 份é€å‡ºçš„申請單..."
+
+#: html/Work/Elements/MyApprovals:5
+#. ($rows)
+msgid "%1 highest priority tickets pending my approval..."
+msgstr "å‰ %1 份待簽核申請單..."
+
+#: bin/rt-crontool:160
+#. ($0)
+msgid "%1 is a tool to act on tickets from an external scheduling tool, such as cron."
+msgstr "%1 是從外部排程程å¼(如 cron)來å°ç”³è«‹å–®é€²è¡Œæ“作的工具。"
+
+#: lib/RT/Queue_Overlay.pm:743
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this queue."
+msgstr "%1 å·²ä¸å†æ˜¯æ­¤è¡¨å–®çš„ %2。"
+
+#: lib/RT/Ticket_Overlay.pm:1596
+#. ($principal->Object->Name, $args{'Type'})
+msgid "%1 is no longer a %2 for this ticket."
+msgstr "%1 å·²ä¸å†æ˜¯æ­¤ç”³è«‹å–®çš„ %2。"
+
+#: lib/RT/Ticket_Overlay.pm:3677
+#. ($args{'Value'}, $cf->Name)
+msgid "%1 is no longer a value for custom field %2"
+msgstr "%1 å·²ä¸å†æ˜¯è‡ªè¨‚æ¬„ä½ %2 的值。"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 isn't a valid Queue id."
+msgstr "%1 ä¸æ˜¯ä¸€å€‹åˆæ³•çš„表單編號。"
+
+#: html/Ticket/Elements/ShowBasics:35
+#. ($TimeWorked)
+msgid "%1 min"
+msgstr "%1 分é˜"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 not shown"
+msgstr "沒有顯示 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 result(s) found"
+msgstr "找到 %1 é …çµæžœ"
+
+#: html/User/Elements/DelegateRights:75
+#. (loc($ObjectType =~ /^RT::(.*)$/))
+msgid "%1 rights"
+msgstr "%1權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 succeeded\\n"
+msgstr "%1 完æˆ\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for $MessageId"
+msgstr "ä¸çŸ¥é“ $MessageID çš„ %1 類別"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 type unknown for %2"
+msgstr "ä¸çŸ¥é“ %2 çš„ %1 類別"
+
+#: NOT FOUND IN SOURCE
+msgid "%1 was created without a CurrentUser\\n"
+msgstr "%1 新增時未指定ç¾è¡Œä½¿ç”¨è€…"
+
+#: lib/RT/Action/ResolveMembers.pm:41
+#. (ref $self)
+msgid "%1 will resolve all members of a resolved group ticket."
+msgstr "%1 會解決在已解決群組裡æˆå“¡çš„申請單。"
+
+#: lib/RT/Action/StallDependent.pm:40
+#. (ref $self)
+msgid "%1 will stall a [local] BASE if it's dependent [or member] of a linked up request."
+msgstr "如果 %1 起始申請單ä¾è³´æ–¼æŸå€‹éˆçµï¼Œæˆ–是æŸå€‹éˆçµçš„æˆå“¡ï¼Œå®ƒå°‡æœƒè¢«å»¶å®•ã€‚"
+
+#: lib/RT/Transaction_Overlay.pm:382 lib/RT/Transaction_Vendor.pm:37
+#. ($self)
+msgid "%1: no attachment specified"
+msgstr "%1:未指定附件"
+
+#: html/Ticket/Elements/ShowTransaction:100 html/Work/Tickets/Elements/ShowTransaction:158
+#. ($size)
+msgid "%1b"
+msgstr "%1 ä½å…ƒçµ„"
+
+#: html/Ticket/Elements/ShowTransaction:97 html/Work/Tickets/Elements/ShowTransaction:155
+#. (int($size/102.4)/10)
+msgid "%1k"
+msgstr "%1k ä½å…ƒçµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "%quant(%1,result) found"
+msgstr "找到 %1 é …çµæžœ"
+
+#: lib/RT/Ticket_Overlay.pm:1185
+#. ($args{'Status'})
+msgid "'%1' is an invalid value for status"
+msgstr "'%1' ä¸æ˜¯ä¸€å€‹åˆæ³•çš„狀態值"
+
+#: NOT FOUND IN SOURCE
+msgid "'%1' not a recognized action. "
+msgstr "'%1'為無法辨識的動作。 "
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete group member)"
+msgstr "(點é¸æ¬²åˆªé™¤çš„æˆå“¡)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check box to delete scrip)"
+msgstr "(點é¸æ¬²åˆªé™¤çš„手續)"
+
+#: html/Admin/Elements/EditCustomFieldValues:24 html/Admin/Elements/EditQueueWatchers:28 html/Admin/Elements/EditScrips:34 html/Admin/Elements/EditTemplates:35 html/Admin/Elements/EditWorkflows:36 html/Admin/Groups/Members.html:51 html/Ticket/Elements/EditLinks:32 html/Ticket/Elements/EditPeople:45 html/User/Groups/Members.html:54 html/Work/Tickets/Elements/EditLinks:20 html/Work/Tickets/Elements/EditPeople:36
+msgid "(Check box to delete)"
+msgstr "(點é¸æ¬²åˆªé™¤çš„é …ç›®)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Check boxes to delete)"
+msgstr "(點é¸æ¬²åˆªé™¤çš„é …ç›®)"
+
+#: html/Ticket/Create.html:178
+msgid "(Enter ticket ids or URLs, seperated with spaces)"
+msgstr "(éµå…¥ç”³è«‹å–®ç·¨è™Ÿæˆ–網å€ï¼Œä»¥ç©ºç™½åˆ†éš”)"
+
+#: html/Admin/Queues/Modify.html:53 html/Admin/Queues/Modify.html:59
+#. ($RT::CorrespondAddress)
+#. ($RT::CommentAddress)
+msgid "(If left blank, will default to %1"
+msgstr "(如果留白, 則é è¨­ç‚º %1)"
+
+#: NOT FOUND IN SOURCE
+msgid "(No Value)"
+msgstr "(沒有值)"
+
+#: html/Admin/Elements/EditCustomFields:32 html/Admin/Elements/ListGlobalCustomFields:31
+msgid "(No custom fields)"
+msgstr "(沒有自訂欄ä½)"
+
+#: html/Admin/Groups/Members.html:49 html/User/Groups/Members.html:52
+msgid "(No members)"
+msgstr "(沒有æˆå“¡)"
+
+#: html/Admin/Elements/EditScrips:31 html/Admin/Elements/ListGlobalScrips:31
+msgid "(No scrips)"
+msgstr "(沒有手續)"
+
+#: html/Admin/Elements/EditTemplates:30
+msgid "(No templates)"
+msgstr "沒有範本"
+
+#: html/Admin/Elements/EditWorkflows:31
+msgid "(No workflows)"
+msgstr "沒有æµç¨‹"
+
+#: html/Ticket/Update.html:83 html/Work/Tickets/Update.html:56
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„密件副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>ä¸æœƒ</b>更改後續的收件者å單。)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a blind carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„密件副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>ä¸æœƒ</b>更改後續的收件者å單。)"
+
+#: html/Ticket/Create.html:78
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of administrative email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的管ç†å“¡é›»å­éƒµä»¶ä½å€ã€‚這<b>將會</b>更改後續的收件者å單。)"
+
+#: html/Ticket/Update.html:79
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>ä¸æœƒ</b>更改後續的收件者å單。)"
+
+#: NOT FOUND IN SOURCE
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. Does <b>not</b> change who will recieve future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>ä¸æœƒ</b>更改後續的收件者å單。)"
+
+#: html/Ticket/Create.html:68
+msgid "(Sends a carbon-copy of this update to a comma-delimited list of email addresses. These people <b>will</b> receive future updates.)"
+msgstr "(é€å‡ºæœ¬ä»½æ›´æ–°çš„副本給å單上以逗號隔開的電å­éƒµä»¶ä½å€ã€‚這<b>將會</b>更改後續的收件者å單。)"
+
+#: html/Ticket/Elements/EditCustomFieldEntries:35 html/Work/Tickets/Elements/EditCustomFieldEntries:43 html/Work/Tickets/Elements/ShowCustomFieldEntries:14
+msgid "(delete)"
+msgstr "(刪除)"
+
+#: html/Admin/Groups/index.html:32 html/User/Groups/index.html:32
+msgid "(empty)"
+msgstr "(空白)"
+
+#: html/Edit/Elements/Index:87 html/Edit/Global/CustomField/index.html:116 html/Edit/Global/Scrip/index.html:111 html/Edit/Global/Template/index.html:106
+msgid "(new)"
+msgstr "(新增)"
+
+#: html/Admin/Users/index.html:38
+msgid "(no name listed)"
+msgstr "(沒有列出姓å)"
+
+#: html/Elements/MyRequests:42 html/Elements/MyTickets:44 html/Work/Elements/MyApprovals:37 html/Work/Elements/MyRequests:43 html/Work/Elements/MyTickets:52
+msgid "(no subject)"
+msgstr "(沒有主題)"
+
+#: html/Admin/Elements/SelectRights:47 html/Elements/SelectCustomFieldValue:29 html/Ticket/Elements/EditCustomField:64 html/Ticket/Elements/EditCustomFieldValues:52 html/Ticket/Elements/ShowCustomFields:35 html/Work/Elements/EditCustomFieldValues:50 html/Work/Elements/EditCustomFields:32 html/Work/Tickets/Elements/EditCustomFieldValues:33 lib/RT/Transaction_Overlay.pm:483
+msgid "(no value)"
+msgstr "(ç„¡)"
+
+#: html/Ticket/Elements/BulkLinks:27 html/Ticket/Elements/EditLinks:98 html/Work/Search/BulkLinks:3 html/Work/Tickets/Elements/EditLinks:102
+msgid "(only one ticket)"
+msgstr "(僅能指定一份申請單)"
+
+#: html/Elements/MyRequests:51 html/Elements/MyTickets:54 html/Work/Elements/List:17 html/Work/Elements/MyRequests:53 html/Work/Elements/MyTickets:67 html/Work/Tickets/Elements/ShowBasics:52
+msgid "(pending approval)"
+msgstr "(等待簽核)"
+
+#: html/Elements/MyRequests:53 html/Elements/MyTickets:56 html/Work/Elements/MyRequests:55 html/Work/Elements/MyTickets:69
+msgid "(pending other tickets)"
+msgstr "(等待其他申請單)"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:246
+msgid "(requestor's group)"
+msgstr "(申請人所屬)"
+
+#: html/Admin/Users/Modify.html:49 html/Edit/Users/Info:26
+msgid "(required)"
+msgstr "(å¿…å¡«)"
+
+#: html/Ticket/Elements/ShowTransaction:103 html/Work/Tickets/Elements/ShowTransaction:44
+msgid "(untitled)"
+msgstr "(未命å)"
+
+#: NOT FOUND IN SOURCE
+msgid ":"
+msgstr ":"
+
+#: html/Ticket/Elements/ShowBasics:31
+msgid "<% $Ticket->Status%>"
+msgstr "<% $Ticket->Status%>"
+
+#: html/Elements/SelectTicketTypes:26
+msgid "<% $_ %>"
+msgstr "<% $_ %>"
+
+#: docs/design_docs/string-extraction-guide.txt:54 html/Elements/CreateTicket:25 html/Work/Elements/104Header:43 lib/RT/StyleGuide.pod:767
+#. ($m->scomp('/Elements/SelectNewTicketQueue'))
+msgid "<input type=\"submit\" value=\"New ticket in\">&nbsp;%1"
+msgstr "<input type=\"submit\" value=\"æ出申請單\">&nbsp;%1"
+
+#: etc/initialdata:203
+msgid "A blank template"
+msgstr "空白範本"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Deleted"
+msgstr "ACE 已刪除"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE Loaded"
+msgstr "ACE 已載入"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be deleted"
+msgstr "無法刪除 ACE"
+
+#: NOT FOUND IN SOURCE
+msgid "ACE could not be found"
+msgstr "找ä¸åˆ° ACE"
+
+#: lib/RT/ACE_Overlay.pm:156 lib/RT/Principal_Overlay.pm:180
+msgid "ACE not found"
+msgstr "找ä¸åˆ° ACE 設定"
+
+#: lib/RT/ACE_Overlay.pm:830
+msgid "ACEs can only be created and deleted."
+msgstr "祇能新增或刪除 ACE 設定。"
+
+#: NOT FOUND IN SOURCE
+msgid "ACLEquivalence"
+msgstr "ACLEquivalence"
+
+#: bin/rt-commit-handler:754
+msgid "Aborting to avoid unintended ticket modifications.\\n"
+msgstr "離開以å…ä¸å°å¿ƒæ›´æ”¹åˆ°ç”³è«‹å–®ã€‚\\n"
+
+#: html/User/Elements/Tabs:31
+msgid "About me"
+msgstr "個人資訊"
+
+#: html/Edit/Users/System:12
+msgid "Access Right"
+msgstr "系統使用登錄權é™"
+
+#: html/Admin/Users/Modify.html:79
+msgid "Access control"
+msgstr "å­˜å–權é™"
+
+#: html/Admin/Elements/EditScrip:56 html/Work/Tickets/Elements/ShowTransaction:21
+msgid "Action"
+msgstr "動作"
+
+#: lib/RT/Scrip_Overlay.pm:148
+#. ($args{'ScripAction'})
+msgid "Action %1 not found"
+msgstr "動作 %1 找ä¸åˆ°"
+
+#: bin/rt-crontool:122
+msgid "Action committed."
+msgstr "動作執行完畢"
+
+#: bin/rt-crontool:118
+msgid "Action prepared..."
+msgstr "動作準備完畢..."
+
+#: html/Work/Elements/List:13 html/Work/Elements/SelectSearch:25 html/Work/Tickets/Create.html:27 html/Work/Tickets/Elements/ShowBasics:12
+msgid "Activated Date"
+msgstr "申請啟動時間"
+
+#: html/Edit/Elements/104Buttons:82 html/Edit/Elements/ListButtons:7
+msgid "Add"
+msgstr "新增"
+
+#: html/Search/Bulk.html:95 html/Work/Search/Bulk.html:74
+msgid "Add AdminCc"
+msgstr "新增管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Search/Bulk.html:91 html/Work/Search/Bulk.html:68
+msgid "Add Cc"
+msgstr "新增副本收件人"
+
+#: html/Ticket/Elements/EditCustomFieldEntries:71 html/Work/Tickets/Elements/ShowCustomFieldEntries:50
+msgid "Add Entry"
+msgstr "新增列"
+
+#: html/Ticket/Create.html:113 html/Ticket/Update.html:98 html/Work/Tickets/Elements/AddAttachments:23
+msgid "Add More Files"
+msgstr "新增更多附件"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:112 html/Admin/Elements/ModifyTemplateAsWorkflow:45
+msgid "Add Next State"
+msgstr "新增下一項關å¡"
+
+#: html/Search/Bulk.html:87 html/Work/Search/Bulk.html:62
+msgid "Add Requestor"
+msgstr "新增申請人"
+
+#: html/Admin/Elements/AddCustomFieldValue:24
+msgid "Add Value"
+msgstr "新增欄ä½å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip to this queue"
+msgstr "新增此表單的手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a Scrip which will apply to all queues"
+msgstr "新增é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a keyword selection to this queue"
+msgstr "新增此表單的關éµå­—"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a new a global scrip"
+msgstr "新增全域手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Add a scrip to this queue"
+msgstr "新增一é“手續到此表單"
+
+#: html/Admin/Global/Scrip.html:54
+msgid "Add a scrip which will apply to all queues"
+msgstr "新增一é“用於所有表單的手續"
+
+#: html/Search/Bulk.html:127 html/Work/Search/Bulk.html:80
+msgid "Add comments or replies to selected tickets"
+msgstr "新增評論或回覆到指定的申請單"
+
+#: html/Admin/Groups/Members.html:41 html/User/Groups/Members.html:38
+msgid "Add members"
+msgstr "新增æˆå“¡"
+
+#: html/Admin/Queues/People.html:65 html/Ticket/Elements/AddWatchers:27
+msgid "Add new watchers"
+msgstr "新增視察員"
+
+#: NOT FOUND IN SOURCE
+msgid "AddNextState"
+msgstr "新增下一項關å¡"
+
+#: lib/RT/Queue_Overlay.pm:643
+#. ($args{'Type'})
+msgid "Added principal as a %1 for this queue"
+msgstr "å–®ä½å·²æ–°å¢žç‚ºæ­¤è¡¨å–®çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1480
+#. ($self->loc($args{'Type'}))
+msgid "Added principal as a %1 for this ticket"
+msgstr "å–®ä½å·²æ–°å¢žç‚ºæ­¤ç”³è«‹å–®çš„ %1"
+
+#: html/Edit/Global/CustomField/Top:52
+msgid "Additional Hints"
+msgstr "é¡å¤–æ示"
+
+#: html/Admin/Elements/ModifyUser:75 html/Admin/Users/Modify.html:121 html/User/Prefs.html:114 html/Work/Preferences/Info:79
+msgid "Address1"
+msgstr "ä½å€"
+
+#: html/Admin/Elements/ModifyUser:77 html/Admin/Users/Modify.html:126 html/User/Prefs.html:118 html/Work/Preferences/Info:81
+msgid "Address2"
+msgstr "ä½å€(續)"
+
+#: NOT FOUND IN SOURCE
+msgid "Adjust Blinking Rate"
+msgstr "調整閃çˆé€Ÿåº¦å¿«æ…¢"
+
+#: html/Edit/Queues/List:12
+msgid "Admin"
+msgstr "管ç†å“¡"
+
+#: html/Ticket/Create.html:73
+msgid "Admin Cc"
+msgstr "管ç†å“¡å‰¯æœ¬"
+
+#: etc/initialdata:280
+msgid "Admin Comment"
+msgstr "管ç†å“¡è©•è«–"
+
+#: etc/initialdata:259
+msgid "Admin Correspondence"
+msgstr "管ç†å“¡å›žè¦†"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin Rights"
+msgstr "管ç†å“¡æ¬Šé™"
+
+#: html/Admin/Queues/index.html:24 html/Admin/Queues/index.html:27
+msgid "Admin queues"
+msgstr "表單管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin users"
+msgstr "使用者管ç†"
+
+#: html/Admin/Global/index.html:25 html/Admin/Global/index.html:27
+msgid "Admin/Global configuration"
+msgstr "管ç†/全域設定"
+
+#: NOT FOUND IN SOURCE
+msgid "Admin/Groups"
+msgstr "管ç†/群組"
+
+#: html/Admin/Queues/Modify.html:24 html/Admin/Queues/Modify.html:28
+msgid "Admin/Queue/Basics"
+msgstr "管ç†/表單/基本資訊"
+
+#: html/Edit/Global/Basic/Top:65
+msgid "AdminAddress"
+msgstr "管ç†å“¡ Email"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminAllPersonalGroups"
+msgstr "管ç†æ‰€æœ‰ä»£ç†äººç¾¤çµ„"
+
+#: etc/initialdata:56 html/Admin/Elements/ModifyTemplateAsWorkflow:155 html/Ticket/Elements/ShowPeople:38 html/Ticket/Update.html:49 html/Work/Tickets/Elements/ShowLinks:11 lib/RT/ACE_Overlay.pm:88
+msgid "AdminCc"
+msgstr "管ç†å“¡å‰¯æœ¬"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminComment"
+msgstr "管ç†å“¡è©•è«–"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminCorrespondence"
+msgstr "管ç†å“¡å›žè¦†"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "AdminCustomFields"
+msgstr "管ç†è‡ªè¨‚欄ä½"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "AdminGroup"
+msgstr "管ç†ç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupDescription"
+msgstr "管ç†ç¾¤çµ„æè¿°"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "AdminGroupMembership"
+msgstr "管ç†ç¾¤çµ„æˆå“¡"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupName"
+msgstr "管ç†ç¾¤çµ„å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupPermission"
+msgstr "管ç†ç¾¤çµ„權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "AdminGroupStatus"
+msgstr "管ç†ç¾¤çµ„狀態"
+
+#: lib/RT/System.pm:58
+msgid "AdminOwnPersonalGroups"
+msgstr "管ç†ä»£ç†äººç¾¤çµ„"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "AdminQueue"
+msgstr "管ç†è¡¨å–®"
+
+#: lib/RT/System.pm:59
+msgid "AdminUsers"
+msgstr "管ç†ä½¿ç”¨è€…"
+
+#: NOT FOUND IN SOURCE
+msgid "Administrative"
+msgstr "行政類"
+
+#: html/Admin/Queues/People.html:47 html/Ticket/Elements/EditPeople:53 html/Work/Tickets/Elements/EditPeople:44
+msgid "Administrative Cc"
+msgstr "管ç†å“¡å‰¯æœ¬"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:233
+msgid "Admins"
+msgstr "主管"
+
+#: NOT FOUND IN SOURCE
+msgid "Advanced Search"
+msgstr "進階查詢"
+
+#: html/Elements/SelectDateRelation:35
+msgid "After"
+msgstr "晚於"
+
+#: NOT FOUND IN SOURCE
+msgid "Age"
+msgstr "經歷時間"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:172 html/Edit/Global/Workflow/Action:35
+msgid "Alias"
+msgstr "執行其他æµç¨‹"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:175
+msgid "Alias for"
+msgstr "相當於"
+
+#: html/Work/Delegates/index.html:13 html/Work/Elements/SelectSearch:11 html/Work/Queues/Select.html:14 html/Work/Queues/index.html:13
+msgid "All"
+msgstr "全部"
+
+#: etc/initialdata:348
+msgid "All Approvals Passed"
+msgstr "完æˆå…¨éƒ¨ç°½æ ¸"
+
+#: html/Edit/Global/Workflow/Condition:16
+msgid "All Condition"
+msgstr "所有æ¢ä»¶"
+
+#: html/Admin/Elements/EditCustomFields:94
+msgid "All Custom Fields"
+msgstr "所有自訂欄ä½"
+
+#: html/Admin/Queues/index.html:52
+msgid "All Queues"
+msgstr "所有表單"
+
+#: NOT FOUND IN SOURCE
+msgid "All Users"
+msgstr "全體員工"
+
+#: NOT FOUND IN SOURCE
+msgid "All done! Now you can proceed to %1."
+msgstr "處ç†å®Œç•¢ï¼æ‚¨ç¾åœ¨å¯ä»¥ç¹¼çºŒé€²è¡Œ %1。"
+
+#: NOT FOUND IN SOURCE
+msgid "Allowance Request"
+msgstr "ç¦åˆ©è£œåŠ©ç”³è«‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Always sends a message to the requestors independent of message sender"
+msgstr "無論寄件來æºç‚ºä½•ï¼Œä¸€å¾‹å¯„信給申請人"
+
+#: NOT FOUND IN SOURCE
+msgid "Amount"
+msgstr "數é¡"
+
+#: html/Edit/Global/Workflow/Condition:13
+msgid "Any Condition"
+msgstr "ä»»æ„æ¢ä»¶"
+
+#: html/Edit/Global/Scrip/List:10 html/Edit/Global/Scrip/Top:86
+msgid "Apply Template"
+msgstr "引用範本"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:138 html/Elements/Tabs:55 html/Work/Approvals/Elements/Approve:6
+msgid "Approval"
+msgstr "簽核"
+
+#: html/Approvals/Display.html:45 html/Approvals/Elements/ShowDependency:41 html/Approvals/index.html:64
+#. ($Ticket->Id, $Ticket->Subject)
+#. ($ticket->id, $msg)
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Approval #%1: %2"
+msgstr "簽核單 #%1:%2"
+
+#: html/Approvals/index.html:53
+#. ($ticket->Id)
+msgid "Approval #%1: Notes not recorded due to a system error"
+msgstr "簽核單 #%1:系統錯誤,記錄失敗"
+
+#: html/Approvals/index.html:51
+#. ($ticket->Id)
+msgid "Approval #%1: Notes recorded"
+msgstr "簽核單 #%1:記錄完畢"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:123
+msgid "Approval Details"
+msgstr "簽核細節"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Due"
+msgstr "簽核時é™"
+
+#: html/Work/Approvals/Elements/Approve:37
+msgid "Approval Notes"
+msgstr "簽核æ„見"
+
+#: etc/initialdata:336
+msgid "Approval Passed"
+msgstr "完æˆæŸé …簽核"
+
+#: etc/initialdata:359
+msgid "Approval Rejected"
+msgstr "é§å›žæŸé …簽核"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Result"
+msgstr "簽核çµæžœ"
+
+#: html/Work/Approvals/Elements/Approve:25
+msgid "Approval Status"
+msgstr "核准çµæžœ"
+
+#: NOT FOUND IN SOURCE
+msgid "Approval Type"
+msgstr "簽核種類"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:25
+msgid "Approval diagram"
+msgstr "簽核æµç¨‹"
+
+#: html/Approvals/Elements/Approve:43 html/Work/Approvals/Elements/Approve:29
+msgid "Approve"
+msgstr "核准"
+
+#: html/Work/Approvals/Elements/Approve:21 html/Work/Elements/List:9
+msgid "Approver"
+msgstr "簽核人"
+
+#: html/Edit/Global/Workflow/Action:25 html/Edit/Global/Workflow/Owner.html:10
+msgid "Approver Setting"
+msgstr "執行簽核人設定"
+
+#: etc/initialdata:486 etc/upgrade/2.1.71:148 html/Edit/Elements/CreateApprovalsQueue:122
+msgid "Approver's notes: %1"
+msgstr "簽核備註:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Apr"
+msgstr "四月"
+
+#: lib/RT/Date.pm:414
+msgid "Apr."
+msgstr "04"
+
+#: NOT FOUND IN SOURCE
+msgid "April"
+msgstr "四月"
+
+#: html/Edit/Elements/104Buttons:24
+msgid "Are you sure to delete checked items?"
+msgstr "您確定è¦åˆªé™¤ï¼Ÿ"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Ascending"
+msgstr "éžå¢ž"
+
+#: html/Search/Bulk.html:136 html/SelfService/Update.html:47 html/Ticket/ModifyAll.html:82 html/Ticket/Update.html:98 html/Work/Search/Bulk.html:88
+msgid "Attach"
+msgstr "附件"
+
+#: html/SelfService/Create.html:64 html/Ticket/Create.html:109 html/Work/Tickets/Elements/AddAttachments:19
+msgid "Attach file"
+msgstr "附加檔案"
+
+#: html/SelfService/Update.html:36 html/Ticket/Create.html:97 html/Ticket/Update.html:87 html/Work/Tickets/Elements/AddAttachments:7 html/Work/Tickets/Elements/ShowAttachments:9
+msgid "Attached file"
+msgstr "ç¾æœ‰é™„件"
+
+#: NOT FOUND IN SOURCE
+msgid "Attachment '%1' could not be loaded"
+msgstr "無法載入附件 '%1'"
+
+#: lib/RT/Transaction_Overlay.pm:390 lib/RT/Transaction_Vendor.pm:50
+msgid "Attachment created"
+msgstr "附件新增完畢"
+
+#: lib/RT/Tickets_Overlay.pm:1208
+msgid "Attachment filename"
+msgstr "附件檔å"
+
+#: html/Ticket/Elements/ShowAttachments:25 html/Work/Tickets/Elements/ShowTransaction:37
+msgid "Attachments"
+msgstr "附件"
+
+#: NOT FOUND IN SOURCE
+msgid "Aug"
+msgstr "八月"
+
+#: lib/RT/Date.pm:418
+msgid "Aug."
+msgstr "08"
+
+#: NOT FOUND IN SOURCE
+msgid "August"
+msgstr "八月"
+
+#: html/Admin/Elements/ModifyUser:65
+msgid "AuthSystem"
+msgstr "èªè­‰æ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoReject"
+msgstr "自動é§å›žè¡¨å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoResolve"
+msgstr "自動完æˆè¡¨å–®è™•ç†"
+
+#: etc/initialdata:206
+msgid "Autoreply"
+msgstr "自動回覆"
+
+#: etc/initialdata:72
+msgid "Autoreply To Requestors"
+msgstr "自動å°ç”³è«‹äººå›žè¦†"
+
+#: NOT FOUND IN SOURCE
+msgid "AutoreplyToRequestors"
+msgstr "自動å°ç”³è«‹äººå›žè¦†"
+
+#: html/Edit/Rights/index.html:17
+msgid "Available Rights:"
+msgstr "權é™é …目列表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Back to Homepage"
+msgstr "回到首é "
+
+#: html/Work/Elements/BackButton:2 html/Work/Search/Bulk.html:101
+msgid "Back to Previous"
+msgstr "回上é "
+
+#: NOT FOUND IN SOURCE
+msgid "Bad PGP Signature: %1\\n"
+msgstr "錯誤的 PGP 簽章:%1\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad attachment id. Couldn't find attachment '%1'\\n"
+msgstr "錯誤的附件編號。無法找到附件 '%1'\\n"
+
+#: bin/rt-commit-handler:826
+#. ($val)
+msgid "Bad data in %1"
+msgstr "%1 的資料錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "Bad transaction number for attachment. %1 should be %2\\n"
+msgstr "附件的處ç†è™Ÿç¢¼éŒ¯èª¤ã€‚%1 應為 %2\\n"
+
+#: html/Admin/Elements/GroupTabs:38 html/Admin/Elements/QueueTabs:38 html/Admin/Elements/UserTabs:37 html/Edit/Global/autohandler:6 html/Edit/Queues/autohandler:21 html/Edit/Users/index.html:94 html/Ticket/Elements/Tabs:89 html/User/Elements/GroupTabs:37
+msgid "Basics"
+msgstr "基本資訊"
+
+#: html/Ticket/Update.html:81 html/Work/Tickets/Update.html:53
+msgid "Bcc"
+msgstr "密件副本"
+
+#: html/Admin/Elements/EditScrip:95 html/Admin/Global/GroupRights.html:84 html/Admin/Global/Template.html:45 html/Admin/Global/UserRights.html:53 html/Admin/Global/Workflow.html:46 html/Admin/Groups/GroupRights.html:72 html/Admin/Groups/Members.html:80 html/Admin/Groups/Modify.html:55 html/Admin/Groups/UserRights.html:54 html/Admin/Queues/GroupRights.html:85 html/Admin/Queues/Template.html:44 html/Admin/Queues/UserRights.html:53 html/Admin/Queues/Workflow.html:44 html/User/Groups/Modify.html:55
+msgid "Be sure to save your changes"
+msgstr "請別忘了儲存修改。"
+
+#: html/Elements/SelectDateRelation:33 lib/RT/CurrentUser.pm:320
+msgid "Before"
+msgstr "æ—©æ–¼"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:44
+msgid "Begin Approval"
+msgstr "開始簽核"
+
+#: NOT FOUND IN SOURCE
+msgid "Begin From "
+msgstr "起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Birthday"
+msgstr "生日"
+
+#: etc/initialdata:202
+msgid "Blank"
+msgstr "空白範本"
+
+#: html/Search/Listing.html:78 html/Work/Search/index.html:53
+msgid "Bookmarkable URL for this search"
+msgstr "將查詢çµæžœè½‰ç‚ºå¯æ”¾å…¥æ›¸ç±¤çš„網å€"
+
+#: html/Ticket/Elements/ShowHistory:38 html/Ticket/Elements/ShowHistory:44
+msgid "Brief headers"
+msgstr "精簡標頭檔"
+
+#: html/Search/Bulk.html:24 html/Search/Bulk.html:25
+msgid "Bulk ticket update"
+msgstr "更新整批申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Business Unit"
+msgstr "事業部"
+
+#: NOT FOUND IN SOURCE
+msgid "Business Unit:"
+msgstr "事業部:"
+
+#: lib/RT/User_Overlay.pm:1529
+msgid "Can not modify system users"
+msgstr "無法更改系統使用者"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "Can this principal see this queue"
+msgstr "該單ä½æ˜¯å¦èƒ½æŸ¥é–±æ­¤è¡¨å–®"
+
+#: lib/RT/CustomField_Overlay.pm:205
+msgid "Can't add a custom field value without a name"
+msgstr "ä¸èƒ½æ–°å¢žæ²’有å稱的自訂欄ä½å€¼"
+
+#: lib/RT/Link_Overlay.pm:131
+msgid "Can't link a ticket to itself"
+msgstr "申請單ä¸èƒ½éˆçµè‡ªå·±ã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:2857
+msgid "Can't merge into a merged ticket. You should never get this error"
+msgstr "ä¸èƒ½æ•´åˆé€²å·²æ•´åˆéŽçš„申請單。這個錯誤ä¸è©²ç™¼ç”Ÿã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:2659 lib/RT/Ticket_Overlay.pm:2738
+msgid "Can't specifiy both base and target"
+msgstr "ä¸èƒ½åŒæ™‚指定起始申請單與目的申請單"
+
+#: html/Edit/Elements/PopFooter:8
+msgid "Cancel"
+msgstr "å–消"
+
+#: html/autohandler:126
+#. ($msg)
+msgid "Cannot create user: %1"
+msgstr "無法新增使用者:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Card No."
+msgstr "å¡è™Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Categories"
+msgstr "分類管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Category"
+msgstr "分類"
+
+#: etc/initialdata:50 html/Admin/Queues/People.html:43 html/SelfService/Create.html:48 html/Ticket/Create.html:63 html/Ticket/Elements/EditPeople:50 html/Ticket/Elements/ShowPeople:34 html/Ticket/Update.html:44 html/Ticket/Update.html:76 html/Work/Tickets/Elements/EditPeople:41 html/Work/Tickets/Elements/ShowLinks:6 html/Work/Tickets/Update.html:43 lib/RT/ACE_Overlay.pm:87
+msgid "Cc"
+msgstr "副本"
+
+#: NOT FOUND IN SOURCE
+msgid "Cc Type"
+msgstr "副本類別"
+
+#: NOT FOUND IN SOURCE
+msgid "Chairperson's Office"
+msgstr "董事長室"
+
+#: NOT FOUND IN SOURCE
+msgid "Change Ticket"
+msgstr "修改申請單"
+
+#: html/SelfService/Prefs.html:30
+msgid "Change password"
+msgstr "更改密碼"
+
+#: html/Edit/Global/Basic/Top:79
+msgid "ChangeOwnerUI"
+msgstr "å¯å¦é¸æ“‡è¡¨å–®æ‰¿è¾¦äºº"
+
+#: html/SelfService/Update.html:39 html/Ticket/Create.html:100 html/Ticket/Elements/EditCustomFieldEntries:35 html/Ticket/Update.html:90 html/Work/Tickets/Elements/ShowCustomFieldEntries:14
+msgid "Check box to delete"
+msgstr "é¸æ“‡æ¬²åˆªé™¤çš„é …ç›®"
+
+#: html/Admin/Elements/SelectRights:30
+msgid "Check box to revoke right"
+msgstr "é¸æ“‡æ¬²æ’¤æ¶ˆçš„權利"
+
+#: html/Ticket/Create.html:183 html/Ticket/Elements/BulkLinks:42 html/Ticket/Elements/EditLinks:113 html/Ticket/Elements/EditLinks:63 html/Ticket/Elements/ShowLinks:56 html/Work/Search/BulkLinks:18 html/Work/Tickets/Elements/EditLinks:117 html/Work/Tickets/Elements/EditLinks:56 html/Work/Tickets/Elements/ShowMembers:4
+msgid "Children"
+msgstr "å­ç”³è«‹å–®"
+
+#: html/Edit/Elements/PickUsers:21 html/Edit/Global/UserRight/List:8 html/Edit/Global/UserRight/Top:19
+msgid "Chinese Name"
+msgstr "中文姓å"
+
+#: NOT FOUND IN SOURCE
+msgid "Chinese/English"
+msgstr "中英文"
+
+#: html/Admin/Elements/ModifyUser:79 html/Admin/Users/Modify.html:131 html/User/Prefs.html:122 html/Work/Preferences/Info:83
+msgid "City"
+msgstr "所在城市"
+
+#: html/Edit/Elements/104Top:30
+msgid "ClassicUI"
+msgstr "傳統介é¢"
+
+#: html/Ticket/Elements/ShowDates:47
+msgid "Closed"
+msgstr "已解決"
+
+#: html/SelfService/Closed.html:24
+msgid "Closed Tickets"
+msgstr "已解決的申請單"
+
+#: html/SelfService/Elements/Tabs:44
+msgid "Closed tickets"
+msgstr "已解決的申請單"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:181 html/Edit/Global/Workflow/Action:54 html/Edit/Global/Workflow/Condition:52
+msgid "Code"
+msgstr "執行程å¼ç¢¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Command not understood!\\n"
+msgstr "指令無法辨識ï¼\\n"
+
+#: html/Ticket/Elements/ShowTransaction:178 html/Ticket/Elements/Tabs:152 html/Work/Search/Bulk.html:89 html/Work/Tickets/Display.html:60 html/Work/Tickets/Elements/ShowTransaction:118 html/Work/Tickets/Elements/ShowTransaction:32
+msgid "Comment"
+msgstr "è©•è«–"
+
+#: html/Admin/Elements/ModifyQueue:44 html/Admin/Queues/Modify.html:57
+msgid "Comment Address"
+msgstr "è©•è«–é›»å­éƒµä»¶åœ°å€"
+
+#: NOT FOUND IN SOURCE
+msgid "Comment not recorded"
+msgstr "評論未被紀錄"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "Comment on tickets"
+msgstr "å°ç”³è«‹å–®æ出評論"
+
+#: lib/RT/Queue_Overlay.pm:85
+msgid "CommentOnTicket"
+msgstr "評論申請單"
+
+#: html/Admin/Elements/ModifyUser:34 html/Work/Tickets/Elements/AddContent:7
+msgid "Comments"
+msgstr "è©•è«–"
+
+#: html/Ticket/ModifyAll.html:69 html/Ticket/Update.html:68 html/Work/Tickets/Update.html:35
+msgid "Comments (Not sent to requestors)"
+msgstr "è©•è«–(ä¸é€çµ¦ç”³è«‹äºº)"
+
+#: html/Search/Bulk.html:131 html/Work/Search/Bulk.html:83
+msgid "Comments (not sent to requestors)"
+msgstr "è©•è«–(ä¸é€çµ¦ç”³è«‹äºº)"
+
+#: html/Elements/ViewUser:26
+#. ($name)
+msgid "Comments about %1"
+msgstr "å° %1 çš„è©•è«–"
+
+#: html/Admin/Users/Modify.html:184 html/Edit/Users/Info:46 html/Ticket/Elements/ShowRequestor:43
+msgid "Comments about this user"
+msgstr "使用者æè¿°"
+
+#: lib/RT/Transaction_Overlay.pm:501
+msgid "Comments added"
+msgstr "新增評論完畢"
+
+#: html/Edit/Elements/PopFooter:4 html/Edit/Elements/PopFooter:6
+msgid "Commit"
+msgstr "確èª"
+
+#: lib/RT/Action/Generic.pm:139
+msgid "Commit Stubbed"
+msgstr "消除更動完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Company Name"
+msgstr "å…¬å¸å稱"
+
+#: html/Edit/Global/Basic/Top:85
+msgid "CompanySpecific"
+msgstr "å„å…¬å¸ç¨ç«‹é¡¯ç¤º"
+
+#: NOT FOUND IN SOURCE
+msgid "Compile Restrictions"
+msgstr "設定查詢æ¢ä»¶"
+
+#: html/Admin/Elements/EditScrip:40 html/Admin/Elements/ModifyTemplateAsWorkflow:127
+msgid "Condition"
+msgstr "æ¢ä»¶"
+
+#: bin/rt-crontool:108
+msgid "Condition matches..."
+msgstr "符åˆæ¢ä»¶..."
+
+#: lib/RT/Scrip_Overlay.pm:164
+msgid "Condition not found"
+msgstr "未找到符åˆçš„ç¾æ³"
+
+#: html/Edit/Global/GroupRight/Top:26 html/Edit/Global/UserRight/Top:45 html/Edit/Groups/Member:56 html/Elements/Tabs:49
+msgid "Configuration"
+msgstr "設定"
+
+#: html/SelfService/Prefs.html:32
+msgid "Confirm"
+msgstr "確èªå¯†ç¢¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Confirm Password"
+msgstr "密碼確èª"
+
+#: html/Work/Approvals/Display.html:25 html/Work/Tickets/Create.html:154 html/Work/Tickets/Create.html:168 html/Work/Tickets/Update.html:77
+msgid "Confirm Submit"
+msgstr "確定é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "Contact System Administrator"
+msgstr "連絡系統管ç†å“¡"
+
+#: html/Admin/Elements/ModifyUser:59
+msgid "ContactInfoSystem"
+msgstr "連絡資訊系統"
+
+#: NOT FOUND IN SOURCE
+msgid "Contacted date '%1' could not be parsed"
+msgstr "無法解讀è¯çµ¡æ—¥æœŸ '%1'"
+
+#: html/Admin/Elements/ModifyTemplate:43 html/Admin/Elements/ModifyTemplateAsWorkflow:200 html/Ticket/ModifyAll.html:86
+msgid "Content"
+msgstr "內容"
+
+#: NOT FOUND IN SOURCE
+msgid "Coould not create group"
+msgstr "無法新增群組"
+
+#: html/Edit/Elements/104Buttons:85
+msgid "Copy"
+msgstr "複製"
+
+#: NOT FOUND IN SOURCE
+msgid "Copy Field From:"
+msgstr "欲複製欄ä½ï¼š"
+
+#: etc/initialdata:271
+msgid "Correspondence"
+msgstr "回覆"
+
+#: html/Admin/Elements/ModifyQueue:38 html/Admin/Queues/Modify.html:50
+msgid "Correspondence Address"
+msgstr "申請單回覆地å€"
+
+#: lib/RT/Transaction_Overlay.pm:497
+msgid "Correspondence added"
+msgstr "新增申請單回覆"
+
+#: NOT FOUND IN SOURCE
+msgid "Correspondence not recorded"
+msgstr "未紀錄申請單回覆"
+
+#: lib/RT/Ticket_Overlay.pm:3608
+msgid "Could not add new custom field value for ticket. "
+msgstr "ä¸èƒ½æ–°å¢žè‡ªè¨‚欄ä½çš„值 "
+
+#: NOT FOUND IN SOURCE
+msgid "Could not add new custom field value for ticket. %1 "
+msgstr "ä¸èƒ½æ–°å¢žè‡ªè¨‚欄ä½çš„值。%1 "
+
+#: lib/RT/Ticket_Overlay.pm:3108 lib/RT/Ticket_Overlay.pm:3116 lib/RT/Ticket_Overlay.pm:3133
+msgid "Could not change owner. "
+msgstr "ä¸èƒ½æ›´æ”¹æ‰¿è¾¦äººã€‚ "
+
+#: html/Admin/Elements/EditCustomField:84 html/Admin/Elements/EditCustomFields:164 html/Edit/Global/CustomField/index.html:120
+#. ($msg)
+msgid "Could not create CustomField"
+msgstr "無法新增自訂欄ä½"
+
+#: html/Edit/Global/Workflow/index.html:126
+#. ($msg)
+msgid "Could not create Scrip"
+msgstr "無法建立訊æ¯é€šçŸ¥"
+
+#: html/Edit/Global/Template/index.html:110
+#. ($msg)
+msgid "Could not create Template"
+msgstr "無法建立通知範本"
+
+#: html/User/Groups/Modify.html:76 lib/RT/Group_Overlay.pm:471 lib/RT/Group_Overlay.pm:478
+msgid "Could not create group"
+msgstr "無法新增群組"
+
+#: html/Edit/Elements/Index:89
+#. ($msg)
+msgid "Could not create item"
+msgstr "無法新增項目"
+
+#: html/Admin/Global/Template.html:74 html/Admin/Queues/Template.html:71
+#. ($msg)
+msgid "Could not create template: %1"
+msgstr "無法新增範本:%1"
+
+#: lib/RT/Ticket_Overlay.pm:1118 lib/RT/Ticket_Overlay.pm:353
+msgid "Could not create ticket. Queue not set"
+msgstr "無法新增申請單。尚未指定表單。"
+
+#: lib/RT/User_Overlay.pm:271 lib/RT/User_Overlay.pm:284 lib/RT/User_Overlay.pm:302 lib/RT/User_Overlay.pm:488
+msgid "Could not create user"
+msgstr "無法新增使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not create watcher for requestor"
+msgstr "無法為申請人新增視察員"
+
+#: html/Admin/Elements/ModifyWorkflow:219 html/Admin/Global/Workflow.html:75 html/Admin/Queues/Workflow.html:71
+#. ($msg)
+msgid "Could not create workflow: %1"
+msgstr "無法新增æµç¨‹ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find a ticket with id %1"
+msgstr "找ä¸åˆ°ç·¨è™Ÿ %1 的申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find group %1."
+msgstr "找ä¸åˆ°ç¾¤çµ„ %1。"
+
+#: lib/RT/Queue_Overlay.pm:621 lib/RT/Ticket_Overlay.pm:1448
+msgid "Could not find or create that user"
+msgstr "找ä¸åˆ°æˆ–無法新增該å使用者"
+
+#: lib/RT/Queue_Overlay.pm:682 lib/RT/Ticket_Overlay.pm:1527
+msgid "Could not find that principal"
+msgstr "找ä¸åˆ°è©²å–®ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Could not find user %1."
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… %1。"
+
+#: html/Admin/Groups/Members.html:87 html/User/Groups/Members.html:89 html/User/Groups/Modify.html:81
+msgid "Could not load group"
+msgstr "無法載入群組"
+
+#: lib/RT/Queue_Overlay.pm:641
+#. ($args{'Type'})
+msgid "Could not make that principal a %1 for this queue"
+msgstr "無法將該單ä½è¨­ç‚ºæ­¤è¡¨å–®çš„ %1。"
+
+#: lib/RT/Ticket_Overlay.pm:1469
+#. ($self->loc($args{'Type'}))
+msgid "Could not make that principal a %1 for this ticket"
+msgstr "無法將該單ä½è¨­ç‚ºæ­¤ç”³è«‹å–®çš„ %1。"
+
+#: lib/RT/Queue_Overlay.pm:740
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this queue"
+msgstr "ç„¡æ³•å°‡å–®ä½ %1 從表單移除。"
+
+#: lib/RT/Ticket_Overlay.pm:1585
+#. ($args{'Type'})
+msgid "Could not remove that principal as a %1 for this ticket"
+msgstr "ç„¡æ³•å°‡å–®ä½ %1 從申請單移除。"
+
+#: lib/RT/Group_Overlay.pm:982
+msgid "Couldn't add member to group"
+msgstr "無法新增æˆå“¡è‡³ç¾¤çµ„"
+
+#: lib/RT/Ticket_Overlay.pm:3618 lib/RT/Ticket_Overlay.pm:3674
+#. ($Msg)
+msgid "Couldn't create a transaction: %1"
+msgstr "無法新增更動報告"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't figure out what to do from gpg's reply\\n"
+msgstr "無法從 gpg 回函辨識出該採å–的行動\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find group\\n"
+msgstr "找ä¸åˆ°ç¾¤çµ„\\n"
+
+#: lib/RT/Interface/Web.pm:962
+msgid "Couldn't find row"
+msgstr "找ä¸åˆ°æ­¤åˆ—資料"
+
+#: lib/RT/Group_Overlay.pm:956
+msgid "Couldn't find that principal"
+msgstr "找ä¸åˆ°è©²å–®ä½"
+
+#: lib/RT/CustomField_Overlay.pm:239
+msgid "Couldn't find that value"
+msgstr "找ä¸åˆ°è©²å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find that watcher"
+msgstr "找ä¸åˆ°è©²è¦–察員"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't find user\\n"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€…\\n"
+
+#: lib/RT/CurrentUser.pm:111
+#. ($self->Id)
+msgid "Couldn't load %1 from the users database.\\n"
+msgstr "無法從使用者資料庫載入 %1。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load KeywordSelects."
+msgstr "無法載入 KeywordSelects。"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load RT config file '%1' %2"
+msgstr "無法載入 RT 設定檔 '%1' %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load Scrips."
+msgstr "無法載入手續。"
+
+#: html/Admin/Groups/GroupRights.html:87 html/Admin/Groups/UserRights.html:74 html/Edit/Global/GroupRight/Add.html:55 html/Edit/Global/GroupRight/Add.html:60 html/Edit/Global/UserRight/Add.html:25 html/Edit/Global/UserRight/Add.html:30 html/Edit/Groups/Member:120 html/Edit/Groups/Members/Add.html:43 html/Edit/Rights/index.html:58 html/Edit/Rights/index.html:63
+#. ($ObjectGroup)
+#. ($Report)
+#. ($Group)
+#. ($id)
+msgid "Couldn't load group %1"
+msgstr "無法載入手續 %1"
+
+#: lib/RT/Link_Overlay.pm:174 lib/RT/Link_Overlay.pm:183 lib/RT/Link_Overlay.pm:210
+msgid "Couldn't load link"
+msgstr "無法載入éˆçµã€‚"
+
+#: html/Admin/Elements/EditCustomFields:145 html/Admin/Queues/CustomFields.html:35 html/Admin/Queues/People.html:120
+#. ($id)
+msgid "Couldn't load queue"
+msgstr "無法載入表單"
+
+#: html/Admin/Queues/GroupRights.html:100 html/Admin/Queues/UserRights.html:71 html/Edit/Global/GroupRight/Add.html:51 html/Edit/Global/GroupRight/index.html:82 html/Edit/Global/GroupRight/index.html:87 html/Edit/Global/UserRight/Add.html:21 html/Edit/Global/UserRight/index.html:83 html/Edit/Global/UserRight/index.html:88 html/Edit/Rights/index.html:54
+#. ($Queue)
+#. ($id)
+msgid "Couldn't load queue %1"
+msgstr "無法載入表單 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load scrip"
+msgstr "無法載入手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Couldn't load template"
+msgstr "無法載入範本"
+
+#: html/Admin/Users/Prefs.html:78
+#. ($id)
+msgid "Couldn't load that user (%1)"
+msgstr "無法載入該å使用者(%1)"
+
+#: html/SelfService/Display.html:114
+#. ($id)
+msgid "Couldn't load ticket '%1'"
+msgstr "無法載入申請單 '%1'"
+
+#: html/Admin/Elements/ModifyUser:85 html/Admin/Users/Modify.html:148 html/User/Prefs.html:134 html/Work/Preferences/Info:89
+msgid "Country"
+msgstr "國家"
+
+#: html/Admin/Elements/CreateUserCalled:25 html/Edit/Elements/PopHeader:33 html/Edit/Global/GroupRight/Add.html:19 html/Ticket/Create.html:134 html/Ticket/Create.html:195
+msgid "Create"
+msgstr "新增"
+
+#: html/Edit/Groups/MemberGroups/Add.html:17
+msgid "Create Subgroup:"
+msgstr "新增å­ç¾¤çµ„:"
+
+#: etc/initialdata:127
+msgid "Create Tickets"
+msgstr "新增申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Create User:"
+msgstr "新增æˆå“¡ï¼š"
+
+#: html/Admin/Elements/EditCustomField:74
+msgid "Create a CustomField"
+msgstr "新增自訂欄ä½"
+
+#: html/Admin/Queues/CustomField.html:47
+#. ($QueueObj->Name())
+msgid "Create a CustomField for queue %1"
+msgstr "為 %1 表單新增自訂欄ä½"
+
+#: html/Admin/Global/CustomField.html:47
+msgid "Create a CustomField which applies to all queues"
+msgstr "為 %1 表單新增自訂欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new Custom Field"
+msgstr "新增自訂欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global Scrip"
+msgstr "新增全域手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new global scrip"
+msgstr "新增全域手續"
+
+#: html/Admin/Groups/Modify.html:66 html/Admin/Groups/Modify.html:92
+msgid "Create a new group"
+msgstr "新增群組"
+
+#: html/User/Groups/Modify.html:66 html/User/Groups/Modify.html:91
+msgid "Create a new personal group"
+msgstr "新增代ç†äººç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new queue"
+msgstr "新增表單"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new scrip"
+msgstr "新增手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new template"
+msgstr "新增範本"
+
+#: html/Ticket/Create.html:24 html/Ticket/Create.html:27 html/Ticket/Create.html:35
+msgid "Create a new ticket"
+msgstr "新增申請單"
+
+#: html/Admin/Users/Modify.html:213 html/Admin/Users/Modify.html:242
+msgid "Create a new user"
+msgstr "新增使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a new workflow"
+msgstr "新增æµç¨‹"
+
+#: html/Admin/Queues/Modify.html:103
+msgid "Create a queue"
+msgstr "新增表單"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a queue called"
+msgstr "新增表單å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Create a request"
+msgstr "æ出申請"
+
+#: html/Admin/Queues/Scrip.html:58
+#. ($QueueObj->Name)
+msgid "Create a scrip for queue %1"
+msgstr "為 %1 表單新增手續"
+
+#: html/Admin/Global/Template.html:68 html/Admin/Queues/Template.html:64
+msgid "Create a template"
+msgstr "新增範本"
+
+#: html/SelfService/Create.html:24
+msgid "Create a ticket"
+msgstr "æ出申請單"
+
+#: html/Admin/Elements/ModifyWorkflow:206 html/Admin/Global/Workflow.html:69 html/Admin/Queues/Workflow.html:64
+msgid "Create a workflow"
+msgstr "新增æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1 / %2 / %3 "
+msgstr "新增失敗:%1 / %2 / %3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create failed: %1/%2/%3"
+msgstr "新增失敗:%1/%2/%3"
+
+#: NOT FOUND IN SOURCE
+msgid "Create new item"
+msgstr "建立新項目"
+
+#: etc/initialdata:129
+msgid "Create new tickets based on this scrip's template"
+msgstr "ä¾æ“šæ­¤é …手續內的模版,新增申請單"
+
+#: html/SelfService/Create.html:77
+msgid "Create ticket"
+msgstr "新增申請單"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "Create tickets in this queue"
+msgstr "在此表單中新增申請單"
+
+#: lib/RT/Queue_Overlay.pm:71
+msgid "Create, delete and modify custom fields"
+msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹è‡ªè¨‚欄ä½"
+
+#: lib/RT/Queue_Overlay.pm:67
+msgid "Create, delete and modify queues"
+msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹è¡¨å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Create, delete and modify the members of any user's personal groups"
+msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹ä»»ä½•ä½¿ç”¨è€…的代ç†äººç¾¤çµ„"
+
+#: lib/RT/System.pm:58
+msgid "Create, delete and modify the members of personal groups"
+msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹ä»£ç†äººç¾¤çµ„"
+
+#: lib/RT/System.pm:59
+msgid "Create, delete and modify users"
+msgstr "新增ã€åˆªé™¤åŠæ›´æ”¹ä½¿ç”¨è€…"
+
+#: lib/RT/Queue_Overlay.pm:83
+msgid "CreateTicket"
+msgstr "新增申請單"
+
+#: html/Elements/SelectDateType:25 html/Ticket/Elements/ShowDates:27 lib/RT/Ticket_Overlay.pm:1212
+msgid "Created"
+msgstr "新增日"
+
+#: html/Admin/Elements/EditCustomField:87
+#. ($CustomFieldObj->Name())
+msgid "Created CustomField %1"
+msgstr "è‡ªè¨‚æ¬„ä½ %1 新增æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Created template %1"
+msgstr "範本 %1 新增æˆåŠŸ"
+
+#: html/Admin/Elements/ModifyWorkflow:221
+#. (loc( $WorkflowObj->Name() ))
+msgid "Created workflow %1"
+msgstr "æµç¨‹ %1 新增æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Currency"
+msgstr "幣別"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Approval Info"
+msgstr "截至目å‰ç°½æ ¸è³‡è¨Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Custom Fields"
+msgstr "ç¾æœ‰è‡ªè¨‚欄ä½"
+
+#: html/Edit/Groups/MemberGroups/Add.html:14
+msgid "Current Groups:"
+msgstr "ç¾æœ‰ç¾¤çµ„列表:"
+
+#: html/Ticket/Elements/EditLinks:27 html/Work/Tickets/Elements/EditLinks:10
+msgid "Current Relationships"
+msgstr "ç¾æœ‰é—œä¿‚"
+
+#: html/Edit/Rights/index.html:20
+msgid "Current Rights:"
+msgstr "ç¾æœ‰æ¬Šé™ï¼š"
+
+#: html/Admin/Elements/EditScrips:29
+msgid "Current Scrips"
+msgstr "ç¾æœ‰æ‰‹çºŒ"
+
+#: html/Work/Tickets/Create.html:49 html/Work/Tickets/Elements/ShowBasics:47
+msgid "Current Status"
+msgstr "ç›®å‰ç‹€æ…‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Current Templates"
+msgstr "ç¾æœ‰ç¯„本"
+
+#: html/Work/Tickets/Elements/EditPeople:9
+msgid "Current Watchers"
+msgstr "ç¾æœ‰è¦–察員"
+
+#: html/Admin/Groups/Members.html:38 html/User/Groups/Members.html:41
+msgid "Current members"
+msgstr "ç¾æœ‰æˆå“¡"
+
+#: html/Admin/Elements/SelectRights:28
+msgid "Current rights"
+msgstr "ç¾æœ‰æ¬Šé™"
+
+#: html/Search/Listing.html:70 html/Work/Search/index.html:42
+msgid "Current search criteria"
+msgstr "ç¾æœ‰æŸ¥è©¢æ¢ä»¶"
+
+#: html/Admin/Queues/People.html:40 html/Ticket/Elements/EditPeople:44 html/Work/Tickets/Elements/EditPeople:32
+msgid "Current watchers"
+msgstr "ç¾æœ‰è¦–察員"
+
+#: html/Admin/Global/CustomField.html:54
+#. ($CustomField)
+msgid "Custom Field #%1"
+msgstr "è‡ªè¨‚æ¬„ä½ #%1"
+
+#: html/Admin/Elements/QueueTabs:52 html/Admin/Elements/SystemTabs:39 html/Admin/Global/index.html:49 html/Edit/Global/autohandler:7 html/Edit/Queues/autohandler:22 html/Ticket/Elements/ShowSummary:35
+msgid "Custom Fields"
+msgstr "自訂欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom Fields which apply to all queues"
+msgstr "é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
+
+#: html/Admin/Elements/EditScrip:72 html/Edit/Global/Scrip/Top:69
+msgid "Custom action cleanup code"
+msgstr "動作後執行程å¼"
+
+#: html/Admin/Elements/EditScrip:64 html/Edit/Global/Scrip/Top:62
+msgid "Custom action preparation code"
+msgstr "動作å‰åŸ·è¡Œç¨‹å¼"
+
+#: html/Admin/Elements/EditScrip:48 html/Edit/Global/Scrip/Top:35 html/Edit/Global/Scrip/Top:61
+msgid "Custom condition"
+msgstr "自訂æ¢ä»¶"
+
+#: lib/RT/Tickets_Overlay.pm:1637
+#. ($CF->Name , $args{OPERATOR} , $args{VALUE})
+msgid "Custom field %1 %2 %3"
+msgstr "è‡ªè¨‚æ¬„ä½ %1 %2 %3"
+
+#: lib/RT/Tickets_Overlay.pm:1632
+#. ($CF->Name)
+msgid "Custom field %1 has a value."
+msgstr "è‡ªè¨‚æ¬„ä½ %1 已有值"
+
+#: lib/RT/Tickets_Overlay.pm:1629
+#. ($CF->Name)
+msgid "Custom field %1 has no value."
+msgstr "è‡ªè¨‚æ¬„ä½ %1 沒有值"
+
+#: lib/RT/Ticket_Overlay.pm:3510
+#. ($args{'Field'})
+msgid "Custom field %1 not found"
+msgstr "找ä¸åˆ°è‡ªè¨‚æ¬„ä½ %1"
+
+#: html/Admin/Elements/EditCustomFields:195
+msgid "Custom field deleted"
+msgstr "自訂欄ä½å·²åˆªé™¤"
+
+#: lib/RT/Ticket_Overlay.pm:3660
+msgid "Custom field not found"
+msgstr "找ä¸åˆ°è‡ªè¨‚欄ä½"
+
+#: lib/RT/CustomField_Overlay.pm:349
+#. ($args{'Content'}, $self->Name)
+msgid "Custom field value %1 could not be found for custom field %2"
+msgstr "ç„¡æ³•å¾žè‡ªè¨‚æ¬„ä½ %2 中找到 %1 這個欄ä½å€¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Custom field value changed from %1 to %2"
+msgstr "自訂欄ä½å€¼å¾ž %1 改為 %2"
+
+#: lib/RT/CustomField_Overlay.pm:249
+msgid "Custom field value could not be deleted"
+msgstr "無法刪除自訂欄ä½å€¼"
+
+#: lib/RT/CustomField_Overlay.pm:355
+msgid "Custom field value could not be found"
+msgstr "找ä¸åˆ°è‡ªè¨‚欄ä½å€¼"
+
+#: lib/RT/CustomField_Overlay.pm:247 lib/RT/CustomField_Overlay.pm:357
+msgid "Custom field value deleted"
+msgstr "自訂欄ä½å€¼åˆªé™¤æˆåŠŸ"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:145 html/Edit/Global/Workflow/Owner.html:90 lib/RT/Transaction_Overlay.pm:505 lib/RT/Transaction_Vendor.pm:5
+msgid "CustomField"
+msgstr "自訂欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Data error"
+msgstr "資料錯誤"
+
+#: html/Edit/Global/Basic/Top:77
+msgid "DatabaseBindRemote"
+msgstr "容許外部連線"
+
+#: html/Edit/Global/Basic/Top:75
+msgid "DatabaseName"
+msgstr "MySQL資料庫"
+
+#: NOT FOUND IN SOURCE
+msgid "Date of Departure"
+msgstr "出發日期"
+
+#: html/SelfService/Display.html:38 html/Ticket/Create.html:160 html/Ticket/Elements/ShowSummary:54 html/Ticket/Elements/Tabs:92 html/Ticket/ModifyAll.html:43 html/Work/Tickets/Elements/ShowTransaction:17
+msgid "Dates"
+msgstr "日期"
+
+#: NOT FOUND IN SOURCE
+msgid "Dec"
+msgstr "å二月"
+
+#: lib/RT/Date.pm:422
+msgid "Dec."
+msgstr "12"
+
+#: NOT FOUND IN SOURCE
+msgid "December"
+msgstr "å二月"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Approval"
+msgstr "é è¨­ç°½æ ¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Default Autoresponse Template"
+msgstr "é è¨­è‡ªå‹•å›žæ‡‰ç¯„本"
+
+#: etc/initialdata:207
+msgid "Default Autoresponse template"
+msgstr "é è¨­è‡ªå‹•å›žæ‡‰ç¯„本"
+
+#: html/Edit/Global/CustomField/Top:46
+msgid "Default Value"
+msgstr "é è¨­å€¼"
+
+#: etc/initialdata:281
+msgid "Default admin comment template"
+msgstr "é è¨­ç®¡ç†å“¡è©•è«–範本"
+
+#: etc/initialdata:260
+msgid "Default admin correspondence template"
+msgstr "é è¨­ç®¡ç†å“¡å›žè¦†ç¯„本"
+
+#: etc/initialdata:272
+msgid "Default correspondence template"
+msgstr "é è¨­å›žè¦†ç¯„本"
+
+#: etc/initialdata:238
+msgid "Default transaction template"
+msgstr "é è¨­æ›´å‹•ç¯„本"
+
+#: lib/RT/Transaction_Overlay.pm:491
+#. ($type, $self->Field, $self->OldValue, $self->NewValue)
+msgid "Default: %1/%2 changed from %3 to %4"
+msgstr "é è¨­ï¼š%1/%2 已自 %3 改為 %4"
+
+#: NOT FOUND IN SOURCE
+msgid "DefaultApproval"
+msgstr "é è¨­ç°½æ ¸"
+
+#: html/User/Delegation.html:24 html/User/Delegation.html:27
+msgid "Delegate rights"
+msgstr "代表團權é™"
+
+#: lib/RT/System.pm:62
+msgid "Delegate specific rights which have been granted to you."
+msgstr "å°‡æ“有的權é™å§”託他人代ç†"
+
+#: lib/RT/System.pm:62
+msgid "DelegateRights"
+msgstr "設定代ç†äºº"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Approval"
+msgstr "代ç†ç°½æ ¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Queue"
+msgstr "代ç†è¡¨å–®å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Queue:"
+msgstr "代ç†è¡¨å–®ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegated Type"
+msgstr "代ç†è¡¨å–®ç¨®é¡ž"
+
+#: html/Edit/Users/index.html:98 html/Work/Delegates/Info:31 html/Work/Delegates/List:8 html/Work/Elements/Tab:41 html/Work/Overview/Info:28
+msgid "Delegates"
+msgstr "代ç†äºº"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Enabled Status"
+msgstr "代ç†å•Ÿå‹•ç‹€æ…‹"
+
+#: html/Work/Delegates/Info:18 html/Work/Overview/Info:18
+msgid "Delegates Info"
+msgstr "代ç†äººè³‡è¨Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Period"
+msgstr "代ç†æœŸé–“"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Permission Setting"
+msgstr "代ç†æ¬Šé™è¨­å®š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Permission:"
+msgstr "代ç†æ¬Šé™ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegates Setting"
+msgstr "代ç†äººè¨­å®š"
+
+#: html/Work/Delegates/Info:46 html/Work/Delegates/List:11 html/Work/Overview/Info:39
+msgid "Delegates Status"
+msgstr "代ç†ç‹€æ…‹"
+
+#: html/User/Elements/Tabs:37
+msgid "Delegation"
+msgstr "代ç†äººæ¬Šé™"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegation Groups"
+msgstr "代ç†äººç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Delegation Rights"
+msgstr "代ç†äººæ¬Šé™"
+
+#: html/Admin/Elements/EditScrips:53 html/Admin/Elements/ModifyTemplateAsWorkflow:113 html/Edit/Elements/104Buttons:84 html/Work/Search/index.html:48 html/Work/Search/index.html:48
+msgid "Delete"
+msgstr "刪除"
+
+#: html/Admin/Elements/EditScrips:52
+msgid "Delete selected scrips"
+msgstr "刪除指定的手續"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "Delete tickets"
+msgstr "刪除申請單"
+
+#: lib/RT/Queue_Overlay.pm:88
+msgid "DeleteTicket"
+msgstr "刪除申請單"
+
+#: lib/RT/Transaction_Overlay.pm:136
+msgid "Deleting this object could break referential integrity"
+msgstr "刪除此物件å¯èƒ½ç ´å£žåƒè€ƒå®Œæ•´æ€§"
+
+#: lib/RT/Queue_Overlay.pm:293
+msgid "Deleting this object would break referential integrity"
+msgstr "刪除此物件å¯èƒ½ç ´å£žåƒè€ƒå®Œæ•´æ€§"
+
+#: lib/RT/User_Overlay.pm:504
+msgid "Deleting this object would violate referential integrity"
+msgstr "刪除此物件會é•ååƒè€ƒå®Œæ•´æ€§"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity."
+msgstr "刪除此物件會é•ååƒè€ƒå®Œæ•´æ€§"
+
+#: NOT FOUND IN SOURCE
+msgid "Deleting this object would violate referential integrity. That's bad."
+msgstr "刪除此物件會é•ååƒè€ƒå®Œæ•´æ€§"
+
+#: html/Approvals/Elements/Approve:44 html/Work/Approvals/Elements/Approve:32
+msgid "Deny"
+msgstr "é§å›ž"
+
+#: NOT FOUND IN SOURCE
+msgid "Department"
+msgstr "部門"
+
+#: html/Edit/Global/UserRight/List:12 html/Edit/Global/UserRight/Top:13
+msgid "Department ID"
+msgstr "部門代碼"
+
+#: html/Edit/Global/UserRight/List:11 html/Edit/Global/UserRight/Top:49 html/Work/Delegates/Info:78 html/Work/Overview/Info:60
+msgid "Department Name"
+msgstr "部門å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Department's"
+msgstr "部門之"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Details"
+msgstr "差旅明細"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure From"
+msgstr "差旅起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Request"
+msgstr "è«‹å‡å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Departure Until"
+msgstr "差旅截止日"
+
+#: html/Ticket/Create.html:181 html/Ticket/Elements/BulkLinks:34 html/Ticket/Elements/EditLinks:105 html/Ticket/Elements/EditLinks:44 html/Ticket/Elements/ShowDependencies:31 html/Ticket/Elements/ShowLinks:36 html/Work/Search/BulkLinks:10 html/Work/Tickets/Elements/EditLinks:109 html/Work/Tickets/Elements/EditLinks:34 html/Work/Tickets/Elements/ShowLinks:21
+msgid "Depended on by"
+msgstr "å¯æŽ¥çºŒè™•ç†çš„申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Dependencies: \\n"
+msgstr "附屬性:\\n"
+
+#: lib/RT/Transaction_Overlay.pm:585
+#. ($value)
+msgid "Dependency by %1 added"
+msgstr "已加入å¯æŽ¥çºŒè™•ç†çš„申請單 %1"
+
+#: lib/RT/Transaction_Overlay.pm:622
+#. ($value)
+msgid "Dependency by %1 deleted"
+msgstr "已移除å¯æŽ¥çºŒè™•ç†çš„申請單 %1"
+
+#: lib/RT/Transaction_Overlay.pm:582
+#. ($value)
+msgid "Dependency on %1 added"
+msgstr "已加入需先處ç†çš„申請單 %1"
+
+#: lib/RT/Transaction_Overlay.pm:619
+#. ($value)
+msgid "Dependency on %1 deleted"
+msgstr "已移除需先處ç†çš„申請單 %1"
+
+#: html/Elements/SelectLinkType:26 html/Ticket/Create.html:180 html/Ticket/Elements/BulkLinks:30 html/Ticket/Elements/EditLinks:101 html/Ticket/Elements/EditLinks:35 html/Ticket/Elements/ShowDependencies:24 html/Ticket/Elements/ShowLinks:26 html/Work/Search/BulkLinks:6 html/Work/Tickets/Elements/EditLinks:105 html/Work/Tickets/Elements/EditLinks:23 html/Work/Tickets/Elements/ShowLinks:16
+msgid "Depends on"
+msgstr "需先處ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "DependsOn"
+msgstr "需先處ç†"
+
+#: html/Elements/SelectSortOrder:34
+msgid "Descending"
+msgstr "éžæ¸›"
+
+#: html/SelfService/Create.html:72 html/Ticket/Create.html:118
+msgid "Describe the issue below"
+msgstr "在以下欄ä½æ述主題"
+
+#: html/Admin/Elements/AddCustomFieldValue:35 html/Admin/Elements/EditCustomField:38 html/Admin/Elements/EditScrip:33 html/Admin/Elements/ModifyQueue:35 html/Admin/Elements/ModifyTemplate:35 html/Admin/Elements/ModifyTemplateAsWorkflow:192 html/Admin/Groups/Modify.html:48 html/Admin/Queues/Modify.html:47 html/Edit/Elements/SelectQueues:4 html/Edit/Global/Workflow/Action:13 html/Elements/SelectGroups:26 html/User/Groups/Modify.html:48
+msgid "Description"
+msgstr "æè¿°"
+
+#: NOT FOUND IN SOURCE
+msgid "Description of Responsibility"
+msgstr "經辦業務說明"
+
+#: NOT FOUND IN SOURCE
+msgid "Description:"
+msgstr "æ述:"
+
+#: html/Work/Tickets/Create.html:132 html/Work/Tickets/Create.html:84 html/Work/Tickets/Elements/EditCustomFields:13 html/Work/Tickets/Elements/EditCustomFields:61 html/Work/Tickets/Elements/ShowCustomFields:14 html/Work/Tickets/Elements/ShowCustomFields:53
+msgid "Details"
+msgstr "細節"
+
+#: NOT FOUND IN SOURCE
+msgid "Direct"
+msgstr "直接"
+
+#: NOT FOUND IN SOURCE
+msgid "Disability"
+msgstr "殘障身分"
+
+#: NOT FOUND IN SOURCE
+msgid "Disability Type"
+msgstr "殘障類別"
+
+#: html/Edit/Global/GroupRight/List:9 html/Edit/Global/GroupRight/Top:16 html/Edit/Groups/List:11 html/Edit/Groups/Top:19 html/Edit/Queues/Basic/Top:69 html/Edit/Queues/List:15 html/Edit/Queues/List:27 html/Work/Delegates/Info:48 html/Work/Delegates/Info:53 html/Work/Delegates/List:12 html/Work/Overview/Info:42
+msgid "Disabled"
+msgstr "åœç”¨"
+
+#: html/Ticket/Elements/Tabs:84
+msgid "Display"
+msgstr "顯示內容"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "Display Access Control List"
+msgstr "顯示權é™æŽ§åˆ¶æ¸…å–®"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "Display Scrip templates for this queue"
+msgstr "顯示此表單的範本"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "Display Scrips for this queue"
+msgstr "顯示此表單的手續"
+
+#: html/Ticket/Elements/ShowHistory:34
+msgid "Display mode"
+msgstr "顯示模å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Display ticket #%1"
+msgstr "顯示第%1號申請單"
+
+#: lib/RT/System.pm:53
+msgid "Do anything and everything"
+msgstr "å…許一切æ“作"
+
+#: html/Elements/Refresh:29
+msgid "Don't refresh this page."
+msgstr "ä¸æ›´æ–°æ­¤é é¢ã€‚"
+
+#: html/Search/Elements/PickRestriction:113 html/Work/Search/PickRestriction:101
+msgid "Don't show search results"
+msgstr "ä¸é¡¯ç¤ºæŸ¥è©¢çµæžœ"
+
+#: html/Edit/Elements/Page:19 html/Edit/Elements/Page:21
+msgid "Down"
+msgstr "下一é "
+
+#: html/Ticket/Elements/ShowTransaction:103
+msgid "Download"
+msgstr "下載"
+
+#: NOT FOUND IN SOURCE
+msgid "Dr."
+msgstr "åšå£«"
+
+#: html/Elements/SelectDateType:31 html/Ticket/Create.html:166 html/Ticket/Elements/EditDates:44 html/Ticket/Elements/ShowDates:43 html/Work/Tickets/Elements/EditBasics:54 lib/RT/Ticket_Overlay.pm:1216
+msgid "Due"
+msgstr "到期日"
+
+#: NOT FOUND IN SOURCE
+msgid "Due Date"
+msgstr "截止日"
+
+#: NOT FOUND IN SOURCE
+msgid "Due date '%1' could not be parsed"
+msgstr "無法解讀日期 '%1'"
+
+#: bin/rt-commit-handler:753
+#. ($1, $msg)
+msgid "ERROR: Couldn't load ticket '%1': %2.\\n"
+msgstr "無法載入申請單 '%1':%2.\\n"
+
+#: html/Work/Tickets/Update.html:47
+msgid "Edit"
+msgstr "編輯"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:132
+msgid "Edit Conditions"
+msgstr "編輯å‰ç½®æ¢ä»¶"
+
+#: html/Admin/Queues/CustomFields.html:45
+#. ($Queue->Name)
+msgid "Edit Custom Fields for %1"
+msgstr "編輯 %1 的自訂欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit Custom Fields for queue %1"
+msgstr "編輯表單 %1 的自訂欄ä½"
+
+#: html/Search/Bulk.html:143 html/Ticket/ModifyLinks.html:35 html/Work/Search/Bulk.html:93
+msgid "Edit Relationships"
+msgstr "編輯申請單關係"
+
+#: html/Edit/Groups/MemberGroups/Add.html:3 html/Edit/Groups/MemberGroups/index.html:22
+msgid "Edit Subgroups"
+msgstr "新增/維護å­ç¾¤çµ„"
+
+#: html/Admin/Queues/Templates.html:41
+#. ($QueueObj->Name)
+msgid "Edit Templates for queue %1"
+msgstr "編輯表單 %1 的範本"
+
+#: html/Admin/Queues/Workflows.html:42
+#. ($QueueObj->Name)
+msgid "Edit Workflows for queue %1"
+msgstr "編輯表單 %1 çš„æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit keywords"
+msgstr "編輯關éµå­—"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit scrips"
+msgstr "編輯手續"
+
+#: html/Admin/Global/index.html:45
+msgid "Edit system templates"
+msgstr "編輯全域範本"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit system workflows"
+msgstr "編輯全域æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit templates for %1"
+msgstr "編輯 %1 的範本"
+
+#: NOT FOUND IN SOURCE
+msgid "Edit workflows for %1"
+msgstr "編輯 %1 çš„æµç¨‹"
+
+#: html/Admin/Elements/ModifyQueue:24 html/Admin/Queues/Modify.html:118
+#. ($QueueObj->Name)
+#. ($QueueObj->Id)
+msgid "Editing Configuration for queue %1"
+msgstr "編輯表單 %1 的設定"
+
+#: html/Admin/Elements/ModifyUser:24
+#. ($UserObj->Name)
+msgid "Editing Configuration for user %1"
+msgstr "編輯使用者 %1 的設定"
+
+#: html/Admin/Elements/EditCustomField:90
+#. ($CustomFieldObj->Name())
+msgid "Editing CustomField %1"
+msgstr "ç·¨è¼¯è‡ªè¨‚æ¬„ä½ %1"
+
+#: html/Admin/Groups/Members.html:31
+#. ($Group->Name)
+msgid "Editing membership for group %1"
+msgstr "編輯群組 %1 çš„æˆå“¡è³‡è¨Š"
+
+#: html/User/Groups/Members.html:128
+#. ($Group->Name)
+msgid "Editing membership for personal group %1"
+msgstr "編輯代ç†äººç¾¤çµ„ %1 çš„æˆå“¡è³‡è¨Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Editing template %1"
+msgstr "編輯範本 %1"
+
+#: html/Admin/Elements/ModifyWorkflow:238
+#. (loc( $WorkflowObj->Name() ))
+msgid "Editing workflow %1"
+msgstr "編輯æµç¨‹ %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Education"
+msgstr "最高學歷"
+
+#: NOT FOUND IN SOURCE
+msgid "EffectiveId"
+msgstr "有效編號"
+
+#: lib/RT/Ticket_Overlay.pm:2673 lib/RT/Ticket_Overlay.pm:2751
+msgid "Either base or target must be specified"
+msgstr "需è¦æŒ‡å®šèµ·å§‹ç”³è«‹å–®æˆ–目的申請單"
+
+#: html/Admin/Users/Modify.html:52 html/Admin/Users/Prefs.html:45 html/Edit/Elements/SelectUsers:4 html/Edit/Users/List:7 html/Elements/SelectUsers:26 html/Ticket/Elements/AddWatchers:55 html/User/Prefs.html:43 html/Work/Delegates/Info:96 html/Work/Overview/Info:78
+msgid "Email"
+msgstr "é›»å­éƒµä»¶ä¿¡ç®±"
+
+#: html/Work/Preferences/Info:16
+msgid "Email Address"
+msgstr "é›»å­éƒµä»¶ä¿¡ç®±"
+
+#: lib/RT/User_Overlay.pm:251
+msgid "Email address in use"
+msgstr "此電å­éƒµä»¶ä¿¡ç®±å·²è¢«ä½¿ç”¨"
+
+#: html/Admin/Elements/ModifyUser:41
+msgid "EmailAddress"
+msgstr "é›»å­éƒµä»¶ä¿¡ç®±ä½å€"
+
+#: html/Admin/Elements/ModifyUser:53
+msgid "EmailEncoding"
+msgstr "é›»å­éƒµä»¶æ–‡å­—編碼方å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Embark Date"
+msgstr "外ç±å“¡å·¥å…¥å¢ƒæ—¥"
+
+#: NOT FOUND IN SOURCE
+msgid "Embarked Date"
+msgstr "抵é”日期"
+
+#: NOT FOUND IN SOURCE
+msgid "Embarked Location"
+msgstr "抵é”地點"
+
+#: NOT FOUND IN SOURCE
+msgid "Enable Delegates"
+msgstr "代ç†å•Ÿå‹•"
+
+#: html/Admin/Elements/EditCustomField:50
+msgid "Enabled (Unchecking this box disables this custom field)"
+msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤è‡ªè¨‚欄ä½)"
+
+#: html/Admin/Groups/Modify.html:52 html/User/Groups/Modify.html:52
+msgid "Enabled (Unchecking this box disables this group)"
+msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤ç¾¤çµ„)"
+
+#: html/Admin/Queues/Modify.html:83
+msgid "Enabled (Unchecking this box disables this queue)"
+msgstr "啟用(å–消勾é¸å°‡åœç”¨æ­¤è¡¨å–®)"
+
+#: html/Admin/Elements/EditCustomFields:97
+msgid "Enabled Custom Fields"
+msgstr "已啟用的自訂欄ä½"
+
+#: html/Edit/Queues/Basic/Top:74 html/Edit/Queues/List:17 html/Edit/Queues/List:29
+msgid "Enabled Date"
+msgstr "啟用日期"
+
+#: NOT FOUND IN SOURCE
+msgid "Enabled Date:"
+msgstr "啟動日期:"
+
+#: html/Admin/Queues/index.html:55
+msgid "Enabled Queues"
+msgstr "已啟用的表單"
+
+#: html/Edit/Queues/Basic/Top:65 html/Edit/Queues/List:13 html/Edit/Queues/List:25
+msgid "Enabled Status"
+msgstr "啟用狀態"
+
+#: html/Admin/Elements/EditCustomField:106 html/Admin/Groups/Modify.html:116 html/Admin/Queues/Modify.html:140 html/Admin/Users/Modify.html:284 html/User/Groups/Modify.html:116
+#. (loc_fuzzy($msg))
+msgid "Enabled status %1"
+msgstr "啟用狀態 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "End of Trial"
+msgstr "試用期滿日"
+
+#: NOT FOUND IN SOURCE
+msgid "English Name"
+msgstr "英文姓å"
+
+#: lib/RT/CustomField_Overlay.pm:427
+msgid "Enter multiple values"
+msgstr "éµå…¥å¤šé‡é …ç›®"
+
+#: html/Edit/Users/Search.html:15
+msgid "Enter one or more conditions below to search for users"
+msgstr "輸入下列單一或複å¼æ¢ä»¶ï¼ŒæŸ¥è©¢ç”¨æˆ¶è³‡æ–™"
+
+#: lib/RT/CustomField_Overlay.pm:424
+msgid "Enter one value"
+msgstr "éµå…¥å–®ä¸€é …ç›®"
+
+#: html/Search/Bulk.html:144 html/Ticket/Elements/EditLinks:94 html/Work/Search/Bulk.html:95 html/Work/Tickets/Elements/EditLinks:98
+msgid "Enter tickets or URIs to link tickets to. Seperate multiple entries with spaces."
+msgstr "輸入申請單å¯éˆçµåˆ°çš„申請單編號或網å€ã€‚以空白隔開。"
+
+#: lib/RT/CustomField_Vendor.pm:20
+msgid "EntryBoolean"
+msgstr "是éžå¡«è¡¨"
+
+#: lib/RT/CustomField_Vendor.pm:17
+msgid "EntryDate"
+msgstr "日期填表"
+
+#: NOT FOUND IN SOURCE
+msgid "EntryExternal"
+msgstr "系統填表"
+
+#: lib/RT/CustomField_Vendor.pm:16
+msgid "EntryFreeform"
+msgstr "輸入填表"
+
+#: NOT FOUND IN SOURCE
+msgid "EntryMultiple"
+msgstr "多é¸å¡«è¡¨"
+
+#: lib/RT/CustomField_Vendor.pm:19
+msgid "EntryNumber"
+msgstr "數值填表"
+
+#: lib/RT/CustomField_Vendor.pm:15
+msgid "EntrySelect"
+msgstr "å–®é¸å¡«è¡¨"
+
+#: lib/RT/CustomField_Vendor.pm:18
+msgid "EntryTime"
+msgstr "時間填表"
+
+#: html/Elements/Login:39 html/SelfService/Error.html:24 html/SelfService/Error.html:25
+msgid "Error"
+msgstr "錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "Error adding watcher"
+msgstr "新增視察員失敗"
+
+#: lib/RT/Queue_Overlay.pm:555
+msgid "Error in parameters to Queue->AddWatcher"
+msgstr "表單->新增視察員的åƒæ•¸æœ‰èª¤"
+
+#: lib/RT/Queue_Overlay.pm:713
+msgid "Error in parameters to Queue->DelWatcher"
+msgstr "表單->刪除視察員的åƒæ•¸æœ‰èª¤"
+
+#: lib/RT/Ticket_Overlay.pm:1401
+msgid "Error in parameters to Ticket->AddWatcher"
+msgstr "申請單->新增視察員的åƒæ•¸æœ‰èª¤"
+
+#: lib/RT/Ticket_Overlay.pm:1558
+msgid "Error in parameters to Ticket->DelWatcher"
+msgstr "申請單->刪除視察員的åƒæ•¸æœ‰èª¤"
+
+#: etc/initialdata:20
+msgid "Everyone"
+msgstr "所有人"
+
+#: bin/rt-crontool:193
+msgid "Example:"
+msgstr "範例:"
+
+#: NOT FOUND IN SOURCE
+msgid "Existing user renamed from %1 to %2"
+msgstr "ç¾æœ‰ä½¿ç”¨è€… %1 已改å為 %2"
+
+#: html/Edit/Elements/104Buttons:88
+msgid "Export"
+msgstr "匯出"
+
+#: html/Admin/Elements/ModifyUser:63
+msgid "ExternalAuthId"
+msgstr "外部èªè­‰å¸³è™Ÿ"
+
+#: html/Admin/Elements/ModifyUser:57
+msgid "ExternalContactInfoId"
+msgstr "外部è¯çµ¡æ–¹å¼å¸³è™Ÿ"
+
+#: html/Edit/Global/Basic/Top:69
+msgid "ExternalDatabaseDSN"
+msgstr "外部資料庫連çµå­—串"
+
+#: html/Edit/Global/Basic/Top:73
+msgid "ExternalDatabasePass"
+msgstr "外部資料庫密碼"
+
+#: html/Edit/Global/Basic/Top:71
+msgid "ExternalDatabaseUser"
+msgstr "外部資料庫用戶"
+
+#: html/Edit/Global/Basic/Top:67
+msgid "ExternalURL"
+msgstr "外部介é¢ç¶²å€"
+
+#: html/Admin/Users/Modify.html:72 html/Edit/Users/Info:41
+msgid "Extra info"
+msgstr "備註"
+
+#: lib/RT/User_Overlay.pm:368
+msgid "Failed to find 'Privileged' users pseudogroup."
+msgstr "找ä¸åˆ°ã€Œå…§éƒ¨æˆå“¡ã€è™›æ“¬ç¾¤çµ„的使用者。"
+
+#: lib/RT/User_Overlay.pm:375
+msgid "Failed to find 'Unprivileged' users pseudogroup"
+msgstr "找ä¸åˆ°ã€Œéžå…§éƒ¨æˆå“¡ã€è™›æ“¬ç¾¤çµ„的使用者。"
+
+#: bin/rt-crontool:137
+#. ($modname, $@)
+msgid "Failed to load module %1. (%2)"
+msgstr "無法載入模組 %1. (%2)"
+
+#: NOT FOUND IN SOURCE
+msgid "Feb"
+msgstr "二月"
+
+#: lib/RT/Date.pm:412
+msgid "Feb."
+msgstr "02"
+
+#: NOT FOUND IN SOURCE
+msgid "February"
+msgstr "二月"
+
+#: NOT FOUND IN SOURCE
+msgid "Female"
+msgstr "女"
+
+#: html/Edit/Global/CustomField/Info:14
+msgid "Field Content:"
+msgstr "欄ä½å…§å®¹ï¼š"
+
+#: html/Edit/Global/CustomField/List:7 html/Edit/Global/CustomField/Top:20
+msgid "Field Description"
+msgstr "欄ä½æè¿°"
+
+#: html/Edit/Global/CustomField/List:6 html/Edit/Global/CustomField/Top:14
+msgid "Field Name"
+msgstr "欄ä½å稱"
+
+#: html/Edit/Global/CustomField/List:5 html/Edit/Global/CustomField/Top:9
+msgid "Field Type"
+msgstr "欄ä½å±¬æ€§"
+
+#: html/Edit/Elements/PickUsers:52 html/Edit/Users/Add.html:47
+msgid "Filter"
+msgstr "篩é¸"
+
+#: html/Edit/Elements/PickUsers:6 html/Edit/Users/Add.html:7 html/Work/Tickets/Cc:4
+msgid "Filter people"
+msgstr "å°è±¡ç¯©é¸"
+
+#: html/Edit/Elements/PickUsers:68 html/Edit/Users/Add.html:63 html/Work/Tickets/Cc:42
+msgid "Filtered list:"
+msgstr "篩é¸åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Fin"
+msgstr "最終"
+
+#: html/Ticket/Create.html:154 html/Ticket/Elements/EditBasics:58 html/Work/Tickets/Elements/EditBasics:52 lib/RT/Tickets_Overlay.pm:1110
+msgid "Final Priority"
+msgstr "最終順ä½"
+
+#: lib/RT/Ticket_Overlay.pm:1207
+msgid "FinalPriority"
+msgstr "最終順ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "Financial Department:"
+msgstr "財務部:"
+
+#: html/Admin/Queues/People.html:60 html/Ticket/Elements/EditPeople:33 html/Work/Tickets/Elements/EditPeople:18
+msgid "Find group whose"
+msgstr "尋找群組的"
+
+#: NOT FOUND IN SOURCE
+msgid "Find new/open tickets"
+msgstr "尋找/開啟申請單"
+
+#: html/Admin/Queues/People.html:56 html/Admin/Users/index.html:45 html/Edit/Users/Top:6 html/Ticket/Elements/EditPeople:29 html/Work/Tickets/Elements/EditPeople:14
+msgid "Find people whose"
+msgstr "尋找人員的"
+
+#: html/Edit/Queues/Top:6
+msgid "Find queues whose"
+msgstr "尋找表單的"
+
+#: html/Search/Listing.html:107 html/Work/Search/index.html:88
+msgid "Find tickets"
+msgstr "尋找申請單"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:118
+msgid "Finish Approval"
+msgstr "簽核完畢"
+
+#: html/Ticket/Elements/Tabs:57
+msgid "First"
+msgstr "第一項"
+
+#: html/Search/Listing.html:40 html/Work/Search/index.html:17
+msgid "First page"
+msgstr "第一é "
+
+#: html/Edit/Global/Workflow/Owner.html:30
+msgid "First-"
+msgstr "一"
+
+#: NOT FOUND IN SOURCE
+msgid "First-level Admins"
+msgstr "一階主管"
+
+#: NOT FOUND IN SOURCE
+msgid "First-level Users"
+msgstr "一階主管員工"
+
+#: NOT FOUND IN SOURCE
+msgid "Fixed shift"
+msgstr "固定ç­"
+
+#: docs/design_docs/string-extraction-guide.txt:33 lib/RT/StyleGuide.pod:746
+msgid "Foo Bar Baz"
+msgstr "甲 乙 丙"
+
+#: docs/design_docs/string-extraction-guide.txt:24 lib/RT/StyleGuide.pod:737
+msgid "Foo!"
+msgstr "甲ï¼"
+
+#: html/Search/Bulk.html:86 html/Work/Search/Bulk.html:55
+msgid "Force change"
+msgstr "強制更æ›"
+
+#: html/Work/Elements/104Header:89
+msgid "Form Processing"
+msgstr "é›»å­è¡¨å–®ä½œæ¥­å€"
+
+#: html/Search/Listing.html:105 html/Work/Search/index.html:86
+#. ($ticketcount)
+msgid "Found %quant(%1,ticket)"
+msgstr "找到 %1 張申請單"
+
+#: lib/RT/Interface/Web.pm:964
+msgid "Found Object"
+msgstr "已找到物件"
+
+#: html/Edit/Global/Workflow/Owner.html:33
+msgid "Fourth-"
+msgstr "å››"
+
+#: html/Admin/Elements/ModifyUser:43
+msgid "FreeformContactInfo"
+msgstr "è¯çµ¡æ–¹å¼"
+
+#: lib/RT/CustomField_Vendor.pm:11
+msgid "FreeformDate"
+msgstr "日期輸入"
+
+#: NOT FOUND IN SOURCE
+msgid "FreeformExternal"
+msgstr "系統欄ä½"
+
+#: lib/RT/CustomField_Overlay.pm:37
+msgid "FreeformMultiple"
+msgstr "多é‡è¼¸å…¥"
+
+#: lib/RT/CustomField_Vendor.pm:13
+msgid "FreeformNumber"
+msgstr "數值輸入"
+
+#: lib/RT/CustomField_Vendor.pm:14
+msgid "FreeformPassword"
+msgstr "密碼輸入"
+
+#: lib/RT/CustomField_Overlay.pm:36
+msgid "FreeformSingle"
+msgstr "單一輸入"
+
+#: lib/RT/CustomField_Vendor.pm:12
+msgid "FreeformTime"
+msgstr "時間輸入"
+
+#: NOT FOUND IN SOURCE
+msgid "Fri"
+msgstr "星期五"
+
+#: lib/RT/Date.pm:392
+msgid "Fri."
+msgstr "星期五"
+
+#: html/Ticket/Elements/ShowHistory:40 html/Ticket/Elements/ShowHistory:50
+msgid "Full headers"
+msgstr "完整標頭檔"
+
+#: NOT FOUND IN SOURCE
+msgid "Gecos"
+msgstr "登入帳號"
+
+#: NOT FOUND IN SOURCE
+msgid "Gender"
+msgstr "性別"
+
+#: NOT FOUND IN SOURCE
+msgid "Getting the current user from a pgp sig\\n"
+msgstr "å–å¾—ç›®å‰ä½¿ç”¨è€…çš„ pgp 簽章\\n"
+
+#: lib/RT/Transaction_Overlay.pm:551
+#. ($New->Name)
+msgid "Given to %1"
+msgstr "交予 %1"
+
+#: html/Admin/Elements/Tabs:40 html/Admin/index.html:37
+msgid "Global"
+msgstr "全域設定"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Approval"
+msgstr "全域簽核"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Keyword Selections"
+msgstr "全域關éµå­—é¸å–"
+
+#: html/Edit/Users/System:24
+msgid "Global Rights:"
+msgstr "æ“有全域權é™åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Global Scrips"
+msgstr "全域手續"
+
+#: html/Edit/Elements/Tab:40
+msgid "Global Setup"
+msgstr "全域設定"
+
+#: html/Admin/Elements/SelectTemplate:37 html/Edit/Elements/SelectTemplate:11
+#. (loc($Template->Name))
+msgid "Global template: %1"
+msgstr "全域範本:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "GlobalApproval"
+msgstr "全域簽核"
+
+#: html/Admin/Elements/EditCustomFields:73 html/Admin/Queues/People.html:58 html/Admin/Queues/People.html:62 html/Admin/Queues/index.html:43 html/Admin/Users/index.html:48 html/Ticket/Elements/EditPeople:31 html/Ticket/Elements/EditPeople:35 html/Work/Tickets/Elements/EditPeople:16 html/Work/Tickets/Elements/EditPeople:20 html/index.html:40
+msgid "Go!"
+msgstr "執行"
+
+#: NOT FOUND IN SOURCE
+msgid "Good pgp sig from %1\\n"
+msgstr "%1 的 pgp 簽章是正確的\\n"
+
+#: html/Search/Listing.html:49
+msgid "Goto page"
+msgstr "到é é¢"
+
+#: html/Elements/GotoTicket:24 html/SelfService/Elements/GotoTicket:24 html/Work/Elements/104Header:49
+msgid "Goto ticket"
+msgstr "跳到申請單"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:224
+msgid "Grand"
+msgstr "上"
+
+#: html/Ticket/Elements/AddWatchers:45 html/User/Elements/DelegateRights:77
+msgid "Group"
+msgstr "群組"
+
+#: NOT FOUND IN SOURCE
+msgid "Group %1 %2: %3"
+msgstr "群組 %1 %2:%3"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Admin"
+msgstr "群組管ç†å“¡"
+
+#: html/Edit/Global/GroupRight/List:5 html/Edit/Global/GroupRight/Top:20 html/Edit/Groups/List:7
+msgid "Group Description"
+msgstr "群組æè¿°"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Management"
+msgstr "群組管ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Members"
+msgstr "群組æˆå“¡"
+
+#: html/Edit/Elements/PickUsers:28 html/Edit/Global/GroupRight/List:4 html/Edit/Global/GroupRight/Top:10 html/Edit/Groups/List:6 html/Edit/Groups/Top:7 html/Edit/Queues/Basic/Add.html:15 html/Edit/Users/Add.html:29 html/Edit/Users/Group:10 html/Edit/Users/Search.html:43 html/Work/Delegates/Add.html:15 html/Work/Tickets/Cc:24
+msgid "Group Name"
+msgstr "群組å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Name:"
+msgstr "群組å稱:"
+
+#: html/Admin/Elements/GroupTabs:44 html/Admin/Elements/QueueTabs:56 html/Admin/Elements/SystemTabs:43 html/Admin/Global/index.html:54 html/Edit/Global/autohandler:12 html/Edit/Queues/autohandler:27 html/Edit/Users/Group:11 html/Edit/Users/index.html:96
+msgid "Group Rights"
+msgstr "群組權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Group Rights:"
+msgstr "æ“有群組權é™åˆ—表:"
+
+#: html/Edit/Elements/Tab:36
+msgid "Group Setup"
+msgstr "群組設定"
+
+#: html/Edit/Global/GroupRight/List:8 html/Edit/Global/GroupRight/Top:14 html/Edit/Groups/List:10 html/Edit/Groups/Top:15
+msgid "Group Status"
+msgstr "群組狀態"
+
+#: lib/RT/Group_Overlay.pm:962
+msgid "Group already has member"
+msgstr "群組內已有此æˆå“¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Group could not be created."
+msgstr "無法新增群組"
+
+#: html/Admin/Groups/Modify.html:76
+#. ($create_msg)
+msgid "Group could not be created: %1"
+msgstr "無法新增群組:%1"
+
+#: lib/RT/Group_Overlay.pm:494
+msgid "Group created"
+msgstr "群組新增完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Group created: %1"
+msgstr "群組 %1 新增完畢"
+
+#: lib/RT/Group_Overlay.pm:1134
+msgid "Group has no such member"
+msgstr "群組沒有這個æˆå“¡"
+
+#: lib/RT/Group_Overlay.pm:942 lib/RT/Queue_Overlay.pm:628 lib/RT/Queue_Overlay.pm:688 lib/RT/Ticket_Overlay.pm:1455 lib/RT/Ticket_Overlay.pm:1533
+msgid "Group not found"
+msgstr "找ä¸åˆ°ç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not found.\\n"
+msgstr "找ä¸åˆ°ç¾¤çµ„。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group not specified.\\n"
+msgstr "未指定群組。\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Group redescribed from %1 to %2"
+msgstr "群組æè¿° %1 已改為 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Group renamed from %1 to %2"
+msgstr "群組 %1 已改å為 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Group with Queue Rights"
+msgstr "æ“有表單權é™ç¾¤çµ„"
+
+#: html/Edit/Global/Workflow/Owner.html:70
+msgid "Group's"
+msgstr "群組之"
+
+#: NOT FOUND IN SOURCE
+msgid "Group:"
+msgstr "群組:"
+
+#: html/Admin/Elements/SelectNewGroupMembers:34 html/Admin/Elements/Tabs:34 html/Admin/Groups/Members.html:63 html/Admin/Queues/People.html:82 html/Admin/index.html:31 html/Edit/Global/GroupRight/Add.html:16 html/Edit/Groups/Admin:12 html/User/Groups/Members.html:66
+msgid "Groups"
+msgstr "群組"
+
+#: lib/RT/Group_Overlay.pm:968
+msgid "Groups can't be members of their members"
+msgstr "ä¸èƒ½å°‡ç¾¤çµ„設為群組內æˆå“¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Groups with Global Rights"
+msgstr "æ“有全域權é™ç¾¤çµ„"
+
+#: html/Edit/Global/GroupRight/List:6 html/Edit/Global/GroupRight/Top:22 html/Edit/Groups/List:8
+msgid "HRMSDefined"
+msgstr "組織架構"
+
+#: NOT FOUND IN SOURCE
+msgid "Health Insurance"
+msgstr "å¥ä¿è£œåŠ©èº«ä»½"
+
+#: lib/RT/Interface/CLI.pm:72 lib/RT/Interface/CLI.pm:72
+msgid "Hello!"
+msgstr "å—¨ï¼"
+
+#: docs/design_docs/string-extraction-guide.txt:40 lib/RT/StyleGuide.pod:753
+#. ($name)
+msgid "Hello, %1"
+msgstr "嗨,%1"
+
+#: html/Edit/Elements/104Top:28
+msgid "Help"
+msgstr "說明"
+
+#: NOT FOUND IN SOURCE
+msgid "Help Desks"
+msgstr "å„項業務窗å£"
+
+#: html/Edit/Global/CustomField/SelectWritable:9 html/Edit/Queues/Basic/Top:80
+msgid "Hidden"
+msgstr "éš±è—"
+
+#: html/Ticket/Elements/ShowHistory:29 html/Ticket/Elements/Tabs:87 html/Work/Tickets/Elements/ShowHistory:8
+msgid "History"
+msgstr "紀錄"
+
+#: html/Admin/Elements/ModifyUser:67
+msgid "HomePhone"
+msgstr "ä½è™•é›»è©±"
+
+#: html/Edit/Elements/104Top:15 html/Edit/Elements/104Top:24 html/Edit/Elements/EDOMHeader:9 html/Elements/Tabs:43
+msgid "Homepage"
+msgstr "主é "
+
+#: NOT FOUND IN SOURCE
+msgid "Hotel Expense"
+msgstr "ä½å®¿è²»"
+
+#: lib/RT/Base.pm:75
+#. (6)
+msgid "I have %quant(%1,concrete mixer)."
+msgstr "我有 %quant(%1,份固體攪拌器)。"
+
+#: NOT FOUND IN SOURCE
+msgid "ID Number"
+msgstr "身分證號"
+
+#: NOT FOUND IN SOURCE
+msgid "ID Type"
+msgstr "身分類別"
+
+#: html/Ticket/Elements/ShowBasics:26 lib/RT/Tickets_Overlay.pm:1037
+msgid "Id"
+msgstr "編號"
+
+#: html/Admin/Users/Modify.html:43 html/User/Prefs.html:38 html/Work/Preferences/Info:14
+msgid "Identity"
+msgstr "身份"
+
+#: etc/initialdata:411 etc/upgrade/2.1.71:86 html/Edit/Elements/CreateApprovalsQueue:58
+msgid "If an approval is rejected, reject the original and delete pending approvals"
+msgstr "若簽核單é­åˆ°é§å›žï¼Œå‰‡é€£å¸¶é§å›žåŽŸç”³è«‹å–®ï¼Œä¸¦åˆªé™¤å…¶ä»–相關的待簽核事項"
+
+#: bin/rt-crontool:189
+msgid "If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT."
+msgstr "如果此工具程å¼ç‚º setgid,惡æ„的本地端用戶å³èƒ½ç”±æ­¤å–å¾— RT 的管ç†å“¡æ¬Šé™ã€‚"
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyPeople.html:37
+msgid "If you've updated anything above, be sure to"
+msgstr "若您已更新以上資料,請記得按一下"
+
+#: lib/RT/Interface/Web.pm:956
+msgid "Illegal value for %1"
+msgstr "%1 的值錯誤"
+
+#: lib/RT/Interface/Web.pm:959
+msgid "Immutable field"
+msgstr "此欄ä½å€¼ä¸å¯æ›´å‹•"
+
+#: html/Edit/Elements/104Buttons:87 html/Edit/Global/Workflow/Import.html:2
+msgid "Import"
+msgstr "匯入"
+
+#: html/Admin/Elements/EditCustomFields:72
+msgid "Include disabled custom fields in listing."
+msgstr "列出åœç”¨çš„自訂欄ä½"
+
+#: html/Admin/Queues/index.html:42 html/Edit/Queues/Top:9
+msgid "Include disabled queues in listing."
+msgstr "列出åœç”¨çš„表單"
+
+#: html/Admin/Users/index.html:46 html/Edit/Users/Search.html:62 html/Edit/Users/Top:9
+msgid "Include disabled users in search."
+msgstr "列出åœç”¨çš„使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "Indirect Employee"
+msgstr "直接/間接員工"
+
+#: lib/RT/Tickets_Overlay.pm:1086
+msgid "Initial Priority"
+msgstr "åˆå§‹å„ªå…ˆé †ä½"
+
+#: lib/RT/Ticket_Overlay.pm:1206 lib/RT/Ticket_Overlay.pm:1208
+msgid "InitialPriority"
+msgstr "åˆå§‹å„ªå…ˆé †ä½"
+
+#: lib/RT/ScripAction_Overlay.pm:105
+msgid "Input error"
+msgstr "輸入錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "Interest noted"
+msgstr "登記æˆåŠŸ"
+
+#: lib/RT/Ticket_Overlay.pm:3913
+msgid "Internal Error"
+msgstr "內部錯誤"
+
+#: lib/RT/Record.pm:142
+#. ($id->{error_message})
+msgid "Internal Error: %1"
+msgstr "內部錯誤:%1"
+
+#: lib/RT/Group_Overlay.pm:641
+msgid "Invalid Group Type"
+msgstr "錯誤的群組類別"
+
+#: lib/RT/Principal_Overlay.pm:127
+msgid "Invalid Right"
+msgstr "錯誤的權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Invalid Type"
+msgstr "錯誤的類型"
+
+#: lib/RT/Interface/Web.pm:961
+msgid "Invalid data"
+msgstr "錯誤的資料"
+
+#: lib/RT/Ticket_Overlay.pm:463
+msgid "Invalid owner. Defaulting to 'nobody'."
+msgstr "錯誤的承辦人。改為é è¨­æ‰¿è¾¦äººã€Œnobodyã€ã€‚"
+
+#: lib/RT/Scrip_Overlay.pm:133 lib/RT/Template_Overlay.pm:251
+msgid "Invalid queue"
+msgstr "錯誤的表單"
+
+#: lib/RT/ACE_Overlay.pm:243 lib/RT/ACE_Overlay.pm:252 lib/RT/ACE_Overlay.pm:258 lib/RT/ACE_Overlay.pm:269 lib/RT/ACE_Overlay.pm:274
+msgid "Invalid right"
+msgstr "錯誤的權é™"
+
+#: lib/RT/Record.pm:117
+#. ($key)
+msgid "Invalid value for %1"
+msgstr "%1 的值錯誤"
+
+#: lib/RT/Ticket_Overlay.pm:3517
+msgid "Invalid value for custom field"
+msgstr "錯誤的自訂欄ä½å€¼"
+
+#: lib/RT/Ticket_Overlay.pm:365
+msgid "Invalid value for status"
+msgstr "錯誤的狀態值"
+
+#: NOT FOUND IN SOURCE
+msgid "IssueStatement"
+msgstr "é€å‡ºé™³è¿°"
+
+#: bin/rt-crontool:190
+msgid "It is incredibly important that nonprivileged users not be allowed to run this tool."
+msgstr "請絕å°ä¸è¦è®“未具權é™çš„使用者執行此工具程å¼ã€‚"
+
+#: bin/rt-crontool:191
+msgid "It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool."
+msgstr "建議您新增一個隸屬於正確群組的低權é™ç³»çµ±ä½¿ç”¨è€…,並以該身份執行此工具程å¼ã€‚"
+
+#: bin/rt-crontool:162
+msgid "It takes several arguments:"
+msgstr "它接å—下列åƒæ•¸ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Item Name"
+msgstr "å“å"
+
+#: NOT FOUND IN SOURCE
+msgid "Items"
+msgstr "ç­†"
+
+#: NOT FOUND IN SOURCE
+msgid "Items pending my approval"
+msgstr "待簽核項目"
+
+#: NOT FOUND IN SOURCE
+msgid "Jan"
+msgstr "一月"
+
+#: lib/RT/Date.pm:411
+msgid "Jan."
+msgstr "01"
+
+#: NOT FOUND IN SOURCE
+msgid "January"
+msgstr "一月"
+
+#: NOT FOUND IN SOURCE
+msgid "Job"
+msgstr "è·ç¨±"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "Join or leave this group"
+msgstr "加入或離開此群組"
+
+#: NOT FOUND IN SOURCE
+msgid "Jul"
+msgstr "七月"
+
+#: lib/RT/Date.pm:417
+msgid "Jul."
+msgstr "07"
+
+#: NOT FOUND IN SOURCE
+msgid "July"
+msgstr "七月"
+
+#: html/Ticket/Elements/Tabs:98
+msgid "Jumbo"
+msgstr "全部資訊"
+
+#: NOT FOUND IN SOURCE
+msgid "Jun"
+msgstr "六月"
+
+#: lib/RT/Date.pm:416
+msgid "Jun."
+msgstr "06"
+
+#: NOT FOUND IN SOURCE
+msgid "June"
+msgstr "六月"
+
+#: NOT FOUND IN SOURCE
+msgid "Keyword"
+msgstr "é—œéµå­—"
+
+#: lib/RT/CustomField_Vendor.pm:23
+msgid "LabelAttachments"
+msgstr "附件標籤"
+
+#: lib/RT/CustomField_Vendor.pm:24
+msgid "LabelContent"
+msgstr "內容標籤"
+
+#: lib/RT/CustomField_Vendor.pm:22
+msgid "LabelSubject"
+msgstr "主題標籤"
+
+#: lib/RT/CustomField_Vendor.pm:21
+msgid "LabelURL"
+msgstr "éˆçµæ¨™ç±¤"
+
+#: html/Admin/Elements/ModifyUser:51
+msgid "Lang"
+msgstr "使用語言"
+
+#: html/User/Prefs.html:54 html/Work/Preferences/Info:29
+msgid "Language"
+msgstr "語言"
+
+#: html/Ticket/Elements/Tabs:72
+msgid "Last"
+msgstr "上次更新"
+
+#: html/Ticket/Elements/EditDates:37 html/Ticket/Elements/ShowDates:39 html/Work/Tickets/Elements/EditBasics:44
+msgid "Last Contact"
+msgstr "上次è¯çµ¡"
+
+#: html/Elements/SelectDateType:28
+msgid "Last Contacted"
+msgstr "上次è¯çµ¡æ—¥æœŸ"
+
+#: html/Search/Elements/TicketHeader:40 html/Work/Search/TicketHeader:19
+msgid "Last Notified"
+msgstr "上次通知"
+
+#: html/Elements/SelectDateType:29
+msgid "Last Updated"
+msgstr "上次更新"
+
+#: NOT FOUND IN SOURCE
+msgid "LastUpdated"
+msgstr "上次更新"
+
+#: NOT FOUND IN SOURCE
+msgid "Left"
+msgstr "剩餘時間"
+
+#: html/Admin/Users/Modify.html:82 html/Edit/Users/Info:62
+msgid "Let this user access RT"
+msgstr "å…許這å使用者登入"
+
+#: html/Admin/Users/Modify.html:86 html/Edit/Users/Info:68
+msgid "Let this user be granted rights"
+msgstr "內部æˆå“¡ï¼ˆå…·æœ‰å€‹äººæ¬Šé™ï¼‰"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting owner to %1 %2"
+msgstr "é™åˆ¶æ‰¿è¾¦äººç‚º %1 到%2"
+
+#: NOT FOUND IN SOURCE
+msgid "Limiting queue to %1 %2"
+msgstr "é™åˆ¶è¡¨å–®ç‚º %1 到 %2"
+
+#: html/Work/Queues/Select.html:4
+msgid "Link a Queue"
+msgstr "申請表單連çµ"
+
+#: lib/RT/Ticket_Overlay.pm:2765
+msgid "Link already exists"
+msgstr "æ­¤éˆçµå·²å­˜åœ¨"
+
+#: lib/RT/Ticket_Overlay.pm:2777
+msgid "Link could not be created"
+msgstr "無法新增éˆçµ"
+
+#: lib/RT/Ticket_Overlay.pm:2785 lib/RT/Ticket_Overlay.pm:2797
+#. ($TransString)
+msgid "Link created (%1)"
+msgstr "éˆçµ(%1)新增完畢"
+
+#: lib/RT/Ticket_Overlay.pm:2698
+#. ($TransString)
+msgid "Link deleted (%1)"
+msgstr "éˆçµ(%1)刪除完畢"
+
+#: lib/RT/Ticket_Overlay.pm:2704
+msgid "Link not found"
+msgstr "找ä¸åˆ°éˆçµ"
+
+#: html/Ticket/ModifyLinks.html:24 html/Ticket/ModifyLinks.html:28
+#. ($Ticket->Id)
+msgid "Link ticket #%1"
+msgstr "éˆçµç”³è«‹å–® #%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Link ticket %1"
+msgstr "éˆçµç”³è«‹å–® %1"
+
+#: html/Ticket/Elements/Tabs:96
+msgid "Links"
+msgstr "éˆçµ"
+
+#: html/Edit/Users/Search.html:11
+msgid "List All Users"
+msgstr "列出所有用戶資料"
+
+#: html/Admin/Users/Modify.html:113 html/User/Prefs.html:107 html/Work/Preferences/Info:75
+msgid "Location"
+msgstr "ä½ç½®"
+
+#: lib/RT.pm:174
+#. ($RT::LogDir)
+msgid "Log directory %1 not found or couldn't be written.\\n RT can't run."
+msgstr "登入目錄 %1 找ä¸åˆ°æˆ–無法寫入\\n。無法執行 RT。"
+
+#: html/Edit/Global/Basic/Top:57
+msgid "LogToFile"
+msgstr "紀錄等級"
+
+#: html/Edit/Global/Basic/Top:59
+msgid "LogToFileNamed"
+msgstr "紀錄檔å"
+
+#: html/Elements/Header:57
+#. ("<b>".$session{'CurrentUser'}->Name."</b>")
+msgid "Logged in as %1"
+msgstr "使用者:%1"
+
+#: docs/design_docs/string-extraction-guide.txt:71 html/Elements/Login:35 html/Elements/Login:44 html/Elements/Login:54 lib/RT/StyleGuide.pod:777
+msgid "Login"
+msgstr "登入"
+
+#: html/Edit/Elements/104Top:17 html/Edit/Elements/104Top:17 html/Edit/Elements/104Top:32 html/Elements/Header:54
+msgid "Logout"
+msgstr "登出"
+
+#: NOT FOUND IN SOURCE
+msgid "Long-term contractor"
+msgstr "長期契約員工"
+
+#: html/Search/Bulk.html:85 html/Work/Search/Bulk.html:54
+msgid "Make Owner"
+msgstr "新增承辦人"
+
+#: html/Search/Bulk.html:109 html/Work/Search/Bulk.html:63
+msgid "Make Status"
+msgstr "新增ç¾æ³"
+
+#: html/Search/Bulk.html:117 html/Work/Search/Bulk.html:75
+msgid "Make date Due"
+msgstr "新增到期日"
+
+#: html/Search/Bulk.html:119 html/Work/Search/Bulk.html:78
+msgid "Make date Resolved"
+msgstr "新增解決日期"
+
+#: html/Search/Bulk.html:113 html/Work/Search/Bulk.html:69
+msgid "Make date Started"
+msgstr "新增實際起始日期"
+
+#: html/Search/Bulk.html:111 html/Work/Search/Bulk.html:66
+msgid "Make date Starts"
+msgstr "新增應起始日期"
+
+#: html/Search/Bulk.html:115 html/Work/Search/Bulk.html:72
+msgid "Make date Told"
+msgstr "新增報告日期"
+
+#: html/Search/Bulk.html:105 html/Work/Search/Bulk.html:57
+msgid "Make priority"
+msgstr "新增優先順ä½"
+
+#: html/Search/Bulk.html:107 html/Work/Search/Bulk.html:60
+msgid "Make queue"
+msgstr "新增表單"
+
+#: html/Search/Bulk.html:103 html/Work/Search/Bulk.html:59
+msgid "Make subject"
+msgstr "新增主題"
+
+#: NOT FOUND IN SOURCE
+msgid "Male"
+msgstr "ç”·"
+
+#: html/Admin/index.html:32
+msgid "Manage groups and group membership"
+msgstr "管ç†ç¾¤çµ„åŠæ‰€å±¬æˆå“¡"
+
+#: html/Admin/index.html:38
+msgid "Manage properties and configuration which apply to all queues"
+msgstr "管ç†é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„屬性與設定"
+
+#: html/Admin/index.html:35
+msgid "Manage queues and queue-specific properties"
+msgstr "管ç†å„表單åŠç›¸é—œå±¬æ€§"
+
+#: html/Admin/index.html:29
+msgid "Manage users and passwords"
+msgstr "管ç†ä½¿ç”¨è€…與密碼"
+
+#: NOT FOUND IN SOURCE
+msgid "Manager"
+msgstr "經ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "Mar"
+msgstr "三月"
+
+#: lib/RT/Date.pm:413
+msgid "Mar."
+msgstr "03"
+
+#: NOT FOUND IN SOURCE
+msgid "March"
+msgstr "三月"
+
+#: NOT FOUND IN SOURCE
+msgid "Marketing Department"
+msgstr "行銷部"
+
+#: html/Edit/Global/CustomField/Top:63
+msgid "Match Pattern"
+msgstr "符åˆæ¨£å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "May"
+msgstr "五月"
+
+#: lib/RT/Date.pm:415
+msgid "May."
+msgstr "05"
+
+#: lib/RT/Transaction_Overlay.pm:598
+#. ($value)
+msgid "Member %1 added"
+msgstr "æˆå“¡ %1 新增完畢"
+
+#: lib/RT/Transaction_Overlay.pm:635
+#. ($value)
+msgid "Member %1 deleted"
+msgstr "æˆå“¡ %1 刪除完畢"
+
+#: lib/RT/Group_Overlay.pm:979
+msgid "Member added"
+msgstr "新增æˆå“¡å®Œç•¢"
+
+#: lib/RT/Group_Overlay.pm:1141
+msgid "Member deleted"
+msgstr "æˆå“¡å·²åˆªé™¤"
+
+#: lib/RT/Group_Overlay.pm:1145
+msgid "Member not deleted"
+msgstr "æˆå“¡æœªåˆªé™¤"
+
+#: html/Elements/SelectLinkType:25
+msgid "Member of"
+msgstr "隸屬於"
+
+#: html/Work/Preferences/index.html:19
+msgid "Member since"
+msgstr "註冊日期"
+
+#: NOT FOUND IN SOURCE
+msgid "MemberOf"
+msgstr "隸屬於"
+
+#: html/Admin/Elements/GroupTabs:41 html/Admin/Elements/ModifyTemplateAsWorkflow:232 html/User/Elements/GroupTabs:41
+msgid "Members"
+msgstr "æˆå“¡"
+
+#: lib/RT/Transaction_Overlay.pm:595
+#. ($value)
+msgid "Membership in %1 added"
+msgstr "所屬群組 %1 加入完畢"
+
+#: lib/RT/Transaction_Overlay.pm:632
+#. ($value)
+msgid "Membership in %1 deleted"
+msgstr "所屬群組 %1 移除完畢"
+
+#: lib/RT/Ticket_Overlay.pm:2954
+msgid "Merge Successful"
+msgstr "æ•´åˆå®Œç•¢"
+
+#: lib/RT/Ticket_Overlay.pm:2874
+msgid "Merge failed. Couldn't set EffectiveId"
+msgstr "æ•´åˆå¤±æ•—。無法設定 EffectiveId"
+
+#: html/Ticket/Elements/BulkLinks:26 html/Ticket/Elements/EditLinks:97 html/Work/Search/BulkLinks:2 html/Work/Tickets/Elements/EditLinks:101
+msgid "Merge into"
+msgstr "æ•´åˆé€²"
+
+#: html/Search/Bulk.html:137 html/Ticket/Update.html:100
+msgid "Message"
+msgstr "訊æ¯"
+
+#: html/Ticket/Elements/ShowTransaction:80
+msgid "Message body not shown because it is too large or is not plain text."
+msgstr "信件內文ä¸æ˜¯ç´”文字,因此無法顯示。"
+
+#: NOT FOUND IN SOURCE
+msgid "Misc. Expense"
+msgstr "雜費"
+
+#: lib/RT/Interface/Web.pm:963
+msgid "Missing a primary key?: %1"
+msgstr "缺少主éµå€¼ï¼Ÿ(%1)"
+
+#: html/Admin/Users/Modify.html:168 html/User/Prefs.html:71 html/Work/Preferences/Info:38
+msgid "Mobile"
+msgstr "行動電話"
+
+#: html/Admin/Elements/ModifyUser:71
+msgid "MobilePhone"
+msgstr "行動電話"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "Modify Access Control List"
+msgstr "更改權é™æŽ§åˆ¶æ¸…å–®"
+
+#: html/Admin/Global/CustomFields.html:43 html/Admin/Global/index.html:50
+msgid "Modify Custom Fields which apply to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "Modify Scrip templates for this queue"
+msgstr "更改此表單的範本"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "Modify Scrips for this queue"
+msgstr "更改此表單的手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify System ACLS"
+msgstr "更改系統權é™æ¸…å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Template %1"
+msgstr "更改範本 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify Workflow"
+msgstr "更改æµç¨‹"
+
+#: html/Admin/Queues/CustomField.html:44
+#. ($QueueObj->Name())
+msgid "Modify a CustomField for queue %1"
+msgstr "更改 %1 表單內的自訂欄ä½"
+
+#: html/Admin/Global/CustomField.html:52
+msgid "Modify a CustomField which applies to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„自訂欄ä½"
+
+#: html/Admin/Queues/Scrip.html:53
+#. ($QueueObj->Name)
+msgid "Modify a scrip for queue %1"
+msgstr "更改 %1 表單內的手續"
+
+#: html/Admin/Global/Scrip.html:47
+msgid "Modify a scrip which applies to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify dates for # %1"
+msgstr "更改 # %1 的日期"
+
+#: html/Ticket/ModifyDates.html:24 html/Ticket/ModifyDates.html:28
+#. ($TicketObj->Id)
+msgid "Modify dates for #%1"
+msgstr "更改 #%1 的日期"
+
+#: html/Ticket/ModifyDates.html:34
+#. ($TicketObj->Id)
+msgid "Modify dates for ticket # %1"
+msgstr "更改申請單 # %1 的日期"
+
+#: html/Admin/Global/GroupRights.html:24 html/Admin/Global/GroupRights.html:27 html/Admin/Global/index.html:55
+msgid "Modify global group rights"
+msgstr "更改全域設定的群組權é™"
+
+#: html/Admin/Global/GroupRights.html:32
+msgid "Modify global group rights."
+msgstr "更改全域設定的群組權é™ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for groups"
+msgstr "更改全域設定的群組權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global rights for users"
+msgstr "更改全域設定的使用者權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify global scrips"
+msgstr "更改全域手續"
+
+#: html/Admin/Global/UserRights.html:24 html/Admin/Global/UserRights.html:27 html/Admin/Global/index.html:59
+msgid "Modify global user rights"
+msgstr "更改全域設定的使用者權é™"
+
+#: html/Admin/Global/UserRights.html:32
+msgid "Modify global user rights."
+msgstr "更改全域設定的使用者權é™ã€‚"
+
+#: lib/RT/Group_Overlay.pm:145
+msgid "Modify group metadata or delete group"
+msgstr "更改群組資料åŠåˆªé™¤ç¾¤çµ„"
+
+#: html/Admin/Groups/GroupRights.html:24 html/Admin/Groups/GroupRights.html:28 html/Admin/Groups/GroupRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify group rights for group %1"
+msgstr "更改 %1 的群組權é™"
+
+#: html/Admin/Queues/GroupRights.html:24 html/Admin/Queues/GroupRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify group rights for queue %1"
+msgstr "更改表單 %1 的群組權é™"
+
+#: lib/RT/Group_Overlay.pm:147
+msgid "Modify membership roster for this group"
+msgstr "更改此群組的æˆå“¡åå–®"
+
+#: lib/RT/System.pm:60
+msgid "Modify one's own RT account"
+msgstr "更改個人的帳號資訊"
+
+#: html/Admin/Queues/People.html:24 html/Admin/Queues/People.html:28
+#. ($QueueObj->Name)
+msgid "Modify people related to queue %1"
+msgstr "更改éˆçµåˆ°è¡¨å–® %1 的人員"
+
+#: html/Ticket/ModifyPeople.html:24 html/Ticket/ModifyPeople.html:28 html/Ticket/ModifyPeople.html:34
+#. ($Ticket->id)
+#. ($Ticket->Id)
+msgid "Modify people related to ticket #%1"
+msgstr "更改申請單 #%1 éˆçµåˆ°çš„人員"
+
+#: html/Admin/Queues/Scrips.html:45
+#. ($QueueObj->Name)
+msgid "Modify scrips for queue %1"
+msgstr "更改表單 %1 的手續"
+
+#: html/Admin/Global/Scrips.html:43 html/Admin/Global/index.html:41
+msgid "Modify scrips which apply to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
+
+#: html/Admin/Global/Template.html:24 html/Admin/Global/Template.html:29 html/Admin/Global/Template.html:80 html/Admin/Queues/Template.html:77
+#. (loc($TemplateObj->Name()))
+#. ($TemplateObj->id)
+msgid "Modify template %1"
+msgstr "更改範本 %1"
+
+#: html/Admin/Global/Templates.html:43
+msgid "Modify templates which apply to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„範本"
+
+#: html/Admin/Groups/Modify.html:86 html/User/Groups/Modify.html:85
+#. ($Group->Name)
+msgid "Modify the group %1"
+msgstr "更改群組 %1"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "Modify the queue watchers"
+msgstr "更改表單視察員"
+
+#: html/Admin/Users/Modify.html:237
+#. ($UserObj->Name)
+msgid "Modify the user %1"
+msgstr "更改使用者 %1"
+
+#: html/Ticket/ModifyAll.html:36
+#. ($Ticket->Id)
+msgid "Modify ticket # %1"
+msgstr "更改申請單 # %1"
+
+#: html/Ticket/Modify.html:24 html/Ticket/Modify.html:27 html/Ticket/Modify.html:33
+#. ($TicketObj->Id)
+msgid "Modify ticket #%1"
+msgstr "更改申請單 # %1"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "Modify tickets"
+msgstr "更改申請單"
+
+#: html/Admin/Groups/UserRights.html:24 html/Admin/Groups/UserRights.html:28 html/Admin/Groups/UserRights.html:34
+#. ($GroupObj->Name)
+msgid "Modify user rights for group %1"
+msgstr "更改群組 %1 的使用者權é™"
+
+#: html/Admin/Queues/UserRights.html:24 html/Admin/Queues/UserRights.html:28
+#. ($QueueObj->Name)
+msgid "Modify user rights for queue %1"
+msgstr "更改表單 %1 的使用者權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Modify watchers for queue '%1'"
+msgstr "更改 '%1' 的視察員"
+
+#: html/Admin/Global/Workflow.html:25 html/Admin/Global/Workflow.html:30 html/Admin/Global/Workflow.html:81 html/Admin/Queues/Workflow.html:77
+#. (loc($WorkflowObj->Name()))
+#. ($WorkflowObj->id)
+msgid "Modify workflow %1"
+msgstr "更改æµç¨‹ %1"
+
+#: html/Admin/Global/Workflows.html:44
+msgid "Modify workflows which apply to all queues"
+msgstr "更改é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„æµç¨‹"
+
+#: lib/RT/Queue_Overlay.pm:69
+msgid "ModifyACL"
+msgstr "更改權é™æ¸…å–®"
+
+#: lib/RT/Group_Overlay.pm:148
+msgid "ModifyOwnMembership"
+msgstr "更改自己是å¦å±¬æ–¼æŸç¾¤çµ„"
+
+#: lib/RT/Queue_Overlay.pm:70
+msgid "ModifyQueueWatchers"
+msgstr "更改表單視察員"
+
+#: lib/RT/Queue_Overlay.pm:75
+msgid "ModifyScrips"
+msgstr "更改手續"
+
+#: lib/RT/System.pm:60
+msgid "ModifySelf"
+msgstr "更改個人帳號"
+
+#: lib/RT/Queue_Overlay.pm:72
+msgid "ModifyTemplate"
+msgstr "更改範本"
+
+#: lib/RT/Queue_Overlay.pm:87
+msgid "ModifyTicket"
+msgstr "更改申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Mon"
+msgstr "星期一"
+
+#: lib/RT/Date.pm:388
+msgid "Mon."
+msgstr "星期一"
+
+#: html/Work/Elements/MyRequests:11 html/Work/Elements/MyTickets:11
+msgid "More"
+msgstr "更多"
+
+#: html/Ticket/Elements/ShowRequestor:41
+#. ($name)
+msgid "More about %1"
+msgstr "關於 %1 的進一步資訊"
+
+#: NOT FOUND IN SOURCE
+msgid "Morning Shift"
+msgstr "æ—©ç­"
+
+#: html/Edit/Elements/ListButtons:16
+msgid "Move All"
+msgstr "全移"
+
+#: html/Admin/Elements/EditCustomFields:60
+msgid "Move down"
+msgstr "下移"
+
+#: html/Admin/Elements/EditCustomFields:52
+msgid "Move up"
+msgstr "上移"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:26
+msgid "Multiple"
+msgstr "多é‡"
+
+#: lib/RT/User_Overlay.pm:242
+msgid "Must specify 'Name' attribute"
+msgstr "必須指定 'Name' 的屬性"
+
+#: html/SelfService/Elements/MyRequests:48
+#. ($friendly_status)
+msgid "My %1 tickets"
+msgstr "我的 %1 申請單"
+
+#: html/Work/Elements/Tab:37
+msgid "My Approvals"
+msgstr "表單簽核"
+
+#: html/Work/Elements/Tab:35
+msgid "My Requests"
+msgstr "表單申請追蹤"
+
+#: html/Work/Elements/Tab:39
+msgid "My Tickets"
+msgstr "表單處ç†"
+
+#: html/Approvals/index.html:24 html/Approvals/index.html:25
+msgid "My approvals"
+msgstr "表單簽核"
+
+#: html/Admin/Elements/AddCustomFieldValue:31 html/Admin/Elements/EditCustomField:33 html/Admin/Elements/ModifyTemplate:27 html/Admin/Elements/ModifyTemplateAsWorkflow:185 html/Admin/Elements/ModifyUser:29 html/Admin/Groups/Modify.html:43 html/Edit/Elements/SelectQueues:3 html/Edit/Queues/List:8 html/Edit/Users/Add.html:22 html/Edit/Users/List:5 html/Edit/Users/Search.html:31 html/Elements/SelectGroups:25 html/Elements/SelectUsers:27 html/User/Groups/Modify.html:43 html/Work/Tickets/Cc:18
+msgid "Name"
+msgstr "å稱"
+
+#: lib/RT/User_Overlay.pm:249
+msgid "Name in use"
+msgstr "帳號已有人使用"
+
+#: NOT FOUND IN SOURCE
+msgid "Nationality"
+msgstr "國ç±"
+
+#: NOT FOUND IN SOURCE
+msgid "Need approval from system administrator"
+msgstr "需先由系統管ç†å“¡é€²è¡Œæ‰¹å‡†"
+
+#: html/Ticket/Elements/ShowDates:52
+msgid "Never"
+msgstr "從未更動"
+
+#: html/Elements/Quicksearch:29 html/Work/Elements/Quicksearch:15 html/Work/Tickets/Create.html:53
+msgid "New"
+msgstr "新建立"
+
+#: html/Admin/Elements/ModifyUser:31 html/Admin/Users/Modify.html:92 html/Edit/Users/Info:33 html/User/Prefs.html:87 html/Work/Preferences/Info:49
+msgid "New Password"
+msgstr "新的密碼"
+
+#: etc/initialdata:317 etc/upgrade/2.1.71:16 html/Edit/Elements/CreateApprovalsQueue:21
+msgid "New Pending Approval"
+msgstr "新的待簽核事項"
+
+#: html/Ticket/Elements/EditLinks:93 html/Work/Tickets/Elements/EditLinks:12
+msgid "New Relationships"
+msgstr "新增關係"
+
+#: html/Work/Elements/Tab:33
+msgid "New Request"
+msgstr "表單申請"
+
+#: html/Ticket/Elements/Tabs:35
+msgid "New Search"
+msgstr "新增查詢"
+
+#: html/Work/Tickets/Elements/EditPeople:7
+msgid "New Watchers"
+msgstr "新增視察員"
+
+#: html/Admin/Global/CustomField.html:40 html/Admin/Global/CustomFields.html:38 html/Admin/Queues/CustomField.html:51 html/Admin/Queues/CustomFields.html:40
+msgid "New custom field"
+msgstr "新增自訂欄ä½"
+
+#: html/Admin/Elements/GroupTabs:53 html/User/Elements/GroupTabs:51
+msgid "New group"
+msgstr "新增群組"
+
+#: html/SelfService/Prefs.html:31
+msgid "New password"
+msgstr "新的密碼"
+
+#: lib/RT/User_Overlay.pm:769
+msgid "New password notification sent"
+msgstr "é€å‡ºæ–°å¯†ç¢¼é€šçŸ¥"
+
+#: html/Admin/Elements/QueueTabs:69
+msgid "New queue"
+msgstr "新增表單"
+
+#: NOT FOUND IN SOURCE
+msgid "New request"
+msgstr "æ出申請單"
+
+#: html/Admin/Elements/SelectRights:41
+msgid "New rights"
+msgstr "新增權é™"
+
+#: html/Admin/Global/Scrip.html:39 html/Admin/Global/Scrips.html:38 html/Admin/Queues/Scrip.html:42 html/Admin/Queues/Scrips.html:54
+msgid "New scrip"
+msgstr "新增手續"
+
+#: html/Work/Search/index.html:62
+msgid "New search"
+msgstr "é‡æ–°æŸ¥è©¢"
+
+#: html/Admin/Global/Template.html:59 html/Admin/Global/Templates.html:38 html/Admin/Queues/Template.html:57 html/Admin/Queues/Templates.html:49
+msgid "New template"
+msgstr "新增範本"
+
+#: html/SelfService/Elements/Tabs:47
+msgid "New ticket"
+msgstr "æ出申請單"
+
+#: lib/RT/Ticket_Overlay.pm:2841
+msgid "New ticket doesn't exist"
+msgstr "沒有新申請單"
+
+#: html/Admin/Elements/UserTabs:51
+msgid "New user"
+msgstr "新增使用者"
+
+#: html/Admin/Elements/CreateUserCalled:25
+msgid "New user called"
+msgstr "新使用者åå­—"
+
+#: html/Admin/Queues/People.html:54 html/Ticket/Elements/EditPeople:28
+msgid "New watchers"
+msgstr "新視察員"
+
+#: html/Admin/Users/Prefs.html:41
+msgid "New window setting"
+msgstr "更新視窗設定"
+
+#: html/Admin/Global/Workflow.html:60 html/Admin/Global/Workflows.html:39 html/Admin/Queues/Workflow.html:57 html/Admin/Queues/Workflows.html:50
+msgid "New workflow"
+msgstr "新增æµç¨‹"
+
+#: html/Ticket/Elements/Tabs:68
+msgid "Next"
+msgstr "下一項"
+
+#: html/Search/Listing.html:47 html/Work/Search/index.html:24
+msgid "Next page"
+msgstr "下一é "
+
+#: html/Admin/Elements/ModifyUser:49
+msgid "NickName"
+msgstr "暱稱"
+
+#: html/Admin/Users/Modify.html:62 html/User/Prefs.html:50 html/Work/Preferences/Info:26
+msgid "Nickname"
+msgstr "暱稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Night Shift"
+msgstr "å°å¤œç­"
+
+#: html/Edit/Global/Basic/Top:27 html/Edit/Queues/Basic/Top:83
+msgid "No"
+msgstr "å¦"
+
+#: html/Admin/Elements/EditCustomField:89 html/Admin/Elements/EditCustomFields:103
+msgid "No CustomField"
+msgstr "無自訂欄ä½"
+
+#: html/Admin/Groups/GroupRights.html:83 html/Admin/Groups/UserRights.html:70
+msgid "No Group defined"
+msgstr "尚未定義群組"
+
+#: html/Admin/Queues/GroupRights.html:96 html/Admin/Queues/UserRights.html:67
+msgid "No Queue defined"
+msgstr "沒有定義好的表單"
+
+#: bin/rt-crontool:55
+msgid "No RT user found. Please consult your RT administrator.\\n"
+msgstr "找ä¸åˆ° RT ä½¿ç”¨è€…ã€‚è«‹å‘ RT 管ç†å“¡æŸ¥è©¢ã€‚\\n"
+
+#: html/Admin/Global/Template.html:78 html/Admin/Queues/Template.html:75
+msgid "No Template"
+msgstr "沒有範本"
+
+#: bin/rt-commit-handler:763
+msgid "No Ticket specified. Aborting ticket "
+msgstr "未指定申請單。退出申請單 "
+
+#: NOT FOUND IN SOURCE
+msgid "No Ticket specified. Aborting ticket modifications\\n\\n"
+msgstr "未指定申請單。退出申請單更改\\n\\n"
+
+#: html/Admin/Elements/ModifyWorkflow:237 html/Admin/Global/Workflow.html:79 html/Admin/Queues/Workflow.html:75
+msgid "No Workflow"
+msgstr "沒有æµç¨‹"
+
+#: html/Approvals/Elements/Approve:45 html/Work/Approvals/Elements/Approve:35
+msgid "No action"
+msgstr "æš«ä¸è™•ç†"
+
+#: lib/RT/Interface/Web.pm:958
+msgid "No column specified"
+msgstr "未指定欄ä½"
+
+#: NOT FOUND IN SOURCE
+msgid "No command found\\n"
+msgstr "找ä¸åˆ°å‘½ä»¤"
+
+#: html/Elements/ViewUser:35 html/Ticket/Elements/ShowRequestor:44
+msgid "No comment entered about this user"
+msgstr "沒有å°é€™å使用者的評論"
+
+#: lib/RT/Ticket_Overlay.pm:2229 lib/RT/Ticket_Overlay.pm:2299
+msgid "No correspondence attached"
+msgstr "沒有附上申請單回覆"
+
+#: lib/RT/Action/Generic.pm:149 lib/RT/Condition/Generic.pm:175 lib/RT/Search/ActiveTicketsInQueue.pm:55 lib/RT/Search/Generic.pm:112
+#. (ref $self)
+msgid "No description for %1"
+msgstr "æ²’æœ‰å° %1 çš„æè¿°"
+
+#: lib/RT/Users_Overlay.pm:149
+msgid "No group specified"
+msgstr "未指定群組"
+
+#: lib/RT/User_Overlay.pm:987
+msgid "No password set"
+msgstr "沒有設定密碼"
+
+#: lib/RT/Queue_Overlay.pm:260
+msgid "No permission to create queues"
+msgstr "沒有新增表單的權é™"
+
+#: lib/RT/Ticket_Overlay.pm:361
+#. ($QueueObj->Name)
+msgid "No permission to create tickets in the queue '%1'"
+msgstr "沒有在表單 '%1' 新增申請單的權é™"
+
+#: lib/RT/User_Overlay.pm:208
+msgid "No permission to create users"
+msgstr "沒有新增使用者的權é™"
+
+#: html/SelfService/Display.html:123
+msgid "No permission to display that ticket"
+msgstr "沒有顯示該申請單的權é™"
+
+#: html/SelfService/Update.html:68
+msgid "No permission to view update ticket"
+msgstr "沒有檢視申請單更新的權é™"
+
+#: lib/RT/Queue_Overlay.pm:675 lib/RT/Ticket_Overlay.pm:1514
+msgid "No principal specified"
+msgstr "未指定單ä½"
+
+#: html/Admin/Queues/People.html:153 html/Admin/Queues/People.html:163
+msgid "No principals selected."
+msgstr "未指定單ä½ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "No protocol specified in %1"
+msgstr "%1 內未指定å”定"
+
+#: html/Admin/Queues/index.html:34
+msgid "No queues matching search criteria found."
+msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è©¢æ¢ä»¶çš„表單。"
+
+#: html/Admin/Elements/SelectRights:81
+msgid "No rights found"
+msgstr "找ä¸åˆ°æ¬Šé™"
+
+#: html/Admin/Elements/SelectRights:32
+msgid "No rights granted."
+msgstr "沒有é¸å®šæ¬Šé™"
+
+#: html/Search/Bulk.html:160 html/Work/Search/Bulk.html:117
+msgid "No search to operate on."
+msgstr "沒有è¦é€²è¡Œçš„查詢"
+
+#: NOT FOUND IN SOURCE
+msgid "No ticket id specified"
+msgstr "未指定申請單編號"
+
+#: lib/RT/Transaction_Overlay.pm:427 lib/RT/Transaction_Overlay.pm:465
+msgid "No transaction type specified"
+msgstr "未指定更動報告類別"
+
+#: NOT FOUND IN SOURCE
+msgid "No user or email address specified"
+msgstr "未指定使用者或電å­éƒµä»¶åœ°å€"
+
+#: html/Admin/Users/index.html:35
+msgid "No users matching search criteria found."
+msgstr "找ä¸åˆ°ç¬¦åˆæŸ¥è©¢æ¢ä»¶çš„使用者。"
+
+#: bin/rt-commit-handler:643
+msgid "No valid RT user found. RT cvs handler disengaged. Please consult your RT administrator.\\n"
+msgstr "找ä¸åˆ°åˆæ ¼çš„ RT 使用者。RT cvs 處ç†å™¨å·²åœç”¨ã€‚è«‹å‘ RT 管ç†è€…è©¢å•ã€‚\\n"
+
+#: lib/RT/Interface/Web.pm:955
+msgid "No value sent to _Set!\\n"
+msgstr "_Set 沒有收到任何值!\\n"
+
+#: html/Search/Elements/TicketRow:36 html/Work/Search/TicketRow:9
+msgid "Nobody"
+msgstr "沒有人"
+
+#: lib/RT/Interface/Web.pm:960
+msgid "Nonexistant field?"
+msgstr "欄ä½ä¸å­˜åœ¨ï¼Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Normal Users"
+msgstr "一般用戶群組"
+
+#: NOT FOUND IN SOURCE
+msgid "Not configured to fetch the content from a %1 in %2"
+msgstr "未設定æˆå¾ž %2 å…§æ“·å– %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Not logged in"
+msgstr "尚未登入"
+
+#: html/Elements/Header:59
+msgid "Not logged in."
+msgstr "尚未登入"
+
+#: lib/RT/Date.pm:369
+msgid "Not set"
+msgstr "尚未設定"
+
+#: html/NoAuth/Reminder.html:26
+msgid "Not yet implemented."
+msgstr "尚未完工。"
+
+#: NOT FOUND IN SOURCE
+msgid "Not yet implemented...."
+msgstr "尚未完工..."
+
+#: html/Approvals/Elements/Approve:48 html/Work/Tickets/Elements/AddContent:9
+msgid "Notes"
+msgstr "備註"
+
+#: NOT FOUND IN SOURCE
+msgid "Notes:"
+msgstr "備註:"
+
+#: lib/RT/User_Overlay.pm:772
+msgid "Notification could not be sent"
+msgstr "無法é€å‡ºé€šçŸ¥"
+
+#: etc/initialdata:93
+msgid "Notify AdminCcs"
+msgstr "通知管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:89
+msgid "Notify AdminCcs as Comment"
+msgstr "以評論方å¼é€šçŸ¥ç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:120
+msgid "Notify Other Recipients"
+msgstr "通知其他收件人"
+
+#: etc/initialdata:116
+msgid "Notify Other Recipients as Comment"
+msgstr "以評論方å¼é€šçŸ¥å…¶ä»–收件人"
+
+#: etc/initialdata:85
+msgid "Notify Owner"
+msgstr "通知承辦人"
+
+#: etc/initialdata:81
+msgid "Notify Owner as Comment"
+msgstr "以評論方å¼é€šçŸ¥æ‰¿è¾¦äºº"
+
+#: etc/initialdata:361
+msgid "Notify Owner of their rejected ticket"
+msgstr "通知承辦人申請單已é§å›ž"
+
+#: etc/initialdata:350
+msgid "Notify Owner of their ticket has been approved by all approvers"
+msgstr "通知承辦人申請單已完æˆå…¨éƒ¨ç°½æ ¸"
+
+#: etc/initialdata:338
+msgid "Notify Owner of their ticket has been approved by some approver"
+msgstr "通知承辦人申請單已完æˆæŸé …簽核"
+
+#: etc/initialdata:319 etc/upgrade/2.1.71:17 html/Edit/Elements/CreateApprovalsQueue:22
+msgid "Notify Owners and AdminCcs of new items pending their approval"
+msgstr "æ•´ç†å¾…簽核事項,通知承辦人åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:77
+msgid "Notify Requestors"
+msgstr "通知申請人"
+
+#: etc/initialdata:103
+msgid "Notify Requestors and Ccs"
+msgstr "通知申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:98
+msgid "Notify Requestors and Ccs as Comment"
+msgstr "以評論方å¼é€šçŸ¥ç”³è«‹äººåŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:112
+msgid "Notify Requestors, Ccs and AdminCcs"
+msgstr "通知申請人ã€å‰¯æœ¬åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:108
+msgid "Notify Requestors, Ccs and AdminCcs as Comment"
+msgstr "以評論方å¼é€šçŸ¥ç”³è«‹äººã€å‰¯æœ¬åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Work/Tickets/Cc:55
+msgid "Notify people:"
+msgstr "通知å°è±¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Nov"
+msgstr "å一月"
+
+#: lib/RT/Date.pm:421
+msgid "Nov."
+msgstr "11"
+
+#: NOT FOUND IN SOURCE
+msgid "November"
+msgstr "å一月"
+
+#: html/Edit/Global/Basic/Top:83
+msgid "OIN104"
+msgstr "104eHRMS 介é¢"
+
+#: html/Edit/Global/Workflow/Export.html:30 html/Work/Copyright.html:23
+msgid "OK"
+msgstr "確定"
+
+#: lib/RT/Record.pm:156
+msgid "Object could not be created"
+msgstr "無法新增物件"
+
+#: lib/RT/Record.pm:175
+msgid "Object created"
+msgstr "物件新增完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Occupation Status"
+msgstr "在è·ç‹€æ…‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Oct"
+msgstr "å月"
+
+#: lib/RT/Date.pm:420
+msgid "Oct."
+msgstr "10"
+
+#: NOT FOUND IN SOURCE
+msgid "October"
+msgstr "å月"
+
+#: NOT FOUND IN SOURCE
+msgid "Office Phone"
+msgstr "辦公室電話"
+
+#: html/Elements/SelectDateRelation:34
+msgid "On"
+msgstr "等於"
+
+#: html/Edit/Global/CustomField/Top:68
+msgid "On Change"
+msgstr "更改申請單時"
+
+#: etc/initialdata:155
+msgid "On Comment"
+msgstr "評論時"
+
+#: etc/initialdata:148
+msgid "On Correspond"
+msgstr "回覆申請單時"
+
+#: etc/initialdata:137 html/Edit/Global/CustomField/Top:57
+msgid "On Create"
+msgstr "新增申請單時"
+
+#: etc/initialdata:169
+msgid "On Owner Change"
+msgstr "承辦人改變時"
+
+#: etc/initialdata:177
+msgid "On Queue Change"
+msgstr "表單改變時"
+
+#: etc/initialdata:183
+msgid "On Resolve"
+msgstr "解決申請單時"
+
+#: etc/initialdata:161
+msgid "On Status Change"
+msgstr "ç¾æ³æ”¹è®Šæ™‚"
+
+#: etc/initialdata:142
+msgid "On Transaction"
+msgstr "發生更動時"
+
+#: html/Approvals/Elements/PendingMyApproval:49
+#. ("<input size='15' value='".( $created_after->Unix >0 && $created_after->ISO)."' name='CreatedAfter'>")
+msgid "Only show approvals for requests created after %1"
+msgstr "僅顯示 %1 之後新增的申請單"
+
+#: html/Approvals/Elements/PendingMyApproval:47
+#. ("<input size='15' value='".($created_before->Unix > 0 &&$created_before->ISO)."' name='CreatedBefore'>")
+msgid "Only show approvals for requests created before %1"
+msgstr "僅顯示 %1 之å‰æ–°å¢žçš„申請單"
+
+#: html/Edit/Global/GroupRight/List:9 html/Edit/Global/GroupRight/Top:16 html/Edit/Groups/List:11 html/Edit/Groups/Top:18 html/Edit/Queues/Basic/Top:68 html/Edit/Queues/List:15 html/Edit/Queues/List:27 html/Elements/Quicksearch:30 html/Work/Delegates/Info:48 html/Work/Delegates/Info:51 html/Work/Delegates/List:12 html/Work/Elements/Quicksearch:16 html/Work/Overview/Info:41 html/Work/Tickets/Display.html:51
+msgid "Open"
+msgstr "é–‹å•Ÿ"
+
+#: html/Ticket/Elements/Tabs:135
+msgid "Open it"
+msgstr "é–‹å•Ÿ"
+
+#: html/SelfService/Elements/Tabs:41
+msgid "Open tickets"
+msgstr "開啟的申請單"
+
+#: html/Admin/Users/Prefs.html:40
+msgid "Open tickets (from listing) in a new window"
+msgstr "在新視窗開啟(列表的)申請單"
+
+#: html/Admin/Users/Prefs.html:39
+msgid "Open tickets (from listing) in another window"
+msgstr "在å¦ä¸€å€‹è¦–窗開啟(列表的)申請單"
+
+#: etc/initialdata:132
+msgid "Open tickets on correspondence"
+msgstr "收到回覆時å³é–‹å•Ÿç”³è«‹å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Opened Tickets"
+msgstr "已申請é‹è¡Œä¸­è¡¨å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Opinion"
+msgstr "æ„見"
+
+#: html/Edit/Global/CustomField/Info:35
+msgid "Option Description"
+msgstr "é¸é …æè¿°"
+
+#: html/Edit/Global/CustomField/Info:29
+msgid "Option Name"
+msgstr "é¸é …å稱"
+
+#: html/Search/Elements/PickRestriction:100 html/Work/Search/PickRestriction:87
+msgid "Ordering and sorting"
+msgstr "é †åºèˆ‡æŽ’åºæ–¹å¼"
+
+#: html/Admin/Elements/ModifyUser:45 html/Admin/Users/Modify.html:116 html/Edit/Elements/SelectUsers:7 html/Edit/Global/Basic/Top:55 html/Elements/SelectUsers:28 html/User/Prefs.html:110 html/Work/Preferences/Info:77
+msgid "Organization"
+msgstr "組織å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Organization:"
+msgstr "組織:"
+
+#: html/Approvals/Elements/Approve:32
+#. ($approving->Id, $approving->Subject)
+msgid "Originating ticket: #%1"
+msgstr "原申請單:#%1"
+
+#: html/Edit/Elements/PickUsers:111 html/Edit/Users/Add.html:106 html/Work/Tickets/Cc:80
+msgid "Other comma-delimited email addresses"
+msgstr "其他e-mail帳號 (僅e-mail通知;多筆帳號請用逗號','å€éš”)"
+
+#: html/Admin/Elements/ModifyQueue:54 html/Admin/Queues/Modify.html:68 html/Edit/Queues/Basic/Top:44
+msgid "Over time, priority moves toward"
+msgstr "優先順ä½éš¨æ™‚間增加調整為"
+
+#: NOT FOUND IN SOURCE
+msgid "Override current custom fields with fields from %1"
+msgstr "以 %1 表單的自訂欄ä½å–代ç¾æœ‰æ¬„ä½"
+
+#: html/Admin/Elements/CheckOverrideGlobalACL:25
+msgid "Override global rights"
+msgstr "å–代全域權é™"
+
+#: html/Admin/Elements/CheckOverrideGlobalACL:36
+#. (loc_fuzzy($msg))
+msgid "OverrideGlobalACL status %1"
+msgstr "å–ä»£å…¨åŸŸæ¬Šé™ %1"
+
+#: html/Work/Elements/Tab:31
+msgid "Overview"
+msgstr "總覽"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "Own tickets"
+msgstr "承辦申請單"
+
+#: lib/RT/Queue_Overlay.pm:86
+msgid "OwnTicket"
+msgstr "承辦申請單"
+
+#: etc/initialdata:38 html/Admin/Elements/ModifyTemplateAsWorkflow:141 html/Edit/Global/Workflow/Owner.html:19 html/Edit/Queues/Basic/Top:51 html/Edit/Queues/Basic/Top:59 html/Elements/MyRequests:31 html/SelfService/Elements/MyRequests:29 html/Ticket/Create.html:47 html/Ticket/Elements/EditPeople:42 html/Ticket/Elements/EditPeople:43 html/Ticket/Elements/ShowPeople:26 html/Ticket/Update.html:62 html/Work/Elements/MyRequests:19 html/Work/Elements/Quicksearch:18 html/Work/Tickets/Elements/EditPeople:28 html/Work/Tickets/Elements/ShowBasics:21 html/Work/Tickets/Update.html:27 lib/RT/ACE_Overlay.pm:85 lib/RT/Tickets_Overlay.pm:1263
+msgid "Owner"
+msgstr "承辦人"
+
+#: NOT FOUND IN SOURCE
+msgid "Owner changed from %1 to %2"
+msgstr "承辦人已從 %1 改為 %2"
+
+#: lib/RT/Transaction_Overlay.pm:539
+#. ($Old->Name , $New->Name)
+msgid "Owner forcibly changed from %1 to %2"
+msgstr "強制將承辦人從 %1 改為 %2"
+
+#: html/Search/Elements/PickRestriction:30 html/Work/Search/PickRestriction:10
+msgid "Owner is"
+msgstr "承辦人"
+
+#: html/Work/Elements/List:27 html/Work/Queues/List:8 html/Work/Tickets/Create.html:55 html/Work/Tickets/Elements/ShowBasics:60
+msgid "Owner's Phone"
+msgstr "承辦人電話"
+
+#: html/Edit/Elements/Page:40
+msgid "Page #"
+msgstr " "
+
+#: html/Admin/Users/Modify.html:173 html/User/Prefs.html:75 html/Work/Preferences/Info:40
+msgid "Pager"
+msgstr "呼å«å™¨"
+
+#: html/Admin/Elements/ModifyUser:73
+msgid "PagerPhone"
+msgstr "呼å«å™¨è™Ÿç¢¼"
+
+#: html/Edit/Global/Workflow/Action:75 html/Edit/Global/Workflow/Condition:65
+msgid "Parameter"
+msgstr "呼å«åƒæ•¸"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:225
+msgid "Parent"
+msgstr "上級"
+
+#: html/Ticket/Create.html:182 html/Ticket/Elements/BulkLinks:38 html/Ticket/Elements/EditLinks:109 html/Ticket/Elements/EditLinks:54 html/Ticket/Elements/ShowLinks:46 html/Work/Search/BulkLinks:14 html/Work/Tickets/Elements/EditLinks:113 html/Work/Tickets/Elements/EditLinks:45 html/Work/Tickets/Elements/ShowLinks:26
+msgid "Parents"
+msgstr "æ¯ç”³è«‹å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Park Space"
+msgstr "åœè»Šä½ç”³è«‹"
+
+#: html/Elements/Login:52 html/User/Prefs.html:83 html/Work/Preferences/Info:46
+msgid "Password"
+msgstr "密碼"
+
+#: html/NoAuth/Reminder.html:24
+msgid "Password Reminder"
+msgstr "密碼æ示"
+
+#: lib/RT/User_Overlay.pm:230 lib/RT/User_Overlay.pm:990
+msgid "Password too short"
+msgstr "密碼太短"
+
+#: html/Admin/Users/Modify.html:292 html/User/Prefs.html:209 html/Work/Preferences/Info:173
+#. (loc_fuzzy($msg))
+msgid "Password: %1"
+msgstr "密碼:%1"
+
+#: html/Admin/Users/Modify.html:294
+msgid "Passwords do not match."
+msgstr "密碼確èªå¤±æ•—。"
+
+#: html/User/Prefs.html:211 html/Work/Preferences/Info:175
+msgid "Passwords do not match. Your password has not been changed"
+msgstr "密碼確èªå¤±æ•—。您的密碼並未改變。"
+
+#: NOT FOUND IN SOURCE
+msgid "Pelase select a queue"
+msgstr "è«‹é¸æ“‡è¡¨å–®å稱"
+
+#: NOT FOUND IN SOURCE
+msgid "Pending Approval"
+msgstr "等待簽核"
+
+#: html/Ticket/Elements/ShowSummary:44 html/Ticket/Elements/Tabs:95 html/Ticket/ModifyAll.html:50
+msgid "People"
+msgstr "人員"
+
+#: NOT FOUND IN SOURCE
+msgid "People with Queue Rights"
+msgstr "æ“有表單權é™äººå“¡"
+
+#: etc/initialdata:125
+msgid "Perform a user-defined action"
+msgstr "執行使用者自訂的動作"
+
+#: lib/RT/ACE_Overlay.pm:230 lib/RT/ACE_Overlay.pm:236 lib/RT/ACE_Overlay.pm:562 lib/RT/ACE_Overlay.pm:572 lib/RT/ACE_Overlay.pm:582 lib/RT/ACE_Overlay.pm:647 lib/RT/CurrentUser.pm:82 lib/RT/CurrentUser.pm:91 lib/RT/CustomField_Overlay.pm:100 lib/RT/CustomField_Overlay.pm:201 lib/RT/CustomField_Overlay.pm:233 lib/RT/CustomField_Overlay.pm:511 lib/RT/CustomField_Overlay.pm:90 lib/RT/Group_Overlay.pm:1096 lib/RT/Group_Overlay.pm:1100 lib/RT/Group_Overlay.pm:1109 lib/RT/Group_Overlay.pm:1160 lib/RT/Group_Overlay.pm:1164 lib/RT/Group_Overlay.pm:1170 lib/RT/Group_Overlay.pm:423 lib/RT/Group_Overlay.pm:515 lib/RT/Group_Overlay.pm:593 lib/RT/Group_Overlay.pm:601 lib/RT/Group_Overlay.pm:698 lib/RT/Group_Overlay.pm:702 lib/RT/Group_Overlay.pm:708 lib/RT/Group_Overlay.pm:901 lib/RT/Group_Overlay.pm:905 lib/RT/Group_Overlay.pm:918 lib/RT/Queue_Overlay.pm:540 lib/RT/Queue_Overlay.pm:550 lib/RT/Queue_Overlay.pm:564 lib/RT/Queue_Overlay.pm:699 lib/RT/Queue_Overlay.pm:708 lib/RT/Queue_Overlay.pm:721 lib/RT/Queue_Overlay.pm:934 lib/RT/Scrip_Overlay.pm:125 lib/RT/Scrip_Overlay.pm:136 lib/RT/Scrip_Overlay.pm:201 lib/RT/Scrip_Overlay.pm:441 lib/RT/Template_Overlay.pm:284 lib/RT/Template_Overlay.pm:87 lib/RT/Template_Overlay.pm:93 lib/RT/Ticket_Overlay.pm:1386 lib/RT/Ticket_Overlay.pm:1396 lib/RT/Ticket_Overlay.pm:1410 lib/RT/Ticket_Overlay.pm:1544 lib/RT/Ticket_Overlay.pm:1553 lib/RT/Ticket_Overlay.pm:1566 lib/RT/Ticket_Overlay.pm:1915 lib/RT/Ticket_Overlay.pm:2053 lib/RT/Ticket_Overlay.pm:2217 lib/RT/Ticket_Overlay.pm:2286 lib/RT/Ticket_Overlay.pm:2647 lib/RT/Ticket_Overlay.pm:2728 lib/RT/Ticket_Overlay.pm:2832 lib/RT/Ticket_Overlay.pm:2847 lib/RT/Ticket_Overlay.pm:3046 lib/RT/Ticket_Overlay.pm:3056 lib/RT/Ticket_Overlay.pm:3061 lib/RT/Ticket_Overlay.pm:3284 lib/RT/Ticket_Overlay.pm:3288 lib/RT/Ticket_Overlay.pm:3487 lib/RT/Ticket_Overlay.pm:3649 lib/RT/Ticket_Overlay.pm:3701 lib/RT/Ticket_Overlay.pm:3907 lib/RT/Transaction_Overlay.pm:415 lib/RT/Transaction_Overlay.pm:422 lib/RT/Transaction_Overlay.pm:451 lib/RT/Transaction_Overlay.pm:458 lib/RT/User_Overlay.pm:1084 lib/RT/User_Overlay.pm:1532 lib/RT/User_Overlay.pm:692 lib/RT/User_Overlay.pm:727 lib/RT/User_Overlay.pm:983
+msgid "Permission Denied"
+msgstr "權é™ä¸è¶³"
+
+#: html/Edit/Rights/index.html:3
+msgid "Permission Settings"
+msgstr "權é™è¨­å®š"
+
+#: NOT FOUND IN SOURCE
+msgid "Permitted Queues:"
+msgstr "æ“有權é™è¡¨å–®åˆ—表:"
+
+#: NOT FOUND IN SOURCE
+msgid "Personal"
+msgstr "代ç†äººç¾¤çµ„"
+
+#: html/User/Elements/Tabs:34
+msgid "Personal Groups"
+msgstr "代ç†äººç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Personal Todo"
+msgstr "ç§äººå¾…辦事項"
+
+#: html/User/Groups/index.html:29 html/User/Groups/index.html:39
+msgid "Personal groups"
+msgstr "代ç†äººç¾¤çµ„"
+
+#: html/User/Elements/DelegateRights:36
+msgid "Personal groups:"
+msgstr "代ç†äººç¾¤çµ„:"
+
+#: html/Work/Preferences/Info:24
+msgid "PersonalHomepage"
+msgstr "個人首é "
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 1: Create/Rename Groups (%1)"
+msgstr "第一階段:群組建立åŠæ”¹å (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 2: Disable/Enable Groups (%1)"
+msgstr "第二階段:群組åœç”¨åŠå•Ÿç”¨ (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 3: Create/Rename Users (%1)"
+msgstr "第三階段:使用者建立åŠæ”¹å (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phase 4: Disable/Enable Users (%1)"
+msgstr "第四階段:使用者åœç”¨åŠå•Ÿç”¨ (%1)"
+
+#: NOT FOUND IN SOURCE
+msgid "Phone"
+msgstr "電話"
+
+#: html/Work/Delegates/Info:90 html/Work/Overview/Info:72
+msgid "Phone number"
+msgstr "電話號碼"
+
+#: html/Admin/Users/Modify.html:155 html/User/Prefs.html:60 html/Work/Preferences/Info:32
+msgid "Phone numbers"
+msgstr "電話號碼"
+
+#: html/Edit/Queues/Basic/Add.html:3 html/Edit/Queues/Basic/Top:55 html/Edit/Users/Add.html:3 html/Work/Delegates/Add.html:3 html/Work/Delegates/Info:34 html/Work/Tickets/ModifyPeople.html:2
+msgid "Pick"
+msgstr "挑é¸"
+
+#: NOT FOUND IN SOURCE
+msgid "Place of Departure"
+msgstr "出發地點"
+
+#: NOT FOUND IN SOURCE
+msgid "Placeholder"
+msgstr "尚未完工"
+
+#: html/Edit/Elements/PickUsers:31 html/Edit/Elements/PickUsers:44 html/Edit/Elements/SelectCustomFieldType:3 html/Work/Elements/SelectOwner:3 html/Work/Tickets/Elements/EditCustomField:187 html/Work/Tickets/Elements/EditCustomFieldEntry:24 html/Work/Tickets/Elements/EditCustomFieldEntry:35
+msgid "Please Select"
+msgstr "è«‹é¸æ“‡"
+
+#: html/Edit/Elements/104Buttons:30
+msgid "Please check items to be deleted first."
+msgstr "è«‹å…ˆé¸ä¸­è¦åˆªé™¤çš„å°è±¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select a group"
+msgstr "è«‹é¸æ“‡ç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select a queue's workflow"
+msgstr "è«‹é¸æ“‡è¡¨å–®æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select one of the category types above."
+msgstr "請從上é¢é¸æ“‡ä¸€é …分類。"
+
+#: NOT FOUND IN SOURCE
+msgid "Please select role"
+msgstr "è«‹é¸æ“‡è§’色"
+
+#: NOT FOUND IN SOURCE
+msgid "Policy"
+msgstr "經營è¦ç« "
+
+#: NOT FOUND IN SOURCE
+msgid "Position"
+msgstr "è·å‹™"
+
+#: NOT FOUND IN SOURCE
+msgid "Position Level"
+msgstr "è·ç­‰"
+
+#: html/Edit/Elements/PickUsers:41 html/Edit/Global/UserRight/List:13 html/Edit/Global/UserRight/Top:23 html/Edit/Queues/Basic/Add.html:26 html/Edit/Users/Add.html:41 html/Work/Delegates/Add.html:26 html/Work/Delegates/Info:84 html/Work/Overview/Info:66
+msgid "Position Name"
+msgstr "è·å‹™å稱"
+
+#: html/Edit/Global/UserRight/List:14 html/Edit/Global/UserRight/Top:33
+msgid "Position Number"
+msgstr "è·å‹™ä»£ç¢¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Position Rank"
+msgstr "è·ç´š"
+
+#: NOT FOUND IN SOURCE
+msgid "Pref"
+msgstr "å好"
+
+#: html/Edit/Elements/104Top:26 html/Elements/Header:51 html/Elements/Tabs:52 html/SelfService/Elements/Tabs:50 html/SelfService/Prefs.html:24 html/User/Prefs.html:24 html/User/Prefs.html:27 html/Work/Elements/Tab:43
+msgid "Preferences"
+msgstr "å好"
+
+#: NOT FOUND IN SOURCE
+msgid "Prefs"
+msgstr "個人資訊"
+
+#: lib/RT/Action/Generic.pm:159
+msgid "Prepare Stubbed"
+msgstr "é å‚™å‹•ä½œå®Œç•¢"
+
+#: html/Ticket/Elements/Tabs:60
+msgid "Prev"
+msgstr "上一項"
+
+#: html/Search/Listing.html:43 html/Work/Search/index.html:20
+msgid "Previous page"
+msgstr "å‰ä¸€é "
+
+#: NOT FOUND IN SOURCE
+msgid "Pri"
+msgstr "優先順ä½"
+
+#: lib/RT/ACE_Overlay.pm:132 lib/RT/ACE_Overlay.pm:207 lib/RT/ACE_Overlay.pm:551
+#. ($args{'PrincipalId'})
+msgid "Principal %1 not found."
+msgstr "找ä¸åˆ°å–®ä½ %1。"
+
+#: html/Search/Elements/PickRestriction:53 html/Ticket/Create.html:153 html/Ticket/Elements/EditBasics:53 html/Ticket/Elements/ShowBasics:38 html/Work/Search/PickRestriction:34 html/Work/Tickets/Elements/EditBasics:41 lib/RT/Tickets_Overlay.pm:1061
+msgid "Priority"
+msgstr "優先順ä½"
+
+#: html/Admin/Elements/ModifyQueue:50 html/Admin/Queues/Modify.html:64
+msgid "Priority starts at"
+msgstr "優先順ä½èµ·å§‹å€¼"
+
+#: etc/initialdata:25
+msgid "Privileged"
+msgstr "內部æˆå“¡"
+
+#: html/Admin/Users/Modify.html:272 html/User/Prefs.html:200 html/Work/Preferences/Info:164
+#. (loc_fuzzy($msg))
+msgid "Privileged status: %1"
+msgstr "內部æˆå“¡ç‹€æ…‹ï¼š%1"
+
+#: html/Admin/Users/index.html:61
+msgid "Privileged users"
+msgstr "內部æˆå“¡"
+
+#: html/Work/Elements/SelectSearch:16
+msgid "Process Status"
+msgstr "處ç†ç‹€æ…‹"
+
+#: html/Edit/Queues/List:10
+msgid "Project Name"
+msgstr "專案å稱"
+
+#: etc/initialdata:23 etc/initialdata:29 etc/initialdata:35 etc/initialdata:59
+msgid "Pseudogroup for internal use"
+msgstr "內部用的虛擬群組"
+
+#: html/Edit/Queues/List:11
+msgid "Public Description"
+msgstr "公開說明"
+
+#: html/Work/Preferences/Info:70
+msgid "Public Info"
+msgstr "公開資訊"
+
+#: html/Work/Elements/104Header:88
+msgid "Public Service"
+msgstr "公共事務å€"
+
+#: NOT FOUND IN SOURCE
+msgid "Purging stale data: %1"
+msgstr "移除éŽæœŸè³‡æ–™: %1"
+
+#: html/Edit/Users/Search.html:4
+msgid "Query"
+msgstr "查詢"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:166 html/Elements/MyRequests:29 html/Elements/MyTickets:29 html/Elements/Quicksearch:28 html/Search/Elements/PickRestriction:45 html/SelfService/Create.html:32 html/Ticket/Create.html:37 html/Ticket/Elements/EditBasics:63 html/Ticket/Elements/ShowBasics:42 html/User/Elements/DelegateRights:79 html/Work/Elements/MyApprovals:10 html/Work/Elements/MyRequests:17 html/Work/Elements/MyTickets:17 html/Work/Elements/Quicksearch:14 html/Work/Search/PickRestriction:26 html/Work/Tickets/Elements/EditBasics:16 lib/RT/Tickets_Overlay.pm:902
+msgid "Queue"
+msgstr "表單"
+
+#: html/Admin/Queues/CustomField.html:41 html/Admin/Queues/Scrip.html:49 html/Admin/Queues/Scrips.html:47 html/Admin/Queues/Templates.html:43 html/Admin/Queues/Workflows.html:44
+#. ($Queue)
+#. ($id)
+msgid "Queue %1 not found"
+msgstr "找ä¸åˆ°è¡¨å–® %1"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue '%1' not found\\n"
+msgstr "找ä¸åˆ°è¡¨å–® '%1'\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Keyword Selections"
+msgstr "表單關éµå­—é¸å–"
+
+#: html/Admin/Elements/ModifyQueue:30 html/Admin/Queues/Modify.html:42 html/Edit/Queues/Basic/Top:13 html/Edit/Queues/Basic/index.html:36 html/Edit/Queues/Global:21 html/Edit/Queues/List:20 html/Edit/Users/Queue:10 html/Work/Delegates/List:6 html/Work/Elements/List:11 html/Work/Queues/List:5 html/Work/Tickets/Create.html:21 html/Work/Tickets/Elements/ShowBasics:6
+msgid "Queue Name"
+msgstr "表單å稱"
+
+#: html/Edit/Queues/List:22 html/Work/Elements/List:25 html/Work/Queues/List:7 html/Work/Tickets/Create.html:34 html/Work/Tickets/Elements/ShowBasics:19
+msgid "Queue Owner"
+msgstr "業務承辦人"
+
+#: html/Edit/Queues/Basic/Top:38
+msgid "Queue Priority"
+msgstr "優先等級"
+
+#: html/Edit/Global/GroupRight/Top:24 html/Edit/Global/UserRight/Top:43 html/Edit/Users/Queue:11 html/Edit/Users/index.html:97
+msgid "Queue Rights"
+msgstr "表單權é™"
+
+#: NOT FOUND IN SOURCE
+msgid "Queue Scrips"
+msgstr "表單手續"
+
+#: html/Edit/Elements/Tab:38
+msgid "Queue Setup"
+msgstr "表單設定"
+
+#: lib/RT/Queue_Overlay.pm:264
+msgid "Queue already exists"
+msgstr "表單已存在"
+
+#: lib/RT/Queue_Overlay.pm:273 lib/RT/Queue_Overlay.pm:279
+msgid "Queue could not be created"
+msgstr "無法新增表單"
+
+#: html/Edit/Queues/autohandler:8 html/Ticket/Create.html:208 html/Work/Tickets/Create.html:180
+msgid "Queue could not be loaded."
+msgstr "無法載入表單"
+
+#: docs/design_docs/string-extraction-guide.txt:83 lib/RT/Queue_Overlay.pm:283 lib/RT/StyleGuide.pod:789
+msgid "Queue created"
+msgstr "表單新增完畢"
+
+#: html/Admin/Elements/ModifyWorkflow:32
+msgid "Queue is not specified."
+msgstr "未指定表單。"
+
+#: html/SelfService/Display.html:70 lib/RT/CustomField_Overlay.pm:97
+msgid "Queue not found"
+msgstr "找ä¸åˆ°è¡¨å–®"
+
+#: html/Admin/Elements/Tabs:37 html/Admin/index.html:34
+msgid "Queues"
+msgstr "表單"
+
+#: html/Work/Elements/Quicksearch:10
+msgid "Quick Search"
+msgstr "表單ç¾æ³"
+
+#: html/Elements/Quicksearch:24
+msgid "Quick search"
+msgstr "表單一覽"
+
+#: html/Elements/Login:44
+#. ($RT::VERSION)
+msgid "RT %1"
+msgstr "RT %1"
+
+#: docs/design_docs/string-extraction-guide.txt:70 lib/RT/StyleGuide.pod:776
+#. ($RT::VERSION, $RT::rtname)
+msgid "RT %1 for %2"
+msgstr "%2:RT %1 版"
+
+#: html/Elements/Footer:32
+#. ($RT::VERSION)
+msgid "RT %1 from <a href=\"http://bestpractical.com\">Best Practical Solutions, LLC</a>."
+msgstr "RT %1 版,<a href=\"http://bestpractical.com\">Best Practical Solutions å…¬å¸</a>出å“。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1。版權所有 1996-%1 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT %1. Copyright 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+msgstr "RT %1。版權所有 1996-2002 Jesse Vincent <jesse\\@bestpractical.com>\\n"
+
+#: html/Admin/index.html:24 html/Admin/index.html:25
+msgid "RT Administration"
+msgstr "RT 管ç†é é¢"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Authentication error."
+msgstr "RT èªè­‰éŒ¯èª¤ã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Bounce: %1"
+msgstr "RT 退信:%1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Configuration error"
+msgstr "RT 設定錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Critical error. Message not recorded!"
+msgstr "RT 致命錯誤。訊æ¯æœªè¢«ç´€éŒ„。"
+
+#: html/Elements/Error:41 html/SelfService/Error.html:40
+msgid "RT Error"
+msgstr "RT 錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Received mail (%1) from itself."
+msgstr "RT 收到從自己寄出的郵件 (%1)。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Recieved mail (%1) from itself."
+msgstr "RT 收到從自己寄出的郵件 (%1)。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT Self Service / Closed Tickets"
+msgstr "RT 自助æœå‹™/已解決的申請單"
+
+#: html/index.html:24 html/index.html:27
+msgid "RT at a glance"
+msgstr "RT 一覽"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't authenticate you"
+msgstr "RT 無法èªè­‰ä½ "
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find requestor via its external database lookup"
+msgstr "RT 無法從外部資料庫查詢找到申請人資訊"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't find the queue: %1"
+msgstr "RT 找ä¸åˆ°è¡¨å–®ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "RT couldn't validate this PGP signature. \\n"
+msgstr "RT 無法確èªé€™å€‹ PGP 簽章。\\n"
+
+#: html/Edit/Elements/104Header:7 html/Edit/Elements/104Top:20 html/Elements/PageLayout:85 html/Work/Elements/104Header:7
+#. ($RT::rtname)
+msgid "RT for %1"
+msgstr "%1 專用æµç¨‹ç³»çµ±"
+
+#: NOT FOUND IN SOURCE
+msgid "RT for %1: %2"
+msgstr "%1 專用 RT 系統:%2"
+
+#: NOT FOUND IN SOURCE
+msgid "RT has proccessed your commands"
+msgstr "RT 已執行您的命令"
+
+#: html/Elements/Login:94
+#. ('2003')
+msgid "RT is &copy; Copyright 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;. It is distributed under <a href=\"http://www.gnu.org/copyleft/gpl.html\">Version 2 of the GNU General Public License.</a>"
+msgstr "RT 版權所有 1996-%1 Jesse Vincent &lt;jesse@bestpractical.com&gt;。<br>æœ¬è»Ÿé«”ä¾ <a href=\"http://www.gnu.org/copyleft/gpl.html\">GNU 通用公共授權第二版</a> 散佈。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT thinks this message may be a bounce"
+msgstr "RT èªç‚ºé€™å¯èƒ½æ˜¯é€€ä¿¡"
+
+#: NOT FOUND IN SOURCE
+msgid "RT will process this message as if it were unsigned.\\n"
+msgstr "RT 以未簽章方å¼è™•ç†é€™å°éƒµä»¶ã€‚\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "RT's email command mode requires PGP authentication. Either you didn't sign your message, or your signature could not be verified."
+msgstr "RT çš„é›»å­éƒµä»¶å‘½ä»¤æ¨¡å¼é ˆè¦ PGP èªè­‰ã€‚您å¯èƒ½æ²’有簽章,或是您的簽章無法辨識。"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::Queue-Role"
+msgstr "表單é‹è¡Œè§’色"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::System-Role"
+msgstr "系統é‹è¡Œè§’色"
+
+#: NOT FOUND IN SOURCE
+msgid "RT::Ticket-Role"
+msgstr "申請單é‹è¡Œè§’色"
+
+#: html/Work/Tickets/Elements/ShowTransaction:14
+msgid "RT_System"
+msgstr "系統訊æ¯"
+
+#: html/Edit/Global/CustomField/SelectWritable:7
+msgid "Read Only"
+msgstr "唯讀"
+
+#: html/Admin/Users/Modify.html:57 html/Admin/Users/Prefs.html:51 html/Edit/Elements/SelectUsers:5 html/Edit/Users/List:6 html/User/Prefs.html:47 html/Work/Preferences/Info:18
+msgid "Real Name"
+msgstr "真實姓å"
+
+#: html/Admin/Elements/ModifyUser:47
+msgid "RealName"
+msgstr "真實姓å"
+
+#: html/Work/Approvals/Display.html:30 html/Work/Tickets/Update.html:81
+msgid "Really reject this ticket?"
+msgstr "您確定è¦é§å›žé€™å¼µç”³è«‹å–®å—Žï¼Ÿ"
+
+#: lib/RT/Transaction_Overlay.pm:592
+#. ($value)
+msgid "Reference by %1 added"
+msgstr "已加入 %1 為åƒè€ƒæœ¬ç”³è«‹å–®"
+
+#: lib/RT/Transaction_Overlay.pm:629
+#. ($value)
+msgid "Reference by %1 deleted"
+msgstr "已移除 %1 為åƒè€ƒæœ¬ç”³è«‹å–®"
+
+#: lib/RT/Transaction_Overlay.pm:589
+#. ($value)
+msgid "Reference to %1 added"
+msgstr "已加入åƒè€ƒç”³è«‹å–® %1"
+
+#: lib/RT/Transaction_Overlay.pm:626
+#. ($value)
+msgid "Reference to %1 deleted"
+msgstr "已移除åƒè€ƒç”³è«‹å–® %1"
+
+#: html/Ticket/Create.html:185 html/Ticket/Elements/BulkLinks:50 html/Ticket/Elements/EditLinks:121 html/Ticket/Elements/EditLinks:81 html/Ticket/Elements/ShowLinks:70 html/Work/Search/BulkLinks:26 html/Work/Tickets/Elements/EditLinks:125 html/Work/Tickets/Elements/EditLinks:81 html/Work/Tickets/Elements/ShowLinks:38
+msgid "Referred to by"
+msgstr "被åƒè€ƒ"
+
+#: html/Elements/SelectLinkType:27 html/Ticket/Create.html:184 html/Ticket/Elements/BulkLinks:46 html/Ticket/Elements/EditLinks:117 html/Ticket/Elements/EditLinks:72 html/Ticket/Elements/ShowLinks:60 html/Work/Search/BulkLinks:22 html/Work/Tickets/Elements/EditLinks:121 html/Work/Tickets/Elements/EditLinks:67 html/Work/Tickets/Elements/ShowLinks:33
+msgid "Refers to"
+msgstr "åƒè€ƒ"
+
+#: NOT FOUND IN SOURCE
+msgid "RefersTo"
+msgstr "åƒè€ƒ"
+
+#: NOT FOUND IN SOURCE
+msgid "Refine"
+msgstr "在çµæžœç¯„åœå…§æŸ¥è©¢"
+
+#: html/Search/Elements/PickRestriction:26 html/Work/Search/PickRestriction:7
+msgid "Refine search"
+msgstr "調整查詢æ¢ä»¶"
+
+#: html/Work/Overview/index.html:12
+msgid "Refresh"
+msgstr "æ›´æ–°"
+
+#: html/Elements/Refresh:35
+#. ($value/60)
+msgid "Refresh this page every %1 minutes."
+msgstr "æ¯ %1 分é˜æ›´æ–°é é¢"
+
+#: html/Ticket/Create.html:174 html/Ticket/Elements/ShowSummary:61 html/Ticket/ModifyAll.html:56
+msgid "Relationships"
+msgstr "關係"
+
+#: html/Edit/Elements/ListButtons:13
+msgid "Remove"
+msgstr "移除"
+
+#: html/Search/Bulk.html:97 html/Work/Search/Bulk.html:77
+msgid "Remove AdminCc"
+msgstr "移除管ç†å“¡å‰¯æœ¬"
+
+#: html/Search/Bulk.html:93 html/Work/Search/Bulk.html:71
+msgid "Remove Cc"
+msgstr "移除副本"
+
+#: html/Search/Bulk.html:89 html/Work/Search/Bulk.html:65
+msgid "Remove Requestor"
+msgstr "移除申請人"
+
+#: html/Ticket/Elements/ShowTransaction:172 html/Ticket/Elements/Tabs:121 html/Work/Tickets/Display.html:54 html/Work/Tickets/Elements/ShowTransaction:115
+msgid "Reply"
+msgstr "回覆"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "Reply to tickets"
+msgstr "å°ç”³è«‹å–®é€²è¡Œå›žè¦†"
+
+#: lib/RT/Queue_Overlay.pm:84
+msgid "ReplyToTicket"
+msgstr "回覆申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Report to Duty"
+msgstr "上下ç­åˆ·å¡"
+
+#: NOT FOUND IN SOURCE
+msgid "Reported on"
+msgstr "到è·æ—¥æœŸ"
+
+#: etc/initialdata:44 html/Ticket/Update.html:39 html/Work/Elements/List:21 html/Work/Elements/MyApprovals:12 html/Work/Elements/MyTickets:20 html/Work/Elements/SelectSearch:31 html/Work/Tickets/Elements/ShowBasics:62 lib/RT/ACE_Overlay.pm:86
+msgid "Requestor"
+msgstr "申請人"
+
+#: html/Edit/Global/Workflow/Owner.html:44
+msgid "Requestor Group's"
+msgstr "申請人所屬群組之"
+
+#: html/Search/Elements/PickRestriction:37 html/Work/Search/PickRestriction:17
+msgid "Requestor email address"
+msgstr "申請人電å­éƒµä»¶ä¿¡ç®±ä½å€"
+
+#: html/Edit/Global/Workflow/Owner.html:28
+msgid "Requestor's"
+msgstr "申請人所屬之第上"
+
+#: html/Work/Elements/List:23
+msgid "Requestor's Phone"
+msgstr "申請人電話"
+
+#: NOT FOUND IN SOURCE
+msgid "Requestor(s)"
+msgstr "申請人"
+
+#: NOT FOUND IN SOURCE
+msgid "RequestorAddresses"
+msgstr "申請人地å€"
+
+#: html/SelfService/Create.html:40 html/Ticket/Create.html:55 html/Ticket/Elements/EditPeople:47 html/Ticket/Elements/ShowPeople:30 html/Work/Tickets/Elements/EditPeople:38
+msgid "Requestors"
+msgstr "申請人"
+
+#: html/Admin/Elements/ModifyQueue:60 html/Admin/Queues/Modify.html:74
+msgid "Requests should be due in"
+msgstr "申請單處ç†æœŸé™"
+
+#: html/Elements/Submit:61
+msgid "Reset"
+msgstr "é‡è¨­"
+
+#: html/Admin/Users/Modify.html:158 html/User/Prefs.html:63 html/Work/Preferences/Info:34
+msgid "Residence"
+msgstr "ä½è™•"
+
+#: NOT FOUND IN SOURCE
+msgid "Resolution"
+msgstr "解決狀態"
+
+#: html/Ticket/Elements/Tabs:131 html/Work/Tickets/Display.html:57
+msgid "Resolve"
+msgstr "解決"
+
+#: html/Ticket/Update.html:137
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Resolve ticket #%1 (%2)"
+msgstr "解決申請單 #%1 (%2)"
+
+#: etc/initialdata:308 html/Elements/SelectDateType:27 lib/RT/Ticket_Overlay.pm:1215
+msgid "Resolved"
+msgstr "已解決"
+
+#: html/Search/Bulk.html:132 html/Ticket/ModifyAll.html:72 html/Ticket/Update.html:71 html/Work/Search/Bulk.html:84 html/Work/Tickets/Update.html:38
+msgid "Response to requestors"
+msgstr "回覆申請人"
+
+#: NOT FOUND IN SOURCE
+msgid "Responsibility Type"
+msgstr "責任å€åˆ†"
+
+#: html/Elements/ListActions:25
+msgid "Results"
+msgstr "çµæžœ"
+
+#: html/Search/Elements/PickRestriction:104 html/Work/Search/PickRestriction:90
+msgid "Results per page"
+msgstr "æ¯é åˆ—出幾筆çµæžœ"
+
+#: html/Admin/Elements/ModifyUser:32 html/Admin/Users/Modify.html:99 html/User/Prefs.html:94 html/Work/Preferences/Info:56
+msgid "Retype Password"
+msgstr "å†æ¬¡è¼¸å…¥å¯†ç¢¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Right %1 not found for %2 %3 in scope %4 (%5)\\n"
+msgstr "在 %4 (%5) 的範åœå…§æ‰¾ä¸åˆ° %2 %3 çš„ %1 權é™\\n"
+
+#: lib/RT/ACE_Overlay.pm:612
+msgid "Right Delegated"
+msgstr "權é™ä»£ç†å®Œç•¢"
+
+#: lib/RT/ACE_Overlay.pm:302
+msgid "Right Granted"
+msgstr "權é™è¨­å®šå®Œç•¢"
+
+#: lib/RT/ACE_Overlay.pm:160
+msgid "Right Loaded"
+msgstr "權é™è¼‰å…¥å®Œç•¢"
+
+#: lib/RT/ACE_Overlay.pm:677 lib/RT/ACE_Overlay.pm:692
+msgid "Right could not be revoked"
+msgstr "無法撤消權é™"
+
+#: html/User/Delegation.html:63
+msgid "Right not found"
+msgstr "找ä¸åˆ°æ¬Šé™"
+
+#: lib/RT/ACE_Overlay.pm:542 lib/RT/ACE_Overlay.pm:637
+msgid "Right not loaded."
+msgstr "權é™ä¸¦æœªè¼‰å…¥ã€‚"
+
+#: lib/RT/ACE_Overlay.pm:688
+msgid "Right revoked"
+msgstr "權é™æ’¤æ¶ˆå®Œç•¢"
+
+#: html/Admin/Elements/UserTabs:40
+msgid "Rights"
+msgstr "權é™åŠä»£ç†äºº"
+
+#: lib/RT/Interface/Web.pm:857
+#. ($object_type)
+msgid "Rights could not be granted for %1"
+msgstr "無法將權é™è³¦äºˆ %1"
+
+#: lib/RT/Interface/Web.pm:887
+#. ($object_type)
+msgid "Rights could not be revoked for %1"
+msgstr "無法撤消 %1 的權é™"
+
+#: html/Edit/Groups/Member:54 html/Edit/Groups/Members/List:10
+msgid "Role Members"
+msgstr "角色æˆå“¡"
+
+#: html/Edit/Groups/Member:37 html/Edit/Groups/Members/Add.html:13 html/Edit/Groups/Members/List:7 html/Edit/Groups/Roles/List:4 html/Edit/Groups/Roles/Top:7
+msgid "Role Name"
+msgstr "角色å稱"
+
+#: html/Admin/Global/GroupRights.html:50 html/Admin/Queues/GroupRights.html:52 html/Edit/Global/Workflow/Owner.html:55 html/Edit/Global/Workflow/Owner.html:81 html/Edit/Groups/Member:24
+msgid "Roles"
+msgstr "角色"
+
+#: NOT FOUND IN SOURCE
+msgid "RootApproval"
+msgstr "交由系統管ç†å“¡ç°½æ ¸"
+
+#: html/Edit/Global/Workflow/Action:23
+msgid "Run Approval"
+msgstr "簽核執行"
+
+#: html/Edit/Global/Basic/Top:81
+msgid "SMTPDebug"
+msgstr "SMTP åµéŒ¯ç´€éŒ„"
+
+#: html/Edit/Global/Basic/Top:63
+msgid "SMTPFrom"
+msgstr "SMTP 寄件ä½å€"
+
+#: html/Edit/Global/Basic/Top:61
+msgid "SMTPServer"
+msgstr "SMTP 伺æœå™¨"
+
+#: NOT FOUND IN SOURCE
+msgid "Sat"
+msgstr "星期六"
+
+#: lib/RT/Date.pm:393
+msgid "Sat."
+msgstr "星期六"
+
+#: html/Edit/Elements/104Buttons:83 html/Work/Preferences/index.html:33 html/Work/Tickets/Elements/EditBasics:63 html/Work/Tickets/Elements/EditLinks:133 html/Work/Tickets/Elements/EditPeople:51
+msgid "Save"
+msgstr "儲存"
+
+#: html/Admin/Queues/People.html:104 html/Ticket/Modify.html:38 html/Ticket/ModifyAll.html:93 html/Ticket/ModifyLinks.html:38 html/Ticket/ModifyPeople.html:37
+msgid "Save Changes"
+msgstr "儲存更改"
+
+#: NOT FOUND IN SOURCE
+msgid "Save changes"
+msgstr "儲存更改"
+
+#: html/Admin/Global/Scrip.html:48 html/Admin/Queues/Scrip.html:54
+#. ($QueueObj->id)
+#. ($ARGS{'id'})
+msgid "Scrip #%1"
+msgstr "手續 #%1"
+
+#: html/Edit/Global/Scrip/List:9 html/Edit/Global/Scrip/Top:41
+msgid "Scrip Action"
+msgstr "訊æ¯é€šçŸ¥å‹•ä½œ"
+
+#: html/Edit/Global/Scrip/List:8 html/Edit/Global/Scrip/Top:15
+msgid "Scrip Condition"
+msgstr "訊æ¯é€šçŸ¥æ¢ä»¶"
+
+#: lib/RT/Scrip_Overlay.pm:180
+msgid "Scrip Created"
+msgstr "手續新增完畢"
+
+#: html/Edit/Global/Scrip/List:7 html/Edit/Global/Scrip/Top:9
+msgid "Scrip Name"
+msgstr "訊æ¯å稱"
+
+#: html/Admin/Elements/EditScrips:85
+msgid "Scrip deleted"
+msgstr "手續刪除完畢"
+
+#: html/Admin/Elements/QueueTabs:45 html/Admin/Elements/SystemTabs:32 html/Admin/Global/index.html:40
+msgid "Scrips"
+msgstr "手續"
+
+#: html/Edit/Global/autohandler:9 html/Edit/Queues/autohandler:24
+msgid "Scrips "
+msgstr "訊æ¯é€šçŸ¥"
+
+#: NOT FOUND IN SOURCE
+msgid "Scrips for %1\\n"
+msgstr "%1 的手續\\n"
+
+#: html/Admin/Queues/Scrips.html:33
+msgid "Scrips which apply to all queues"
+msgstr "é©ç”¨æ–¼æ‰€æœ‰è¡¨å–®çš„手續"
+
+#: html/Edit/Elements/104Buttons:86 html/Elements/SimpleSearch:26 html/Search/Elements/PickRestriction:125 html/Ticket/Elements/Tabs:158 html/Work/Elements/Tab:45 html/Work/Search/PickRestriction:108
+msgid "Search"
+msgstr "查詢"
+
+#: NOT FOUND IN SOURCE
+msgid "Search Criteria"
+msgstr "查詢æ¢ä»¶"
+
+#: html/Approvals/Elements/PendingMyApproval:38
+msgid "Search for approvals"
+msgstr "簽核單查詢"
+
+#: html/Edit/Global/Workflow/Owner.html:31
+msgid "Second-"
+msgstr "二"
+
+#: NOT FOUND IN SOURCE
+msgid "Second-level Users"
+msgstr "二階主管員工"
+
+#: bin/rt-crontool:187
+msgid "Security:"
+msgstr "安全性:"
+
+#: lib/RT/Queue_Overlay.pm:66
+msgid "SeeQueue"
+msgstr "查閱表單"
+
+#: html/Edit/Elements/ListButtons:10
+msgid "Select All"
+msgstr "å…¨é¸"
+
+#: html/Admin/Groups/index.html:39
+msgid "Select a group"
+msgstr "é¸æ“‡ç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "Select a queue"
+msgstr "é¸æ“‡è¡¨å–®"
+
+#: html/Work/Queues/Select.html:8
+msgid "Select a queue to link to"
+msgstr "è«‹é¸æ“‡æ¬²é€£çµè¡¨å–®"
+
+#: html/Admin/Users/index.html:24 html/Admin/Users/index.html:27
+msgid "Select a user"
+msgstr "é¸æ“‡ä½¿ç”¨è€…"
+
+#: html/Admin/Global/CustomField.html:37 html/Admin/Global/CustomFields.html:35
+msgid "Select custom field"
+msgstr "é¸æ“‡è‡ªè¨‚欄ä½"
+
+#: html/Admin/Elements/GroupTabs:51 html/User/Elements/GroupTabs:49
+msgid "Select group"
+msgstr "é¸æ“‡ç¾¤çµ„"
+
+#: lib/RT/CustomField_Overlay.pm:421
+msgid "Select multiple values"
+msgstr "é¸æ“‡å¤šé‡é …ç›®"
+
+#: lib/RT/CustomField_Overlay.pm:418
+msgid "Select one value"
+msgstr "é¸æ“‡å–®ä¸€é …ç›®"
+
+#: html/Admin/Elements/QueueTabs:66
+msgid "Select queue"
+msgstr "é¸æ“‡è¡¨å–®"
+
+#: html/Admin/Global/Scrip.html:36 html/Admin/Global/Scrips.html:35 html/Admin/Queues/Scrip.html:39 html/Admin/Queues/Scrips.html:51
+msgid "Select scrip"
+msgstr "é¸æ“‡æ‰‹çºŒ"
+
+#: html/Admin/Global/Template.html:56 html/Admin/Global/Templates.html:35 html/Admin/Queues/Template.html:54 html/Admin/Queues/Templates.html:46
+msgid "Select template"
+msgstr "é¸æ“‡ç¯„本"
+
+#: html/Admin/Elements/UserTabs:48
+msgid "Select user"
+msgstr "é¸æ“‡ä½¿ç”¨è€…"
+
+#: html/Admin/Global/Workflow.html:57 html/Admin/Global/Workflows.html:36 html/Admin/Queues/Workflow.html:54 html/Admin/Queues/Workflows.html:47
+msgid "Select workflow"
+msgstr "é¸æ“‡æµç¨‹"
+
+#: NOT FOUND IN SOURCE
+msgid "SelectExternal"
+msgstr "系統é¸é …"
+
+#: lib/RT/CustomField_Overlay.pm:35
+msgid "SelectMultiple"
+msgstr "多é‡é¸é …"
+
+#: lib/RT/CustomField_Overlay.pm:34
+msgid "SelectSingle"
+msgstr "單一é¸é …"
+
+#: html/Edit/Elements/PickUsers:87 html/Edit/Users/Add.html:78
+msgid "Selected users:"
+msgstr "新增å°è±¡ï¼š"
+
+#: NOT FOUND IN SOURCE
+msgid "Self Service"
+msgstr "自助æœå‹™"
+
+#: etc/initialdata:113
+msgid "Send mail to all watchers"
+msgstr "寄信給所有視察員"
+
+#: etc/initialdata:109
+msgid "Send mail to all watchers as a \"comment\""
+msgstr "以評論方å¼å¯„信給所有視察員"
+
+#: etc/initialdata:104
+msgid "Send mail to requestors and Ccs"
+msgstr "寄信給申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:99
+msgid "Send mail to requestors and Ccs as a comment"
+msgstr "以評論方å¼å¯„信給申請人åŠå‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:78
+msgid "Sends a message to the requestors"
+msgstr "寄信給申請人"
+
+#: etc/initialdata:117 etc/initialdata:121
+msgid "Sends mail to explicitly listed Ccs and Bccs"
+msgstr "寄信給特定的副本åŠå¯†ä»¶å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:94
+msgid "Sends mail to the administrative Ccs"
+msgstr "寄信給管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:90
+msgid "Sends mail to the administrative Ccs as a comment"
+msgstr "以評論寄信給管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:82 etc/initialdata:86
+msgid "Sends mail to the owner"
+msgstr "寄信給申請人"
+
+#: NOT FOUND IN SOURCE
+msgid "Sep"
+msgstr "ä¹æœˆ"
+
+#: lib/RT/Date.pm:419
+msgid "Sep."
+msgstr "09"
+
+#: NOT FOUND IN SOURCE
+msgid "September"
+msgstr "ä¹æœˆ"
+
+#: NOT FOUND IN SOURCE
+msgid "Setting %1's 'Disabled' property to %2"
+msgstr "%1 的「åœç”¨ã€å±¬æ€§å·²è¨­ç‚º %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Shift Type"
+msgstr "ç­åˆ¥å±¬æ€§"
+
+#: NOT FOUND IN SOURCE
+msgid "Show Results"
+msgstr "顯示çµæžœ"
+
+#: html/Approvals/Elements/PendingMyApproval:43
+msgid "Show approved requests"
+msgstr "顯示已批准的簽核單"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show basics"
+msgstr "顯示基本資訊"
+
+#: html/Approvals/Elements/PendingMyApproval:44
+msgid "Show denied requests"
+msgstr "顯示已é§å›žçš„簽核單"
+
+#: html/Ticket/Create.html:143 html/Ticket/Create.html:33
+msgid "Show details"
+msgstr "顯示細節"
+
+#: html/Approvals/Elements/PendingMyApproval:42
+msgid "Show pending requests"
+msgstr "顯示待處ç†çš„簽核單"
+
+#: html/Approvals/Elements/PendingMyApproval:45
+msgid "Show requests awaiting other approvals"
+msgstr "顯示尚待他人批准的簽核單"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "Show ticket private commentary"
+msgstr "顯示申請單內的ç§äººè©•è«–"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "Show ticket summaries"
+msgstr "顯示申請單摘è¦"
+
+#: lib/RT/Queue_Overlay.pm:68
+msgid "ShowACL"
+msgstr "顯示權é™æ¸…å–®"
+
+#: lib/RT/Queue_Overlay.pm:77
+msgid "ShowScrips"
+msgstr "顯示手續"
+
+#: lib/RT/Queue_Overlay.pm:74
+msgid "ShowTemplate"
+msgstr "顯示範本"
+
+#: lib/RT/Queue_Overlay.pm:78
+msgid "ShowTicket"
+msgstr "顯示申請單"
+
+#: lib/RT/Queue_Overlay.pm:80
+msgid "ShowTicketComments"
+msgstr "顯示申請單的評論"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Sign up as a ticket Requestor or ticket or queue Cc"
+msgstr "登記æˆç‚ºç”³è«‹äººæˆ–副本收件人"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "Sign up as a ticket or queue AdminCc"
+msgstr "登記æˆç‚ºç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: html/Admin/Elements/ModifyUser:38 html/Admin/Users/Modify.html:190 html/Admin/Users/Prefs.html:31 html/Edit/Users/Info:52 html/User/Prefs.html:148 html/Work/Preferences/Info:113
+msgid "Signature"
+msgstr "ç°½å檔"
+
+#: NOT FOUND IN SOURCE
+msgid "Signed in as %1"
+msgstr "使用者:%1"
+
+#: html/Admin/Elements/SelectSingleOrMultiple:25
+msgid "Single"
+msgstr "單一"
+
+#: html/Edit/Elements/104Top:21 html/Elements/Header:50
+msgid "Skip Menu"
+msgstr "ç•¥éŽé¸å–®"
+
+#: html/Admin/Elements/AddCustomFieldValue:27
+msgid "Sort"
+msgstr "é †åº"
+
+#: NOT FOUND IN SOURCE
+msgid "Sort key"
+msgstr "排åºæ–¹å¼"
+
+#: html/Search/Elements/PickRestriction:108 html/Work/Search/PickRestriction:95
+msgid "Sort results by"
+msgstr "çµæžœæŽ’åºæ–¹å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "SortOrder"
+msgstr "排åºé †åº"
+
+#: html/Admin/Elements/EditScrip:80 html/Edit/Global/Scrip/Top:75 html/Work/Elements/List:8 html/Work/Elements/MyApprovals:11
+msgid "Stage"
+msgstr "é—œå¡"
+
+#: html/Edit/Global/Workflow/Top:8
+msgid "Stage Action"
+msgstr "é—œå¡é‹è¡Œå‹•ä½œ"
+
+#: html/Edit/Global/Workflow/Top:5
+msgid "Stage Condition"
+msgstr "é—œå¡é‹è¡Œæ¢ä»¶"
+
+#: html/Work/Elements/Quicksearch:17
+msgid "Stalled"
+msgstr "延宕"
+
+#: NOT FOUND IN SOURCE
+msgid "Start page"
+msgstr "首é "
+
+#: html/Elements/SelectDateType:26 html/Ticket/Elements/EditDates:31 html/Ticket/Elements/ShowDates:35 html/Work/Tickets/Elements/EditBasics:35
+msgid "Started"
+msgstr "實際起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Started date '%1' could not be parsed"
+msgstr "無法解讀起始日期 '%1"
+
+#: html/Elements/SelectDateType:30 html/Ticket/Create.html:165 html/Ticket/Elements/EditDates:26 html/Ticket/Elements/ShowDates:31 html/Work/Tickets/Elements/EditBasics:26
+msgid "Starts"
+msgstr "應起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts By"
+msgstr "應起始日"
+
+#: NOT FOUND IN SOURCE
+msgid "Starts date '%1' could not be parsed"
+msgstr "無法解讀起始日期 '%1"
+
+#: html/Admin/Elements/ModifyUser:81 html/Admin/Users/Modify.html:137 html/User/Prefs.html:126 html/Work/Preferences/Info:85
+msgid "State"
+msgstr "å·ž"
+
+#: html/Elements/MyRequests:30 html/Elements/MyTickets:30 html/Search/Elements/PickRestriction:73 html/SelfService/Elements/MyRequests:28 html/SelfService/Update.html:30 html/Ticket/Create.html:41 html/Ticket/Elements/EditBasics:37 html/Ticket/Elements/ShowBasics:30 html/Ticket/Update.html:59 html/Work/Elements/List:15 html/Work/Elements/MyRequests:18 html/Work/Elements/MyTickets:18 html/Work/Search/PickRestriction:54 html/Work/Tickets/Elements/EditBasics:19 html/Work/Tickets/Update.html:22 lib/RT/Ticket_Overlay.pm:1209 lib/RT/Tickets_Overlay.pm:927
+msgid "Status"
+msgstr "ç¾æ³"
+
+#: etc/initialdata:294
+msgid "Status Change"
+msgstr "ç¾æ³æ”¹è®Šæ™‚"
+
+#: lib/RT/Transaction_Overlay.pm:477
+#. ($self->loc($self->OldValue), $self->loc($self->NewValue))
+msgid "Status changed from %1 to %2"
+msgstr "ç¾æ³å¾ž %1 改為 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "StatusChange"
+msgstr "ç¾æ³æ”¹è®Šæ™‚"
+
+#: html/Ticket/Elements/Tabs:146
+msgid "Steal"
+msgstr "強制更æ›æ‰¿è¾¦äºº"
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "Steal tickets"
+msgstr "強制承辦申請單"
+
+#: lib/RT/Queue_Overlay.pm:91
+msgid "StealTicket"
+msgstr "強制承辦申請單"
+
+#: lib/RT/Transaction_Overlay.pm:545
+#. ($Old->Name)
+msgid "Stolen from %1 "
+msgstr "承辦人從 %1 強制更æ›"
+
+#: html/Edit/Groups/Member:68
+msgid "Subgroup"
+msgstr "å­ç¾¤çµ„"
+
+#: html/Elements/MyRequests:28 html/Elements/MyTickets:28 html/Search/Bulk.html:135 html/Search/Elements/PickRestriction:42 html/SelfService/Create.html:56 html/SelfService/Elements/MyRequests:27 html/SelfService/Update.html:31 html/Ticket/Create.html:83 html/Ticket/Elements/EditBasics:27 html/Ticket/ModifyAll.html:78 html/Ticket/Update.html:75 html/Work/Elements/MyApprovals:9 html/Work/Elements/MyRequests:16 html/Work/Elements/MyTickets:16 html/Work/Search/Bulk.html:87 html/Work/Search/PickRestriction:22 html/Work/Tickets/Elements/AddSubject:8 html/Work/Tickets/Elements/EditBasics:8 html/Work/Tickets/Elements/ShowBasics:36 html/Work/Tickets/Elements/ShowSubject:8 lib/RT/Ticket_Overlay.pm:1205 lib/RT/Tickets_Overlay.pm:1006
+msgid "Subject"
+msgstr "主題"
+
+#: docs/design_docs/string-extraction-guide.txt:89 lib/RT/StyleGuide.pod:795 lib/RT/Transaction_Overlay.pm:567
+#. ($self->Data)
+msgid "Subject changed to %1"
+msgstr "標題已改為 %1"
+
+#: html/Edit/Users/Info:71 html/Elements/Submit:58 html/Work/Search/Bulk.html:103
+msgid "Submit"
+msgstr "é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "Submit Workflow"
+msgstr "é€å‡ºæµç¨‹"
+
+#: lib/RT/Group_Overlay.pm:746
+msgid "Succeeded"
+msgstr "設定æˆåŠŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "Sun"
+msgstr "星期日"
+
+#: lib/RT/Date.pm:394
+msgid "Sun."
+msgstr "星期日"
+
+#: html/Edit/Users/System:17 lib/RT/System.pm:53
+msgid "SuperUser"
+msgstr "系統管ç†å“¡"
+
+#: html/Edit/Global/Basic/Top:29
+msgid "Sync now"
+msgstr "執行åŒæ­¥"
+
+#: html/Edit/Global/Basic/Top:87
+msgid "Sync104HRMS"
+msgstr "自動åŒæ­¥104HRMS"
+
+#: NOT FOUND IN SOURCE
+msgid "Synchronizing HRMS data. This may take a while..."
+msgstr "正在åŒæ­¥åŒ– HRMS 人事系統資料。請ç¨å¾…..."
+
+#: html/User/Elements/DelegateRights:76
+msgid "System"
+msgstr "系統"
+
+#: html/Edit/Global/Scrip/Top:18 html/Edit/Global/Scrip/Top:44
+msgid "System Defined"
+msgstr "系統定義"
+
+#: html/Admin/Elements/SelectRights:81 lib/RT/ACE_Overlay.pm:566 lib/RT/Interface/Web.pm:856 lib/RT/Interface/Web.pm:886
+msgid "System Error"
+msgstr "系統錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. Right not granted."
+msgstr "系統錯誤。設定權é™å¤±æ•—。"
+
+#: NOT FOUND IN SOURCE
+msgid "System Error. right not granted"
+msgstr "系統錯誤。設定權é™å¤±æ•—。"
+
+#: html/Edit/Users/index.html:95
+msgid "System Rights"
+msgstr "系統權é™"
+
+#: lib/RT/ACE_Overlay.pm:615
+msgid "System error. Right not delegated."
+msgstr "系統錯誤。權é™ä»£ç†å¤±æ•—。"
+
+#: lib/RT/ACE_Overlay.pm:145 lib/RT/ACE_Overlay.pm:222 lib/RT/ACE_Overlay.pm:305 lib/RT/ACE_Overlay.pm:897
+msgid "System error. Right not granted."
+msgstr "系統錯誤。設定權é™å¤±æ•—。"
+
+#: NOT FOUND IN SOURCE
+msgid "System error. Unable to grant rights."
+msgstr "系統錯誤。無法設定權é™ã€‚"
+
+#: html/Admin/Global/GroupRights.html:34 html/Admin/Groups/GroupRights.html:36 html/Admin/Queues/GroupRights.html:35
+msgid "System groups"
+msgstr "系統群組"
+
+#: NOT FOUND IN SOURCE
+msgid "SystemInternal"
+msgstr "系統內部用"
+
+#: etc/initialdata:41 etc/initialdata:47 etc/initialdata:53
+msgid "SystemRolegroup for internal use"
+msgstr "內部使用的系統角色群組"
+
+#: lib/RT/CurrentUser.pm:318
+msgid "TEST_STRING"
+msgstr "TEST_STRING"
+
+#: NOT FOUND IN SOURCE
+msgid "TabbedUI"
+msgstr "é ç±¤ä»‹é¢"
+
+#: html/Ticket/Elements/Tabs:142
+msgid "Take"
+msgstr "å—ç†"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "Take tickets"
+msgstr "自行承辦申請單"
+
+#: lib/RT/Queue_Overlay.pm:89
+msgid "TakeTicket"
+msgstr "自行承辦申請單"
+
+#: lib/RT/Transaction_Overlay.pm:530
+msgid "Taken"
+msgstr "å·²å—ç†"
+
+#: html/Admin/Elements/EditScrip:88
+msgid "Template"
+msgstr "範本"
+
+#: html/Admin/Global/Template.html:90 html/Admin/Queues/Template.html:89
+#. ($TemplateObj->Id())
+msgid "Template #%1"
+msgstr "範本 #%1"
+
+#: html/Edit/Global/Template/List:9 html/Edit/Global/Template/Top:17
+msgid "Template Content"
+msgstr "通知範本內容"
+
+#: html/Edit/Global/Template/List:8 html/Edit/Global/Template/Top:13
+msgid "Template Description"
+msgstr "通知範本æè¿°"
+
+#: html/Edit/Global/Template/List:7 html/Edit/Global/Template/Top:9
+msgid "Template Name"
+msgstr "通知範本å稱"
+
+#: html/Admin/Elements/EditTemplates:88
+msgid "Template deleted"
+msgstr "範本已刪除"
+
+#: lib/RT/Scrip_Overlay.pm:156
+msgid "Template not found"
+msgstr "找ä¸åˆ°ç¯„本"
+
+#: NOT FOUND IN SOURCE
+msgid "Template not found\\n"
+msgstr "找ä¸åˆ°ç¯„本\\n"
+
+#: lib/RT/Template_Overlay.pm:359
+msgid "Template parsed"
+msgstr "範本剖æžå®Œç•¢"
+
+#: html/Admin/Elements/QueueTabs:48 html/Admin/Elements/SystemTabs:35 html/Admin/Global/index.html:44
+msgid "Templates"
+msgstr "範本"
+
+#: html/Edit/Global/autohandler:8 html/Edit/Queues/autohandler:23
+msgid "Templates "
+msgstr "通知範本"
+
+#: NOT FOUND IN SOURCE
+msgid "Templates for %1\\n"
+msgstr "找ä¸åˆ° %1 的範本\\n"
+
+#: lib/RT/Interface/Web.pm:954
+msgid "That is already the current value"
+msgstr "已經是目å‰æ¬„ä½çš„值"
+
+#: lib/RT/CustomField_Overlay.pm:242
+msgid "That is not a value for this custom field"
+msgstr "這ä¸æ˜¯è©²è‡ªè¨‚欄ä½çš„值"
+
+#: lib/RT/Ticket_Overlay.pm:1926
+msgid "That is the same value"
+msgstr "åŒæ¨£çš„值"
+
+#: lib/RT/ACE_Overlay.pm:287 lib/RT/ACE_Overlay.pm:596
+msgid "That principal already has that right"
+msgstr "這項單ä½å·²ç¶“æ“有該權é™"
+
+#: lib/RT/Queue_Overlay.pm:633
+#. ($args{'Type'})
+msgid "That principal is already a %1 for this queue"
+msgstr "這項單ä½å·²ç¶“是這個表單的 %1"
+
+#: lib/RT/Ticket_Overlay.pm:1460
+#. ($self->loc($args{'Type'}))
+msgid "That principal is already a %1 for this ticket"
+msgstr "這項單ä½å·²ç¶“是這份申請單的 %1"
+
+#: lib/RT/Queue_Overlay.pm:732
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this queue"
+msgstr "這項單ä½ä¸æ˜¯é€™å€‹è¡¨å–®çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1577
+#. ($args{'Type'})
+msgid "That principal is not a %1 for this ticket"
+msgstr "這項單ä½ä¸æ˜¯é€™ä»½ç”³è«‹å–®çš„ %1"
+
+#: lib/RT/Ticket_Overlay.pm:1922
+msgid "That queue does not exist"
+msgstr "此表單ä¸å­˜åœ¨"
+
+#: lib/RT/Ticket_Overlay.pm:3293
+msgid "That ticket has unresolved dependencies"
+msgstr "這份申請單有尚未解決的附屬申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "That user already has that right"
+msgstr "使用者已具有該項權é™"
+
+#: lib/RT/Ticket_Overlay.pm:3097
+msgid "That user already owns that ticket"
+msgstr "該使用者已經承辦這份申請單"
+
+#: lib/RT/Ticket_Overlay.pm:3069
+msgid "That user does not exist"
+msgstr "使用者ä¸å­˜åœ¨"
+
+#: lib/RT/User_Overlay.pm:381
+msgid "That user is already privileged"
+msgstr "這å使用者已經是內部æˆå“¡"
+
+#: lib/RT/User_Overlay.pm:402
+msgid "That user is already unprivileged"
+msgstr "這å使用者屬於éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
+
+#: lib/RT/User_Overlay.pm:394
+msgid "That user is now privileged"
+msgstr "使用者加入內部æˆå“¡ç¾¤çµ„完畢"
+
+#: lib/RT/User_Overlay.pm:415
+msgid "That user is now unprivileged"
+msgstr "這å使用者已加入éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
+
+#: NOT FOUND IN SOURCE
+msgid "That user is now unprivilegedileged"
+msgstr "這å使用者已加入éžå…§éƒ¨æˆå“¡ç¾¤çµ„"
+
+#: lib/RT/Ticket_Overlay.pm:3090
+msgid "That user may not own tickets in that queue"
+msgstr "使用者å¯èƒ½æ²’有承辦表單裡的申請單"
+
+#: lib/RT/Link_Overlay.pm:205
+msgid "That's not a numerical id"
+msgstr "這ä¸æ˜¯ä¸€å€‹æ•¸å­—編號"
+
+#: html/SelfService/Display.html:31 html/Ticket/Create.html:149 html/Ticket/Elements/ShowSummary:27
+msgid "The Basics"
+msgstr "基本資訊"
+
+#: lib/RT/ACE_Overlay.pm:87
+msgid "The CC of a ticket"
+msgstr "申請單的副本收件人"
+
+#: lib/RT/ACE_Overlay.pm:88
+msgid "The administrative CC of a ticket"
+msgstr "申請單的管ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: lib/RT/Ticket_Overlay.pm:2255
+msgid "The comment has been recorded"
+msgstr "評論已被紀錄"
+
+#: bin/rt-crontool:197
+msgid "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they haven't been touched in 4 hours:"
+msgstr "下列命令會找到 'general' 表單內所有é‹ä½œä¸­çš„申請單,並將其中 4 å°æ™‚內未處ç†çš„申請單優先程度設為 99:"
+
+#: bin/rt-commit-handler:755 bin/rt-commit-handler:765
+msgid "The following commands were not proccessed:\\n\\n"
+msgstr "以下命令未被執行:\\n\\n"
+
+#: lib/RT/Interface/Web.pm:957
+msgid "The new value has been set."
+msgstr "新的欄ä½å€¼è¨­å®šå®Œæˆã€‚"
+
+#: lib/RT/ACE_Overlay.pm:85
+msgid "The owner of a ticket"
+msgstr "申請單的承辦人"
+
+#: lib/RT/ACE_Overlay.pm:86
+msgid "The requestor of a ticket"
+msgstr "申請單的申請人"
+
+#: html/Admin/Elements/EditUserComments:25
+msgid "These comments aren't generally visible to the user"
+msgstr "該使用者ä¸æœƒçœ‹è¦‹é€™äº›è©•è«–"
+
+#: html/Edit/Global/Workflow/Owner.html:32
+msgid "Third-"
+msgstr "三"
+
+#: NOT FOUND IN SOURCE
+msgid "This ticket %1 %2 (%3)\\n"
+msgstr "申請單 %1 %2 (%3)\\n"
+
+#: bin/rt-crontool:188
+msgid "This tool allows the user to run arbitrary perl modules from within RT."
+msgstr "此工具程å¼æœƒè®“使用者經由 RT 執行任æ„命令。"
+
+#: lib/RT/Transaction_Overlay.pm:200
+msgid "This transaction appears to have no content"
+msgstr "此項更動報告沒有內容"
+
+#: html/Ticket/Elements/ShowRequestor:46
+#. ($rows)
+msgid "This user's %1 highest priority tickets"
+msgstr "使用者é€å‡ºçš„å‰ %1 份優先處ç†ç”³è«‹å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "This user's 25 highest priority tickets"
+msgstr "使用者é€å‡ºçš„å‰ 25 份優先處ç†ç”³è«‹å–®"
+
+#: NOT FOUND IN SOURCE
+msgid "Thu"
+msgstr "星期四"
+
+#: lib/RT/Date.pm:391
+msgid "Thu."
+msgstr "星期四"
+
+#: html/Admin/Elements/ModifyTemplateAsWorkflow:163 html/Edit/Global/Workflow/Condition:24
+msgid "Ticket"
+msgstr "申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 %2"
+msgstr "申請單 # %1 %2"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket # %1 Jumbo update: %2"
+msgstr "更新申請單 # %1 的全部資訊:%2"
+
+#: html/Ticket/ModifyAll.html:24 html/Ticket/ModifyAll.html:28
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket #%1 Jumbo update: %2"
+msgstr "更新申請單 #%1 的全部資訊:%2"
+
+#: html/Approvals/Elements/ShowDependency:45
+#. ($link->BaseObj->Id, $link->BaseObj->Subject)
+msgid "Ticket #%1: %2"
+msgstr "申請單 #%1: %2"
+
+#: lib/RT/Ticket_Overlay.pm:632 lib/RT/Ticket_Overlay.pm:653
+#. ($self->Id, $QueueObj->Name)
+msgid "Ticket %1 created in queue '%2'"
+msgstr "申請單 #%1 æˆåŠŸæ–°å¢žæ–¼ '%2' 表單"
+
+#: bin/rt-commit-handler:759
+#. ($Ticket->Id)
+msgid "Ticket %1 loaded\\n"
+msgstr "載入申請單 %1\\n"
+
+#: html/Search/Bulk.html:213 html/Work/Search/Bulk.html:169
+#. ($Ticket->Id,$_)
+msgid "Ticket %1: %2"
+msgstr "申請單 %1:%2"
+
+#: html/Edit/Queues/Basic/Top:30 html/Edit/Queues/List:30 html/Work/Queues/List:9
+msgid "Ticket Due"
+msgstr "表單處ç†æœŸé™"
+
+#: html/Ticket/History.html:24 html/Ticket/History.html:27
+#. ($Ticket->Id, $Ticket->Subject)
+msgid "Ticket History # %1 %2"
+msgstr "申請單處ç†ç´€éŒ„ # %1 %2"
+
+#: html/Work/Elements/List:6
+msgid "Ticket ID"
+msgstr "單號"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Id"
+msgstr "申請單編號"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket Processing Due"
+msgstr "表單é‹è¡ŒæœŸé™"
+
+#: etc/initialdata:309
+msgid "Ticket Resolved"
+msgstr "申請單已解決"
+
+#: html/Edit/Queues/Basic/Top:20 html/Edit/Queues/Category/List:6 html/Edit/Queues/Category/Top:7 html/Edit/Queues/List:21 html/Work/Delegates/List:7 html/Work/Delegates/index.html:11 html/Work/Elements/List:12 html/Work/Elements/SelectSearch:9 html/Work/Queues/List:6 html/Work/Queues/Select.html:12 html/Work/Queues/index.html:11 html/Work/Tickets/Create.html:43 html/Work/Tickets/Elements/ShowBasics:34
+msgid "Ticket Type"
+msgstr "表單種類"
+
+#: html/Search/Elements/PickRestriction:62 html/Work/Search/PickRestriction:43
+msgid "Ticket attachment"
+msgstr "申請單附件"
+
+#: lib/RT/Tickets_Overlay.pm:1185
+msgid "Ticket content"
+msgstr "申請單內容"
+
+#: lib/RT/Tickets_Overlay.pm:1231
+msgid "Ticket content type"
+msgstr "申請單內容類別"
+
+#: lib/RT/Ticket_Overlay.pm:520 lib/RT/Ticket_Overlay.pm:529 lib/RT/Ticket_Overlay.pm:539 lib/RT/Ticket_Overlay.pm:642
+msgid "Ticket could not be created due to an internal error"
+msgstr "內部錯誤,無法新增申請單"
+
+#: lib/RT/Transaction_Overlay.pm:469
+msgid "Ticket created"
+msgstr "申請單新增完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket creation failed"
+msgstr "申請單新增失敗"
+
+#: lib/RT/Transaction_Overlay.pm:474
+msgid "Ticket deleted"
+msgstr "申請單刪除完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket id not found"
+msgstr "找ä¸åˆ°ç”³è«‹å–®ç·¨è™Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket killed"
+msgstr "申請單刪除完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "Ticket not found"
+msgstr "找ä¸åˆ°ç”³è«‹å–®"
+
+#: etc/initialdata:295
+msgid "Ticket status changed"
+msgstr "申請單ç¾æ³å·²æ”¹è®Š"
+
+#: html/Ticket/Update.html:38
+msgid "Ticket watchers"
+msgstr "申請單視察員"
+
+#: html/Elements/Tabs:46
+msgid "Tickets"
+msgstr "申請單"
+
+#: lib/RT/Tickets_Overlay.pm:1402
+#. ($self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}))
+msgid "Tickets %1 %2"
+msgstr "申請單 %1 %2"
+
+#: lib/RT/Tickets_Overlay.pm:1367
+#. ($self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}))
+msgid "Tickets %1 by %2"
+msgstr "申請單 %1 (%2)"
+
+#: NOT FOUND IN SOURCE
+msgid "Tickets I own"
+msgstr "待處ç†çš„申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Tickets I requested"
+msgstr "é€å‡ºçš„申請單"
+
+#: html/Elements/ViewUser:25
+#. ($name)
+msgid "Tickets from %1"
+msgstr "%1 的申請單"
+
+#: html/Approvals/Elements/ShowDependency:26
+msgid "Tickets which depend on this approval:"
+msgstr "批准之後,å¯æŽ¥çºŒè™•ç†ï¼š"
+
+#: html/Ticket/Create.html:156 html/Ticket/Elements/EditBasics:47 html/Work/Tickets/Elements/EditBasics:32
+msgid "Time Left"
+msgstr "剩餘時間"
+
+#: html/Ticket/Create.html:155 html/Ticket/Elements/EditBasics:42 html/Work/Tickets/Elements/EditBasics:24
+msgid "Time Worked"
+msgstr "處ç†æ™‚é–“"
+
+#: lib/RT/Tickets_Overlay.pm:1158
+msgid "Time left"
+msgstr "剩餘時間"
+
+#: html/Elements/Footer:36
+msgid "Time to display"
+msgstr "顯示時間"
+
+#: lib/RT/Tickets_Overlay.pm:1134
+msgid "Time worked"
+msgstr "已處ç†æ™‚é–“"
+
+#: NOT FOUND IN SOURCE
+msgid "TimeLeft"
+msgstr "剩餘時間"
+
+#: lib/RT/Ticket_Overlay.pm:1210
+msgid "TimeWorked"
+msgstr "已處ç†æ™‚é–“"
+
+#: bin/rt-commit-handler:401
+msgid "To generate a diff of this commit:"
+msgstr "產生這次更動的差異檔:"
+
+#: bin/rt-commit-handler:390
+msgid "To generate a diff of this commit:\\n"
+msgstr "產生這次更動的差異檔:\\n"
+
+#: lib/RT/Ticket_Overlay.pm:1213
+msgid "Told"
+msgstr "告知日期"
+
+#: html/Edit/Elements/Page:47
+msgid "Total"
+msgstr "é "
+
+#: etc/initialdata:237
+msgid "Transaction"
+msgstr "æ›´å‹•"
+
+#: lib/RT/Transaction_Overlay.pm:666
+#. ($self->Data)
+msgid "Transaction %1 purged"
+msgstr "清除更動報告 %1"
+
+#: lib/RT/Transaction_Overlay.pm:126
+msgid "Transaction Created"
+msgstr "更動報告已新增"
+
+#: lib/RT/Transaction_Overlay.pm:90
+msgid "Transaction->Create couldn't, as you didn't specify a ticket id"
+msgstr "未指定申請單編號,無法新增更動"
+
+#: NOT FOUND IN SOURCE
+msgid "TransactionBatch"
+msgstr "批次更動時"
+
+#: NOT FOUND IN SOURCE
+msgid "TransactionCreate"
+msgstr "新增更動時"
+
+#: lib/RT/Transaction_Overlay.pm:721
+msgid "Transactions are immutable"
+msgstr "ä¸å¯æ›´æ”¹æ›´å‹•å ±å‘Š"
+
+#: NOT FOUND IN SOURCE
+msgid "Trying to delete a right: %1"
+msgstr "試圖刪除æŸé …權é™ï¼š%1"
+
+#: NOT FOUND IN SOURCE
+msgid "Tue"
+msgstr "星期二"
+
+#: lib/RT/Date.pm:389
+msgid "Tue."
+msgstr "星期二"
+
+#: html/Admin/Elements/EditCustomField:43 html/Admin/Elements/ModifyTemplateAsWorkflow:135 html/Ticket/Elements/AddWatchers:32 html/Ticket/Elements/AddWatchers:43 html/Ticket/Elements/AddWatchers:53 lib/RT/Ticket_Overlay.pm:1211 lib/RT/Tickets_Overlay.pm:978
+msgid "Type"
+msgstr "類別"
+
+#: lib/RT/ScripCondition_Overlay.pm:103
+msgid "Unimplemented"
+msgstr "尚無實作"
+
+#: html/Admin/Users/Modify.html:67
+msgid "Unix login"
+msgstr "外部系統登入帳號"
+
+#: html/Admin/Elements/ModifyUser:61
+msgid "UnixUsername"
+msgstr "外部系統登入帳號"
+
+#: lib/RT/Attachment_Overlay.pm:281 lib/RT/Attachment_Overlay.pm:313
+#. ($self->ContentEncoding)
+msgid "Unknown ContentEncoding %1"
+msgstr "ä¸å¯è§£çš„å…§å®¹æ–‡å­—ç·¨ç¢¼æ–¹å¼ %1"
+
+#: html/Elements/SelectResultsPerPage:36
+msgid "Unlimited"
+msgstr "全數顯示"
+
+#: etc/initialdata:32
+msgid "Unprivileged"
+msgstr "éžå…§éƒ¨æˆå“¡"
+
+#: lib/RT/Transaction_Overlay.pm:526
+msgid "Untaken"
+msgstr "未被å—ç†"
+
+#: html/Edit/Elements/Page:13 html/Edit/Elements/Page:15
+msgid "Up"
+msgstr "上一é "
+
+#: html/Elements/MyTickets:63 html/Search/Bulk.html:32 html/Work/Elements/MyTickets:83 html/Work/Search/Bulk.html:10 html/Work/Tickets/Elements/EditCustomFieldEntries:63
+msgid "Update"
+msgstr "處ç†"
+
+#: html/Admin/Users/Prefs.html:61
+msgid "Update ID"
+msgstr "更新編號"
+
+#: html/Search/Bulk.html:129 html/Ticket/ModifyAll.html:65 html/Ticket/Update.html:65 html/Work/Search/Bulk.html:81 html/Work/Tickets/Update.html:32
+msgid "Update Type"
+msgstr "更新類別"
+
+#: html/Search/Listing.html:60 html/Work/Search/index.html:32
+msgid "Update all these tickets at once"
+msgstr "整批更新申請單"
+
+#: html/Admin/Users/Prefs.html:48
+msgid "Update email"
+msgstr "æ›´æ–°é›»å­éƒµä»¶ä¿¡ç®±"
+
+#: html/Admin/Users/Prefs.html:54
+msgid "Update name"
+msgstr "更新帳號"
+
+#: lib/RT/Interface/Web.pm:467
+msgid "Update not recorded."
+msgstr "更新未被記錄"
+
+#: html/Search/Bulk.html:80 html/Work/Search/Bulk.html:52
+msgid "Update selected tickets"
+msgstr "æ›´æ–°é¸æ“‡çš„申請單"
+
+#: html/Admin/Users/Prefs.html:35
+msgid "Update signature"
+msgstr "更新簽章"
+
+#: html/Ticket/ModifyAll.html:62
+msgid "Update ticket"
+msgstr "更新申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Update ticket # %1"
+msgstr "更新申請單 # %1"
+
+#: html/SelfService/Update.html:24 html/SelfService/Update.html:63
+#. ($Ticket->id)
+msgid "Update ticket #%1"
+msgstr "更新申請單 #%1"
+
+#: html/Ticket/Update.html:139
+#. ($Ticket->id, $Ticket->Subject)
+msgid "Update ticket #%1 (%2)"
+msgstr "更新申請單 #%1 (%2)"
+
+#: lib/RT/Interface/Web.pm:465
+msgid "Update type was neither correspondence nor comment."
+msgstr "更新的內容並éžç”³è«‹å–®å›žè¦†ä¹Ÿä¸æ˜¯è©•è«–"
+
+#: html/Elements/SelectDateType:32 html/Ticket/Elements/ShowDates:51 lib/RT/Ticket_Overlay.pm:1214
+msgid "Updated"
+msgstr "å‰æ¬¡æ›´æ–°"
+
+#: html/Work/Preferences/index.html:15
+msgid "User"
+msgstr "使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 %2: %3\\n"
+msgstr "使用者 %1 %2:%3\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User %1 Password: %2\\n"
+msgstr "使用者 %1 密碼:%2\\n"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'"
+
+#: NOT FOUND IN SOURCE
+msgid "User '%1' not found\\n"
+msgstr "找ä¸åˆ°ä½¿ç”¨è€… '%1'\\n"
+
+#: etc/initialdata:124 etc/initialdata:191
+msgid "User Defined"
+msgstr "使用者自訂"
+
+#: html/Admin/Users/Prefs.html:58
+msgid "User ID"
+msgstr "使用者 ID"
+
+#: html/Edit/Elements/SelectUsers:3 html/Elements/SelectUsers:25
+msgid "User Id"
+msgstr "使用者 ID"
+
+#: html/Edit/Elements/PickUsers:12 html/Edit/Global/UserRight/List:7 html/Edit/Global/UserRight/Top:9 html/Edit/Users/Add.html:13 html/Edit/Users/Search.html:23 html/Work/Delegates/Info:60 html/Work/Tickets/Cc:10
+msgid "User Number"
+msgstr "員工編號"
+
+#: html/Admin/Elements/GroupTabs:46 html/Admin/Elements/QueueTabs:59 html/Admin/Elements/SystemTabs:46 html/Admin/Global/index.html:58 html/Edit/Global/autohandler:11 html/Edit/Queues/autohandler:26
+msgid "User Rights"
+msgstr "使用者權é™"
+
+#: html/Edit/Elements/Tab:34
+msgid "User Setup"
+msgstr "使用者設定"
+
+#: NOT FOUND IN SOURCE
+msgid "User Shift"
+msgstr "å“¡å·¥ç­åˆ¥"
+
+#: html/Admin/Users/Modify.html:225
+#. ($msg)
+msgid "User could not be created: %1"
+msgstr "無法新增使用者:%1"
+
+#: lib/RT/User_Overlay.pm:326
+msgid "User created"
+msgstr "使用者新增完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "User created: %1"
+msgstr "使用者 %1 新增完畢"
+
+#: NOT FOUND IN SOURCE
+msgid "User created: %1 (%2)"
+msgstr "使用者 %1 (%2) 新增完畢"
+
+#: html/Admin/Global/GroupRights.html:66 html/Admin/Groups/GroupRights.html:53 html/Admin/Queues/GroupRights.html:68
+msgid "User defined groups"
+msgstr "使用者定義的群組"
+
+#: lib/RT/User_Overlay.pm:580 lib/RT/User_Overlay.pm:597
+msgid "User loaded"
+msgstr "已載入使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User notified"
+msgstr "已通知使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "User renamed from %1 to %2"
+msgstr "使用者 %1 已改å為 %2"
+
+#: html/Admin/Users/Prefs.html:24 html/Admin/Users/Prefs.html:28
+msgid "User view"
+msgstr "使用者ç§äººè³‡æ–™"
+
+#: NOT FOUND IN SOURCE
+msgid "UserDefined"
+msgstr "使用者自定"
+
+#: html/Admin/Users/Modify.html:47 html/Elements/Login:51 html/Ticket/Elements/AddWatchers:34
+msgid "Username"
+msgstr "帳號"
+
+#: html/Admin/Elements/SelectNewGroupMembers:25 html/Admin/Elements/Tabs:31 html/Admin/Groups/Members.html:54 html/Admin/Queues/People.html:67 html/Admin/index.html:28 html/Edit/Groups/Admin:9 html/User/Groups/Members.html:57 html/Work/Tickets/Elements/ShowTransaction:11
+msgid "Users"
+msgstr "使用者"
+
+#: html/Admin/Users/index.html:64
+msgid "Users matching search criteria"
+msgstr "符åˆæŸ¥è©¢æ¢ä»¶çš„使用者"
+
+#: NOT FOUND IN SOURCE
+msgid "ValueOfQueue"
+msgstr "é¸æ“‡è¡¨å–®"
+
+#: html/Admin/Elements/EditCustomField:56
+msgid "Values"
+msgstr "欄ä½å€¼"
+
+#: lib/RT/Queue_Overlay.pm:81
+msgid "Watch"
+msgstr "視察"
+
+#: lib/RT/Queue_Overlay.pm:82
+msgid "WatchAsAdminCc"
+msgstr "以管ç†å“¡å‰¯æœ¬æ”¶ä»¶äººèº«ä»½è¦–察"
+
+#: NOT FOUND IN SOURCE
+msgid "Watcher loaded"
+msgstr "æˆåŠŸè¼‰å…¥è¦–察員資訊"
+
+#: html/Admin/Elements/QueueTabs:41 html/Edit/Elements/SelectQueues:5
+msgid "Watchers"
+msgstr "視察員"
+
+#: html/Admin/Elements/ModifyUser:55
+msgid "WebEncoding"
+msgstr "網é æ–‡å­—編碼方å¼"
+
+#: NOT FOUND IN SOURCE
+msgid "Wed"
+msgstr "星期三"
+
+#: lib/RT/Date.pm:390
+msgid "Wed."
+msgstr "星期三"
+
+#: etc/initialdata:503 etc/upgrade/2.1.71:161 html/Edit/Elements/CreateApprovalsQueue:135
+msgid "When a ticket has been approved by all approvers, add correspondence to the original ticket"
+msgstr "當申請單通éŽæ‰€æœ‰ç°½æ ¸å¾Œï¼Œå°‡æ­¤è¨Šæ¯å›žè¦†åˆ°åŽŸç”³è«‹å–®"
+
+#: etc/initialdata:467 etc/upgrade/2.1.71:135 html/Edit/Elements/CreateApprovalsQueue:107
+msgid "When a ticket has been approved by any approver, add correspondence to the original ticket"
+msgstr "當申請單通éŽæŸé …簽核後,將此訊æ¯å›žè¦†åˆ°åŽŸç”³è«‹å–®"
+
+#: etc/initialdata:138
+msgid "When a ticket is created"
+msgstr "新增申請單時"
+
+#: etc/initialdata:400 etc/upgrade/2.1.71:79 html/Edit/Elements/CreateApprovalsQueue:51
+msgid "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"
+msgstr "簽核單新增之後,通知應å—ç†çš„承辦人åŠç®¡ç†å“¡å‰¯æœ¬æ”¶ä»¶äºº"
+
+#: etc/initialdata:143
+msgid "When anything happens"
+msgstr "當任何事情發生時"
+
+#: etc/initialdata:184
+msgid "Whenever a ticket is resolved"
+msgstr "當申請單解決時"
+
+#: etc/initialdata:170
+msgid "Whenever a ticket's owner changes"
+msgstr "當申請單更æ›æ‰¿è¾¦äººæ™‚"
+
+#: etc/initialdata:178
+msgid "Whenever a ticket's queue changes"
+msgstr "當申請單更æ›è¡¨å–®æ™‚"
+
+#: etc/initialdata:162
+msgid "Whenever a ticket's status changes"
+msgstr "當申請單更新ç¾æ³æ™‚"
+
+#: etc/initialdata:192
+msgid "Whenever a user-defined condition occurs"
+msgstr "當使用者自訂的情æ³ç™¼ç”Ÿæ™‚"
+
+#: etc/initialdata:156
+msgid "Whenever comments come in"
+msgstr "當評論é€é”時"
+
+#: etc/initialdata:149
+msgid "Whenever correspondence comes in"
+msgstr "當回覆é€é”時"
+
+#: html/Admin/Users/Modify.html:163 html/User/Prefs.html:67 html/Work/Preferences/Info:36
+msgid "Work"
+msgstr "å…¬å¸"
+
+#: html/Admin/Elements/ModifyUser:69
+msgid "WorkPhone"
+msgstr "å…¬å¸é›»è©±"
+
+#: html/Ticket/Elements/ShowBasics:34 html/Ticket/Update.html:64
+msgid "Worked"
+msgstr "處ç†æ™‚é–“"
+
+#: html/Admin/Global/Workflow.html:91 html/Admin/Queues/Workflow.html:89
+#. ($WorkflowObj->Id())
+msgid "Workflow #%1"
+msgstr "æµç¨‹ #%1"
+
+#: html/Edit/Global/Workflow/List:15
+msgid "Workflow Begin"
+msgstr "æµç¨‹é–‹å§‹"
+
+#: html/Edit/Global/Workflow/List:20
+msgid "Workflow End"
+msgstr "æµç¨‹çµæŸ"
+
+#: html/Admin/Elements/EditWorkflows:90
+msgid "Workflow deleted"
+msgstr "æµç¨‹å·²åˆªé™¤"
+
+#: html/Edit/Global/autohandler:10 html/Edit/Queues/autohandler:25
+msgid "Workflows"
+msgstr "æµç¨‹"
+
+#: html/Edit/Global/CustomField/SelectWritable:5
+msgid "Writable"
+msgstr "å¯è®€å¯«"
+
+#: html/autohandler:144
+msgid "XXX CHANGEME You are not an authorized user"
+msgstr "XXX CHANGEME 您是未經授權的使用者"
+
+#: html/Edit/Global/Basic/Top:25 html/Edit/Queues/Basic/Top:82
+msgid "Yes"
+msgstr "是"
+
+#: lib/RT/Ticket_Overlay.pm:3200
+msgid "You already own this ticket"
+msgstr "您已是這份申請單的承辦人"
+
+#: html/autohandler:136
+msgid "You are not an authorized user"
+msgstr "您ä¸æ˜¯è¢«æŽˆæ¬Šçš„使用者"
+
+#: html/Ticket/Elements/ShowTransaction:81
+msgid "You can access it with the Download button on the right."
+msgstr "您å¯ä»¥æŒ‰å³æ–¹çš„「下載ã€éµä¾†å–得。"
+
+#: lib/RT/Ticket_Overlay.pm:3082
+msgid "You can only reassign tickets that you own or that are unowned"
+msgstr "祇能é‡æ–°æŒ‡æ´¾æ‚¨æ‰€æ‰¿è¾¦æˆ–是沒有承辦人的申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "You don't have permission to view that ticket.\\n"
+msgstr "您沒有看那份申請單的權é™ã€‚\\n"
+
+#: docs/design_docs/string-extraction-guide.txt:47 lib/RT/StyleGuide.pod:760
+#. ($num, $queue)
+msgid "You found %1 tickets in queue %2"
+msgstr "您會在表單 %2 找到 %1 的申請單"
+
+#: html/NoAuth/Logout.html:30
+msgid "You have been logged out of RT."
+msgstr "您已登出 RT。"
+
+#: html/SelfService/Display.html:77
+msgid "You have no permission to create tickets in that queue."
+msgstr "您沒有在該表單新增申請單的權é™ã€‚"
+
+#: lib/RT/Ticket_Overlay.pm:1935
+msgid "You may not create requests in that queue."
+msgstr "您ä¸èƒ½åœ¨è©²è¡¨å–®ä¸­æ出申請。"
+
+#: html/Edit/Global/Basic/Top:42
+msgid "You need to restart the Request Tracker service for saved changes to take effect."
+msgstr "您必須é‡æ–°å•Ÿå‹• Request Tracker æœå‹™ï¼Œå„²å­˜çš„更動纔會生效。"
+
+#: html/NoAuth/Logout.html:34
+msgid "You're welcome to login again"
+msgstr "歡迎下次å†ä¾†"
+
+#: NOT FOUND IN SOURCE
+msgid "Your %1 requests"
+msgstr "您æ出的 %1 申請單"
+
+#: NOT FOUND IN SOURCE
+msgid "Your RT administrator has misconfigured the mail aliases which invoke RT"
+msgstr "RT 管ç†å“¡å¯èƒ½è¨­éŒ¯äº†ç”± RT 寄出的郵件收件人標頭檔"
+
+#: etc/initialdata:484 etc/upgrade/2.1.71:146 html/Edit/Elements/CreateApprovalsQueue:119
+msgid "Your request has been approved by %1. Other approvals may still be pending."
+msgstr "申請單已由 %1 批准。å¯èƒ½é‚„有其他待簽核的步驟。"
+
+#: etc/initialdata:522 etc/upgrade/2.1.71:180 html/Edit/Elements/CreateApprovalsQueue:154
+msgid "Your request has been approved."
+msgstr "您的申請單已完æˆç°½æ ¸ç¨‹åºã€‚"
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected"
+msgstr "您的申請單已被é§å›ž"
+
+#: NOT FOUND IN SOURCE
+msgid "Your request was rejected by %1."
+msgstr "您的申請單已被 %1 é§å›žã€‚"
+
+#: etc/initialdata:427 etc/upgrade/2.1.71:101 html/Edit/Elements/CreateApprovalsQueue:73
+msgid "Your request was rejected."
+msgstr "您的申請單已被é§å›žã€‚"
+
+#: html/autohandler:170
+msgid "Your username or password is incorrect"
+msgstr "您的帳號或密碼有誤"
+
+#: html/Admin/Elements/ModifyUser:83 html/Admin/Users/Modify.html:143 html/User/Prefs.html:130 html/Work/Preferences/Info:87
+msgid "Zip"
+msgstr "郵éžå€è™Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "[no subject]"
+msgstr "[沒有標題]"
+
+#: NOT FOUND IN SOURCE
+msgid "ago"
+msgstr "éŽæœŸ"
+
+#: NOT FOUND IN SOURCE
+msgid "alert"
+msgstr "急訊"
+
+#: NOT FOUND IN SOURCE
+msgid "approving"
+msgstr "待簽核"
+
+#: html/User/Elements/DelegateRights:58
+#. ($right->PrincipalObj->Object->SelfDescription)
+msgid "as granted to %1"
+msgstr "權é™åŒ %1"
+
+#: html/SelfService/Closed.html:27
+msgid "closed"
+msgstr "已解決"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:33
+msgid "contains"
+msgstr "包å«"
+
+#: html/Elements/SelectAttachmentField:25
+msgid "content"
+msgstr "內容"
+
+#: html/Elements/SelectAttachmentField:26
+msgid "content-type"
+msgstr "é¡žåž‹"
+
+#: lib/RT/Ticket_Overlay.pm:2326
+msgid "correspondence (probably) not sent"
+msgstr "申請單回覆(å¯èƒ½)未é€å‡º"
+
+#: lib/RT/Ticket_Overlay.pm:2336
+msgid "correspondence sent"
+msgstr "申請單回覆已é€å‡º"
+
+#: NOT FOUND IN SOURCE
+msgid "critical"
+msgstr "åš´é‡"
+
+#: html/Admin/Elements/ModifyQueue:62 html/Admin/Queues/Modify.html:76 html/Edit/Queues/Basic/Top:34 html/Edit/Queues/List:32 html/Work/Queues/List:11 lib/RT/Date.pm:319
+msgid "days"
+msgstr "天"
+
+#: NOT FOUND IN SOURCE
+msgid "dead"
+msgstr "拒絕處ç†"
+
+#: NOT FOUND IN SOURCE
+msgid "debug"
+msgstr "åµéŒ¯"
+
+#: html/Search/Listing.html:74
+msgid "delete"
+msgstr "刪除"
+
+#: lib/RT/Queue_Overlay.pm:62
+msgid "deleted"
+msgstr "已刪除"
+
+#: html/Search/Elements/PickRestriction:67 html/Work/Search/PickRestriction:47
+msgid "does not match"
+msgstr "ä¸ç¬¦åˆ"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:34
+msgid "doesn't contain"
+msgstr "ä¸åŒ…å«"
+
+#: NOT FOUND IN SOURCE
+msgid "emergency"
+msgstr "å±é›£"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "equal to"
+msgstr "等於"
+
+#: NOT FOUND IN SOURCE
+msgid "error"
+msgstr "錯誤"
+
+#: NOT FOUND IN SOURCE
+msgid "false"
+msgstr "å‡"
+
+#: html/Elements/SelectAttachmentField:27
+msgid "filename"
+msgstr "檔å"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "greater than"
+msgstr "大於"
+
+#: lib/RT/Group_Overlay.pm:193
+#. ($self->Name)
+msgid "group '%1'"
+msgstr "群組 '%1'"
+
+#: lib/RT/Date.pm:315
+msgid "hours"
+msgstr "å°æ™‚"
+
+#: NOT FOUND IN SOURCE
+msgid "id"
+msgstr "編號"
+
+#: NOT FOUND IN SOURCE
+msgid "info"
+msgstr "資訊"
+
+#: html/Elements/SelectBoolean:31 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:35 html/Search/Elements/PickRestriction:46 html/Search/Elements/PickRestriction:75 html/Search/Elements/PickRestriction:87 html/Work/Search/PickRestriction:27 html/Work/Search/PickRestriction:56 html/Work/Search/PickRestriction:75
+msgid "is"
+msgstr "是"
+
+#: html/Elements/SelectBoolean:35 html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectMatch:36 html/Search/Elements/PickRestriction:47 html/Search/Elements/PickRestriction:76 html/Search/Elements/PickRestriction:88 html/Work/Search/PickRestriction:28 html/Work/Search/PickRestriction:57 html/Work/Search/PickRestriction:76
+msgid "isn't"
+msgstr "ä¸æ˜¯"
+
+#: html/Elements/SelectCustomFieldOperator:37 html/Elements/SelectEqualityOperator:37
+msgid "less than"
+msgstr "å°æ–¼"
+
+#: html/Edit/Global/Workflow/Owner.html:35
+msgid "level Admins"
+msgstr "層主管"
+
+#: html/Search/Elements/PickRestriction:66 html/Work/Search/PickRestriction:46
+msgid "matches"
+msgstr "符åˆ"
+
+#: lib/RT/Date.pm:311
+msgid "min"
+msgstr "分"
+
+#: html/Ticket/Update.html:64
+msgid "minutes"
+msgstr "分é˜"
+
+#: bin/rt-commit-handler:764
+msgid "modifications\\n\\n"
+msgstr "更改\\n\\n"
+
+#: lib/RT/Date.pm:327
+msgid "months"
+msgstr "月"
+
+#: lib/RT/Queue_Overlay.pm:57
+msgid "new"
+msgstr "新建立"
+
+#: html/Admin/Elements/EditCustomFields:42
+msgid "no name"
+msgstr "沒有å稱"
+
+#: html/Admin/Elements/EditScrips:42
+msgid "no value"
+msgstr "沒有值"
+
+#: html/Admin/Elements/EditQueueWatchers:26 html/Edit/Groups/Member:40 html/Edit/Groups/Members/Add.html:17 html/Edit/Groups/Members/List:8 html/Edit/Queues/List:32 html/Ticket/Elements/EditWatchers:27 html/Work/Delegates/Info:37 html/Work/Delegates/Info:48 html/Work/Overview/Info:31 html/Work/Queues/List:11 html/Work/Tickets/Elements/EditWatchers:5 html/Work/Tickets/Elements/ShowAttachments:30 html/Work/Tickets/Elements/ShowBasics:27
+msgid "none"
+msgstr "ç„¡"
+
+#: html/Elements/SelectEqualityOperator:37
+msgid "not equal to"
+msgstr "ä¸ç­‰æ–¼"
+
+#: NOT FOUND IN SOURCE
+msgid "notice"
+msgstr "æ示"
+
+#: NOT FOUND IN SOURCE
+msgid "notlike"
+msgstr "ä¸ç¬¦åˆ"
+
+#: html/Edit/Elements/PickUsers:17 html/Edit/Users/Add.html:18 html/Edit/Users/Search.html:28 html/Work/Tickets/Cc:15
+msgid "number"
+msgstr "號"
+
+#: html/SelfService/Elements/MyRequests:60 lib/RT/Queue_Overlay.pm:58
+msgid "open"
+msgstr "é–‹å•Ÿ"
+
+#: NOT FOUND IN SOURCE
+msgid "opened"
+msgstr "已開啟"
+
+#: lib/RT/Group_Overlay.pm:198
+#. ($self->Name, $user->Name)
+msgid "personal group '%1' for user '%2'"
+msgstr "使用者「%2ã€çš„「%1ã€ä»£ç†äººç¾¤çµ„"
+
+#: lib/RT/Group_Overlay.pm:206
+#. ($queue->Name, $self->Type)
+msgid "queue %1 %2"
+msgstr "表單 %1 %2"
+
+#: lib/RT/Queue_Overlay.pm:61
+msgid "rejected"
+msgstr "å·²é§å›ž"
+
+#: lib/RT/Queue_Overlay.pm:60
+msgid "resolved"
+msgstr "已處ç†"
+
+#: html/Edit/Global/Basic/Top:53
+msgid "rtname"
+msgstr "伺æœå™¨å稱"
+
+#: lib/RT/Date.pm:307
+msgid "sec"
+msgstr "秒"
+
+#: lib/RT/Queue_Overlay.pm:59
+msgid "stalled"
+msgstr "延宕"
+
+#: lib/RT/Group_Overlay.pm:201
+#. ($self->Type)
+msgid "system %1"
+msgstr "系統 %1"
+
+#: lib/RT/Group_Overlay.pm:212
+#. ($self->Type)
+msgid "system group '%1'"
+msgstr "系統群組 '%1'"
+
+#: html/Elements/Error:42 html/SelfService/Error.html:41
+msgid "the calling component did not specify why"
+msgstr "呼å«å…ƒä»¶æœªæŒ‡æ˜ŽåŽŸå› "
+
+#: lib/RT/URI/fsck_com_rt.pm:234
+#. ($self->Object->Id)
+msgid "ticket #%1"
+msgstr "申請單 #%1"
+
+#: lib/RT/Group_Overlay.pm:209
+#. ($self->Instance, $self->Type)
+msgid "ticket #%1 %2"
+msgstr "申請單 #%1 %2"
+
+#: html/Work/Elements/SelectSearch:28
+msgid "till"
+msgstr "至"
+
+#: html/Edit/Elements/PickUsers:15 html/Edit/Global/Workflow/Condition:31 html/Edit/Users/Add.html:16 html/Edit/Users/Search.html:26 html/Work/Tickets/Cc:13
+msgid "to"
+msgstr "到"
+
+#: NOT FOUND IN SOURCE
+msgid "true"
+msgstr "真"
+
+#: lib/RT/Group_Overlay.pm:215
+#. ($self->Id)
+msgid "undescribed group %1"
+msgstr "沒有æ述的群組 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "unresolved"
+msgstr "未處ç†"
+
+#: lib/RT/Group_Overlay.pm:190
+#. ($user->Object->Name)
+msgid "user %1"
+msgstr "使用者 %1"
+
+#: NOT FOUND IN SOURCE
+msgid "warning"
+msgstr "警告"
+
+#: lib/RT/Date.pm:323
+msgid "weeks"
+msgstr "週"
+
+#: NOT FOUND IN SOURCE
+msgid "with template %1"
+msgstr "範本:%1"
+
+#: lib/RT/Date.pm:331
+msgid "years"
+msgstr "å¹´"
+
diff --git a/rt/lib/RT/Interface/CLI.pm b/rt/lib/RT/Interface/CLI.pm
new file mode 100644
index 0000000..a3c840a
--- /dev/null
+++ b/rt/lib/RT/Interface/CLI.pm
@@ -0,0 +1,246 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+
+use RT;
+package RT::Interface::CLI;
+
+
+
+BEGIN {
+ use Exporter ();
+ use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+ # set the version for version checking
+ $VERSION = do { my @r = (q$Revision: 1.1.1.1 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
+
+ @ISA = qw(Exporter);
+
+ # your exported package globals go here,
+ # as well as any optionally exported functions
+ @EXPORT_OK = qw(&CleanEnv
+ &GetCurrentUser &GetMessageContent &debug &loc);
+}
+
+=head1 NAME
+
+ RT::Interface::CLI - helper functions for creating a commandline RT interface
+
+=head1 SYNOPSIS
+
+ use lib "/path/to/rt/libraries/";
+
+ use RT::Interface::CLI qw(CleanEnv
+ GetCurrentUser GetMessageContent loc);
+
+ #Clean out all the nasties from the environment
+ CleanEnv();
+
+ #let's talk to RT'
+ use RT;
+
+ #Load RT's config file
+ RT::LoadConfig();
+
+ # Connect to the database. set up loggign
+ RT::Init();
+
+ #Get the current user all loaded
+ my $CurrentUser = GetCurrentUser();
+
+ print loc('Hello!'); # Synonym of $CuurentUser->loc('Hello!');
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::Interface::CLI);
+
+=end testing
+
+=cut
+
+
+=head2 CleanEnv
+
+Removes some of the nastiest nasties from the user\'s environment.
+
+=cut
+
+sub CleanEnv {
+ $ENV{'PATH'} = '/bin:/usr/bin'; # or whatever you need
+ $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
+ $ENV{'SHELL'} = '/bin/sh' if defined $ENV{'SHELL'};
+ $ENV{'ENV'} = '' if defined $ENV{'ENV'};
+ $ENV{'IFS'} = '' if defined $ENV{'IFS'};
+}
+
+
+
+
+{
+
+ my $CurrentUser; # shared betwen GetCurrentUser and loc
+
+# {{{ sub GetCurrentUser
+
+=head2 GetCurrentUser
+
+ Figures out the uid of the current user and returns an RT::CurrentUser object
+loaded with that user. if the current user isn't found, returns a copy of RT::Nobody.
+
+=cut
+
+sub GetCurrentUser {
+
+ require RT::CurrentUser;
+
+ #Instantiate a user object
+
+ my $Gecos= ($^O eq 'MSWin32') ? Win32::LoginName() : (getpwuid($<))[0];
+
+ #If the current user is 0, then RT will assume that the User object
+ #is that of the currentuser.
+
+ $CurrentUser = new RT::CurrentUser();
+ $CurrentUser->LoadByGecos($Gecos);
+
+ unless ($CurrentUser->Id) {
+ $RT::Logger->debug("No user with a unix login of '$Gecos' was found. ");
+ }
+
+ return($CurrentUser);
+}
+# }}}
+
+
+# {{{ sub loc
+
+=head2 loc
+
+ Synonym of $CurrentUser->loc().
+
+=cut
+
+sub loc {
+ die "No current user yet" unless $CurrentUser ||= RT::CurrentUser->new;
+ return $CurrentUser->loc(@_);
+}
+# }}}
+
+}
+
+
+# {{{ sub GetMessageContent
+
+=head2 GetMessageContent
+
+Takes two arguments a source file and a boolean "edit". If the source file
+is undef or "", assumes an empty file. Returns an edited file as an
+array of lines.
+
+=cut
+
+sub GetMessageContent {
+ my %args = ( Source => undef,
+ Content => undef,
+ Edit => undef,
+ CurrentUser => undef,
+ @_);
+ my $source = $args{'Source'};
+
+ my $edit = $args{'Edit'};
+
+ my $currentuser = $args{'CurrentUser'};
+ my @lines;
+
+ use File::Temp qw/ tempfile/;
+
+ #Load the sourcefile, if it's been handed to us
+ if ($source) {
+ open (SOURCE, "<$source");
+ @lines = (<SOURCE>);
+ close (SOURCE);
+ }
+ elsif ($args{'Content'}) {
+ @lines = split('\n',$args{'Content'});
+ }
+ #get us a tempfile.
+ my ($fh, $filename) = tempfile();
+
+ #write to a tmpfile
+ for (@lines) {
+ print $fh $_;
+ }
+ close ($fh);
+
+ #Edit the file if we need to
+ if ($edit) {
+
+ unless ($ENV{'EDITOR'}) {
+ $RT::Logger->crit('No $EDITOR variable defined'. "\n");
+ return undef;
+ }
+ system ($ENV{'EDITOR'}, $filename);
+ }
+
+ open (READ, "<$filename");
+ my @newlines = (<READ>);
+ close (READ);
+
+ unlink ($filename) unless (debug());
+ return(\@newlines);
+
+}
+
+# }}}
+
+# {{{ sub debug
+
+sub debug {
+ my $val = shift;
+ my ($debug);
+ if ($val) {
+ $RT::Logger->debug($val."\n");
+ if ($debug) {
+ print STDERR "$val\n";
+ }
+ }
+ if ($debug) {
+ return(1);
+ }
+}
+
+# }}}
+
+
+eval "require RT::Interface::CLI_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/CLI_Vendor.pm});
+eval "require RT::Interface::CLI_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/CLI_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm
new file mode 100755
index 0000000..241f5f3
--- /dev/null
+++ b/rt/lib/RT/Interface/Email.pm
@@ -0,0 +1,760 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Interface::Email;
+
+use strict;
+use Mail::Address;
+use MIME::Entity;
+use RT::EmailParser;
+use File::Temp;
+
+BEGIN {
+ use Exporter ();
+ use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+ # set the version for version checking
+ $VERSION = do { my @r = (q$Revision: 1.1.1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
+
+ @ISA = qw(Exporter);
+
+ # your exported package globals go here,
+ # as well as any optionally exported functions
+ @EXPORT_OK = qw(
+ &CreateUser
+ &GetMessageContent
+ &CheckForLoops
+ &CheckForSuspiciousSender
+ &CheckForAutoGenerated
+ &MailError
+ &ParseCcAddressesFromHead
+ &ParseSenderAddressFromHead
+ &ParseErrorsToAddressFromHead
+ &ParseAddressFromHeader
+ &Gateway);
+
+}
+
+=head1 NAME
+
+ RT::Interface::CLI - helper functions for creating a commandline RT interface
+
+=head1 SYNOPSIS
+
+ use lib "!!RT_LIB_PATH!!";
+ use lib "!!RT_ETC_PATH!!";
+
+ use RT::Interface::Email qw(Gateway CreateUser);
+
+=head1 DESCRIPTION
+
+
+=begin testing
+
+ok(require RT::Interface::Email);
+
+=end testing
+
+
+=head1 METHODS
+
+=cut
+
+
+# {{{ sub CheckForLoops
+
+sub CheckForLoops {
+ my $head = shift;
+
+ #If this instance of RT sent it our, we don't want to take it in
+ my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
+ chomp ($RTLoop); #remove that newline
+ if ($RTLoop eq "$RT::rtname") {
+ return (1);
+ }
+
+ # TODO: We might not trap the case where RT instance A sends a mail
+ # to RT instance B which sends a mail to ...
+ return (undef);
+}
+
+# }}}
+
+# {{{ sub CheckForSuspiciousSender
+
+sub CheckForSuspiciousSender {
+ my $head = shift;
+
+ #if it's from a postmaster or mailer daemon, it's likely a bounce.
+
+ #TODO: better algorithms needed here - there is no standards for
+ #bounces, so it's very difficult to separate them from anything
+ #else. At the other hand, the Return-To address is only ment to be
+ #used as an error channel, we might want to put up a separate
+ #Return-To address which is treated differently.
+
+ #TODO: search through the whole email and find the right Ticket ID.
+
+ my ($From, $junk) = ParseSenderAddressFromHead($head);
+
+ if (($From =~ /^mailer-daemon/i) or
+ ($From =~ /^postmaster/i)){
+ return (1);
+
+ }
+
+ return (undef);
+
+}
+
+# }}}
+
+# {{{ sub CheckForAutoGenerated
+sub CheckForAutoGenerated {
+ my $head = shift;
+
+ my $Precedence = $head->get("Precedence") || "" ;
+ if ($Precedence =~ /^(bulk|junk)/i) {
+ return (1);
+ }
+ else {
+ return (0);
+ }
+}
+
+# }}}
+
+
+# {{{ sub MailError
+sub MailError {
+ my %args = (To => $RT::OwnerEmail,
+ Bcc => undef,
+ From => $RT::CorrespondAddress,
+ Subject => 'There has been an error',
+ Explanation => 'Unexplained error',
+ MIMEObj => undef,
+ Attach => undef,
+ LogLevel => 'crit',
+ @_);
+
+
+ $RT::Logger->log(level => $args{'LogLevel'},
+ message => $args{'Explanation'}
+ );
+ my $entity = MIME::Entity->build( Type =>"multipart/mixed",
+ From => $args{'From'},
+ Bcc => $args{'Bcc'},
+ To => $args{'To'},
+ Subject => $args{'Subject'},
+ 'X-RT-Loop-Prevention' => $RT::rtname,
+ );
+
+ $entity->attach( Data => $args{'Explanation'}."\n");
+
+ my $mimeobj = $args{'MIMEObj'};
+ if ($mimeobj) {
+ $mimeobj->sync_headers();
+ $entity->add_part($mimeobj);
+ }
+
+ if ($args{'Attach'}) {
+ $entity->attach(Data => $args{'Attach'}, Type => 'message/rfc822');
+
+ }
+
+
+ if ($RT::MailCommand eq 'sendmailpipe') {
+ open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0);
+ print MAIL $entity->as_string;
+ close(MAIL);
+ }
+ else {
+ $entity->send($RT::MailCommand, $RT::MailParams);
+ }
+}
+
+# }}}
+
+# {{{ Create User
+
+sub CreateUser {
+ my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_;
+ my $NewUser = RT::User->new($RT::SystemUser);
+
+ my ($Val, $Message) =
+ $NewUser->Create(Name => ($Username || $Address),
+ EmailAddress => $Address,
+ RealName => $Name,
+ Password => undef,
+ Privileged => 0,
+ Comments => 'Autocreated on ticket submission'
+ );
+
+ unless ($Val) {
+
+ # Deal with the race condition of two account creations at once
+ #
+ if ($Username) {
+ $NewUser->LoadByName($Username);
+ }
+
+ unless ($NewUser->Id) {
+ $NewUser->LoadByEmail($Address);
+ }
+
+ unless ($NewUser->Id) {
+ MailError( To => $ErrorsTo,
+ Subject => "User could not be created",
+ Explanation => "User creation failed in mailgateway: $Message",
+ MIMEObj => $entity,
+ LogLevel => 'crit'
+ );
+ }
+ }
+
+ #Load the new user object
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByEmail($Address);
+
+ unless ($CurrentUser->id) {
+ $RT::Logger->warning("Couldn't load user '$Address'.". "giving up");
+ MailError( To => $ErrorsTo,
+ Subject => "User could not be loaded",
+ Explanation => "User '$Address' could not be loaded in the mail gateway",
+ MIMEObj => $entity,
+ LogLevel => 'crit'
+ );
+ }
+
+ return $CurrentUser;
+}
+# }}}
+# {{{ ParseCcAddressesFromHead
+
+=head2 ParseCcAddressesFromHead HASHREF
+
+Takes a hashref object containing QueueObj, Head and CurrentUser objects.
+Returns a list of all email addresses in the To and Cc
+headers b<except> the current Queue\'s email addresses, the CurrentUser\'s
+email address and anything that the configuration sub RT::IsRTAddress matches.
+
+=cut
+
+sub ParseCcAddressesFromHead {
+ my %args = ( Head => undef,
+ QueueObj => undef,
+ CurrentUser => undef,
+ @_ );
+
+ my (@Addresses);
+
+ my @ToObjs = Mail::Address->parse($args{'Head'}->get('To'));
+ my @CcObjs = Mail::Address->parse($args{'Head'}->get('Cc'));
+
+ foreach my $AddrObj (@ToObjs, @CcObjs) {
+ my $Address = $AddrObj->address;
+ $Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address);
+ next if ($args{'CurrentUser'}->EmailAddress =~ /^$Address$/i);
+ next if ($args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i);
+ next if ($args{'QueueObj'}->CommentAddress =~ /^$Address$/i);
+ next if (RT::EmailParser::IsRTAddress(undef, $Address));
+
+ push (@Addresses, $Address);
+ }
+ return (@Addresses);
+}
+
+
+# }}}
+
+# {{{ ParseSenderAdddressFromHead
+
+=head2 ParseSenderAddressFromHead
+
+Takes a MIME::Header object. Returns a tuple: (user@host, friendly name)
+of the From (evaluated in order of Reply-To:, From:, Sender)
+
+=cut
+
+sub ParseSenderAddressFromHead {
+ my $head = shift;
+ #Figure out who's sending this message.
+ my $From = $head->get('Reply-To') ||
+ $head->get('From') ||
+ $head->get('Sender');
+ return (ParseAddressFromHeader($From));
+}
+# }}}
+
+# {{{ ParseErrorsToAdddressFromHead
+
+=head2 ParseErrorsToAddressFromHead
+
+Takes a MIME::Header object. Return a single value : user@host
+of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender)
+
+=cut
+
+sub ParseErrorsToAddressFromHead {
+ my $head = shift;
+ #Figure out who's sending this message.
+
+ foreach my $header ('Errors-To' , 'Reply-To', 'From', 'Sender' ) {
+ # If there's a header of that name
+ my $headerobj = $head->get($header);
+ if ($headerobj) {
+ my ($addr, $name ) = ParseAddressFromHeader($headerobj);
+ # If it's got actual useful content...
+ return ($addr) if ($addr);
+ }
+ }
+}
+# }}}
+
+# {{{ ParseAddressFromHeader
+
+=head2 ParseAddressFromHeader ADDRESS
+
+Takes an address from $head->get('Line') and returns a tuple: user@host, friendly name
+
+=cut
+
+
+sub ParseAddressFromHeader{
+ my $Addr = shift;
+
+ my @Addresses = Mail::Address->parse($Addr);
+
+ my $AddrObj = $Addresses[0];
+
+ unless (ref($AddrObj)) {
+ return(undef,undef);
+ }
+
+ my $Name = ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address);
+
+ #Lets take the from and load a user object.
+ my $Address = $AddrObj->address;
+
+ return ($Address, $Name);
+}
+# }}}
+
+
+
+=head2 Gateway ARGSREF
+
+
+Takes parameters:
+
+ action
+ queue
+ message
+
+
+This performs all the "guts" of the mail rt-mailgate program, and is
+designed to be called from the web interface with a message, user
+object, and so on.
+
+Returns:
+
+ An array of:
+
+ (status code, message, optional ticket object)
+
+ status code is a numeric value.
+
+ for temporary failures, status code should be -75
+
+ for permanent failures which are handled by RT, status code should be 0
+
+ for succces, the status code should be 1
+
+
+
+=cut
+
+sub Gateway {
+ my $argsref = shift;
+
+ my %args = %$argsref;
+
+ # Set some reasonable defaults
+ $args{'action'} = 'correspond' unless ( $args{'action'} );
+ $args{'queue'} = '1' unless ( $args{'queue'} );
+
+ # Validate the action
+ unless ( $args{'action'} =~ /^(comment|correspond|action)$/ ) {
+
+ # Can't safely loc this. What object do we loc around?
+ $RT::Logger->crit("Mail gateway called with an invalid action paramenter '".$args{'action'}."' for queue '".$args{'queue'}."'");
+
+ return ( -75, "Invalid 'action' parameter", undef );
+ }
+
+ my $parser = RT::EmailParser->new();
+ my ( $fh, $temp_file );
+ for ( 1 .. 10 ) {
+
+ # on NFS and NTFS, it is possible that tempfile() conflicts
+ # with other processes, causing a race condition. we try to
+ # accommodate this by pausing and retrying.
+ last if ( $fh, $temp_file ) = eval { File::Temp::tempfile(undef, UNLINK => 0) };
+ sleep 1;
+ }
+ if ($fh) {
+ binmode $fh; #thank you, windows
+ $fh->autoflush(1);
+ print $fh $args{'message'};
+ close($fh);
+
+ if ( -f $temp_file ) {
+ $parser->ParseMIMEEntityFromFile($temp_file);
+ File::Temp::unlink0( $fh, $temp_file );
+ if ($parser->Entity) {
+ delete $args{'message'};
+ }
+ }
+
+ }
+
+ #If for some reason we weren't able to parse the message using a temp file
+ # try it with a scalar
+ if ($args{'message'}) {
+ $parser->ParseMIMEEntityFromScalar($args{'message'});
+
+ }
+
+ if (!$parser->Entity()) {
+ MailError(
+ To => $RT::OwnerEmail,
+ Subject => "RT Bounce: Unparseable message",
+ Explanation => "RT couldn't process the message below",
+ Attach => $args{'message'}
+ );
+
+ return(0,"Failed to parse this message. Something is likely badly wrong with the message");
+ }
+
+ my $Message = $parser->Entity();
+ my $head = $Message->head;
+
+ my ( $CurrentUser, $AuthStat, $status, $error );
+
+ # Initalize AuthStat so comparisons work correctly
+ $AuthStat = -9999999;
+
+ my $ErrorsTo = ParseErrorsToAddressFromHead($head);
+
+ my $MessageId = $head->get('Message-Id')
+ || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";
+
+ #Pull apart the subject line
+ my $Subject = $head->get('Subject') || '';
+ chomp $Subject;
+
+ $args{'ticket'} ||= $parser->ParseTicketId($Subject);
+
+ my $SystemTicket;
+ if ( $args{'ticket'} ) {
+ $SystemTicket = RT::Ticket->new($RT::SystemUser);
+ $SystemTicket->Load( $args{'ticket'} );
+ }
+
+ #Set up a queue object
+ my $SystemQueueObj = RT::Queue->new($RT::SystemUser);
+ $SystemQueueObj->Load( $args{'queue'} );
+
+ # We can safely have no queue of we have a known-good ticket
+ unless ( $args{'ticket'} || $SystemQueueObj->id ) {
+ return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
+ }
+
+ # Authentication Level
+ # -1 - Get out. this user has been explicitly declined
+ # 0 - User may not do anything (Not used at the moment)
+ # 1 - Normal user
+ # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
+
+ push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins;
+
+ # Since this needs loading, no matter what
+
+ for (@RT::MailPlugins) {
+ my $Code;
+ my $NewAuthStat;
+ if ( ref($_) eq "CODE" ) {
+ $Code = $_;
+ }
+ else {
+ $_ = "RT::Interface::Email::$_" unless /^RT::Interface::Email::/;
+ eval "require $_;";
+ if ($@) {
+ die ("Couldn't load module $_: $@");
+ next;
+ }
+ no strict 'refs';
+ if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) {
+ die ("No GetCurrentUser code found in $_ module");
+ next;
+ }
+ }
+
+ ( $CurrentUser, $NewAuthStat ) = $Code->(
+ Message => $Message,
+ CurrentUser => $CurrentUser,
+ AuthLevel => $AuthStat,
+ Action => $args{'action'},
+ Ticket => $SystemTicket,
+ Queue => $SystemQueueObj
+ );
+
+ # If a module returns a "-1" then we discard the ticket, so.
+ $AuthStat = -1 if $NewAuthStat == -1;
+
+ # You get the highest level of authentication you were assigned.
+ $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat;
+ last if $AuthStat == -1;
+ }
+
+ # {{{ If authentication fails and no new user was created, get out.
+ if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) {
+
+ # If the plugins refused to create one, they lose.
+ unless ( $AuthStat == -1 ) {
+
+ # Notify the RT Admin of the failure.
+ # XXX Should this be configurable?
+ MailError(
+ To => $RT::OwnerEmail,
+ Subject => "Could not load a valid user",
+ Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for this email ($ErrorsTo).
+
+You might need to grant 'Everyone' the right 'CreateTicket' for the
+queue @{[$args{'queue'}]}.
+
+EOT
+ MIMEObj => $Message,
+ LogLevel => 'error'
+ );
+
+ # Also notify the requestor that his request has been dropped.
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Could not load a valid user",
+ Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for your email.
+
+EOT
+ MIMEObj => $Message,
+ LogLevel => 'error'
+ );
+ }
+ return ( 0, "Could not load a valid user", undef );
+ }
+
+ # }}}
+
+ # {{{ Lets check for mail loops of various sorts.
+ my $IsAutoGenerated = CheckForAutoGenerated($head);
+
+ my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+
+ my $IsALoop = CheckForLoops($head);
+
+ my $SquelchReplies = 0;
+
+ #If the message is autogenerated, we need to know, so we can not
+ # send mail to the sender
+ if ( $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
+ $SquelchReplies = 1;
+ $ErrorsTo = $RT::OwnerEmail;
+ }
+
+ # }}}
+
+ # {{{ Drop it if it's disallowed
+ if ( $AuthStat == 0 ) {
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Permission Denied",
+ Explanation => "You do not have permission to communicate with RT",
+ MIMEObj => $Message
+ );
+ }
+
+ # }}}
+ # {{{ Warn someone if it's a loop
+
+ # Warn someone if it's a loop, before we drop it on the ground
+ if ($IsALoop) {
+ $RT::Logger->crit("RT Recieved mail ($MessageId) from itself.");
+
+ #Should we mail it to RTOwner?
+ if ($RT::LoopsToRTOwner) {
+ MailError(
+ To => $RT::OwnerEmail,
+ Subject => "RT Bounce: $Subject",
+ Explanation => "RT thinks this message may be a bounce",
+ MIMEObj => $Message
+ );
+
+ #Do we actually want to store it?
+ return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops);
+ }
+ }
+
+ # }}}
+
+ # {{{ Squelch replies if necessary
+ # Don't let the user stuff the RT-Squelch-Replies-To header.
+ if ( $head->get('RT-Squelch-Replies-To') ) {
+ $head->add(
+ 'RT-Relocated-Squelch-Replies-To',
+ $head->get('RT-Squelch-Replies-To')
+ );
+ $head->delete('RT-Squelch-Replies-To');
+ }
+
+ if ($SquelchReplies) {
+ ## TODO: This is a hack. It should be some other way to
+ ## indicate that the transaction should be "silent".
+
+ my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
+ $head->add( 'RT-Squelch-Replies-To', $Sender );
+ }
+
+ # }}}
+
+ my $Ticket = RT::Ticket->new($CurrentUser);
+
+ # {{{ If we don't have a ticket Id, we're creating a new ticket
+ if ( !$args{'ticket'} ) {
+
+ # {{{ Create a new ticket
+
+ my @Cc;
+ my @Requestors = ( $CurrentUser->id );
+
+ if ($RT::ParseNewMessageForTicketCcs) {
+ @Cc = ParseCcAddressesFromHead(
+ Head => $head,
+ CurrentUser => $CurrentUser,
+ QueueObj => $SystemQueueObj
+ );
+ }
+
+ my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
+ Queue => $SystemQueueObj->Id,
+ Subject => $Subject,
+ Requestor => \@Requestors,
+ Cc => \@Cc,
+ MIMEObj => $Message
+ );
+ if ( $id == 0 ) {
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Ticket creation failed",
+ Explanation => $ErrStr,
+ MIMEObj => $Message
+ );
+ $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr ");
+ return ( 0, "Ticket creation failed", $Ticket );
+ }
+
+ # }}}
+ }
+
+ # }}}
+
+ # If the action is comment, add a comment.
+ elsif ( $args{'action'} =~ /^(comment|correspond)$/i ) {
+ $Ticket->Load( $args{'ticket'} );
+ unless ( $Ticket->Id ) {
+ my $message = "Could not find a ticket with id " . $args{'ticket'};
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Message not recorded",
+ Explanation => $message,
+ MIMEObj => $Message
+ );
+
+ return ( 0, $message );
+ }
+
+ my ( $status, $msg );
+ if ( $args{'action'} =~ /^correspond$/ ) {
+ ( $status, $msg ) = $Ticket->Correspond( MIMEObj => $Message );
+ }
+ else {
+ ( $status, $msg ) = $Ticket->Comment( MIMEObj => $Message );
+ }
+ unless ($status) {
+
+ #Warn the sender that we couldn't actually submit the comment.
+ MailError(
+ To => $ErrorsTo,
+ Subject => "Message not recorded",
+ Explanation => $msg,
+ MIMEObj => $Message
+ );
+ return ( 0, "Message not recorded", $Ticket );
+ }
+ }
+
+ else {
+
+ #Return mail to the sender with an error
+ MailError(
+ To => $ErrorsTo,
+ Subject => "RT Configuration error",
+ Explanation => "'"
+ . $args{'action'}
+ . "' not a recognized action."
+ . " Your RT administrator has misconfigured "
+ . "the mail aliases which invoke RT",
+ MIMEObj => $Message
+ );
+ $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" );
+ return (
+ -75,
+ "Configuration error: "
+ . $args{'action'}
+ . " not a recognized action",
+ $Ticket
+ );
+
+ }
+
+ return ( 1, "Success", $Ticket );
+}
+
+eval "require RT::Interface::Email_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm});
+eval "require RT::Interface::Email_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Interface/Email/Auth/MailFrom.pm b/rt/lib/RT/Interface/Email/Auth/MailFrom.pm
new file mode 100644
index 0000000..eb778ff
--- /dev/null
+++ b/rt/lib/RT/Interface/Email/Auth/MailFrom.pm
@@ -0,0 +1,131 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Interface::Email::Auth::MailFrom;
+use RT::Interface::Email qw(ParseSenderAddressFromHead CreateUser);
+
+# This is what the ordinary, non-enhanced gateway does at the moment.
+
+sub GetCurrentUser {
+ my %args = ( Message => undef,
+ CurrentUser => undef,
+ AuthLevel => undef,
+ Ticket => undef,
+ Queue => undef,
+ Action => undef,
+ @_ );
+
+ # We don't need to do any external lookups
+ my ( $Address, $Name ) = ParseSenderAddressFromHead( $args{'Message'}->head );
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByEmail($Address);
+
+ unless ( $CurrentUser->Id ) {
+ $CurrentUser->LoadByName($Address);
+ }
+
+ if ( $CurrentUser->Id ) {
+ return ( $CurrentUser, 1 );
+ }
+
+
+
+ # If the user can't be loaded, we may need to create one. Figure out the acl situation.
+ my $unpriv = RT::Group->new($RT::SystemUser);
+ $unpriv->LoadSystemInternalGroup('Unprivileged');
+ unless ( $unpriv->Id ) {
+ $RT::Logger->crit( "Auth::MailFrom couldn't find the 'Unprivileged' internal group" );
+ return ( $args{'CurrentUser'}, -1 );
+ }
+
+ my $everyone = RT::Group->new($RT::SystemUser);
+ $everyone->LoadSystemInternalGroup('Everyone');
+ unless ( $everyone->Id ) {
+ $RT::Logger->crit( "Auth::MailFrom couldn't find the 'Everyone' internal group");
+ return ( $args{'CurrentUser'}, -1 );
+ }
+
+ # but before we do that, we need to make sure that the created user would have the right
+ # to do what we're doing.
+ if ( $args{'Ticket'} && $args{'Ticket'}->Id ) {
+ # We have a ticket. that means we're commenting or corresponding
+ if ( $args{'Action'} =~ /^comment$/i ) {
+
+ # check to see whether "Everybody" or "Unprivileged users" can comment on tickets
+ unless ( $everyone->PrincipalObj->HasRight(
+ Object => $args{'Queue'},
+ Right => 'CommentOnTicket'
+ )
+ || $unpriv->PrincipalObj->HasRight(
+ Object => $args{'Queue'},
+ Right => 'CommentOnTicket'
+ )
+ ) {
+ return ( $args{'CurrentUser'}, 0 );
+ }
+ }
+ elsif ( $args{'Action'} =~ /^correspond$/i ) {
+
+ # check to see whether "Everybody" or "Unprivileged users" can correspond on tickets
+ unless ( $everyone->PrincipalObj->HasRight(Object => $args{'Queue'},
+ Right => 'ReplyToTicket'
+ )
+ || $unpriv->PrincipalObj->HasRight(
+ Object => $args{'Queue'},
+ Right => 'ReplyToTicket'
+ )
+ ) {
+ return ( $args{'CurrentUser'}, 0 );
+ }
+
+ }
+ else {
+ return ( $args{'CurrentUser'}, 0 );
+ }
+ }
+
+ # We're creating a ticket
+ elsif ( $args{'Queue'} && $args{'Queue'}->Id ) {
+
+ # check to see whether "Everybody" or "Unprivileged users" can create tickets in this queue
+ unless ( $everyone->PrincipalObj->HasRight( Object => $args{'Queue'},
+ Right => 'CreateTicket' )
+ || $unpriv->PrincipalObj->HasRight( Object => $args{'Queue'},
+ Right => 'CreateTicket' )
+ ) {
+ return ( $args{'CurrentUser'}, 0 );
+ }
+
+ }
+
+ $CurrentUser = CreateUser( undef, $Address, $Name, $args{'Message'} );
+
+ return ( $CurrentUser, 1 );
+}
+
+eval "require RT::Interface::Email::Auth::MailFrom_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/MailFrom_Vendor.pm});
+eval "require RT::Interface::Email::Auth::MailFrom_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/MailFrom_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm b/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm
new file mode 100644
index 0000000..f00e2d8
--- /dev/null
+++ b/rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm
@@ -0,0 +1,63 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::Interface::Email::Filter::SpamAssassin;
+
+use Mail::SpamAssassin;
+my $spamtest = Mail::SpamAssassin->new();
+
+sub GetCurrentUser {
+ my $item = shift;
+ my $status = $spamtest->check ($item);
+ return (undef, 0) unless $status->is_spam();
+ eval { $status->rewrite_mail() };
+ if ($status->get_hits > $status->get_required_hits()*1.5) {
+ # Spammy indeed
+ return (undef, -1);
+ }
+ return (undef, 0);
+}
+
+=head1 NAME
+
+RT::Interface::Email::Filter::SpamAssassin - Spam filter for RT
+
+=head1 SYNOPSIS
+
+ @RT::MailPlugins = ("Filter::SpamAssassin", ...);
+
+=head1 DESCRIPTION
+
+This plugin checks to see if an incoming mail is spam (using
+C<spamassassin>) and if so, rewrites its headers. If the mail is very
+definitely spam - 1.5x more hits than required - then it is dropped on
+the floor; otherwise, it is passed on as normal.
+
+=cut
+
+eval "require RT::Interface::Email::Filter::SpamAssassin_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Vendor.pm});
+eval "require RT::Interface::Email::Filter::SpamAssassin_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email/Filter/SpamAssassin_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Interface/REST.pm b/rt/lib/RT/Interface/REST.pm
new file mode 100644
index 0000000..1ec4f21
--- /dev/null
+++ b/rt/lib/RT/Interface/REST.pm
@@ -0,0 +1,252 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# lib/RT/Interface/REST.pm
+#
+
+package RT::Interface::REST;
+use strict;
+use RT;
+
+BEGIN {
+ use Exporter ();
+ use vars qw($VERSION @ISA @EXPORT);
+
+ $VERSION = do { my @r = (q$Revision: 1.1 $ =~ /\d+/g); sprintf "%d."."%02d"x$#r, @r };
+
+ @ISA = qw(Exporter);
+ @EXPORT = qw(expand_list form_parse form_compose vpush vsplit);
+}
+
+my $field = '[a-zA-Z][a-zA-Z0-9_-]*';
+
+sub expand_list {
+ my ($list) = @_;
+ my ($elt, @elts, %elts);
+
+ foreach $elt (split /,/, $list) {
+ if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) }
+ else { push @elts, $elt }
+ }
+
+ @elts{@elts}=();
+ return sort {$a<=>$b} keys %elts;
+}
+
+# Returns a reference to an array of parsed forms.
+sub form_parse {
+ my $state = 0;
+ my @forms = ();
+ my @lines = split /\n/, $_[0];
+ my ($c, $o, $k, $e) = ("", [], {}, "");
+
+ LINE:
+ while (@lines) {
+ my $line = shift @lines;
+
+ next LINE if $line eq '';
+
+ if ($line eq '--') {
+ # We reached the end of one form. We'll ignore it if it was
+ # empty, and store it otherwise, errors and all.
+ if ($e || $c || @$o) {
+ push @forms, [ $c, $o, $k, $e ];
+ $c = ""; $o = []; $k = {}; $e = "";
+ }
+ $state = 0;
+ }
+ elsif ($state != -1) {
+ if ($state == 0 && $line =~ /^#/) {
+ # Read an optional block of comments (only) at the start
+ # of the form.
+ $state = 1;
+ $c = $line;
+ while (@lines && $lines[0] =~ /^#/) {
+ $c .= "\n".shift @lines;
+ }
+ $c .= "\n";
+ }
+ elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) {
+ # Read a field: value specification.
+ my $f = $1;
+ my @v = ($2 || ());
+
+ # Read continuation lines, if any.
+ while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) {
+ push @v, shift @lines;
+ }
+ pop @v while (@v && $v[-1] eq '');
+
+ # Strip longest common leading indent from text.
+ my ($ws, $ls) = ("");
+ foreach $ls (map {/^(\s+)/} @v[1..$#v]) {
+ $ws = $ls if (!$ws || length($ls) < length($ws));
+ }
+ s/^$ws// foreach @v;
+
+ push(@$o, $f) unless exists $k->{$f};
+ vpush($k, $f, join("\n", @v));
+
+ $state = 1;
+ }
+ elsif ($line !~ /^#/) {
+ # We've found a syntax error, so we'll reconstruct the
+ # form parsed thus far, and add an error marker. (>>)
+ $state = -1;
+ $e = form_compose([[ "", $o, $k, "" ]]);
+ $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n";
+ }
+ }
+ else {
+ # We saw a syntax error earlier, so we'll accumulate the
+ # contents of this form until the end.
+ $e .= "$line\n";
+ }
+ }
+ push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o);
+
+ my $l;
+ foreach $l (keys %$k) {
+ $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY');
+ }
+
+ return \@forms;
+}
+
+# Returns text representing a set of forms.
+sub form_compose {
+ my ($forms) = @_;
+ my (@text, $form);
+
+ foreach $form (@$forms) {
+ my ($c, $o, $k, $e) = @$form;
+ my $text = "";
+
+ if ($c) {
+ $c =~ s/\n*$/\n/;
+ $text = "$c\n";
+ }
+ if ($e) {
+ $text .= $e;
+ }
+ elsif ($o) {
+ my (@lines, $key);
+
+ foreach $key (@$o) {
+ my ($line, $sp, $v);
+ my @values = (ref $k->{$key} eq 'ARRAY') ?
+ @{ $k->{$key} } :
+ $k->{$key};
+
+ $sp = " "x(length("$key: "));
+ $sp = " "x4 if length($sp) > 16;
+
+ foreach $v (@values) {
+ if ($v =~ /\n/) {
+ $v =~ s/^/$sp/gm;
+ $v =~ s/^$sp//;
+
+ if ($line) {
+ push @lines, "$line\n\n";
+ $line = "";
+ }
+ elsif (@lines && $lines[-1] !~ /\n\n$/) {
+ $lines[-1] .= "\n";
+ }
+ push @lines, "$key: $v\n\n";
+ }
+ elsif ($line &&
+ length($line)+length($v)-rindex($line, "\n") >= 70)
+ {
+ $line .= ",\n$sp$v";
+ }
+ else {
+ $line = $line ? "$line, $v" : "$key: $v";
+ }
+ }
+
+ $line = "$key:" unless @values;
+ if ($line) {
+ if ($line =~ /\n/) {
+ if (@lines && $lines[-1] !~ /\n\n$/) {
+ $lines[-1] .= "\n";
+ }
+ $line .= "\n";
+ }
+ push @lines, "$line\n";
+ }
+ }
+
+ $text .= join "", @lines;
+ }
+ else {
+ chomp $text;
+ }
+ push @text, $text;
+ }
+
+ return join "\n--\n\n", @text;
+}
+
+# Add a value to a (possibly multi-valued) hash key.
+sub vpush {
+ my ($hash, $key, $val) = @_;
+ my @val = ref $val eq 'ARRAY' ? @$val : $val;
+
+ if (exists $hash->{$key}) {
+ unless (ref $hash->{$key} eq 'ARRAY') {
+ my @v = $hash->{$key} ne '' ? $hash->{$key} : ();
+ $hash->{$key} = \@v;
+ }
+ push @{ $hash->{$key} }, @val;
+ }
+ else {
+ $hash->{$key} = $val;
+ }
+}
+
+# "Normalise" a hash key that's known to be multi-valued.
+sub vsplit {
+ my ($val) = @_;
+ my ($line, $word, @words);
+
+ foreach $line (map {split /\n/} (ref $val eq 'ARRAY') ? @$val : $val)
+ {
+ # XXX: This should become a real parser, à la Text::ParseWords.
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
+ push @words, split /\s*,\s*/, $line;
+ }
+
+ return \@words;
+}
+
+1;
+
+=head1 NAME
+
+ RT::Interface::REST - helper functions for the REST interface.
+
+=head1 SYNOPSIS
+
+ Only the REST should use this module.
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
new file mode 100644
index 0000000..8d66239
--- /dev/null
+++ b/rt/lib/RT/Interface/Web.pm
@@ -0,0 +1,1453 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+## Portions Copyright 2000 Tobias Brox <tobix@fsck.com>
+
+## This is a library of static subs to be used by the Mason web
+## interface to RT
+
+
+=head1 NAME
+
+RT::Interface::Web
+
+=begin testing
+
+use_ok(RT::Interface::Web);
+
+=end testing
+
+=cut
+
+
+package RT::Interface::Web;
+use strict;
+
+
+
+
+
+# {{{ sub NewApacheHandler
+
+=head2 NewApacheHandler
+
+ Takes extra options to pass to HTML::Mason::ApacheHandler->new
+ Returns a new Mason::ApacheHandler object
+
+=cut
+
+sub NewApacheHandler {
+ require HTML::Mason::ApacheHandler;
+ my $ah = new HTML::Mason::ApacheHandler(
+
+ comp_root => [
+ [ local => $RT::MasonLocalComponentRoot ],
+ [ standard => $RT::MasonComponentRoot ]
+ ],
+ args_method => "CGI",
+ default_escape_flags => 'h',
+ allow_globals => [qw(%session)],
+ data_dir => "$RT::MasonDataDir",
+ autoflush => 1,
+ @_
+ );
+
+ $ah->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+
+ return ($ah);
+}
+
+# }}}
+
+# {{{ sub NewCGIHandler
+
+=head2 NewCGIHandler
+
+ Returns a new Mason::CGIHandler object
+
+=cut
+
+sub NewCGIHandler {
+ my %args = (
+ @_
+ );
+
+ my $handler = HTML::Mason::CGIHandler->new(
+ comp_root => [
+ [ local => $RT::MasonLocalComponentRoot ],
+ [ standard => $RT::MasonComponentRoot ]
+ ],
+ data_dir => "$RT::MasonDataDir",
+ default_escape_flags => 'h',
+ allow_globals => [qw(%session)],
+ autoflush => 1,
+ );
+
+
+ $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+
+
+ return ($handler);
+
+}
+# }}}
+
+
+# {{{ EscapeUTF8
+
+=head2 EscapeUTF8 SCALARREF
+
+does a css-busting but minimalist escaping of whatever html you're passing in.
+
+=cut
+
+sub EscapeUTF8 {
+ my $ref = shift;
+ my $val = $$ref;
+ use bytes;
+ $val =~ s/&/&#38;/g;
+ $val =~ s/</&lt;/g;
+ $val =~ s/>/&gt;/g;
+ $val =~ s/\(/&#40;/g;
+ $val =~ s/\)/&#41;/g;
+ $val =~ s/"/&#34;/g;
+ $val =~ s/'/&#39;/g;
+ $$ref = $val;
+ Encode::_utf8_on($$ref);
+
+}
+
+# }}}
+
+# {{{ WebCanonicalizeInfo
+
+=head2 WebCanonicalizeInfo();
+
+Different web servers set different environmental varibles. This
+function must return something suitable for REMOTE_USER. By default,
+just downcase $ENV{'REMOTE_USER'}
+
+=cut
+
+sub WebCanonicalizeInfo {
+ my $user;
+
+ if ( defined $ENV{'REMOTE_USER'} ) {
+ $user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) );
+ }
+
+ return $user;
+}
+
+# }}}
+
+# {{{ WebExternalAutoInfo
+
+=head2 WebExternalAutoInfo($user);
+
+Returns a hash of user attributes, used when WebExternalAuto is set.
+
+=cut
+
+sub WebExternalAutoInfo {
+ my $user = shift;
+
+ my %user_info;
+
+ $user_info{'Privileged'} = 1;
+
+ if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) {
+ # Populate fields with information from Unix /etc/passwd
+
+ my ($comments, $realname) = (getpwnam($user))[5, 6];
+ $user_info{'Comments'} = $comments if defined $comments;
+ $user_info{'RealName'} = $realname if defined $realname;
+ }
+ elsif ($^O eq 'MSWin32' and eval 'use Net::AdminMisc; 1') {
+ # Populate fields with information from NT domain controller
+ }
+
+ # and return the wad of stuff
+ return {%user_info};
+}
+
+# }}}
+
+
+package HTML::Mason::Commands;
+use strict;
+use vars qw/$r $m %session/;
+
+
+# {{{ loc
+
+=head2 loc ARRAY
+
+loc is a nice clean global routine which calls $session{'CurrentUser'}->loc()
+with whatever it's called with. If there is no $session{'CurrentUser'},
+it creates a temporary user, so we have something to get a localisation handle
+through
+
+=cut
+
+sub loc {
+
+ if ($session{'CurrentUser'} &&
+ UNIVERSAL::can($session{'CurrentUser'}, 'loc')){
+ return($session{'CurrentUser'}->loc(@_));
+ }
+ elsif ( my $u = eval { RT::CurrentUser->new($RT::SystemUser->Id) } ) {
+ return ($u->loc(@_));
+ }
+ else {
+ # pathetic case -- SystemUser is gone.
+ return $_[0];
+ }
+}
+
+# }}}
+
+
+# {{{ loc_fuzzy
+
+=head2 loc_fuzzy STRING
+
+loc_fuzzy is for handling localizations of messages that may already
+contain interpolated variables, typically returned from libraries
+outside RT's control. It takes the message string and extracts the
+variable array automatically by matching against the candidate entries
+inside the lexicon file.
+
+=cut
+
+sub loc_fuzzy {
+ my $msg = shift;
+
+ if ($session{'CurrentUser'} &&
+ UNIVERSAL::can($session{'CurrentUser'}, 'loc')){
+ return($session{'CurrentUser'}->loc_fuzzy($msg));
+ }
+ else {
+ my $u = RT::CurrentUser->new($RT::SystemUser->Id);
+ return ($u->loc_fuzzy($msg));
+ }
+}
+
+# }}}
+
+
+# {{{ sub Abort
+# Error - calls Error and aborts
+sub Abort {
+
+ if ($session{'ErrorDocument'} &&
+ $session{'ErrorDocumentType'}) {
+ $r->content_type($session{'ErrorDocumentType'});
+ $m->comp($session{'ErrorDocument'} , Why => shift);
+ $m->abort;
+ }
+ else {
+ $m->comp("/Elements/Error" , Why => shift);
+ $m->abort;
+ }
+}
+
+# }}}
+
+# {{{ sub CreateTicket
+
+=head2 CreateTicket ARGS
+
+Create a new ticket, using Mason's %ARGS. returns @results.
+
+=cut
+
+sub CreateTicket {
+ my %ARGS = (@_);
+
+ my (@Actions);
+
+ my $Ticket = new RT::Ticket( $session{'CurrentUser'} );
+
+ my $Queue = new RT::Queue( $session{'CurrentUser'} );
+ unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
+ Abort('Queue not found');
+ }
+
+ unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
+ Abort('You have no permission to create tickets in that queue.');
+ }
+
+ my $due = new RT::Date( $session{'CurrentUser'} );
+ $due->Set( Format => 'unknown', Value => $ARGS{'Due'} );
+ my $starts = new RT::Date( $session{'CurrentUser'} );
+ $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} );
+
+ my @Requestors = split ( /\s*,\s*/, $ARGS{'Requestors'} );
+ my @Cc = split ( /\s*,\s*/, $ARGS{'Cc'} );
+ my @AdminCc = split ( /\s*,\s*/, $ARGS{'AdminCc'} );
+
+ my $MIMEObj = MakeMIMEEntity(
+ Subject => $ARGS{'Subject'},
+ From => $ARGS{'From'},
+ Cc => $ARGS{'Cc'},
+ Body => $ARGS{'Content'},
+ );
+
+ if ($ARGS{'Attachments'}) {
+ $MIMEObj->make_multipart;
+ $MIMEObj->add_part($_) foreach values %{$ARGS{'Attachments'}};
+ }
+
+ my %create_args = (
+ Queue => $ARGS{'Queue'},
+ Owner => $ARGS{'Owner'},
+ InitialPriority => $ARGS{'InitialPriority'},
+ FinalPriority => $ARGS{'FinalPriority'},
+ TimeLeft => $ARGS{'TimeLeft'},
+ TimeEstimated => $ARGS{'TimeEstimated'},
+ TimeWorked => $ARGS{'TimeWorked'},
+ Requestor => \@Requestors,
+ Cc => \@Cc,
+ AdminCc => \@AdminCc,
+ Subject => $ARGS{'Subject'},
+ Status => $ARGS{'Status'},
+ Due => $due->ISO,
+ Starts => $starts->ISO,
+ MIMEObj => $MIMEObj
+ );
+ foreach my $arg (%ARGS) {
+ if ($arg =~ /^CustomField-(\d+)(.*?)$/) {
+ next if ($arg =~ /-Magic$/);
+ $create_args{"CustomField-".$1} = $ARGS{"$arg"};
+ }
+ }
+ my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
+ unless ( $id && $Trans ) {
+ Abort($ErrMsg);
+ }
+ my @linktypes = qw( DependsOn MemberOf RefersTo );
+
+ foreach my $linktype (@linktypes) {
+ foreach my $luri ( split ( / /, $ARGS{"new-$linktype"} ) ) {
+ $luri =~ s/\s*$//; # Strip trailing whitespace
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Target => $luri,
+ Type => $linktype
+ );
+ push ( @Actions, $msg ) unless ($val);
+ }
+
+ foreach my $luri ( split ( / /, $ARGS{"$linktype-new"} ) ) {
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Base => $luri,
+ Type => $linktype
+ );
+
+ push ( @Actions, $msg ) unless ($val);
+ }
+ }
+
+ push ( @Actions, split("\n", $ErrMsg) );
+ unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
+ Abort( "No permission to view newly created ticket #"
+ . $Ticket->id . "." );
+ }
+ return ( $Ticket, @Actions );
+
+}
+
+# }}}
+
+# {{{ sub LoadTicket - loads a ticket
+
+=head2 LoadTicket id
+
+Takes a ticket id as its only variable. if it's handed an array, it takes
+the first value.
+
+Returns an RT::Ticket object as the current user.
+
+=cut
+
+sub LoadTicket {
+ my $id = shift;
+
+ if ( ref($id) eq "ARRAY" ) {
+ $id = $id->[0];
+ }
+
+ unless ($id) {
+ Abort("No ticket specified");
+ }
+
+ my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+ $Ticket->Load($id);
+ unless ( $Ticket->id ) {
+ Abort("Could not load ticket $id");
+ }
+ return $Ticket;
+}
+
+# }}}
+
+# {{{ sub ProcessUpdateMessage
+
+sub ProcessUpdateMessage {
+
+ #TODO document what else this takes.
+ my %args = (
+ ARGSRef => undef,
+ Actions => undef,
+ TicketObj => undef,
+ @_
+ );
+
+ #Make the update content have no 'weird' newlines in it
+ if ( $args{ARGSRef}->{'UpdateContent'} ||
+ $args{ARGSRef}->{'UpdateAttachments'}) {
+
+ if (
+ $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() )
+ {
+ $args{ARGSRef}->{'UpdateSubject'} = undef;
+ }
+
+ my $Message = MakeMIMEEntity(
+ Subject => $args{ARGSRef}->{'UpdateSubject'},
+ Body => $args{ARGSRef}->{'UpdateContent'},
+ );
+
+ if ($args{ARGSRef}->{'UpdateAttachments'}) {
+ $Message->make_multipart;
+ $Message->add_part($_) foreach values %{$args{ARGSRef}->{'UpdateAttachments'}};
+ }
+
+ ## TODO: Implement public comments
+ if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
+ my ( $Transaction, $Description ) = $args{TicketObj}->Comment(
+ CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
+ BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+ MIMEObj => $Message,
+ TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
+ );
+ push ( @{ $args{Actions} }, $Description );
+ }
+ elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
+ my ( $Transaction, $Description ) = $args{TicketObj}->Correspond(
+ CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
+ BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+ MIMEObj => $Message,
+ TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
+ );
+ push ( @{ $args{Actions} }, $Description );
+ }
+ else {
+ push ( @{ $args{'Actions'} },
+ loc("Update type was neither correspondence nor comment.").
+ " ".
+ loc("Update not recorded.")
+ );
+ }
+ }
+}
+
+# }}}
+
+# {{{ sub MakeMIMEEntity
+
+=head2 MakeMIMEEntity PARAMHASH
+
+Takes a paramhash Subject, Body and AttachmentFieldName.
+
+ Returns a MIME::Entity.
+
+=cut
+
+sub MakeMIMEEntity {
+
+ #TODO document what else this takes.
+ my %args = (
+ Subject => undef,
+ From => undef,
+ Cc => undef,
+ Body => undef,
+ AttachmentFieldName => undef,
+# map Encode::encode_utf8($_), @_,
+ @_,
+ );
+
+ #Make the update content have no 'weird' newlines in it
+
+ $args{'Body'} =~ s/\r\n/\n/gs;
+ my $Message;
+ {
+ # MIME::Head is not happy in utf-8 domain. This only happens
+ # when processing an incoming email (so far observed).
+ no utf8;
+ use bytes;
+ $Message = MIME::Entity->build(
+ Subject => $args{'Subject'} || "",
+ From => $args{'From'},
+ Cc => $args{'Cc'},
+ Charset => 'utf8',
+ Data => [ $args{'Body'} ]
+ );
+ }
+
+ my $cgi_object = $m->cgi_object;
+
+ if (my $filehandle = $cgi_object->upload( $args{'AttachmentFieldName'} ) ) {
+
+
+
+ use File::Temp qw(tempfile tempdir);
+
+ #foreach my $filehandle (@filenames) {
+
+ my ( $fh, $temp_file );
+ for ( 1 .. 10 ) {
+ # on NFS and NTFS, it is possible that tempfile() conflicts
+ # with other processes, causing a race condition. we try to
+ # accommodate this by pausing and retrying.
+ last if ($fh, $temp_file) = eval { tempfile() };
+ sleep 1;
+ }
+
+ binmode $fh; #thank you, windows
+ my ($buffer);
+ while ( my $bytesread = read( $filehandle, $buffer, 4096 ) ) {
+ print $fh $buffer;
+ }
+
+ my $uploadinfo = $cgi_object->uploadInfo($filehandle);
+
+ # Prefer the cached name first over CGI.pm stringification.
+ my $filename = $RT::Mason::CGI::Filename;
+ $filename = "$filehandle" unless defined($filename);
+
+ $filename =~ s#^.*[\\/]##;
+
+ $Message->attach(
+ Path => $temp_file,
+ Filename => Encode::decode_utf8($filename),
+ Type => $uploadinfo->{'Content-Type'},
+ );
+ close($fh);
+
+ # }
+
+ }
+
+ $Message->make_singlepart();
+ RT::I18N::SetMIMEEntityToUTF8($Message); # convert text parts into utf-8
+
+ return ($Message);
+
+}
+
+# }}}
+
+# {{{ sub ProcessSearchQuery
+
+=head2 ProcessSearchQuery
+
+ Takes a form such as the one filled out in webrt/Search/Elements/PickRestriction and turns it into something that RT::Tickets can understand.
+
+TODO Doc exactly what comes in the paramhash
+
+
+=cut
+
+sub ProcessSearchQuery {
+ my %args = @_;
+
+ ## TODO: The only parameter here is %ARGS. Maybe it would be
+ ## cleaner to load this parameter as $ARGS, and use $ARGS->{...}
+ ## instead of $args{ARGS}->{...} ? :)
+
+ #Searches are sticky.
+ if ( defined $session{'tickets'} ) {
+
+ # Reset the old search
+ $session{'tickets'}->GotoFirstItem;
+ }
+ else {
+
+ # Init a new search
+ $session{'tickets'} = RT::Tickets->new( $session{'CurrentUser'} );
+ }
+
+ #Import a bookmarked search if we have one
+ if ( defined $args{ARGS}->{'Bookmark'} ) {
+ $session{'tickets'}->ThawLimits( $args{ARGS}->{'Bookmark'} );
+ }
+
+ # {{{ Goto next/prev page
+ if ( $args{ARGS}->{'GotoPage'} eq 'Next' ) {
+ $session{'tickets'}->NextPage;
+ }
+ elsif ( $args{ARGS}->{'GotoPage'} eq 'Prev' ) {
+ $session{'tickets'}->PrevPage;
+ }
+ elsif ( $args{ARGS}->{'GotoPage'} > 0 ) {
+ $session{'tickets'}->GotoPage( $args{ARGS}->{GotoPage} - 1 );
+ }
+
+ # }}}
+
+ # {{{ Deal with limiting the search
+
+ if ( $args{ARGS}->{'RefreshSearchInterval'} ) {
+ $session{'tickets_refresh_interval'} =
+ $args{ARGS}->{'RefreshSearchInterval'};
+ }
+
+ if ( $args{ARGS}->{'TicketsSortBy'} ) {
+ $session{'tickets_sort_by'} = $args{ARGS}->{'TicketsSortBy'};
+ $session{'tickets_sort_order'} = $args{ARGS}->{'TicketsSortOrder'};
+ $session{'tickets'}->OrderBy(
+ FIELD => $args{ARGS}->{'TicketsSortBy'},
+ ORDER => $args{ARGS}->{'TicketsSortOrder'}
+ );
+ }
+
+ # }}}
+
+ # {{{ Set the query limit
+ if ( defined $args{ARGS}->{'RowsPerPage'} ) {
+ $RT::Logger->debug(
+ "limiting to " . $args{ARGS}->{'RowsPerPage'} . " rows" );
+
+ $session{'tickets_rows_per_page'} = $args{ARGS}->{'RowsPerPage'};
+ $session{'tickets'}->RowsPerPage( $args{ARGS}->{'RowsPerPage'} );
+ }
+
+ # }}}
+ # {{{ Limit priority
+ if ( $args{ARGS}->{'ValueOfPriority'} ne '' ) {
+ $session{'tickets'}->LimitPriority(
+ VALUE => $args{ARGS}->{'ValueOfPriority'},
+ OPERATOR => $args{ARGS}->{'PriorityOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit owner
+ if ( $args{ARGS}->{'ValueOfOwner'} ne '' ) {
+ $session{'tickets'}->LimitOwner(
+ VALUE => $args{ARGS}->{'ValueOfOwner'},
+ OPERATOR => $args{ARGS}->{'OwnerOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit requestor email
+ if ( $args{ARGS}->{'ValueOfWatcherRole'} ne '' ) {
+ $session{'tickets'}->LimitWatcher(
+ TYPE => $args{ARGS}->{'WatcherRole'},
+ VALUE => $args{ARGS}->{'ValueOfWatcherRole'},
+ OPERATOR => $args{ARGS}->{'WatcherRoleOp'},
+
+ );
+ }
+
+ # }}}
+ # {{{ Limit Queue
+ if ( $args{ARGS}->{'ValueOfQueue'} ne '' ) {
+ $session{'tickets'}->LimitQueue(
+ VALUE => $args{ARGS}->{'ValueOfQueue'},
+ OPERATOR => $args{ARGS}->{'QueueOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit Status
+ if ( $args{ARGS}->{'ValueOfStatus'} ne '' ) {
+ if ( ref( $args{ARGS}->{'ValueOfStatus'} ) ) {
+ foreach my $value ( @{ $args{ARGS}->{'ValueOfStatus'} } ) {
+ $session{'tickets'}->LimitStatus(
+ VALUE => $value,
+ OPERATOR => $args{ARGS}->{'StatusOp'},
+ );
+ }
+ }
+ else {
+ $session{'tickets'}->LimitStatus(
+ VALUE => $args{ARGS}->{'ValueOfStatus'},
+ OPERATOR => $args{ARGS}->{'StatusOp'},
+ );
+ }
+
+ }
+
+ # }}}
+ # {{{ Limit Subject
+ if ( $args{ARGS}->{'ValueOfSubject'} ne '' ) {
+ my $val = $args{ARGS}->{'ValueOfSubject'};
+ if ($args{ARGS}->{'SubjectOp'} =~ /like/) {
+ $val = "%".$val."%";
+ }
+ $session{'tickets'}->LimitSubject(
+ VALUE => $val,
+ OPERATOR => $args{ARGS}->{'SubjectOp'},
+ );
+ }
+
+ # }}}
+ # {{{ Limit Dates
+ if ( $args{ARGS}->{'ValueOfDate'} ne '' ) {
+ my $date = ParseDateToISO( $args{ARGS}->{'ValueOfDate'} );
+ $args{ARGS}->{'DateType'} =~ s/_Date$//;
+
+ if ( $args{ARGS}->{'DateType'} eq 'Updated' ) {
+ $session{'tickets'}->LimitTransactionDate(
+ VALUE => $date,
+ OPERATOR => $args{ARGS}->{'DateOp'},
+ );
+ }
+ else {
+ $session{'tickets'}->LimitDate( FIELD => $args{ARGS}->{'DateType'},
+ VALUE => $date,
+ OPERATOR => $args{ARGS}->{'DateOp'},
+ );
+ }
+ }
+
+ # }}}
+ # {{{ Limit Content
+ if ( $args{ARGS}->{'ValueOfAttachmentField'} ne '' ) {
+ my $val = $args{ARGS}->{'ValueOfAttachmentField'};
+ if ($args{ARGS}->{'AttachmentFieldOp'} =~ /like/) {
+ $val = "%".$val."%";
+ }
+ $session{'tickets'}->Limit(
+ FIELD => $args{ARGS}->{'AttachmentField'},
+ VALUE => $val,
+ OPERATOR => $args{ARGS}->{'AttachmentFieldOp'},
+ );
+ }
+
+ # }}}
+
+ # {{{ Limit CustomFields
+
+ foreach my $arg ( keys %{ $args{ARGS} } ) {
+ my $id;
+ if ( $arg =~ /^CustomField(\d+)$/ ) {
+ $id = $1;
+ }
+ else {
+ next;
+ }
+ next unless ( $args{ARGS}->{$arg} );
+
+ my $form = $args{ARGS}->{$arg};
+ my $oper = $args{ARGS}->{ "CustomFieldOp" . $id };
+ foreach my $value ( ref($form) ? @{$form} : ($form) ) {
+ my $quote = 1;
+ if ($oper =~ /like/i) {
+ $value = "%".$value."%";
+ }
+ if ( $value =~ /^null$/i ) {
+
+ #Don't quote the string 'null'
+ $quote = 0;
+
+ # Convert the operator to something apropriate for nulls
+ $oper = 'IS' if ( $oper eq '=' );
+ $oper = 'IS NOT' if ( $oper eq '!=' );
+ }
+ $session{'tickets'}->LimitCustomField( CUSTOMFIELD => $id,
+ OPERATOR => $oper,
+ QUOTEVALUE => $quote,
+ VALUE => $value );
+ }
+ }
+
+ # }}}
+
+
+}
+
+# }}}
+
+# {{{ sub ParseDateToISO
+
+=head2 ParseDateToISO
+
+Takes a date in an arbitrary format.
+Returns an ISO date and time in GMT
+
+=cut
+
+sub ParseDateToISO {
+ my $date = shift;
+
+ my $date_obj = RT::Date->new($session{'CurrentUser'});
+ $date_obj->Set(
+ Format => 'unknown',
+ Value => $date
+ );
+ return ( $date_obj->ISO );
+}
+
+# }}}
+
+# {{{ sub Config
+# TODO: This might eventually read the cookies, user configuration
+# information from the DB, queue configuration information from the
+# DB, etc.
+
+sub Config {
+ my $args = shift;
+ my $key = shift;
+ return $args->{$key} || $RT::WebOptions{$key};
+}
+
+# }}}
+
+# {{{ sub ProcessACLChanges
+
+sub ProcessACLChanges {
+ my $ARGSref = shift;
+
+ my %ARGS = %$ARGSref;
+
+ my ( $ACL, @results );
+
+
+ foreach my $arg (keys %ARGS) {
+ if ($arg =~ /GrantRight-(\d+)-(.*?)-(\d+)$/) {
+ my $principal_id = $1;
+ my $object_type = $2;
+ my $object_id = $3;
+ my $rights = $ARGS{$arg};
+
+ my $principal = RT::Principal->new($session{'CurrentUser'});
+ $principal->Load($principal_id);
+
+ my $obj;
+
+ if ($object_type eq 'RT::System') {
+ $obj = $RT::System;
+ } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
+ $obj = $object_type->new($session{'CurrentUser'});
+ $obj->Load($object_id);
+ } else {
+ push (@results, loc("System Error"). ': '.
+ loc("Rights could not be granted for [_1]", $object_type));
+ next;
+ }
+
+ my @rights = ref($ARGS{$arg}) eq 'ARRAY' ? @{$ARGS{$arg}} : ($ARGS{$arg});
+ foreach my $right (@rights) {
+ next unless ($right);
+ my ($val, $msg) = $principal->GrantRight(Object => $obj, Right => $right);
+ push (@results, $msg);
+ }
+ }
+ elsif ($arg =~ /RevokeRight-(\d+)-(.*?)-(\d+)-(.*?)$/) {
+ my $principal_id = $1;
+ my $object_type = $2;
+ my $object_id = $3;
+ my $right = $4;
+
+ my $principal = RT::Principal->new($session{'CurrentUser'});
+ $principal->Load($principal_id);
+ next unless ($right);
+ my $obj;
+
+ if ($object_type eq 'RT::System') {
+ $obj = $RT::System;
+ } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
+ $obj = $object_type->new($session{'CurrentUser'});
+ $obj->Load($object_id);
+ } else {
+ die;
+ push (@results, loc("System Error"). ': '.
+ loc("Rights could not be revoked for [_1]", $object_type));
+ next;
+ }
+ my ($val, $msg) = $principal->RevokeRight(Object => $obj, Right => $right);
+ push (@results, $msg);
+ }
+
+
+ }
+
+ return (@results);
+
+ }
+
+# }}}
+
+# {{{ sub UpdateRecordObj
+
+=head2 UpdateRecordObj ( ARGSRef => \%ARGS, Object => RT::Record, AttributesRef => \@attribs)
+
+@attribs is a list of ticket fields to check and update if they differ from the B<Object>'s current values. ARGSRef is a ref to HTML::Mason's %ARGS.
+
+Returns an array of success/failure messages
+
+=cut
+
+sub UpdateRecordObject {
+ my %args = (
+ ARGSRef => undef,
+ AttributesRef => undef,
+ Object => undef,
+ AttributePrefix => undef,
+ @_
+ );
+
+ my (@results);
+
+ my $object = $args{'Object'};
+ my $attributes = $args{'AttributesRef'};
+ my $ARGSRef = $args{'ARGSRef'};
+ foreach my $attribute (@$attributes) {
+ my $value;
+ if ( defined $ARGSRef->{$attribute} ) {
+ $value = $ARGSRef->{$attribute};
+ }
+ elsif (
+ defined( $args{'AttributePrefix'} )
+ && defined(
+ $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute }
+ )
+ ) {
+ $value = $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute };
+
+ } else {
+ next;
+ }
+
+ $value =~ s/\r\n/\n/gs;
+
+ if ($value ne $object->$attribute()){
+
+ my $method = "Set$attribute";
+ my ( $code, $msg ) = $object->$method($value);
+
+ push @results, loc($attribute) . ': ' . loc_fuzzy($msg);
+=for loc
+ "[_1] could not be set to [_2].", # loc
+ "That is already the current value", # loc
+ "No value sent to _Set!\n", # loc
+ "Illegal value for [_1]", # loc
+ "The new value has been set.", # loc
+ "No column specified", # loc
+ "Immutable field", # loc
+ "Nonexistant field?", # loc
+ "Invalid data", # loc
+ "Couldn't find row", # loc
+ "Missing a primary key?: [_1]", # loc
+ "Found Object", # loc
+=cut
+ };
+ }
+ return (@results);
+}
+
+# }}}
+
+# {{{ Sub ProcessCustomFieldUpdates
+
+sub ProcessCustomFieldUpdates {
+ my %args = (
+ CustomFieldObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $Object = $args{'CustomFieldObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my @attribs = qw( Name Type Description Queue SortOrder);
+ my @results = UpdateRecordObject(
+ AttributesRef => \@attribs,
+ Object => $Object,
+ ARGSRef => $ARGSRef
+ );
+
+ if ( $ARGSRef->{ "CustomField-" . $Object->Id . "-AddValue-Name" } ) {
+
+ my ( $addval, $addmsg ) = $Object->AddValue(
+ Name =>
+ $ARGSRef->{ "CustomField-" . $Object->Id . "-AddValue-Name" },
+ Description => $ARGSRef->{ "CustomField-"
+ . $Object->Id
+ . "-AddValue-Description" },
+ SortOrder => $ARGSRef->{ "CustomField-"
+ . $Object->Id
+ . "-AddValue-SortOrder" },
+ );
+ push ( @results, $addmsg );
+ }
+ my @delete_values = (
+ ref $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } eq
+ 'ARRAY' )
+ ? @{ $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } }
+ : ( $ARGSRef->{ 'CustomField-' . $Object->Id . '-DeleteValue' } );
+ foreach my $id (@delete_values) {
+ next unless defined $id;
+ my ( $err, $msg ) = $Object->DeleteValue($id);
+ push ( @results, $msg );
+ }
+
+ my $vals = $Object->Values();
+ while (my $cfv = $vals->Next()) {
+ if (my $so = $ARGSRef->{ 'CustomField-' . $Object->Id . '-SortOrder' . $cfv->Id }) {
+ if ($cfv->SortOrder != $so) {
+ my ( $err, $msg ) = $cfv->SetSortOrder($so);
+ push ( @results, $msg );
+ }
+ }
+ }
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketBasics
+
+=head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketBasics {
+
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $TicketObj = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ # {{{ Set basic fields
+ my @attribs = qw(
+ Subject
+ FinalPriority
+ Priority
+ TimeEstimated
+ TimeWorked
+ TimeLeft
+ Status
+ Queue
+ );
+
+ if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
+ my $tempqueue = RT::Queue->new($RT::SystemUser);
+ $tempqueue->Load( $ARGSRef->{'Queue'} );
+ if ( $tempqueue->id ) {
+ $ARGSRef->{'Queue'} = $tempqueue->Id();
+ }
+ }
+
+ my @results = UpdateRecordObject(
+ AttributesRef => \@attribs,
+ Object => $TicketObj,
+ ARGSRef => $ARGSRef
+ );
+
+ # We special case owner changing, so we can use ForceOwnerChange
+ if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
+ my ($ChownType);
+ if ( $ARGSRef->{'ForceOwnerChange'} ) {
+ $ChownType = "Force";
+ }
+ else {
+ $ChownType = "Give";
+ }
+
+ my ( $val, $msg ) =
+ $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
+ push ( @results, $msg );
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ Sub ProcessTicketCustomFieldUpdates
+
+sub ProcessTicketCustomFieldUpdates {
+ my %args = (
+ ARGSRef => undef,
+ @_
+ );
+
+ my @results;
+
+ my $ARGSRef = $args{'ARGSRef'};
+
+ # Build up a list of tickets that we want to work with
+ my %tickets_to_mod;
+ my %custom_fields_to_mod;
+ foreach my $arg ( keys %{$ARGSRef} ) {
+ if ( $arg =~ /^Ticket-(\d+)-CustomField-(\d+)-/ ) {
+
+ # For each of those tickets, find out what custom fields we want to work with.
+ $custom_fields_to_mod{$1}{$2} = 1;
+ }
+ }
+
+ # For each of those tickets
+ foreach my $tick ( keys %custom_fields_to_mod ) {
+ my $Ticket = $args{'TicketObj'};
+ if (!$Ticket or $Ticket->id != $tick) {
+ $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+ $Ticket->Load($tick);
+ }
+
+ # For each custom field
+ foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) {
+
+ my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+ $CustomFieldObj->LoadById($cf);
+
+ foreach my $arg ( keys %{$ARGSRef} ) {
+ # since http won't pass in a form element with a null value, we need
+ # to fake it
+ if ($arg =~ /^(.*?)-Values-Magic$/ ) {
+ # We don't care about the magic, if there's really a values element;
+ next if (exists $ARGSRef->{$1.'-Values'}) ;
+
+ $arg = $1."-Values";
+ $ARGSRef->{$1."-Values"} = undef;
+
+ }
+ next unless ( $arg =~ /^Ticket-$tick-CustomField-$cf-/ );
+ my @values =
+ ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' )
+ ? @{ $ARGSRef->{$arg} }
+ : split /\n/, $ARGSRef->{$arg} ;
+ if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
+ foreach my $value (@values) {
+ next unless length($value);
+ my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+ Field => $cf,
+ Value => $value
+ );
+ push ( @results, $msg );
+ }
+ }
+ elsif ( $arg =~ /-DeleteValues$/ ) {
+ foreach my $value (@values) {
+ next unless length($value);
+ my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+ Field => $cf,
+ Value => $value
+ );
+ push ( @results, $msg );
+ }
+ }
+ elsif ( $arg =~ /-Values$/ and $CustomFieldObj->Type !~ /Entry/) {
+ my $cf_values = $Ticket->CustomFieldValues($cf);
+
+ my %values_hash;
+ foreach my $value (@values) {
+ next unless length($value);
+
+ # build up a hash of values that the new set has
+ $values_hash{$value} = 1;
+
+ unless ( $cf_values->HasEntry($value) ) {
+ my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+ Field => $cf,
+ Value => $value
+ );
+ push ( @results, $msg );
+ }
+
+ }
+ while ( my $cf_value = $cf_values->Next ) {
+ unless ( $values_hash{ $cf_value->Content } == 1 ) {
+ my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+ Field => $cf,
+ Value => $cf_value->Content
+ );
+ push ( @results, $msg);
+
+ }
+
+ }
+ }
+ elsif ( $arg =~ /-Values$/ ) {
+ my $cf_values = $Ticket->CustomFieldValues($cf);
+
+ # keep everything up to the point of difference, delete the rest
+ my $delete_flag;
+ foreach my $old_cf (@{$cf_values->ItemsArrayRef}) {
+ if (!$delete_flag and @values and $old_cf->Content eq $values[0]) {
+ shift @values;
+ next;
+ }
+
+ $delete_flag ||= 1;
+ $old_cf->Delete;
+ }
+
+ # now add/replace extra things, if any
+ foreach my $value (@values) {
+ my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+ Field => $cf,
+ Value => $value
+ );
+ push ( @results, $msg );
+ }
+ }
+ else {
+ push ( @results, "User asked for an unknown update type for custom field " . $cf->Name . " for ticket " . $Ticket->id );
+ }
+ }
+ }
+ return (@results);
+ }
+}
+
+# }}}
+
+# {{{ sub ProcessTicketWatchers
+
+=head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketWatchers {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+ my (@results);
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ # {{{ Munge watchers
+
+ foreach my $key ( keys %$ARGSRef ) {
+
+ # {{{ Delete deletable watchers
+ if ( ( $key =~ /^Ticket-DelWatcher-Type-(.*)-Principal-(\d+)$/ ) ) {
+ my ( $code, $msg ) =
+ $Ticket->DeleteWatcher(PrincipalId => $2,
+ Type => $1);
+ push @results, $msg;
+ }
+
+ # Delete watchers in the simple style demanded by the bulk manipulator
+ elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {
+ my ( $code, $msg ) = $Ticket->DeleteWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 );
+ push @results, $msg;
+ }
+
+ # }}}
+
+ # Add new wathchers by email address
+ elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
+ and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) )
+ {
+
+ #They're in this order because otherwise $1 gets clobbered :/
+ my ( $code, $msg ) = $Ticket->AddWatcher(
+ Type => $ARGSRef->{$key},
+ Email => $ARGSRef->{ "WatcherAddressEmail" . $1 }
+ );
+ push @results, $msg;
+ }
+
+ #Add requestors in the simple style demanded by the bulk manipulator
+ elsif ( $key =~ /^Add(Requestor|Cc|AdminCc)$/ ) {
+ my ( $code, $msg ) = $Ticket->AddWatcher(
+ Type => $1,
+ Email => $ARGSRef->{$key}
+ );
+ push @results, $msg;
+ }
+
+ # Add new watchers by owner
+ elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
+ and ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) ) {
+
+ #They're in this order because otherwise $1 gets clobbered :/
+ my ( $code, $msg ) =
+ $Ticket->AddWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 );
+ push @results, $msg;
+ }
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketDates
+
+=head2 ProcessTicketDates ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketDates {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my (@results);
+
+ # {{{ Set date fields
+ my @date_fields = qw(
+ Told
+ Resolved
+ Starts
+ Started
+ Due
+ );
+
+ #Run through each field in this list. update the value if apropriate
+ foreach my $field (@date_fields) {
+ my ( $code, $msg );
+
+ my $DateObj = RT::Date->new( $session{'CurrentUser'} );
+
+ #If it's something other than just whitespace
+ if ( $ARGSRef->{ $field . '_Date' } ne '' ) {
+ $DateObj->Set(
+ Format => 'unknown',
+ Value => $ARGSRef->{ $field . '_Date' }
+ );
+ my $obj = $field . "Obj";
+ if ( ( defined $DateObj->Unix )
+ and ( $DateObj->Unix ne $Ticket->$obj()->Unix() ) )
+ {
+ my $method = "Set$field";
+ my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
+ push @results, "$msg";
+ }
+ }
+ }
+
+ # }}}
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketLinks
+
+=head2 ProcessTicketLinks ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketLinks {
+ my %args = ( TicketObj => undef,
+ ARGSRef => undef,
+ @_ );
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my (@results);
+
+ # Delete links that are gone gone gone.
+ foreach my $arg ( keys %$ARGSRef ) {
+ if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
+ my $base = $1;
+ my $type = $2;
+ my $target = $3;
+
+ push @results,
+ "Trying to delete: Base: $base Target: $target Type $type";
+ my ( $val, $msg ) = $Ticket->DeleteLink( Base => $base,
+ Type => $type,
+ Target => $target );
+
+ push @results, $msg;
+
+ }
+
+ }
+
+ my @linktypes = qw( DependsOn MemberOf RefersTo );
+
+ foreach my $linktype (@linktypes) {
+ if ( $ARGSRef->{ $Ticket->Id . "-$linktype" } ) {
+ for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) ) {
+ $luri =~ s/\s*$//; # Strip trailing whitespace
+ my ( $val, $msg ) = $Ticket->AddLink( Target => $luri,
+ Type => $linktype );
+ push @results, $msg;
+ }
+ }
+ if ( $ARGSRef->{ "$linktype-" . $Ticket->Id } ) {
+
+ for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) ) {
+ my ( $val, $msg ) = $Ticket->AddLink( Base => $luri,
+ Type => $linktype );
+
+ push @results, $msg;
+ }
+ }
+ }
+
+ #Merge if we need to
+ if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
+ my ( $val, $msg ) =
+ $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
+ push @results, $msg;
+ }
+
+ return (@results);
+}
+
+# }}}
+
+eval "require RT::Interface::Web_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm});
+eval "require RT::Interface::Web_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Link.pm b/rt/lib/RT/Link.pm
new file mode 100644
index 0000000..962c378
--- /dev/null
+++ b/rt/lib/RT/Link.pm
@@ -0,0 +1,302 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Link
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Link;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Links');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(240) 'Base'.
+ varchar(240) 'Target'.
+ varchar(20) 'Type'.
+ int(11) 'LocalTarget'.
+ int(11) 'LocalBase'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Base => '',
+ Target => '',
+ Type => '',
+ LocalTarget => '0',
+ LocalBase => '0',
+
+ @_);
+ $self->SUPER::Create(
+ Base => $args{'Base'},
+ Target => $args{'Target'},
+ Type => $args{'Type'},
+ LocalTarget => $args{'LocalTarget'},
+ LocalBase => $args{'LocalBase'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Base
+
+Returns the current value of Base.
+(In the database, Base is stored as varchar(240).)
+
+
+
+=item SetBase VALUE
+
+
+Set Base to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Base will be stored as a varchar(240).)
+
+
+=cut
+
+
+=item Target
+
+Returns the current value of Target.
+(In the database, Target is stored as varchar(240).)
+
+
+
+=item SetTarget VALUE
+
+
+Set Target to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Target will be stored as a varchar(240).)
+
+
+=cut
+
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(20).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(20).)
+
+
+=cut
+
+
+=item LocalTarget
+
+Returns the current value of LocalTarget.
+(In the database, LocalTarget is stored as int(11).)
+
+
+
+=item SetLocalTarget VALUE
+
+
+Set LocalTarget to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, LocalTarget will be stored as a int(11).)
+
+
+=cut
+
+
+=item LocalBase
+
+Returns the current value of LocalBase.
+(In the database, LocalBase is stored as int(11).)
+
+
+
+=item SetLocalBase VALUE
+
+
+Set LocalBase to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, LocalBase will be stored as a int(11).)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Base =>
+ {read => 1, write => 1, type => 'varchar(240)', default => ''},
+ Target =>
+ {read => 1, write => 1, type => 'varchar(240)', default => ''},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(20)', default => ''},
+ LocalTarget =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ LocalBase =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Link_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Link_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Link_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Link_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Link_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Link_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Link_Overlay, RT::Link_Vendor, RT::Link_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Link_Overlay.pm b/rt/lib/RT/Link_Overlay.pm
new file mode 100644
index 0000000..ac1bc37
--- /dev/null
+++ b/rt/lib/RT/Link_Overlay.pm
@@ -0,0 +1,360 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Link - an RT Link object
+
+=head1 SYNOPSIS
+
+ use RT::Link;
+
+=head1 DESCRIPTION
+
+This module should never be called directly by client code. it's an internal module which
+should only be accessed through exported APIs in Ticket other similar objects.
+
+=head1 METHODS
+
+
+=begin testing
+
+
+use RT::Link;
+my $link = RT::Link->new($RT::SystemUser);
+
+
+ok (ref $link);
+ok (UNIVERSAL::isa($link, 'RT::Link'));
+ok (UNIVERSAL::isa($link, 'RT::Base'));
+ok (UNIVERSAL::isa($link, 'RT::Record'));
+ok (UNIVERSAL::isa($link, 'DBIx::SearchBuilder::Record'));
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+use Carp;
+use RT::URI;
+
+
+# {{{ sub Create
+
+=head2 Create PARAMHASH
+
+Create a new link object. Takes 'Base', 'Target' and 'Type'.
+Returns undef on failure or a Link Id on success.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = ( Base => undef,
+ Target => undef,
+ Type => undef,
+ @_ );
+
+ my $base = RT::URI->new( $self->CurrentUser );
+ $base->FromURI( $args{'Base'} );
+
+ unless ( $base->Scheme ) {
+ $RT::Logger->warning( "$self couldn't resolve base:'"
+ . $args{'Base'} . " - "
+ . $base->Scheme
+ . "' into a URI\n" );
+
+ #use Data::Dumper;
+ #$RT::Logger->warning(scalar Dumper $base);
+ return (undef);
+ }
+
+ my $target = RT::URI->new( $self->CurrentUser );
+ $target->FromURI( $args{'Target'} );
+
+ unless ( $target->Resolver ) {
+ $RT::Logger->warning( "$self couldn't resolve target:'"
+ . $args{'Target'} . " - "
+ . "' into a URI\n" );
+
+ #use Data::Dumper;
+ #$RT::Logger->warning(scalar Dumper $target);
+ return (undef);
+ }
+
+ my $base_id = 0;
+ my $target_id = 0;
+
+
+
+
+ if ( $base->IsLocal ) {
+ unless (UNIVERSAL::can($base->Object, 'Id')) {
+ return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Base'}));
+
+ }
+ $base_id = $base->Object->Id;
+ }
+ if ( $target->IsLocal ) {
+ unless (UNIVERSAL::can($target->Object, 'Id')) {
+ return (undef, $self->loc("[_1] appears to be a local object, but can't be found in the database", $args{'Target'}));
+
+ }
+ $target_id = $target->Object->Id;
+ }
+
+ # {{{ We don't want references to ourself
+ if ( $base->URI eq $target->URI ) {
+ return ( 0, $self->loc("Can't link a ticket to itself") );
+ }
+
+ # }}}
+
+ my ( $id, $msg ) = $self->SUPER::Create( Base => $base->URI,
+ Target => $target->URI,
+ LocalBase => $base_id,
+ LocalTarget => $target_id,
+ Type => $args{'Type'} );
+ return ( $id, $msg );
+}
+
+# }}}
+ # {{{ sub LoadByParams
+
+=head2 LoadByParams
+
+ Load an RT::Link object from the database. Takes three parameters
+
+ Base => undef,
+ Target => undef,
+ Type =>undef
+
+ Base and Target are expected to be integers which refer to Tickets or URIs
+ Type is the link type
+
+=cut
+
+sub LoadByParams {
+ my $self = shift;
+ my %args = ( Base => undef,
+ Target => undef,
+ Type => undef,
+ @_ );
+
+ my $base = RT::URI->new($self->CurrentUser);
+ $base->FromURI( $args{'Base'} );
+
+ my $target = RT::URI->new($self->CurrentUser);
+ $target->FromURI( $args{'Target'} );
+
+ unless ($base->Resolver && $target->Resolver) {
+ return ( 0, $self->loc("Couldn't load link") );
+ }
+
+
+ my ( $id, $msg ) = $self->LoadByCols( Base => $base->URI,
+ Type => $args{'Type'},
+ Target => $target->URI );
+
+ unless ($id) {
+ return ( 0, $self->loc("Couldn't load link") );
+ }
+}
+
+# }}}
+# {{{ sub Load
+
+=head2 Load
+
+ Load an RT::Link object from the database. Takes one parameter, the id of an entry in the links table.
+
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift;
+
+
+
+
+ if ( $identifier !~ /^\d+$/ ) {
+ return ( 0, $self->loc("That's not a numerical id") );
+ }
+ else {
+ my ( $id, $msg ) = $self->LoadById($identifier);
+ unless ( $self->Id ) {
+ return ( 0, $self->loc("Couldn't load link") );
+ }
+ return ( $id, $msg );
+ }
+}
+
+# }}}
+
+
+# {{{ TargetURI
+
+=head2 TargetURI
+
+returns an RT::URI object for the "Target" of this link.
+
+=cut
+
+sub TargetURI {
+ my $self = shift;
+ my $URI = RT::URI->new($self->CurrentUser);
+ $URI->FromURI($self->Target);
+ return ($URI);
+}
+
+# }}}
+# {{{ sub TargetObj
+
+=head2 TargetObj
+
+=cut
+
+sub TargetObj {
+ my $self = shift;
+ return $self->TargetURI->Object;
+}
+# }}}
+
+# {{{ BaseURI
+
+=head2 BaseURI
+
+returns an RT::URI object for the "Base" of this link.
+
+=cut
+
+sub BaseURI {
+ my $self = shift;
+ my $URI = RT::URI->new($self->CurrentUser);
+ $URI->FromURI($self->Base);
+ return ($URI);
+}
+
+# }}}
+# {{{ sub BaseObj
+
+=head2 BaseObj
+
+=cut
+
+sub BaseObj {
+ my $self = shift;
+ return $self->BaseURI->Object;
+}
+# }}}
+
+
+
+# Static methods:
+
+# {{{ sub BaseIsLocal
+
+=head2 BaseIsLocal
+
+Returns true if the base of this link is a local ticket
+
+=cut
+
+sub BaseIsLocal {
+ my $self = shift;
+ $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal");
+ return $self->BaseURI->IsLocal;
+}
+
+# }}}
+
+# {{{ sub TargetIsLocal
+
+=head2 TargetIsLocal
+
+Returns true if the target of this link is a local ticket
+
+=cut
+
+sub TargetIsLocal {
+ my $self = shift;
+ $RT::Logger->crit("Link::BaseIsLocal is deprecated in favor of Link->BaseURI->IsLocal");
+ return $self->TargetURI->IsLocal;
+}
+
+# }}}
+
+
+# {{{ sub BaseAsHREF
+
+=head2 BaseAsHREF
+
+Returns an HTTP url to access the base of this link
+
+=cut
+
+sub BaseAsHREF {
+ my $self = shift;
+ $RT::Logger->crit("Link::BaseAsHREF deprecated in favor of ->BaseURI->AsHREF");
+ return $self->BaseURI->HREF;
+}
+# }}}
+
+# {{{ sub TargetAsHREF
+
+=head2 TargetAsHREF
+
+return an HTTP url to access the target of this link
+
+=cut
+
+sub TargetAsHREF {
+ my $self = shift;
+ $RT::Logger->crit("Link::TargetAsHREF deprecated in favor of ->TargetURI->AsHREF");
+ return $self->TargetURI->HREF;
+}
+# }}}
+
+# {{{ sub AsHREF - Converts Link URIs to HTTP URLs
+
+=head2 URI
+
+Takes a URI and returns an http: url to access that object.
+
+=cut
+
+
+sub AsHREF {
+ my $self=shift;
+
+ $RT::Logger->crit("AsHREF is gone. look at URI::HREF to figure out what to do with \$URI");
+}
+
+# }}}
+
+1;
+
diff --git a/rt/lib/RT/Links.pm b/rt/lib/RT/Links.pm
new file mode 100644
index 0000000..7a1773a
--- /dev/null
+++ b/rt/lib/RT/Links.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Links -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Links
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Links;
+
+use RT::SearchBuilder;
+use RT::Link;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Links';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Link item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Link->new($self->CurrentUser));
+}
+
+ eval "require RT::Links_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Links_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Links_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Links_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Links_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Links_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Links_Overlay, RT::Links_Vendor, RT::Links_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Links_Overlay.pm b/rt/lib/RT/Links_Overlay.pm
new file mode 100644
index 0000000..d788a42
--- /dev/null
+++ b/rt/lib/RT/Links_Overlay.pm
@@ -0,0 +1,125 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Links - A collection of Link objects
+
+=head1 SYNOPSIS
+
+ use RT::Links;
+ my $links = new RT::Links($CurrentUser);
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Links);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+use RT::URI;
+
+# {{{ sub Limit
+sub Limit {
+ my $self = shift;
+ my %args = ( ENTRYAGGREGATOR => 'AND',
+ OPERATOR => '=',
+ @_);
+
+ #if someone's trying to search for tickets, try to resolve the uris for searching.
+
+ if ( ( $args{'OPERATOR'} eq '=') and
+ ( $args{'FIELD'} eq 'Base') or ($args{'FIELD'} eq 'Target')
+ ) {
+ my $dummy = RT::URI->new($self->CurrentUser);
+ $dummy->FromURI($args{'VALUE'});
+ # $uri = $dummy->URI;
+ }
+
+
+ # If we're limiting by target, order by base
+ # (Order by the thing that's changing)
+
+ if ( ($args{'FIELD'} eq 'Target') or
+ ($args{'FIELD'} eq 'LocalTarget') ) {
+ $self->OrderBy (ALIAS => 'main',
+ FIELD => 'Base',
+ ORDER => 'ASC');
+ }
+ elsif ( ($args{'FIELD'} eq 'Base') or
+ ($args{'FIELD'} eq 'LocalBase') ) {
+ $self->OrderBy (ALIAS => 'main',
+ FIELD => 'Target',
+ ORDER => 'ASC');
+ }
+
+
+ $self->SUPER::Limit(%args);
+}
+# }}}
+
+# {{{ LimitRefersTo
+
+=head2 LimitRefersTo URI
+
+find all things that refer to URI
+
+=cut
+
+sub LimitRefersTo {
+ my $self = shift;
+ my $URI = shift;
+
+ $self->Limit(FIELD => 'Type', VALUE => 'RefersTo');
+ $self->Limit(FIELD => 'Target', VALUE => $URI);
+}
+
+# }}}
+# {{{ LimitReferredToBy
+
+=head2 LimitReferredToBy URI
+
+find all things that URI refers to
+
+=cut
+
+sub LimitReferredToBy {
+ my $self = shift;
+ my $URI = shift;
+
+ $self->Limit(FIELD => 'Type', VALUE => 'RefersTo');
+ $self->Limit(FIELD => 'Base', VALUE => $URI);
+}
+
+# }}}
+1;
+
diff --git a/rt/lib/RT/Principal.pm b/rt/lib/RT/Principal.pm
new file mode 100644
index 0000000..cffee4c
--- /dev/null
+++ b/rt/lib/RT/Principal.pm
@@ -0,0 +1,212 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Principal
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Principal;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Principals');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(16) 'PrincipalType'.
+ int(11) 'ObjectId'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ PrincipalType => '',
+ ObjectId => '',
+ Disabled => '0',
+
+ @_);
+ $self->SUPER::Create(
+ PrincipalType => $args{'PrincipalType'},
+ ObjectId => $args{'ObjectId'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item PrincipalType
+
+Returns the current value of PrincipalType.
+(In the database, PrincipalType is stored as varchar(16).)
+
+
+
+=item SetPrincipalType VALUE
+
+
+Set PrincipalType to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PrincipalType will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item ObjectId
+
+Returns the current value of ObjectId.
+(In the database, ObjectId is stored as int(11).)
+
+
+
+=item SetObjectId VALUE
+
+
+Set ObjectId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ObjectId will be stored as a int(11).)
+
+
+=cut
+
+
+=item Disabled
+
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
+
+
+
+=item SetDisabled VALUE
+
+
+Set Disabled to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Disabled will be stored as a smallint(6).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ PrincipalType =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ ObjectId =>
+ {read => 1, write => 1, type => 'int(11)', default => ''},
+ Disabled =>
+ {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::Principal_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principal_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Principal_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principal_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Principal_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principal_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Principal_Overlay, RT::Principal_Vendor, RT::Principal_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Principal_Overlay.pm b/rt/lib/RT/Principal_Overlay.pm
new file mode 100644
index 0000000..b788e36
--- /dev/null
+++ b/rt/lib/RT/Principal_Overlay.pm
@@ -0,0 +1,576 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+
+no warnings qw(redefine);
+use vars qw(%_ACL_KEY_CACHE);
+
+use RT::Group;
+use RT::User;
+
+# {{{ IsGroup
+
+=head2 IsGroup
+
+Returns true if this principal is a group.
+Returns undef, otherwise
+
+=cut
+
+sub IsGroup {
+ my $self = shift;
+ if ($self->PrincipalType eq 'Group') {
+ return(1);
+ }
+ else {
+ return undef;
+ }
+}
+
+# }}}
+
+# {{{ IsUser
+
+=head2 IsUser
+
+Returns true if this principal is a User.
+Returns undef, otherwise
+
+=cut
+
+sub IsUser {
+ my $self = shift;
+ if ($self->PrincipalType eq 'User') {
+ return(1);
+ }
+ else {
+ return undef;
+ }
+}
+
+# }}}
+
+# {{{ Object
+
+=head2 Object
+
+Returns the user or group associated with this principal
+
+=cut
+
+sub Object {
+ my $self = shift;
+
+ unless ($self->{'object'}) {
+ if ($self->IsUser) {
+ $self->{'object'} = RT::User->new($self->CurrentUser);
+ }
+ elsif ($self->IsGroup) {
+ $self->{'object'} = RT::Group->new($self->CurrentUser);
+ }
+ else {
+ $RT::Logger->crit("Found a principal (".$self->Id.") that was neither a user nor a group");
+ return(undef);
+ }
+ $self->{'object'}->Load($self->ObjectId());
+ }
+ return ($self->{'object'});
+
+
+}
+# }}}
+
+# {{{ ACL Related routines
+
+# {{{ GrantRight
+
+=head2 GrantRight { Right => RIGHTNAME, Object => undef }
+
+A helper function which calls RT::ACE->Create
+
+=cut
+
+sub GrantRight {
+ my $self = shift;
+ my %args = ( Right => undef,
+ Object => undef,
+ @_);
+
+
+ #if we haven't specified any sort of right, we're talking about a global right
+ if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
+ $args{'Object'} = $RT::System;
+ }
+
+ unless ($args{'Right'}) {
+ return(0, $self->loc("Invalid Right"));
+ }
+
+
+ #ACL check handled in ACE.pm
+ my $ace = RT::ACE->new( $self->CurrentUser );
+
+
+ my $type = $self->_GetPrincipalTypeForACL();
+
+ # If it's a user, we really want to grant the right to their
+ # user equivalence group
+ return ( $ace->Create(RightName => $args{'Right'},
+ Object => $args{'Object'},
+ PrincipalType => $type,
+ PrincipalId => $self->Id
+ ) );
+}
+# }}}
+
+# {{{ RevokeRight
+
+=head2 RevokeRight { Right => "RightName", Object => "object" }
+
+Delete a right that a user has
+
+=cut
+
+sub RevokeRight {
+
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Object => undef,
+ @_
+ );
+
+ #if we haven't specified any sort of right, we're talking about a global right
+ if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
+ $args{'Object'} = $RT::System;
+ }
+ #ACL check handled in ACE.pm
+ my $type = $self->_GetPrincipalTypeForACL();
+
+ my $ace = RT::ACE->new( $self->CurrentUser );
+ $ace->LoadByValues(
+ RightName => $args{'Right'},
+ Object => $args{'Object'},
+ PrincipalType => $type,
+ PrincipalId => $self->Id
+ );
+
+ unless ( $ace->Id ) {
+ return ( 0, $self->loc("ACE not found") );
+ }
+ return ( $ace->Delete );
+}
+
+# }}}
+
+
+
+# {{{ sub HasRight
+
+=head2 sub HasRight (Right => 'right' Object => undef)
+
+
+Checks to see whether this principal has the right "Right" for the Object
+specified. If the Object parameter is omitted, checks to see whether the
+user has the right globally.
+
+This still hard codes to check to see if a user has queue-level rights
+if we ask about a specific ticket.
+
+
+This takes the params:
+
+ Right => name of a right
+
+ And either:
+
+ Object => an RT style object (->id will get its id)
+
+
+
+
+Returns 1 if a matching ACE was found.
+
+Returns undef if no ACE was found.
+
+=cut
+
+sub HasRight {
+
+ my $self = shift;
+ my %args = ( Right => undef,
+ Object => undef,
+ EquivObjects => undef,
+ @_ );
+
+ if ( $self->Disabled ) {
+ $RT::Logger->err( "Disabled User: " . $self->id . " failed access check for " . $args{'Right'} );
+ return (undef);
+ }
+
+ if ( !defined $args{'Right'} ) {
+ require Carp;
+ $RT::Logger->debug( Carp::cluck("HasRight called without a right") );
+ return (undef);
+ }
+
+ if ( defined( $args{'Object'} )) {
+ return (undef) unless (UNIVERSAL::can( $args{'Object'}, 'id' ) );
+ push(@{$args{'EquivObjects'}}, $args{Object});
+ }
+ elsif ( $args{'ObjectId'} && $args{'ObjectType'} ) {
+ $RT::Logger->crit(Carp::cluck("API not supprted"));
+ }
+ else {
+ $RT::Logger->crit("$self HasRight called with no valid object");
+ return (undef);
+ }
+
+ # If this object is a ticket, we care about ticket roles and queue roles
+ if ( (ref($args{'Object'}) eq 'RT::Ticket') && $args{'Object'}->Id) {
+ # this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object
+ # and ask all the rest of our questions about the queue.
+ push (@{$args{'EquivObjects'}}, $args{'Object'}->QueueObj);
+
+ }
+
+
+ # {{{ If we've cached a win or loss for this lookup say so
+
+ # {{{ Construct a hashkey to cache decisions in
+ my $hashkey = do {
+ no warnings 'uninitialized';
+
+ # We don't worry about the hash ordering, as this is only
+ # temporarily used; also if the key changes it would be
+ # invalidated anyway.
+ join (
+ ";:;", $self->Id, map {
+ $_, # the key of each arguments
+ ($_ eq 'EquivObjects') # for object arrayref...
+ ? map(_ReferenceId($_), @{$args{$_}}) # calculate each
+ : _ReferenceId( $args{$_} ) # otherwise just the value
+ } keys %args
+ );
+ };
+ # }}}
+
+ #Anything older than 60 seconds needs to be rechecked
+ my $cache_timeout = ( time - 60 );
+
+ # {{{ if we've cached a positive result for this query, return 1
+ if ( ( defined $self->_ACLCache->{"$hashkey"} )
+ && ( $self->_ACLCache->{"$hashkey"}{'val'} == 1 )
+ && ( defined $self->_ACLCache->{"$hashkey"}{'set'} )
+ && ( $self->_ACLCache->{"$hashkey"}{'set'} > $cache_timeout ) ) {
+
+ #$RT::Logger->debug("Cached ACL win for ". $args{'Right'}.$args{'Scope'}. $args{'AppliesTo'}."\n");
+ return ( 1);
+ }
+ # }}}
+
+ # {{{ if we've cached a negative result for this query return undef
+ elsif ( ( defined $self->_ACLCache->{"$hashkey"} )
+ && ( $self->_ACLCache->{"$hashkey"}{'val'} == -1 )
+ && ( defined $self->_ACLCache->{"$hashkey"}{'set'} )
+ && ( $self->_ACLCache->{"$hashkey"}{'set'} > $cache_timeout ) ) {
+
+ #$RT::Logger->debug("Cached ACL loss decision for ". $args{'Right'}.$args{'Scope'}. $args{'AppliesTo'}."\n");
+
+ return (undef);
+ }
+ # }}}
+
+ # }}}
+
+
+
+ # {{{ Out of date docs
+
+ # We want to grant the right if:
+
+
+ # # The user has the right as a member of a system-internal or
+ # # user-defined group
+ #
+ # Find all records from the ACL where they're granted to a group
+ # of type "UserDefined" or "System"
+ # for the object "System or the object "Queue N" and the group we're looking
+ # at has the recursive member $self->Id
+ #
+ # # The user has the right based on a role
+ #
+ # Find all the records from ACL where they're granted to the role "foo"
+ # for the object "System" or the object "Queue N" and the group we're looking
+ # at is of domain ("RT::Queue-Role" and applies to the right queue)
+ # or ("RT::Ticket-Role" and applies to the right ticket)
+ # and the type is the same as the type of the ACL and the group has
+ # the recursive member $self->Id
+ #
+
+ # }}}
+
+ my ( $or_look_at_object_rights, $or_check_roles );
+ my $right = $args{'Right'};
+
+ # {{{ Construct Right Match
+
+ # If an object is defined, we want to look at rights for that object
+
+ my @look_at_objects;
+ push (@look_at_objects, "ACL.ObjectType = 'RT::System'")
+ unless $self->can('_IsOverrideGlobalACL') and $self->_IsOverrideGlobalACL($args{Object});
+
+
+
+ foreach my $obj (@{$args{'EquivObjects'}}) {
+ next unless (UNIVERSAL::can($obj, 'id'));
+ my $type = ref($obj);
+ my $id = $obj->id;
+
+ unless ($id) {
+ use Carp;
+ Carp::cluck("Trying to check $type rights for an unspecified $type");
+ $RT::Logger->crit("Trying to check $type rights for an unspecified $type");
+ }
+ push @look_at_objects, "(ACL.ObjectType = '$type' AND ACL.ObjectId = '$id')";
+ }
+
+
+ # }}}
+
+ # {{{ Build that honkin-big SQL query
+
+
+
+ my $query_base = "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE ".
+ # Only find superuser or rights with the name $right
+ "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') ".
+ # Never find disabled groups.
+ "AND Principals.Disabled = 0 " .
+ "AND CachedGroupMembers.Disabled = 0 ".
+ "AND Principals.id = Groups.id " . # We always grant rights to Groups
+
+ # See if the principal is a member of the group recursively or _is the rightholder_
+ # never find recursively disabled group members
+ # also, check to see if the right is being granted _directly_ to this principal,
+ # as is the case when we want to look up group rights
+ "AND Principals.id = CachedGroupMembers.GroupId AND CachedGroupMembers.MemberId = '" . $self->Id . "' ".
+
+ # Make sure the rights apply to the entire system or to the object in question
+ "AND ( ".join(' OR ', @look_at_objects).") ";
+
+
+
+ # The groups query does the query based on group membership and individual user rights
+
+ my $groups_query = $query_base .
+
+ # limit the result set to groups of types ACLEquivalence (user) UserDefined, SystemInternal and Personal
+ "AND ( ( ACL.PrincipalId = Principals.id AND ACL.PrincipalType = 'Group' AND ".
+ "(Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence' OR Groups.Domain = 'Personal'))".
+
+ " ) ";
+ $self->_Handle->ApplyLimits(\$groups_query, 1); #only return one result
+
+ my @roles;
+ foreach my $object (@{$args{'EquivObjects'}}) {
+ push (@roles, $self->_RolesForObject(ref($object), $object->id));
+ }
+
+ # The roles query does the query based on roles
+ my $roles_query;
+ if (@roles) {
+ $roles_query = $query_base . "AND ".
+ " ( (".join (' OR ', @roles)." ) ".
+ " AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.id AND Principals.PrincipalType = 'Group') ";
+ $self->_Handle->ApplyLimits(\$roles_query, 1); #only return one result
+
+ }
+
+
+
+ # }}}
+
+ # {{{ Actually check the ACL by performing an SQL query
+ # $RT::Logger->debug("Now Trying $groups_query");
+ my $hitcount = $self->_Handle->FetchResult($groups_query);
+
+ # }}}
+
+ # {{{ if there's a match, the right is granted
+ if ($hitcount) {
+
+ # Cache a positive hit.
+ $self->_ACLCache->{"$hashkey"}{'set'} = time;
+ $self->_ACLCache->{"$hashkey"}{'val'} = 1;
+ return (1);
+ }
+ # }}}
+ # {{{ If there's no match on groups, try it on roles
+ else {
+
+ $hitcount = $self->_Handle->FetchResult($roles_query);
+
+ if ($hitcount) {
+
+ # Cache a positive hit.
+ $self->_ACLCache->{"$hashkey"}{'set'} = time;
+ $self->_ACLCache->{"$hashkey"}{'val'} = 1;
+ return (1);
+ }
+
+ else {
+ # cache a negative hit
+ $self->_ACLCache->{"$hashkey"}{'set'} = time;
+ $self->_ACLCache->{"$hashkey"}{'val'} = -1;
+
+ return (undef);
+ }
+ }
+ # }}}
+}
+
+# }}}
+
+# {{{ _RolesForObject
+
+
+
+=head2 _RolesForObject( $object_type, $object_id)
+
+Returns an SQL clause finding role groups for Objects
+
+=cut
+
+
+sub _RolesForObject {
+ my $self = shift;
+ my $type = shift;
+ my $id = shift;
+
+ unless ($id) {
+ $id = '0';
+ }
+
+ # This should never be true.
+ unless ($id =~ /^\d+$/) {
+ $RT::Logger->crit("RT::Prinicipal::_RolesForObject called with type $type and a non-integer id: '$id'");
+ $id = "'$id'";
+ }
+
+ my $clause = "(Groups.Domain = '".$type."-Role' AND Groups.Instance = $id) ";
+
+ return($clause);
+}
+
+# }}}
+
+# }}}
+
+# {{{ ACL caching
+
+# {{{ _ACLCache
+
+=head2 _ACLCache
+
+# Function: _ACLCache
+# Type : private instance
+# Args : none
+# Lvalue : hash: ACLCache
+# Desc : Returns a reference to the Key cache hash
+
+=cut
+
+sub _ACLCache {
+ return(\%_ACL_KEY_CACHE);
+}
+
+# }}}
+
+# {{{ _InvalidateACLCache
+
+=head2 _InvalidateACLCache
+
+Cleans out and reinitializes the user rights key cache
+
+=cut
+
+sub _InvalidateACLCache {
+ %_ACL_KEY_CACHE = ();
+}
+
+# }}}
+
+# }}}
+
+
+# {{{ _GetPrincipalTypeForACL
+
+=head2 _GetPrincipalTypeForACL
+
+Gets the principal type. if it's a user, it's a user. if it's a role group and it has a Type,
+return that. if it has no type, return group.
+
+=cut
+
+sub _GetPrincipalTypeForACL {
+ my $self = shift;
+ my $type;
+ if ($self->PrincipalType eq 'Group' && $self->Object->Domain =~ /Role$/) {
+ $type = $self->Object->Type;
+ }
+ else {
+ $type = $self->PrincipalType;
+ }
+
+ return($type);
+}
+
+# }}}
+
+# {{{ _ReferenceId
+
+=head2 _ReferenceId
+
+Returns a list uniquely representing an object or normal scalar.
+
+For scalars, its string value is returned; for objects that has an
+id() method, its class name and Id are returned as a string seperated by a "-".
+
+=cut
+
+sub _ReferenceId {
+ my $scalar = shift;
+
+ # just return the value for non-objects
+ return $scalar unless UNIVERSAL::can($scalar, 'id');
+
+ # an object -- return the class and id
+ return(ref($scalar)."-". $scalar->id);
+}
+
+# }}}
+
+1;
diff --git a/rt/lib/RT/Principals.pm b/rt/lib/RT/Principals.pm
new file mode 100644
index 0000000..c45a4c7
--- /dev/null
+++ b/rt/lib/RT/Principals.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Principals -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Principals
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Principals;
+
+use RT::SearchBuilder;
+use RT::Principal;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Principals';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Principal item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Principal->new($self->CurrentUser));
+}
+
+ eval "require RT::Principals_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principals_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Principals_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principals_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Principals_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Principals_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Principals_Overlay, RT::Principals_Vendor, RT::Principals_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Principals_Overlay.pm b/rt/lib/RT/Principals_Overlay.pm
new file mode 100644
index 0000000..0d8c54c
--- /dev/null
+++ b/rt/lib/RT/Principals_Overlay.pm
@@ -0,0 +1,52 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Principals - a collection of RT::Principal objects
+
+=head1 SYNOPSIS
+
+ use RT::Principals;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Principals);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+
+
+1;
diff --git a/rt/lib/RT/Queue.pm b/rt/lib/RT/Queue.pm
new file mode 100755
index 0000000..b362c9f
--- /dev/null
+++ b/rt/lib/RT/Queue.pm
@@ -0,0 +1,371 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Queue
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Queue;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Queues');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ varchar(120) 'CorrespondAddress'.
+ varchar(120) 'CommentAddress'.
+ int(11) 'InitialPriority'.
+ int(11) 'FinalPriority'.
+ int(11) 'DefaultDueIn'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Description => '',
+ CorrespondAddress => '',
+ CommentAddress => '',
+ InitialPriority => '0',
+ FinalPriority => '0',
+ DefaultDueIn => '0',
+ Disabled => '0',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ CorrespondAddress => $args{'CorrespondAddress'},
+ CommentAddress => $args{'CommentAddress'},
+ InitialPriority => $args{'InitialPriority'},
+ FinalPriority => $args{'FinalPriority'},
+ DefaultDueIn => $args{'DefaultDueIn'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item CorrespondAddress
+
+Returns the current value of CorrespondAddress.
+(In the database, CorrespondAddress is stored as varchar(120).)
+
+
+
+=item SetCorrespondAddress VALUE
+
+
+Set CorrespondAddress to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CorrespondAddress will be stored as a varchar(120).)
+
+
+=cut
+
+
+=item CommentAddress
+
+Returns the current value of CommentAddress.
+(In the database, CommentAddress is stored as varchar(120).)
+
+
+
+=item SetCommentAddress VALUE
+
+
+Set CommentAddress to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CommentAddress will be stored as a varchar(120).)
+
+
+=cut
+
+
+=item InitialPriority
+
+Returns the current value of InitialPriority.
+(In the database, InitialPriority is stored as int(11).)
+
+
+
+=item SetInitialPriority VALUE
+
+
+Set InitialPriority to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, InitialPriority will be stored as a int(11).)
+
+
+=cut
+
+
+=item FinalPriority
+
+Returns the current value of FinalPriority.
+(In the database, FinalPriority is stored as int(11).)
+
+
+
+=item SetFinalPriority VALUE
+
+
+Set FinalPriority to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, FinalPriority will be stored as a int(11).)
+
+
+=cut
+
+
+=item DefaultDueIn
+
+Returns the current value of DefaultDueIn.
+(In the database, DefaultDueIn is stored as int(11).)
+
+
+
+=item SetDefaultDueIn VALUE
+
+
+Set DefaultDueIn to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, DefaultDueIn will be stored as a int(11).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+=item Disabled
+
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
+
+
+
+=item SetDisabled VALUE
+
+
+Set Disabled to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Disabled will be stored as a smallint(6).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ CorrespondAddress =>
+ {read => 1, write => 1, type => 'varchar(120)', default => ''},
+ CommentAddress =>
+ {read => 1, write => 1, type => 'varchar(120)', default => ''},
+ InitialPriority =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ FinalPriority =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ DefaultDueIn =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ Disabled =>
+ {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::Queue_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queue_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Queue_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queue_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Queue_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queue_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Queue_Overlay, RT::Queue_Vendor, RT::Queue_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Queue_Overlay.pm b/rt/lib/RT/Queue_Overlay.pm
new file mode 100644
index 0000000..fcc185b
--- /dev/null
+++ b/rt/lib/RT/Queue_Overlay.pm
@@ -0,0 +1,1015 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Queue - an RT Queue object
+
+=head1 SYNOPSIS
+
+ use RT::Queue;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+use RT::Queue;
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use vars qw(@STATUS @ACTIVE_STATUS @INACTIVE_STATUS $RIGHTS);
+use RT::Groups;
+use RT::ACL;
+
+
+@ACTIVE_STATUS = qw(new open stalled);
+@INACTIVE_STATUS = qw(resolved rejected deleted);
+@STATUS = (@ACTIVE_STATUS, @INACTIVE_STATUS);
+
+# $self->loc('new'); # For the string extractor to get a string to localize
+# $self->loc('open'); # For the string extractor to get a string to localize
+# $self->loc('stalled'); # For the string extractor to get a string to localize
+# $self->loc('resolved'); # For the string extractor to get a string to localize
+# $self->loc('rejected'); # For the string extractor to get a string to localize
+# $self->loc('deleted'); # For the string extractor to get a string to localize
+
+
+$RIGHTS = {
+ SeeQueue => 'Can this principal see this queue', # loc_pair
+ AdminQueue => 'Create, delete and modify queues', # loc_pair
+ ShowACL => 'Display Access Control List', # loc_pair
+ ModifyACL => 'Modify Access Control List', # loc_pair
+ ModifyQueueWatchers => 'Modify the queue watchers', # loc_pair
+ AdminCustomFields => 'Create, delete and modify custom fields', # loc_pair
+ ModifyTemplate => 'Modify Scrip templates for this queue', # loc_pair
+ ShowTemplate => 'Display Scrip templates for this queue', # loc_pair
+
+ ModifyScrips => 'Modify Scrips for this queue', # loc_pair
+ ShowScrips => 'Display Scrips for this queue', # loc_pair
+
+ ShowTicket => 'Show ticket summaries', # loc_pair
+ ShowTicketComments => 'Show ticket private commentary', # loc_pair
+
+ Watch => 'Sign up as a ticket Requestor or ticket or queue Cc', # loc_pair
+ WatchAsAdminCc => 'Sign up as a ticket or queue AdminCc', # loc_pair
+ CreateTicket => 'Create tickets in this queue', # loc_pair
+ ReplyToTicket => 'Reply to tickets', # loc_pair
+ CommentOnTicket => 'Comment on tickets', # loc_pair
+ OwnTicket => 'Own tickets', # loc_pair
+ ModifyTicket => 'Modify tickets', # loc_pair
+ DeleteTicket => 'Delete tickets', # loc_pair
+ TakeTicket => 'Take tickets', # loc_pair
+ StealTicket => 'Steal tickets', # loc_pair
+
+};
+
+# Tell RT::ACE that this sort of object can get acls granted
+$RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
+
+# TODO: This should be refactored out into an RT::ACLedObject or something
+# stuff the rights into a hash of rights that can exist.
+
+foreach my $right ( keys %{$RIGHTS} ) {
+ $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+}
+
+
+=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 the rights do
+
+=cut
+
+sub AvailableRights {
+ my $self = shift;
+ return($RIGHTS);
+}
+
+# {{{ ActiveStatusArray
+
+=head2 ActiveStatusArray
+
+Returns an array of all ActiveStatuses for this queue
+
+=cut
+
+sub ActiveStatusArray {
+ my $self = shift;
+ return (@ACTIVE_STATUS);
+}
+
+# }}}
+
+# {{{ InactiveStatusArray
+
+=head2 InactiveStatusArray
+
+Returns an array of all InactiveStatuses for this queue
+
+=cut
+
+sub InactiveStatusArray {
+ my $self = shift;
+ return (@INACTIVE_STATUS);
+}
+
+# }}}
+
+# {{{ StatusArray
+
+=head2 StatusArray
+
+Returns an array of all statuses for this queue
+
+=cut
+
+sub StatusArray {
+ my $self = shift;
+ return (@STATUS);
+}
+
+# }}}
+
+# {{{ IsValidStatus
+
+=head2 IsValidStatus VALUE
+
+Returns true if VALUE is a valid status. Otherwise, returns 0
+
+=for testing
+my $q = RT::Queue->new($RT::SystemUser);
+ok($q->IsValidStatus('new')== 1, 'New is a valid status');
+ok($q->IsValidStatus('f00')== 0, 'f00 is not a valid status');
+
+=cut
+
+sub IsValidStatus {
+ my $self = shift;
+ my $value = shift;
+
+ my $retval = grep ( /^$value$/, $self->StatusArray );
+ return ($retval);
+
+}
+
+# }}}
+
+# {{{ IsActiveStatus
+
+=head2 IsActiveStatus VALUE
+
+Returns true if VALUE is a Active status. Otherwise, returns 0
+
+=for testing
+my $q = RT::Queue->new($RT::SystemUser);
+ok($q->IsActiveStatus('new')== 1, 'New is a Active status');
+ok($q->IsActiveStatus('rejected')== 0, 'Rejected is an inactive status');
+ok($q->IsActiveStatus('f00')== 0, 'f00 is not a Active status');
+
+=cut
+
+sub IsActiveStatus {
+ my $self = shift;
+ my $value = shift;
+
+ my $retval = grep ( /^$value$/, $self->ActiveStatusArray );
+ return ($retval);
+
+}
+
+# }}}
+
+# {{{ IsInactiveStatus
+
+=head2 IsInactiveStatus VALUE
+
+Returns true if VALUE is a Inactive status. Otherwise, returns 0
+
+=for testing
+my $q = RT::Queue->new($RT::SystemUser);
+ok($q->IsInactiveStatus('new')== 0, 'New is a Active status');
+ok($q->IsInactiveStatus('rejected')== 1, 'rejeected is an Inactive status');
+ok($q->IsInactiveStatus('f00')== 0, 'f00 is not a Active status');
+
+=cut
+
+sub IsInactiveStatus {
+ my $self = shift;
+ my $value = shift;
+
+ my $retval = grep ( /^$value$/, $self->InactiveStatusArray );
+ return ($retval);
+
+}
+
+# }}}
+
+
+# {{{ sub Create
+
+=head2 Create
+
+Create takes the name of the new queue
+If you pass the ACL check, it creates the queue and returns its queue id.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => undef,
+ CorrespondAddress => '',
+ Description => '',
+ CommentAddress => '',
+ InitialPriority => "0",
+ FinalPriority => "0",
+ DefaultDueIn => "0",
+ @_
+ );
+
+ unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
+ { #Check them ACLs
+ return ( 0, $self->loc("No permission to create queues") );
+ }
+
+ unless ( $self->ValidateName( $args{'Name'} ) ) {
+ return ( 0, $self->loc('Queue already exists') );
+ }
+
+ #TODO better input validation
+ $RT::Handle->BeginTransaction();
+
+ my $id = $self->SUPER::Create(%args);
+ unless ($id) {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc('Queue could not be created') );
+ }
+
+ my $create_ret = $self->_CreateQueueGroups();
+ unless ($create_ret) {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc('Queue could not be created') );
+ }
+
+ $RT::Handle->Commit();
+ return ( $id, $self->loc("Queue created") );
+}
+
+# }}}
+
+# {{{ sub Delete
+
+sub Delete {
+ my $self = shift;
+ return ( 0,
+ $self->loc('Deleting this object would break referential integrity') );
+}
+
+# }}}
+
+# {{{ sub SetDisabled
+
+=head2 SetDisabled
+
+Takes a boolean.
+1 will cause this queue to no longer be avaialble for tickets.
+0 will re-enable this queue
+
+=cut
+
+# }}}
+
+# {{{ sub Load
+
+=head2 Load
+
+Takes either a numerical id or a textual Name and loads the specified queue.
+
+=cut
+
+sub Load {
+ my $self = shift;
+
+ my $identifier = shift;
+ if ( !$identifier ) {
+ return (undef);
+ }
+
+ if ( $identifier =~ /^(\d+)$/ ) {
+ $self->SUPER::LoadById($identifier);
+ }
+ else {
+ $self->LoadByCols( Name => $identifier );
+ }
+
+ return ( $self->Id );
+
+}
+
+# }}}
+
+# {{{ sub ValidateName
+
+=head2 ValidateName NAME
+
+Takes a queue name. Returns true if it's an ok name for
+a new queue. Returns undef if there's already a queue by that name.
+
+=cut
+
+sub ValidateName {
+ my $self = shift;
+ my $name = shift;
+
+ my $tempqueue = new RT::Queue($RT::SystemUser);
+ $tempqueue->Load($name);
+
+ #If we couldn't load it :)
+ unless ( $tempqueue->id() ) {
+ return (1);
+ }
+
+ #If this queue exists, return undef
+ #Avoid the ACL check.
+ if ( $tempqueue->Name() ) {
+ return (undef);
+ }
+
+ #If the queue doesn't exist, return 1
+ else {
+ return (1);
+ }
+
+}
+
+# }}}
+
+# {{{ sub Templates
+
+=head2 Templates
+
+Returns an RT::Templates object of all of this queue's templates.
+
+=cut
+
+sub Templates {
+ my $self = shift;
+
+ my $templates = RT::Templates->new( $self->CurrentUser );
+
+ if ( $self->CurrentUserHasRight('ShowTemplate') ) {
+ $templates->LimitToQueue( $self->id );
+ }
+
+ return ($templates);
+}
+
+# }}}
+
+# {{{ Dealing with custom fields
+
+# {{{ CustomField
+
+=item CustomField NAME
+
+Load the queue-specific custom field named NAME
+
+=cut
+
+sub CustomField {
+ my $self = shift;
+ my $name = shift;
+ my $cf = RT::CustomField->new($self->CurrentUser);
+ $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id);
+ return ($cf);
+}
+
+
+# {{{ CustomFields
+
+=item CustomFields
+
+Returns an RT::CustomFields object containing all global custom fields, as well as those tied to this queue
+
+=cut
+
+sub CustomFields {
+ my $self = shift;
+
+ my $cfs = RT::CustomFields->new( $self->CurrentUser );
+ if ( $self->CurrentUserHasRight('SeeQueue') ) {
+ $cfs->LimitToGlobalOrQueue( $self->Id );
+ }
+ return ($cfs);
+}
+
+# }}}
+
+# }}}
+
+
+# {{{ Routines dealing with watchers.
+
+# {{{ _CreateQueueGroups
+
+=head2 _CreateQueueGroups
+
+Create the ticket groups and relationships for this ticket.
+This routine expects to be called from Ticket->Create _inside of a transaction_
+
+It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
+
+It will return true on success and undef on failure.
+
+=begin testing
+
+my $Queue = RT::Queue->new($RT::SystemUser); my ($id, $msg) = $Queue->Create(Name => "Foo",
+ );
+ok ($id, "Foo $id was created");
+ok(my $group = RT::Group->new($RT::SystemUser));
+ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'Cc'));
+ok ($group->Id, "Found the requestors object for this Queue");
+
+
+ok ((my $add_id, $add_msg) = $Queue->AddWatcher(Type => 'Cc', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
+ok ($add_id, "Add succeeded: ($add_msg)");
+ok(my $bob = RT::User->new($RT::SystemUser), "Creating a bob rt::user");
+$bob->LoadByEmail('bob@fsck.com');
+ok($bob->Id, "Found the bob rt user");
+ok ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $bob->PrincipalId), "The Queue actually has bob at fsck.com as a requestor");;
+ok ((my $add_id, $add_msg) = $Queue->DeleteWatcher(Type =>'Cc', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
+ok (!$Queue->IsWatcher(Type => 'Cc', Principal => $bob->PrincipalId), "The Queue no longer has bob at fsck.com as a requestor");;
+
+
+$group = RT::Group->new($RT::SystemUser);
+ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'Cc'));
+ok ($group->Id, "Found the cc object for this Queue");
+$group = RT::Group->new($RT::SystemUser);
+ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'AdminCc'));
+ok ($group->Id, "Found the AdminCc object for this Queue");
+
+=end testing
+
+=cut
+
+
+sub _CreateQueueGroups {
+ my $self = shift;
+
+ my @types = qw(Cc AdminCc Requestor Owner);
+
+ foreach my $type (@types) {
+ my $type_obj = RT::Group->new($self->CurrentUser);
+ my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id,
+ Type => $type,
+ Domain => 'RT::Queue-Role');
+ unless ($id) {
+ $RT::Logger->error("Couldn't create a Queue group of type '$type' for ticket ".
+ $self->Id.": ".$msg);
+ return(undef);
+ }
+ }
+ return(1);
+
+}
+
+
+# }}}
+
+# {{{ sub AddWatcher
+
+=head2 AddWatcher
+
+AddWatcher takes a parameter hash. The keys are as follows:
+
+Type One of Requestor, Cc, AdminCc
+
+PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
+Email The email address of the new watcher. If a user with this
+ email address can't be found, a new nonprivileged user will be created.
+
+If the watcher you\'re trying to set has an RT account, set the Owner paremeter to their User Id. Otherwise, set the Email parameter to their Email address.
+
+=cut
+
+sub AddWatcher {
+ my $self = shift;
+ my %args = (
+ Type => undef,
+ PrincipalId => undef,
+ Email => undef,
+ @_
+ );
+
+ # {{{ Check ACLS
+ #If the watcher we're trying to add is for the current user
+ if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
+ # If it's an AdminCc and they don't have
+ # 'WatchAsAdminCc' or 'ModifyTicket', bail
+ if ( $args{'Type'} eq 'AdminCc' ) {
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
+ or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+
+ # If it's a Requestor or Cc and they don't have
+ # 'Watch' or 'ModifyTicket', bail
+ elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
+
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
+ or $self->CurrentUserHasRight('Watch') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+ else {
+ $RT::Logger->warn( "$self -> AddWatcher got passed a bogus type");
+ return ( 0, $self->loc('Error in parameters to Queue->AddWatcher') );
+ }
+ }
+
+ # If the watcher isn't the current user
+ # and the current user doesn't have 'ModifyQueueWatcher'
+ # bail
+ else {
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+
+ # }}}
+
+ return ( $self->_AddWatcher(%args) );
+}
+
+#This contains the meat of AddWatcher. but can be called from a routine like
+# Create, which doesn't need the additional acl check
+sub _AddWatcher {
+ my $self = shift;
+ my %args = (
+ Type => undef,
+ Silent => undef,
+ PrincipalId => undef,
+ Email => undef,
+ @_
+ );
+
+
+ my $principal = RT::Principal->new($self->CurrentUser);
+ if ($args{'PrincipalId'}) {
+ $principal->Load($args{'PrincipalId'});
+ }
+ elsif ($args{'Email'}) {
+
+ my $user = RT::User->new($self->CurrentUser);
+ $user->LoadByEmail($args{'Email'});
+
+ unless ($user->Id) {
+ $user->Load($args{'Email'});
+ }
+ if ($user->Id) { # If the user exists
+ $principal->Load($user->PrincipalId);
+ } else {
+
+ # if the user doesn't exist, we need to create a new user
+ my $new_user = RT::User->new($RT::SystemUser);
+
+ my ( $Val, $Message ) = $new_user->Create(
+ Name => $args{'Email'},
+ EmailAddress => $args{'Email'},
+ RealName => $args{'Email'},
+ Privileged => 0,
+ Comments => 'Autocreated when added as a watcher');
+ unless ($Val) {
+ $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
+ # Deal with the race condition of two account creations at once
+ $new_user->LoadByEmail($args{'Email'});
+ }
+ $principal->Load($new_user->PrincipalId);
+ }
+ }
+ # If we can't find this watcher, we need to bail.
+ unless ($principal->Id) {
+ return(0, $self->loc("Could not find or create that user"));
+ }
+
+
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
+ unless ($group->id) {
+ return(0,$self->loc("Group not found"));
+ }
+
+ if ( $group->HasMember( $principal)) {
+
+ return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
+ }
+
+
+ my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
+ unless ($m_id) {
+ $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id."\n".$m_msg);
+
+ return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
+ }
+ return ( 1, $self->loc('Added principal as a [_1] for this queue', $args{'Type'}) );
+}
+
+# }}}
+
+# {{{ sub DeleteWatcher
+
+=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
+
+
+Deletes a queue watcher. Takes two arguments:
+
+Type (one of Requestor,Cc,AdminCc)
+
+and one of
+
+PrincipalId (an RT::Principal Id of the watcher you want to remove)
+ OR
+Email (the email address of an existing wathcer)
+
+
+=cut
+
+
+sub DeleteWatcher {
+ my $self = shift;
+
+ my %args = ( Type => undef,
+ PrincipalId => undef,
+ @_ );
+
+ unless ($args{'PrincipalId'} ) {
+ return(0, $self->loc("No principal specified"));
+ }
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($args{'PrincipalId'});
+
+ # If we can't find this watcher, we need to bail.
+ unless ($principal->Id) {
+ return(0, $self->loc("Could not find that principal"));
+ }
+
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
+ unless ($group->id) {
+ return(0,$self->loc("Group not found"));
+ }
+
+ # {{{ Check ACLS
+ #If the watcher we're trying to add is for the current user
+ if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
+ # If it's an AdminCc and they don't have
+ # 'WatchAsAdminCc' or 'ModifyQueue', bail
+ if ( $args{'Type'} eq 'AdminCc' ) {
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
+ or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+
+ # If it's a Requestor or Cc and they don't have
+ # 'Watch' or 'ModifyQueue', bail
+ elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
+ or $self->CurrentUserHasRight('Watch') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+ else {
+ $RT::Logger->warn( "$self -> DelWatcher got passed a bogus type");
+ return ( 0, $self->loc('Error in parameters to Queue->DelWatcher') );
+ }
+ }
+
+ # If the watcher isn't the current user
+ # and the current user doesn't have 'ModifyQueueWathcers' bail
+ else {
+ unless ( $self->CurrentUserHasRight('ModifyQueueWatchers') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+
+ # }}}
+
+
+ # see if this user is already a watcher.
+
+ unless ( $group->HasMember($principal)) {
+ return ( 0,
+ $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
+ }
+
+ my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
+ unless ($m_id) {
+ $RT::Logger->error("Failed to delete ".$principal->Id.
+ " as a member of group ".$group->Id."\n".$m_msg);
+
+ return ( 0, $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
+ }
+
+ return ( 1, $self->loc("[_1] is no longer a [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
+}
+
+# }}}
+
+# {{{ AdminCcAddresses
+
+=head2 AdminCcAddresses
+
+returns String: All queue AdminCc email addresses as a string
+
+=cut
+
+sub AdminCcAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('SeeQueue') ) {
+ return undef;
+ }
+
+ return ( $self->AdminCc->MemberEmailAddressesAsString )
+
+}
+
+# }}}
+
+# {{{ CcAddresses
+
+=head2 CcAddresses
+
+returns String: All queue Ccs as a string of email addresses
+
+=cut
+
+sub CcAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('SeeQueue') ) {
+ return undef;
+ }
+
+ return ( $self->Cc->MemberEmailAddressesAsString);
+
+}
+# }}}
+
+
+# {{{ sub Cc
+
+=head2 Cc
+
+Takes nothing.
+Returns an RT::Group object which contains this Queue's Ccs.
+If the user doesn't have "ShowQueue" permission, returns an empty group
+
+=cut
+
+sub Cc {
+ my $self = shift;
+
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $self->CurrentUserHasRight('SeeQueue') ) {
+ $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
+ }
+ return ($group);
+
+}
+
+# }}}
+
+# {{{ sub AdminCc
+
+=head2 AdminCc
+
+Takes nothing.
+Returns an RT::Group object which contains this Queue's AdminCcs.
+If the user doesn't have "ShowQueue" permission, returns an empty group
+
+=cut
+
+sub AdminCc {
+ my $self = shift;
+
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $self->CurrentUserHasRight('SeeQueue') ) {
+ $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
+ }
+ return ($group);
+
+}
+
+# }}}
+
+# {{{ IsWatcher, IsCc, IsAdminCc
+
+# {{{ sub IsWatcher
+# a generic routine to be called by IsRequestor, IsCc and IsAdminCc
+
+=head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
+
+Takes a param hash with the attributes Type and PrincipalId
+
+Type is one of Requestor, Cc, AdminCc and Owner
+
+PrincipalId is an RT::Principal id
+
+Returns true if that principal is a member of the group Type for this queue
+
+
+=cut
+
+sub IsWatcher {
+ my $self = shift;
+
+ my %args = ( Type => 'Cc',
+ PrincipalId => undef,
+ @_
+ );
+
+ # Load the relevant group.
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
+ # Ask if it has the member in question
+
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($args{'PrincipalId'});
+ unless ($principal->Id) {
+ return (undef);
+ }
+
+ return ($group->HasMemberRecursively($principal));
+}
+
+# }}}
+
+
+# {{{ sub IsCc
+
+=head2 IsCc PRINCIPAL_ID
+
+ Takes an RT::Principal id.
+ Returns true if the principal is a requestor of the current queue.
+
+
+=cut
+
+sub IsCc {
+ my $self = shift;
+ my $cc = shift;
+
+ return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
+
+}
+
+# }}}
+
+# {{{ sub IsAdminCc
+
+=head2 IsAdminCc PRINCIPAL_ID
+
+ Takes an RT::Principal id.
+ Returns true if the principal is a requestor of the current queue.
+
+=cut
+
+sub IsAdminCc {
+ my $self = shift;
+ my $person = shift;
+
+ return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
+
+}
+
+# }}}
+
+
+# }}}
+
+
+
+
+
+# }}}
+
+# {{{ ACCESS CONTROL
+
+# {{{ sub _Set
+sub _Set {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('AdminQueue') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ return ( $self->SUPER::_Set(@_) );
+}
+
+# }}}
+
+# {{{ sub _Value
+
+sub _Value {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('SeeQueue') ) {
+ return (undef);
+ }
+
+ return ( $self->__Value(@_) );
+}
+
+# }}}
+
+# {{{ sub CurrentUserHasRight
+
+=head2 CurrentUserHasRight
+
+Takes one argument. A textual string with the name of the right we want to check.
+Returns true if the current user has that right for this queue.
+Returns undef otherwise.
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+
+ return (
+ $self->HasRight(
+ Principal => $self->CurrentUser,
+ Right => "$right"
+ )
+ );
+
+}
+
+# }}}
+
+# {{{ sub HasRight
+
+=head2 HasRight
+
+Takes a param hash with the fields 'Right' and 'Principal'.
+Principal defaults to the current user.
+Returns true if the principal has that right for this queue.
+Returns undef otherwise.
+
+=cut
+
+# TAKES: Right and optional "Principal" which defaults to the current user
+sub HasRight {
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Principal => $self->CurrentUser,
+ @_
+ );
+ unless ( defined $args{'Principal'} ) {
+ $RT::Logger->debug("Principal undefined in Queue::HasRight");
+
+ }
+ return (
+ $args{'Principal'}->HasRight(
+ Object => $self,
+ Right => $args{'Right'}
+ )
+ );
+}
+
+# }}}
+
+# }}}
+
+1;
diff --git a/rt/lib/RT/Queues.pm b/rt/lib/RT/Queues.pm
new file mode 100755
index 0000000..60aec90
--- /dev/null
+++ b/rt/lib/RT/Queues.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Queues -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Queues
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Queues;
+
+use RT::SearchBuilder;
+use RT::Queue;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Queues';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Queue item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Queue->new($self->CurrentUser));
+}
+
+ eval "require RT::Queues_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queues_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Queues_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queues_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Queues_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Queues_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Queues_Overlay, RT::Queues_Vendor, RT::Queues_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Queues_Overlay.pm b/rt/lib/RT/Queues_Overlay.pm
new file mode 100644
index 0000000..b85144b
--- /dev/null
+++ b/rt/lib/RT/Queues_Overlay.pm
@@ -0,0 +1,130 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Queues - a collection of RT::Queue objects
+
+=head1 SYNOPSIS
+
+ use RT::Queues;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Queues);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "Queues";
+ $self->{'primary_key'} = "id";
+
+ # By default, order by name
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'Name',
+ ORDER => 'ASC');
+
+ return ($self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ sub _DoSearch
+
+=head2 _DoSearch
+
+ A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that _Disabled rows never get seen unless
+we're explicitly trying to see them.
+
+=cut
+
+sub _DoSearch {
+ my $self = shift;
+
+ #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
+ unless($self->{'find_disabled_rows'}) {
+ $self->LimitToEnabled();
+ }
+
+ return($self->SUPER::_DoSearch(@_));
+
+}
+
+# }}}
+
+
+# {{{ sub Limit
+sub Limit {
+ my $self = shift;
+ my %args = ( ENTRYAGGREGATOR => 'AND',
+ @_);
+ $self->SUPER::Limit(%args);
+}
+# }}}
+
+# {{{ sub Next
+
+=head2 Next
+
+Returns the next queue that this user can see.
+
+=cut
+
+sub Next {
+ my $self = shift;
+
+
+ my $Queue = $self->SUPER::Next();
+ if ((defined($Queue)) and (ref($Queue))) {
+
+ if ($Queue->CurrentUserHasRight('SeeQueue')) {
+ return($Queue);
+ }
+
+ #If the user doesn't have the right to show this queue
+ else {
+ return($self->Next());
+ }
+ }
+ #if there never was any queue
+ else {
+ return(undef);
+ }
+
+}
+# }}}
+
+1;
+
diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm
new file mode 100755
index 0000000..7a86906
--- /dev/null
+++ b/rt/lib/RT/Record.pm
@@ -0,0 +1,458 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Record - Base class for RT record objects
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+
+=begin testing
+
+ok (require RT::Record);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+package RT::Record;
+use RT::Date;
+use RT::User;
+
+use RT::Base;
+use DBIx::SearchBuilder::Record::Cachable;
+
+use strict;
+use vars qw/@ISA/;
+
+@ISA = qw(RT::Base);
+
+if ($RT::DontCacheSearchBuilderRecords ) {
+ push (@ISA, 'DBIx::SearchBuilder::Record');
+} else {
+ push (@ISA, 'DBIx::SearchBuilder::Record::Cachable');
+
+}
+
+# {{{ sub _Init
+
+sub _Init {
+ my $self = shift;
+ $self->CurrentUser(@_);
+
+}
+
+# }}}
+
+# {{{ _PrimaryKeys
+
+=head2 _PrimaryKeys
+
+The primary keys for RT classes is 'id'
+
+=cut
+
+sub _PrimaryKeys {
+ my $self = shift;
+ return ( ['id'] );
+}
+
+# }}}
+
+# {{{ sub _Handle
+sub _Handle {
+ my $self = shift;
+ return ($RT::Handle);
+}
+
+# }}}
+
+# {{{ sub Create
+
+=item Create PARAMHASH
+
+Takes a PARAMHASH of Column -> Value pairs.
+If any Column has a Validate$PARAMNAME subroutine defined and the
+value provided doesn't pass validation, this routine returns
+an error.
+
+If this object's table has any of the following atetributes defined as
+'Auto', this routine will automatically fill in their values.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %attribs = (@_);
+ foreach my $key ( keys %attribs ) {
+ my $method = "Validate$key";
+ unless ( $self->$method( $attribs{$key} ) ) {
+ if (wantarray) {
+ return ( 0, $self->loc('Invalid value for [_1]', $key) );
+ }
+ else {
+ return (0);
+ }
+ }
+ }
+ my $now = RT::Date->new( $self->CurrentUser );
+ $now->Set( Format => 'unix', Value => time );
+ $attribs{'Created'} = $now->ISO() if ( $self->_Accessible( 'Created', 'auto' ) && !$attribs{'Created'});
+
+ if ($self->_Accessible( 'Creator', 'auto' ) && !$attribs{'Creator'}) {
+ $attribs{'Creator'} = $self->CurrentUser->id || '0';
+ }
+ $attribs{'LastUpdated'} = $now->ISO()
+ if ( $self->_Accessible( 'LastUpdated', 'auto' ) && !$attribs{'LastUpdated'});
+
+ $attribs{'LastUpdatedBy'} = $self->CurrentUser->id || '0'
+ if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) && !$attribs{'LastUpdatedBy'});
+
+ my $id = $self->SUPER::Create(%attribs);
+ if ( UNIVERSAL::isa( $id, 'Class::ReturnValue' ) ) {
+ if ( $id->errno ) {
+ if (wantarray) {
+ return ( 0,
+ $self->loc( "Internal Error: [_1]", $id->{error_message} ) );
+ }
+ else {
+ return (0);
+ }
+ }
+ }
+ # If the object was created in the database,
+ # load it up now, so we're sure we get what the database
+ # has. Arguably, this should not be necessary, but there
+ # isn't much we can do about it.
+
+ unless ($id) {
+ if (wantarray) {
+ return ( $id, $self->loc('Object could not be created') );
+ }
+ else {
+ return ($id);
+ }
+
+ }
+
+ if (UNIVERSAL::isa('errno',$id)) {
+ exit(0);
+ warn "It's here!";
+ return(undef);
+ }
+
+ $self->Load($id) if ($id);
+
+
+
+ if (wantarray) {
+ return ( $id, $self->loc('Object created') );
+ }
+ else {
+ return ($id);
+ }
+
+}
+
+# }}}
+
+# {{{ sub LoadByCols
+
+=head2 LoadByCols
+
+Override DBIx::SearchBuilder::LoadByCols to do case-insensitive loads if the
+DB is case sensitive
+
+=cut
+
+sub LoadByCols {
+ my $self = shift;
+ my %hash = (@_);
+
+ # If this database is case sensitive we need to uncase objects for
+ # explicit loading
+ if ( $self->_Handle->CaseSensitive ) {
+ my %newhash;
+ foreach my $key ( keys %hash ) {
+
+ # If we've been passed an empty value, we can't do the lookup.
+ # We don't need to explicitly downcase integers or an id.
+ if ( $key =~ '^id$'
+ || !defined( $hash{$key} )
+ || $hash{$key} =~ /^\d+$/
+ )
+ {
+ $newhash{$key} = $hash{$key};
+ }
+ else {
+ my ($op, $val);
+ ($key, $op, $val) = $self->_Handle->_MakeClauseCaseInsensitive($key, '=', $hash{$key});
+ $newhash{$key}->{operator} = $op;
+ $newhash{$key}->{value} = $val;
+ }
+ }
+
+ # We've clobbered everything we care about. bash the old hash
+ # and replace it with the new hash
+ %hash = %newhash;
+ }
+ $self->SUPER::LoadByCols(%hash);
+}
+
+# }}}
+
+# {{{ Datehandling
+
+# There is room for optimizations in most of those subs:
+
+# {{{ LastUpdatedObj
+
+sub LastUpdatedObj {
+ my $self = shift;
+ my $obj = new RT::Date( $self->CurrentUser );
+
+ $obj->Set( Format => 'sql', Value => $self->LastUpdated );
+ return $obj;
+}
+
+# }}}
+
+# {{{ CreatedObj
+
+sub CreatedObj {
+ my $self = shift;
+ my $obj = new RT::Date( $self->CurrentUser );
+
+ $obj->Set( Format => 'sql', Value => $self->Created );
+
+ return $obj;
+}
+
+# }}}
+
+# {{{ AgeAsString
+#
+# TODO: This should be deprecated
+#
+sub AgeAsString {
+ my $self = shift;
+ return ( $self->CreatedObj->AgeAsString() );
+}
+
+# }}}
+
+# {{{ LastUpdatedAsString
+
+# TODO this should be deprecated
+
+sub LastUpdatedAsString {
+ my $self = shift;
+ if ( $self->LastUpdated ) {
+ return ( $self->LastUpdatedObj->AsString() );
+
+ }
+ else {
+ return "never";
+ }
+}
+
+# }}}
+
+# {{{ CreatedAsString
+#
+# TODO This should be deprecated
+#
+sub CreatedAsString {
+ my $self = shift;
+ return ( $self->CreatedObj->AsString() );
+}
+
+# }}}
+
+# {{{ LongSinceUpdateAsString
+#
+# TODO This should be deprecated
+#
+sub LongSinceUpdateAsString {
+ my $self = shift;
+ if ( $self->LastUpdated ) {
+
+ return ( $self->LastUpdatedObj->AgeAsString() );
+
+ }
+ else {
+ return "never";
+ }
+}
+
+# }}}
+
+# }}} Datehandling
+
+# {{{ sub _Set
+sub _Set {
+ my $self = shift;
+
+ my %args = (
+ Field => undef,
+ Value => undef,
+ IsSQL => undef,
+ @_
+ );
+
+ #if the user is trying to modify the record
+ # TODO: document _why_ this code is here
+
+ if ( ( !defined( $args{'Field'} ) ) || ( !defined( $args{'Value'} ) ) ) {
+ $args{'Value'} = 0;
+ }
+
+ $self->_SetLastUpdated();
+ my ( $val, $msg ) = $self->SUPER::_Set(
+ Field => $args{'Field'},
+ Value => $args{'Value'},
+ IsSQL => $args{'IsSQL'}
+ );
+}
+
+# }}}
+
+# {{{ sub _SetLastUpdated
+
+=head2 _SetLastUpdated
+
+This routine updates the LastUpdated and LastUpdatedBy columns of the row in question
+It takes no options. Arguably, this is a bug
+
+=cut
+
+sub _SetLastUpdated {
+ my $self = shift;
+ use RT::Date;
+ my $now = new RT::Date( $self->CurrentUser );
+ $now->SetToNow();
+
+ if ( $self->_Accessible( 'LastUpdated', 'auto' ) ) {
+ my ( $msg, $val ) = $self->__Set(
+ Field => 'LastUpdated',
+ Value => $now->ISO
+ );
+ }
+ if ( $self->_Accessible( 'LastUpdatedBy', 'auto' ) ) {
+ my ( $msg, $val ) = $self->__Set(
+ Field => 'LastUpdatedBy',
+ Value => $self->CurrentUser->id
+ );
+ }
+}
+
+# }}}
+
+# {{{ sub CreatorObj
+
+=head2 CreatorObj
+
+Returns an RT::User object with the RT account of the creator of this row
+
+=cut
+
+sub CreatorObj {
+ my $self = shift;
+ unless ( exists $self->{'CreatorObj'} ) {
+
+ $self->{'CreatorObj'} = RT::User->new( $self->CurrentUser );
+ $self->{'CreatorObj'}->Load( $self->Creator );
+ }
+ return ( $self->{'CreatorObj'} );
+}
+
+# }}}
+
+# {{{ sub LastUpdatedByObj
+
+=head2 LastUpdatedByObj
+
+ Returns an RT::User object of the last user to touch this object
+
+=cut
+
+sub LastUpdatedByObj {
+ my $self = shift;
+ unless ( exists $self->{LastUpdatedByObj} ) {
+ $self->{'LastUpdatedByObj'} = RT::User->new( $self->CurrentUser );
+ $self->{'LastUpdatedByObj'}->Load( $self->LastUpdatedBy );
+ }
+ return $self->{'LastUpdatedByObj'};
+}
+
+# }}}
+
+
+require Encode::compat if $] < 5.007001;
+require Encode;
+
+sub __Value {
+ my $self = shift;
+ my $field = shift;
+ my %args = ( decode_utf8 => 1,
+ @_ );
+
+ unless (defined $field && $field) {
+ $RT::Logger->error("$self __Value called with undef field");
+ }
+ my $value = $self->SUPER::__Value($field);
+
+ return('') if ( !defined($value) || $value eq '');
+
+ return Encode::decode_utf8($value) || $value if $args{'decode_utf8'};
+ return $value;
+}
+
+# Set up defaults for DBIx::SearchBuilder::Record::Cachable
+
+sub _CacheConfig {
+ {
+ 'cache_p' => 1,
+ 'fast_update_p' => 1,
+ 'cache_for_sec' => 30,
+ }
+}
+
+=head2 _DecodeUTF8
+
+ When passed a string will "decode" it int a proper UTF-8 string
+
+=cut
+
+eval "require RT::Record_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Vendor.pm});
+eval "require RT::Record_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm
new file mode 100755
index 0000000..a69dde0
--- /dev/null
+++ b/rt/lib/RT/Scrip.pm
@@ -0,0 +1,500 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Scrip
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Scrip;
+use RT::Record;
+use RT::Queue;
+use RT::Template;
+use RT::ScripCondition;
+use RT::ScripAction;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Scrips');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(255) 'Description'.
+ int(11) 'ScripCondition'.
+ int(11) 'ScripAction'.
+ text 'ConditionRules'.
+ text 'ActionRules'.
+ text 'CustomIsApplicableCode'.
+ text 'CustomPrepareCode'.
+ text 'CustomCommitCode'.
+ varchar(32) 'Stage'.
+ int(11) 'Queue'.
+ int(11) 'Template'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Description => '',
+ ScripCondition => '0',
+ ScripAction => '0',
+ ConditionRules => '',
+ ActionRules => '',
+ CustomIsApplicableCode => '',
+ CustomPrepareCode => '',
+ CustomCommitCode => '',
+ Stage => '',
+ Queue => '0',
+ Template => '0',
+
+ @_);
+ $self->SUPER::Create(
+ Description => $args{'Description'},
+ ScripCondition => $args{'ScripCondition'},
+ ScripAction => $args{'ScripAction'},
+ ConditionRules => $args{'ConditionRules'},
+ ActionRules => $args{'ActionRules'},
+ CustomIsApplicableCode => $args{'CustomIsApplicableCode'},
+ CustomPrepareCode => $args{'CustomPrepareCode'},
+ CustomCommitCode => $args{'CustomCommitCode'},
+ Stage => $args{'Stage'},
+ Queue => $args{'Queue'},
+ Template => $args{'Template'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item ScripCondition
+
+Returns the current value of ScripCondition.
+(In the database, ScripCondition is stored as int(11).)
+
+
+
+=item SetScripCondition VALUE
+
+
+Set ScripCondition to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ScripCondition will be stored as a int(11).)
+
+
+=cut
+
+
+=item ScripConditionObj
+
+Returns the ScripCondition Object which has the id returned by ScripCondition
+
+
+=cut
+
+sub ScripConditionObj {
+ my $self = shift;
+ my $ScripCondition = RT::ScripCondition->new($self->CurrentUser);
+ $ScripCondition->Load($self->__Value('ScripCondition'));
+ return($ScripCondition);
+}
+
+=item ScripAction
+
+Returns the current value of ScripAction.
+(In the database, ScripAction is stored as int(11).)
+
+
+
+=item SetScripAction VALUE
+
+
+Set ScripAction to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ScripAction will be stored as a int(11).)
+
+
+=cut
+
+
+=item ScripActionObj
+
+Returns the ScripAction Object which has the id returned by ScripAction
+
+
+=cut
+
+sub ScripActionObj {
+ my $self = shift;
+ my $ScripAction = RT::ScripAction->new($self->CurrentUser);
+ $ScripAction->Load($self->__Value('ScripAction'));
+ return($ScripAction);
+}
+
+=item ConditionRules
+
+Returns the current value of ConditionRules.
+(In the database, ConditionRules is stored as text.)
+
+
+
+=item SetConditionRules VALUE
+
+
+Set ConditionRules to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ConditionRules will be stored as a text.)
+
+
+=cut
+
+
+=item ActionRules
+
+Returns the current value of ActionRules.
+(In the database, ActionRules is stored as text.)
+
+
+
+=item SetActionRules VALUE
+
+
+Set ActionRules to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ActionRules will be stored as a text.)
+
+
+=cut
+
+
+=item CustomIsApplicableCode
+
+Returns the current value of CustomIsApplicableCode.
+(In the database, CustomIsApplicableCode is stored as text.)
+
+
+
+=item SetCustomIsApplicableCode VALUE
+
+
+Set CustomIsApplicableCode to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomIsApplicableCode will be stored as a text.)
+
+
+=cut
+
+
+=item CustomPrepareCode
+
+Returns the current value of CustomPrepareCode.
+(In the database, CustomPrepareCode is stored as text.)
+
+
+
+=item SetCustomPrepareCode VALUE
+
+
+Set CustomPrepareCode to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomPrepareCode will be stored as a text.)
+
+
+=cut
+
+
+=item CustomCommitCode
+
+Returns the current value of CustomCommitCode.
+(In the database, CustomCommitCode is stored as text.)
+
+
+
+=item SetCustomCommitCode VALUE
+
+
+Set CustomCommitCode to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomCommitCode will be stored as a text.)
+
+
+=cut
+
+
+=item Stage
+
+Returns the current value of Stage.
+(In the database, Stage is stored as varchar(32).)
+
+
+
+=item SetStage VALUE
+
+
+Set Stage to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Stage will be stored as a varchar(32).)
+
+
+=cut
+
+
+=item Queue
+
+Returns the current value of Queue.
+(In the database, Queue is stored as int(11).)
+
+
+
+=item SetQueue VALUE
+
+
+Set Queue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Queue will be stored as a int(11).)
+
+
+=cut
+
+
+=item QueueObj
+
+Returns the Queue Object which has the id returned by Queue
+
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+ my $Queue = RT::Queue->new($self->CurrentUser);
+ $Queue->Load($self->__Value('Queue'));
+ return($Queue);
+}
+
+=item Template
+
+Returns the current value of Template.
+(In the database, Template is stored as int(11).)
+
+
+
+=item SetTemplate VALUE
+
+
+Set Template to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Template will be stored as a int(11).)
+
+
+=cut
+
+
+=item TemplateObj
+
+Returns the Template Object which has the id returned by Template
+
+
+=cut
+
+sub TemplateObj {
+ my $self = shift;
+ my $Template = RT::Template->new($self->CurrentUser);
+ $Template->Load($self->__Value('Template'));
+ return($Template);
+}
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ ScripCondition =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ ScripAction =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ ConditionRules =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ ActionRules =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ CustomIsApplicableCode =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ CustomPrepareCode =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ CustomCommitCode =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ Stage =>
+ {read => 1, write => 1, type => 'varchar(32)', default => ''},
+ Queue =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Template =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Scrip_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrip_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Scrip_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrip_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Scrip_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrip_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Scrip_Overlay, RT::Scrip_Vendor, RT::Scrip_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ScripAction.pm b/rt/lib/RT/ScripAction.pm
new file mode 100755
index 0000000..26824df
--- /dev/null
+++ b/rt/lib/RT/ScripAction.pm
@@ -0,0 +1,279 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::ScripAction
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::ScripAction;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('ScripActions');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ varchar(60) 'ExecModule'.
+ varchar(255) 'Argument'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Description => '',
+ ExecModule => '',
+ Argument => '',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ ExecModule => $args{'ExecModule'},
+ Argument => $args{'Argument'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item ExecModule
+
+Returns the current value of ExecModule.
+(In the database, ExecModule is stored as varchar(60).)
+
+
+
+=item SetExecModule VALUE
+
+
+Set ExecModule to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ExecModule will be stored as a varchar(60).)
+
+
+=cut
+
+
+=item Argument
+
+Returns the current value of Argument.
+(In the database, Argument is stored as varchar(255).)
+
+
+
+=item SetArgument VALUE
+
+
+Set Argument to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Argument will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ ExecModule =>
+ {read => 1, write => 1, type => 'varchar(60)', default => ''},
+ Argument =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::ScripAction_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripAction_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripAction_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripAction_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripAction_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripAction_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ScripAction_Overlay, RT::ScripAction_Vendor, RT::ScripAction_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ScripAction_Overlay.pm b/rt/lib/RT/ScripAction_Overlay.pm
new file mode 100644
index 0000000..e759871
--- /dev/null
+++ b/rt/lib/RT/ScripAction_Overlay.pm
@@ -0,0 +1,233 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::ScripAction - RT Action object
+
+=head1 SYNOPSIS
+
+ use RT::ScripAction;
+
+
+=head1 DESCRIPTION
+
+This module should never be called directly by client code. it's an internal module which
+should only be accessed through exported APIs in other modules.
+
+
+=begin testing
+
+ok (require RT::ScripAction);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+use RT::Template;
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "ScripActions";
+ return ($self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ sub _Accessible
+sub _Accessible {
+ my $self = shift;
+ my %Cols = ( Name => 'read',
+ Description => 'read',
+ ExecModule => 'read',
+ Argument => 'read',
+ Creator => 'read/auto',
+ Created => 'read/auto',
+ LastUpdatedBy => 'read/auto',
+ LastUpdated => 'read/auto'
+ );
+ return($self->SUPER::_Accessible(@_, %Cols));
+}
+# }}}
+
+# {{{ sub Create
+=head2 Create
+
+ Takes a hash. Creates a new Action entry.
+ should be better documented.
+=cut
+
+sub Create {
+ my $self = shift;
+ #TODO check these args and do smart things.
+ return($self->SUPER::Create(@_));
+}
+# }}}
+
+# {{{ sub Delete
+sub Delete {
+ my $self = shift;
+
+ return (0, "ScripAction->Delete not implemented");
+}
+# }}}
+
+# {{{ sub Load
+sub Load {
+ my $self = shift;
+ my $identifier = shift;
+
+ if (!$identifier) {
+ return (0, $self->loc('Input error'));
+ }
+
+ if ($identifier !~ /\D/) {
+ $self->SUPER::Load($identifier);
+ }
+ else {
+ $self->LoadByCol('Name', $identifier);
+
+ }
+
+ if (@_) {
+ # Set the template Id to the passed in template
+ my $template = shift;
+
+ $self->{'Template'} = $template;
+ }
+ return ($self->loc('[_1] ScripAction loaded', $self->Id));
+}
+# }}}
+
+# {{{ sub LoadAction
+
+=head2 LoadAction HASH
+
+ Takes a hash consisting of TicketObj and TransactionObj. Loads an RT::Action:: module.
+
+=cut
+
+sub LoadAction {
+ my $self = shift;
+ my %args = ( TransactionObj => undef,
+ TicketObj => undef,
+ @_ );
+
+ $self->{_TicketObj} = $args{TicketObj};
+
+ #TODO: Put this in an eval
+ $self->ExecModule =~ /^(\w+)$/;
+ my $module = $1;
+ my $type = "RT::Action::". $module;
+
+ eval "require $type" || die "Require of $type failed.\n$@\n";
+
+ $self->{'Action'} = $type->new ( 'ScripActionObj' => $self,
+ 'TicketObj' => $args{'TicketObj'},
+ 'ScripObj' => $args{'ScripObj'},
+ 'TransactionObj' => $args{'TransactionObj'},
+ 'TemplateObj' => $self->TemplateObj,
+ 'Argument' => $self->Argument,
+ );
+}
+# }}}
+
+# {{{ sub TemplateObj
+
+=head2 TemplateObj
+
+Return this action\'s template object
+
+=cut
+
+sub TemplateObj {
+ my $self = shift;
+ return undef unless $self->{Template};
+ if ( !$self->{'TemplateObj'} ) {
+ $self->{'TemplateObj'} = RT::Template->new( $self->CurrentUser );
+ $self->{'TemplateObj'}->LoadById( $self->{'Template'} );
+
+ if ( ( $self->{'TemplateObj'}->__Value('Queue') == 0 )
+ && $self->{'_TicketObj'} ) {
+ my $tmptemplate = RT::Template->new( $self->CurrentUser );
+ my ( $ok, $err ) = $tmptemplate->LoadQueueTemplate(
+ Queue => $self->{'_TicketObj'}->QueueObj->id,
+ Name => $self->{'TemplateObj'}->Name);
+
+ if ( $tmptemplate->id ) {
+ # found the queue-specific template with the same name
+ $self->{'TemplateObj'} = $tmptemplate;
+ }
+ }
+
+ }
+
+ return ( $self->{'TemplateObj'} );
+}
+# }}}
+
+# The following methods call the action object
+
+# {{{ sub Prepare
+
+sub Prepare {
+ my $self = shift;
+ return ($self->{'Action'}->Prepare());
+
+}
+# }}}
+
+# {{{ sub Commit
+sub Commit {
+ my $self = shift;
+ return($self->{'Action'}->Commit());
+
+
+}
+# }}}
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return ($self->{'Action'}->Describe());
+
+}
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+ my $self=shift;
+ $self->{'_TicketObj'} = undef;
+ $self->{'Action'} = undef;
+ $self->{'TemplateObj'} = undef;
+}
+# }}}
+
+
+1;
+
+
diff --git a/rt/lib/RT/ScripActions.pm b/rt/lib/RT/ScripActions.pm
new file mode 100755
index 0000000..614ff37
--- /dev/null
+++ b/rt/lib/RT/ScripActions.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::ScripActions -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::ScripActions
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::ScripActions;
+
+use RT::SearchBuilder;
+use RT::ScripAction;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'ScripActions';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::ScripAction item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::ScripAction->new($self->CurrentUser));
+}
+
+ eval "require RT::ScripActions_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripActions_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripActions_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripActions_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripActions_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripActions_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ScripActions_Overlay, RT::ScripActions_Vendor, RT::ScripActions_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ScripActions_Overlay.pm b/rt/lib/RT/ScripActions_Overlay.pm
new file mode 100644
index 0000000..83cd646
--- /dev/null
+++ b/rt/lib/RT/ScripActions_Overlay.pm
@@ -0,0 +1,87 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::ScripActions - Collection of Action objects
+
+=head1 SYNOPSIS
+
+ use RT::ScripActions;
+
+
+=head1 DESCRIPTION
+
+
+=begin testing
+
+ok (require RT::ScripActions);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "ScripActions";
+ $self->{'primary_key'} = "id";
+ return ( $self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ sub LimitToType
+sub LimitToType {
+ my $self = shift;
+ my $type = shift;
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => "$type")
+ if defined $type;
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => "Correspond")
+ if $type eq "Create";
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => 'any');
+
+}
+# }}}
+
+# {{{ sub NewItem
+sub NewItem {
+ my $self = shift;
+ return(RT::ScripAction->new($self->CurrentUser));
+
+}
+# }}}
+
+
+1;
+
diff --git a/rt/lib/RT/ScripCondition.pm b/rt/lib/RT/ScripCondition.pm
new file mode 100755
index 0000000..fe0aa2d
--- /dev/null
+++ b/rt/lib/RT/ScripCondition.pm
@@ -0,0 +1,302 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::ScripCondition
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::ScripCondition;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('ScripConditions');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ varchar(60) 'ExecModule'.
+ varchar(255) 'Argument'.
+ varchar(60) 'ApplicableTransTypes'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Description => '',
+ ExecModule => '',
+ Argument => '',
+ ApplicableTransTypes => '',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ ExecModule => $args{'ExecModule'},
+ Argument => $args{'Argument'},
+ ApplicableTransTypes => $args{'ApplicableTransTypes'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item ExecModule
+
+Returns the current value of ExecModule.
+(In the database, ExecModule is stored as varchar(60).)
+
+
+
+=item SetExecModule VALUE
+
+
+Set ExecModule to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ExecModule will be stored as a varchar(60).)
+
+
+=cut
+
+
+=item Argument
+
+Returns the current value of Argument.
+(In the database, Argument is stored as varchar(255).)
+
+
+
+=item SetArgument VALUE
+
+
+Set Argument to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Argument will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item ApplicableTransTypes
+
+Returns the current value of ApplicableTransTypes.
+(In the database, ApplicableTransTypes is stored as varchar(60).)
+
+
+
+=item SetApplicableTransTypes VALUE
+
+
+Set ApplicableTransTypes to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ApplicableTransTypes will be stored as a varchar(60).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ ExecModule =>
+ {read => 1, write => 1, type => 'varchar(60)', default => ''},
+ Argument =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ ApplicableTransTypes =>
+ {read => 1, write => 1, type => 'varchar(60)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::ScripCondition_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripCondition_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripCondition_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripCondition_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripCondition_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripCondition_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ScripCondition_Overlay, RT::ScripCondition_Vendor, RT::ScripCondition_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ScripCondition_Overlay.pm b/rt/lib/RT/ScripCondition_Overlay.pm
new file mode 100644
index 0000000..158bc57
--- /dev/null
+++ b/rt/lib/RT/ScripCondition_Overlay.pm
@@ -0,0 +1,210 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::ScripCondition - RT scrip conditional
+
+=head1 SYNOPSIS
+
+ use RT::ScripCondition;
+
+
+=head1 DESCRIPTION
+
+This module should never be called directly by client code. it's an internal module which
+should only be accessed through exported APIs in other modules.
+
+
+=begin testing
+
+ok (require RT::ScripCondition);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "ScripConditions";
+ return ($self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ sub _Accessible
+sub _Accessible {
+ my $self = shift;
+ my %Cols = ( Name => 'read',
+ Description => 'read',
+ ApplicableTransTypes => 'read',
+ ExecModule => 'read',
+ Argument => 'read',
+ Creator => 'read/auto',
+ Created => 'read/auto',
+ LastUpdatedBy => 'read/auto',
+ LastUpdated => 'read/auto'
+ );
+ return($self->SUPER::_Accessible(@_, %Cols));
+}
+# }}}
+
+# {{{ sub Create
+
+=head2 Create
+
+ Takes a hash. Creates a new Condition entry.
+ should be better documented.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ return($self->SUPER::Create(@_));
+}
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete
+
+No API available for deleting things just yet.
+
+=cut
+
+sub Delete {
+ my $self = shift;
+ return(0, $self->loc('Unimplemented'));
+}
+# }}}
+
+# {{{ sub Load
+
+=head2 Load IDENTIFIER
+
+Loads a condition takes a name or ScripCondition id.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift;
+
+ unless (defined $identifier) {
+ return (undef);
+ }
+
+ if ($identifier !~ /\D/) {
+ return ($self->SUPER::LoadById($identifier));
+ }
+ else {
+ return ($self->LoadByCol('Name', $identifier));
+ }
+}
+# }}}
+
+# {{{ sub LoadCondition
+
+=head2 LoadCondition HASH
+
+takes a hash which has the following elements: TransactionObj and TicketObj.
+Loads the Condition module in question.
+
+=cut
+
+
+sub LoadCondition {
+ my $self = shift;
+ my %args = ( TransactionObj => undef,
+ TicketObj => undef,
+ @_ );
+
+ #TODO: Put this in an eval
+ $self->ExecModule =~ /^(\w+)$/;
+ my $module = $1;
+ my $type = "RT::Condition::". $module;
+
+ eval "require $type" || die "Require of $type failed.\n$@\n";
+
+ $self->{'Condition'} = $type->new ( 'ScripConditionObj' => $self,
+ 'TicketObj' => $args{'TicketObj'},
+ 'ScripObj' => $args{'ScripObj'},
+ 'TransactionObj' => $args{'TransactionObj'},
+ 'Argument' => $self->Argument,
+ 'ApplicableTransTypes' => $self->ApplicableTransTypes,
+ );
+}
+# }}}
+
+# {{{ The following methods call the Condition object
+
+
+# {{{ sub Describe
+
+=head2 Describe
+
+Helper method to call the condition module\'s Describe method.
+
+=cut
+
+sub Describe {
+ my $self = shift;
+ return ($self->{'Condition'}->Describe());
+
+}
+# }}}
+
+# {{{ sub IsApplicable
+
+=head2 IsApplicable
+
+Helper method to call the condition module\'s IsApplicable method.
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ return ($self->{'Condition'}->IsApplicable());
+
+}
+# }}}
+
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+ my $self=shift;
+ $self->{'Condition'} = undef;
+}
+# }}}
+
+
+1;
+
+
diff --git a/rt/lib/RT/ScripConditions.pm b/rt/lib/RT/ScripConditions.pm
new file mode 100755
index 0000000..34f788d
--- /dev/null
+++ b/rt/lib/RT/ScripConditions.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::ScripConditions -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::ScripConditions
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::ScripConditions;
+
+use RT::SearchBuilder;
+use RT::ScripCondition;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'ScripConditions';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::ScripCondition item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::ScripCondition->new($self->CurrentUser));
+}
+
+ eval "require RT::ScripConditions_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripConditions_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripConditions_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripConditions_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::ScripConditions_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/ScripConditions_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::ScripConditions_Overlay, RT::ScripConditions_Vendor, RT::ScripConditions_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/ScripConditions_Overlay.pm b/rt/lib/RT/ScripConditions_Overlay.pm
new file mode 100644
index 0000000..8bef908
--- /dev/null
+++ b/rt/lib/RT/ScripConditions_Overlay.pm
@@ -0,0 +1,87 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::ScripConditions - Collection of Action objects
+
+=head1 SYNOPSIS
+
+ use RT::ScripConditions;
+
+
+=head1 DESCRIPTION
+
+
+
+=begin testing
+
+ok (require RT::ScripConditions);
+
+=end testing
+
+=head1 METHODS
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "ScripConditions";
+ $self->{'primary_key'} = "id";
+ return ( $self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ sub LimitToType
+sub LimitToType {
+ my $self = shift;
+ my $type = shift;
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => "$type")
+ if defined $type;
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => "Correspond")
+ if $type eq "Create";
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Type',
+ VALUE => 'any');
+
+}
+# }}}
+
+# {{{ sub NewItem
+sub NewItem {
+ my $self = shift;
+ return(RT::ScripCondition->new($self->CurrentUser));
+}
+# }}}
+
+
+1;
+
diff --git a/rt/lib/RT/Scrip_Overlay.pm b/rt/lib/RT/Scrip_Overlay.pm
new file mode 100644
index 0000000..4f6c735
--- /dev/null
+++ b/rt/lib/RT/Scrip_Overlay.pm
@@ -0,0 +1,515 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Scrip - an RT Scrip object
+
+=head1 SYNOPSIS
+
+ use RT::Scrip;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok (require RT::Scrip);
+
+
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Create(Name => 'ScripTest');
+ok($q->Id, "Created a scriptest queue");
+
+my $s1 = RT::Scrip->new($RT::SystemUser);
+my ($val, $msg) =$s1->Create( Queue => $q->Id,
+ ScripAction => 'User Defined',
+ ScripCondition => 'User Defined',
+ CustomIsApplicableCode => 'if ($self->TicketObj->Subject =~ /fire/) { return (1);} else { return(0)}',
+ CustomPrepareCode => 'return 1',
+ CustomCommitCode => '$self->TicketObj->SetPriority("87");',
+ Template => 'Blank'
+ );
+ok($val,$msg);
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id,
+ Subject => "hair on fire",
+ );
+ok($tv, $tm);
+
+ok ($ticket->Priority == '87', "Ticket priority is set right");
+
+
+my $ticket2 = RT::Ticket->new($RT::SystemUser);
+my ($t2v,$t2tv,$t2m) = $ticket2->Create(Queue => $q->Id,
+ Subject => "hair in water",
+ );
+ok($t2v, $t2m);
+
+ok ($ticket2->Priority != '87', "Ticket priority is set right");
+
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+# {{{ sub Create
+
+=head2 Create
+
+Creates a new entry in the Scrips table. Takes a paramhash with:
+
+ Queue => 0,
+ Description => undef,
+ Template => undef,
+ ScripAction => undef,
+ ScripCondition => undef,
+ CustomPrepareCode => undef,
+ CustomCommitCode => undef,
+ CustomIsApplicableCode => undef,
+
+
+
+
+Returns (retval, msg);
+retval is 0 for failure or scrip id. msg is a textual description of what happened.
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Queue => 0,
+ Template => 0, # name or id
+ ScripAction => 0, # name or id
+ ScripCondition => 0, # name or id
+ Stage => 'TransactionCreate',
+ Description => undef,
+ CustomPrepareCode => undef,
+ CustomCommitCode => undef,
+ CustomIsApplicableCode => undef,
+
+ @_
+ );
+
+
+ if (! $args{'Queue'} ) {
+ unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ModifyScrips') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ $args{'Queue'} = 0; # avoid undef sneaking in
+ }
+ else {
+ my $QueueObj = new RT::Queue( $self->CurrentUser );
+ $QueueObj->Load( $args{'Queue'} );
+ unless ( $QueueObj->id() ) {
+ return ( 0, $self->loc('Invalid queue') );
+ }
+ unless ( $QueueObj->CurrentUserHasRight('ModifyScrips') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ $args{'Queue'} = $QueueObj->id();
+ }
+
+ #TODO +++ validate input
+
+ require RT::ScripAction;
+ my $action = new RT::ScripAction( $self->CurrentUser );
+ if ($args{'ScripAction'}) {
+ $action->Load( $args{'ScripAction'});
+ }
+ return ( 0, $self->loc( "Action [_1] not found", $args{'ScripAction'} ) )
+ unless $action->Id;
+
+ require RT::Template;
+ my $template = new RT::Template( $self->CurrentUser );
+ if ($args{'Template'} ) {
+ $template->Load( $args{'Template'});
+ }
+ return ( 0, $self->loc('Template not found') ) unless $template->Id;
+
+ require RT::ScripCondition;
+ my $condition = new RT::ScripCondition( $self->CurrentUser );
+ if ($args{'ScripCondition'} ) {
+ $condition->Load( $args{'ScripCondition'} );
+ }
+ unless ( $condition->Id ) {
+ return ( 0, $self->loc('Condition not found') );
+ }
+
+ my ($id,$msg) = $self->SUPER::Create(
+ Queue => $args{'Queue'},
+ Template => $template->Id,
+ ScripCondition => $condition->id,
+ Stage => $args{'Stage'},
+ ScripAction => $action->Id,
+ Description => $args{'Description'},
+ CustomPrepareCode => $args{'CustomPrepareCode'},
+ CustomCommitCode => $args{'CustomCommitCode'},
+ CustomIsApplicableCode => $args{'CustomIsApplicableCode'},
+
+ );
+ if ($id) {
+ return ( $id, $self->loc('Scrip Created') );
+ }
+ else {
+ return($id,$msg);
+ }
+}
+
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete
+
+Delete this object
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ($self->CurrentUserHasRight('ModifyScrips')) {
+ return (0, $self->loc('Permission Denied'));
+ }
+
+ return ($self->SUPER::Delete(@_));
+}
+# }}}
+
+# {{{ sub QueueObj
+
+=head2 QueueObj
+
+Retuns an RT::Queue object with this Scrip\'s queue
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+
+ if (!$self->{'QueueObj'}) {
+ require RT::Queue;
+ $self->{'QueueObj'} = RT::Queue->new($self->CurrentUser);
+ $self->{'QueueObj'}->Load($self->__Value('Queue'));
+ }
+ return ($self->{'QueueObj'});
+}
+
+# }}}
+
+# {{{ sub ActionObj
+
+
+=head2 ActionObj
+
+Retuns an RT::Action object with this Scrip\'s Action
+
+=cut
+
+sub ActionObj {
+ my $self = shift;
+
+ unless (defined $self->{'ScripActionObj'}) {
+ require RT::ScripAction;
+
+ $self->{'ScripActionObj'} = RT::ScripAction->new($self->CurrentUser);
+ #TODO: why are we loading Actions with templates like this.
+ # two seperate methods might make more sense
+ $self->{'ScripActionObj'}->Load($self->ScripAction, $self->Template);
+ }
+ return ($self->{'ScripActionObj'});
+}
+
+# }}}
+
+# {{{ sub ConditionObj
+
+=head2 ConditionObj
+
+Retuns an RT::ScripCondition object with this Scrip's IsApplicable
+
+=cut
+
+sub ConditionObj {
+ my $self = shift;
+
+ unless ( defined $self->{'ScripConditionObj'} ) {
+ require RT::ScripCondition;
+ $self->{'ScripConditionObj'} =
+ RT::ScripCondition->new( $self->CurrentUser );
+ if ( $self->ScripCondition ) {
+ $self->{'ScripConditionObj'}->Load( $self->ScripCondition );
+ }
+ }
+ return ( $self->{'ScripConditionObj'} );
+}
+
+# }}}
+
+# {{{ sub TemplateObj
+=head2 TemplateObj
+
+Retuns an RT::Template object with this Scrip\'s Template
+
+=cut
+
+sub TemplateObj {
+ my $self = shift;
+
+ unless (defined $self->{'TemplateObj'}) {
+ require RT::Template;
+ $self->{'TemplateObj'} = RT::Template->new($self->CurrentUser);
+ $self->{'TemplateObj'}->Load($self->Template);
+ }
+ return ($self->{'TemplateObj'});
+}
+
+# }}}
+
+
+# {{{ Dealing with this instance of a scrip
+
+=head2 Apply { TicketObj => undef, TransactionObj => undef}
+
+This method instantiates the ScripCondition and ScripAction objects for a
+single execution of this scrip. it then calls the IsApplicable method of the
+ScripCondition.
+If that succeeds, it calls the Prepare method of the
+ScripAction. If that succeeds, it calls the Commit method of the ScripAction.
+
+Usually, the ticket and transaction objects passed to this method
+should be loaded by the SuperUser role
+
+=cut
+
+
+# {{{ sub Apply
+
+sub Apply {
+ my $self = shift;
+ my %args = ( TicketObj => undef,
+ TransactionObj => undef,
+ @_ );
+
+ # We want to make sure that if a scrip dies, we don't get
+ # hurt
+ eval {
+
+ #Load the scrip's Condition object
+ $self->ConditionObj->LoadCondition(
+ ScripObj => $self,
+ TicketObj => $args{'TicketObj'},
+ TransactionObj => $args{'TransactionObj'},
+ );
+
+ unless ( $self->IsApplicable() ) {
+ $self->ConditionObj->DESTROY;
+ return (undef);
+ }
+
+ #If it's applicable, prepare and commit it
+ $self->ActionObj->LoadAction( ScripObj => $self,
+ TicketObj => $args{'TicketObj'},
+ TransactionObj => $args{'TransactionObj'},
+ );
+
+ unless ( $self->Prepare() ) {
+ $RT::Logger->info(
+ "$self: Couldn't prepare " . $self->ActionObj->Name );
+ $self->ActionObj->DESTROY();
+ $self->ConditionObj->DESTROY();
+ return (undef);
+ }
+ unless ( $self->Commit() ) {
+ $RT::Logger->info(
+ "$self: Couldn't commit " . $self->ActionObj->Name );
+ $self->ActionObj->DESTROY();
+ $self->ConditionObj->DESTROY();
+ return (undef);
+ }
+
+ #Searchbuilder caching isn't perfectly coherent. got to reload the ticket object, since it
+ # may have changed
+ $args{'TicketObj'}->Load($args{'TicketObj'}->Id);
+
+ #We're done with it. lets clean up.
+ #TODO: something else isn't letting these get garbage collected. check em out.
+ $self->ActionObj->DESTROY();
+ $self->ConditionObj->DESTROY();
+ return (1);
+ };
+ if ($@) {
+ $RT::Logger->error( "Scrip " . $self->Id . " died. - " . $@ );
+ }
+
+}
+# }}}
+
+# {{{ sub IsApplicable
+
+=head2 IsApplicable
+
+Calls the Condition object\'s IsApplicable method
+
+=cut
+
+sub IsApplicable {
+ my $self = shift;
+ return ($self->ConditionObj->IsApplicable(@_));
+}
+
+# }}}
+
+# {{{ sub Prepare
+
+=head2 Prepare
+
+Calls the action object's prepare method
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+ $self->ActionObj->Prepare(@_);
+}
+
+# }}}
+
+# {{{ sub Commit
+
+=head2 Commit
+
+Calls the action object's commit method
+
+=cut
+
+sub Commit {
+ my $self = shift;
+ $self->ActionObj->Commit(@_);
+}
+
+# }}}
+
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+ my $self = shift;
+ $self->{'ActionObj'} = undef;
+}
+# }}}
+
+# {{{ ACL related methods
+
+# {{{ sub _Set
+
+# does an acl check and then passes off the call
+sub _Set {
+ my $self = shift;
+
+ unless ($self->CurrentUserHasRight('ModifyScrips')) {
+ $RT::Logger->debug("CurrentUser can't modify Scrips for ".$self->Queue."\n");
+ return (0, $self->loc('Permission Denied'));
+ }
+ return $self->__Set(@_);
+}
+
+# }}}
+
+# {{{ sub _Value
+# does an acl check and then passes off the call
+sub _Value {
+ my $self = shift;
+
+ unless ($self->CurrentUserHasRight('ShowScrips')) {
+ $RT::Logger->debug("CurrentUser can't modify Scrips for ".$self->__Value('Queue')."\n");
+ return (undef);
+ }
+
+ return $self->__Value(@_);
+}
+# }}}
+
+# {{{ sub CurrentUserHasRight
+
+=head2 CurrentUserHasRight
+
+Helper menthod for HasRight. Presets Principal to CurrentUser then
+calls HasRight.
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+ return ($self->HasRight( Principal => $self->CurrentUser->UserObj,
+ Right => $right ));
+
+}
+
+# }}}
+
+# {{{ sub HasRight
+
+=head2 HasRight
+
+Takes a param-hash consisting of "Right" and "Principal" Principal is
+an RT::User object or an RT::CurrentUser object. "Right" is a textual
+Right string that applies to Scrips.
+
+=cut
+
+sub HasRight {
+ my $self = shift;
+ my %args = ( Right => undef,
+ Principal => undef,
+ @_ );
+
+ if ((defined $self->SUPER::_Value('Queue')) and ($self->SUPER::_Value('Queue') != 0)) {
+ return ( $args{'Principal'}->HasRight(
+ Right => $args{'Right'},
+ Object => $self->QueueObj
+ )
+ );
+
+ }
+ else {
+ return( $args{'Principal'}->HasRight( Object => $RT::System, Right => $args{'Right'}) );
+ }
+}
+# }}}
+
+# }}}
+
+1;
+
+
diff --git a/rt/lib/RT/Scrips.pm b/rt/lib/RT/Scrips.pm
new file mode 100755
index 0000000..a394431
--- /dev/null
+++ b/rt/lib/RT/Scrips.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Scrips -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Scrips
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Scrips;
+
+use RT::SearchBuilder;
+use RT::Scrip;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Scrips';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Scrip item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Scrip->new($self->CurrentUser));
+}
+
+ eval "require RT::Scrips_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Scrips_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Scrips_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Scrips_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Scrips_Overlay, RT::Scrips_Vendor, RT::Scrips_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Scrips_Overlay.pm b/rt/lib/RT/Scrips_Overlay.pm
new file mode 100644
index 0000000..d201480
--- /dev/null
+++ b/rt/lib/RT/Scrips_Overlay.pm
@@ -0,0 +1,208 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Scrips - a collection of RT Scrip objects
+
+=head1 SYNOPSIS
+
+ use RT::Scrips;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Scrips);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub LimitToQueue
+
+=head2 LimitToQueue
+
+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
+
+=cut
+
+sub LimitToQueue {
+ my $self = shift;
+ my $queue = shift;
+
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Queue',
+ VALUE => "$queue")
+ if defined $queue;
+
+}
+# }}}
+
+# {{{ sub LimitToGlobal
+
+=head2 LimitToGlobal
+
+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
+
+=cut
+
+
+sub LimitToGlobal {
+ my $self = shift;
+
+ $self->Limit (ENTRYAGGREGATOR => 'OR',
+ FIELD => 'Queue',
+ VALUE => 0);
+
+}
+# }}}
+
+# {{{ sub NewItem
+sub NewItem {
+ my $self = shift;
+
+ return(new RT::Scrip($self->CurrentUser));
+}
+# }}}
+
+# {{{ sub Next
+
+=head2 Next
+
+Returns the next scrip that this user can see.
+
+=cut
+
+sub Next {
+ my $self = shift;
+
+
+ my $Scrip = $self->SUPER::Next();
+ if ((defined($Scrip)) and (ref($Scrip))) {
+
+ if ($Scrip->CurrentUserHasRight('ShowScrips')) {
+ return($Scrip);
+ }
+
+ #If the user doesn't have the right to show this scrip
+ else {
+ return($self->Next());
+ }
+ }
+ #if there never was any scrip
+ else {
+ return(undef);
+ }
+
+}
+# }}}
+
+sub Apply {
+ my ($self, %args) = @_;
+
+ #We're really going to need a non-acled ticket for the scrips to work
+ my ($TicketObj, $TransactionObj);
+
+ if ( ($TicketObj = $args{'TicketObj'}) ) {
+ $TicketObj->CurrentUser($self->CurrentUser);
+ }
+ else {
+ $TicketObj = RT::Ticket->new($self->CurrentUser);
+ $TicketObj->Load( $args{'Ticket'} )
+ || $RT::Logger->err("$self couldn't load ticket $args{'Ticket'}\n");
+ }
+
+ if ( ($TransactionObj = $args{'TransactionObj'}) ) {
+ $TransactionObj->CurrentUser($self->CurrentUser);
+ }
+ else {
+ $TransactionObj = RT::Transaction->new($self->CurrentUser);
+ $TransactionObj->Load( $args{'Transaction'} )
+ || $RT::Logger->err("$self couldn't load transaction $args{'Transaction'}\n");
+ }
+
+ # {{{ Deal with Scrips
+
+ $self->LimitToQueue( $TicketObj->QueueObj->Id )
+ ; #Limit it to $Ticket->QueueObj->Id
+ $self->LimitToGlobal()
+ unless $TicketObj->QueueObj->Disabled; # or to "global"
+
+
+ $self->Limit(FIELD => "Stage", VALUE => $args{'Stage'});
+
+
+ my $ConditionsAlias = $self->NewAlias('ScripConditions');
+
+ $self->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'ScripCondition',
+ ALIAS2 => $ConditionsAlias,
+ FIELD2 => 'id'
+ );
+
+ #We only want things where the scrip applies to this sort of transaction
+ $self->Limit(
+ ALIAS => $ConditionsAlias,
+ FIELD => 'ApplicableTransTypes',
+ OPERATOR => 'LIKE',
+ VALUE => $args{'Type'},
+ ENTRYAGGREGATOR => 'OR',
+ ) if $args{'Type'};
+
+ # Or where the scrip applies to any transaction
+ $self->Limit(
+ ALIAS => $ConditionsAlias,
+ FIELD => 'ApplicableTransTypes',
+ OPERATOR => 'LIKE',
+ VALUE => "Any",
+ ENTRYAGGREGATOR => 'OR',
+ );
+
+ #Iterate through each script and check it's applicability.
+ while ( my $Scrip = $self->Next() ) {
+ $Scrip->Apply (TicketObj => $TicketObj,
+ TransactionObj => $TransactionObj);
+ }
+
+ $TicketObj->CurrentUser( $TicketObj->OriginalUser );
+ $TransactionObj->CurrentUser( $TransactionObj->OriginalUser );
+
+ # }}}
+}
+
+
+1;
+
diff --git a/rt/lib/RT/Search/ActiveTicketsInQueue.pm b/rt/lib/RT/Search/ActiveTicketsInQueue.pm
new file mode 100644
index 0000000..766e42e
--- /dev/null
+++ b/rt/lib/RT/Search/ActiveTicketsInQueue.pm
@@ -0,0 +1,78 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Search::ActiveTicketsInQueue
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+Find all active tickets in the queue named in the argument passed in
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Search::Generic);
+
+=end testing
+
+
+=cut
+
+package RT::Search::ActiveTicketsInQueue;
+
+use strict;
+use base qw(RT::Search::Generic);
+
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return ($self->loc("No description for [_1]", ref $self));
+}
+# }}}
+
+# {{{ sub Prepare
+sub Prepare {
+ my $self = shift;
+
+ $self->TicketsObj->LimitQueue(VALUE => $self->Argument);
+
+ foreach my $status (RT::Queue->ActiveStatusArray()) {
+ $self->TicketsObj->LimitStatus(VALUE => $status);
+ }
+
+ return(1);
+}
+# }}}
+
+eval "require RT::Search::ActiveTicketsInQueue_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Search/ActiveTicketsInQueue_Vendor.pm});
+eval "require RT::Search::ActiveTicketsInQueue_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Search/ActiveTicketsInQueue_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Search/Generic.pm b/rt/lib/RT/Search/Generic.pm
new file mode 100644
index 0000000..f872c2a
--- /dev/null
+++ b/rt/lib/RT/Search/Generic.pm
@@ -0,0 +1,128 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Search::Generic - ;
+
+=head1 SYNOPSIS
+
+ use RT::Search::Generic;
+ my $tickets = RT::Tickets->new($CurrentUser);
+ my $foo = RT::Search::Generic->new(Argument => $arg,
+ TicketsObj => $tickets);
+ $foo->Prepare();
+ while ( my $ticket = $foo->Next ) {
+ # Do something with each ticket we've found
+ }
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::Search::Generic);
+
+=end testing
+
+
+=cut
+
+package RT::Search::Generic;
+
+use strict;
+
+# {{{ sub new
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless ($self, $class);
+ $self->_Init(@_);
+ return $self;
+}
+# }}}
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ my %args = (
+ TicketsObj => undef,
+ Argument => undef,
+ @_ );
+
+ $self->{'TicketsObj'} = $args{'TicketsObj'};
+ $self->{'Argument'} = $args{'Argument'};
+}
+# }}}
+
+# {{{ sub Argument
+
+=head2 Argument
+
+Return the optional argument associated with this Search
+
+=cut
+
+sub Argument {
+ my $self = shift;
+ return($self->{'Argument'});
+}
+# }}}
+
+
+=head2 TicketsObj
+
+Return the Tickets object passed into this search
+
+=cut
+
+sub TicketsObj {
+ my $self = shift;
+ return($self->{'TicketsObj'});
+}
+
+# {{{ sub Describe
+sub Describe {
+ my $self = shift;
+ return ($self->loc("No description for [_1]", ref $self));
+}
+# }}}
+
+# {{{ sub Prepare
+sub Prepare {
+ my $self = shift;
+ return(1);
+}
+# }}}
+
+eval "require RT::Search::Generic_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Search/Generic_Vendor.pm});
+eval "require RT::Search::Generic_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Search/Generic_Local.pm});
+
+1;
diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm
new file mode 100644
index 0000000..22c9aff
--- /dev/null
+++ b/rt/lib/RT/SearchBuilder.pm
@@ -0,0 +1,200 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::SearchBuilder - a baseclass for RT collection objects
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+=begin testing
+
+ok (require RT::SearchBuilder);
+
+=end testing
+
+
+=cut
+
+package RT::SearchBuilder;
+
+use RT::Base;
+use DBIx::SearchBuilder;
+
+use strict;
+use vars qw(@ISA);
+@ISA = qw(DBIx::SearchBuilder RT::Base);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+
+ $self->{'user'} = shift;
+ unless(defined($self->CurrentUser)) {
+ use Carp;
+ Carp::confess("$self was created without a CurrentUser");
+ $RT::Logger->err("$self was created without a CurrentUser");
+ return(0);
+ }
+ $self->SUPER::_Init( 'Handle' => $RT::Handle);
+}
+# }}}
+
+# {{{ sub LimitToEnabled
+
+=head2 LimitToEnabled
+
+Only find items that haven\'t been disabled
+
+=cut
+
+sub LimitToEnabled {
+ my $self = shift;
+
+ $self->Limit( FIELD => 'Disabled',
+ VALUE => '0',
+ OPERATOR => '=' );
+}
+# }}}
+
+# {{{ sub LimitToDisabled
+
+=head2 LimitToDeleted
+
+Only find items that have been deleted.
+
+=cut
+
+sub LimitToDeleted {
+ my $self = shift;
+
+ $self->{'find_disabled_rows'} = 1;
+ $self->Limit( FIELD => 'Disabled',
+ OPERATOR => '=',
+ VALUE => '1'
+ );
+}
+# }}}
+
+# {{{ sub FindAllRows
+
+=head2 FindAllRows
+
+Find all matching rows, regardless of whether they are disabled or not
+
+=cut
+
+sub FindAllRows {
+ shift->{'find_disabled_rows'} = 1;
+}
+
+# {{{ sub Limit
+
+=head2 Limit PARAMHASH
+
+This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus
+making sure that by default lots of things don't do extra work trying to
+match lower(colname) agaist lc($val);
+
+=cut
+
+sub Limit {
+ my $self = shift;
+ my %args = ( CASESENSITIVE => 1,
+ @_ );
+
+ return $self->SUPER::Limit(%args);
+}
+
+# }}}
+
+# {{{ sub ItemsArrayRef
+
+=item ItemsArrayRef
+
+Return this object's ItemsArray.
+If it has a SortOrder attribute, sort the array by SortOrder.
+Otherwise, if it has a "Name" attribute, sort alphabetically by Name
+Otherwise, just give up and return it in the order it came from the db.
+
+
+=begin testing
+
+use_ok(RT::Queues);
+ok(my $queues = RT::Queues->new($RT::SystemUser), 'Created a queues object');
+ok( $queues->UnLimit(),'Unlimited the result set of the queues object');
+my $items = $queues->ItemsArrayRef();
+my @items = @{$items};
+
+ok($queues->NewItem->_Accessible('Name','read'));
+my @sorted = sort {lc($a->Name) cmp lc($b->Name)} @items;
+ok (@sorted, "We have an array of queues, sorted". join(',',map {$_->Name} @sorted));
+
+ok (@items, "We have an array of queues, raw". join(',',map {$_->Name} @items));
+my @sorted_ids = map {$_->id } @sorted;
+my @items_ids = map {$_->id } @items;
+
+is ($#sorted, $#items);
+is ($sorted[0]->Name, $items[0]->Name);
+is ($sorted[-1]->Name, $items[-1]->Name);
+is_deeply(\@items_ids, \@sorted_ids, "ItemsArrayRef sorts alphabetically by name");;
+
+
+=end testing
+
+=cut
+
+sub ItemsArrayRef {
+ my $self = shift;
+ my @items;
+
+ if ($self->NewItem()->_Accessible('SortOrder','read')) {
+ @items = sort { $a->SortOrder <=> $b->SortOrder } @{$self->SUPER::ItemsArrayRef()};
+ }
+ elsif ($self->NewItem()->_Accessible('Name','read')) {
+ @items = sort { lc($a->Name) cmp lc($b->Name) } @{$self->SUPER::ItemsArrayRef()};
+ }
+ else {
+ @items = @{$self->SUPER::ItemsArrayRef()};
+ }
+
+ return(\@items);
+
+}
+
+# }}}
+
+eval "require RT::SearchBuilder_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/SearchBuilder_Vendor.pm});
+eval "require RT::SearchBuilder_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/SearchBuilder_Local.pm});
+
+1;
+
+
diff --git a/rt/lib/RT/StyleGuide.pod b/rt/lib/RT/StyleGuide.pod
new file mode 100644
index 0000000..95b2e3a
--- /dev/null
+++ b/rt/lib/RT/StyleGuide.pod
@@ -0,0 +1,891 @@
+=head1 NAME
+
+RT::StyleGuide - RT Style Guide
+
+=head1 INTRODUCTION
+
+All code and documentation that is submitted to be included in the RT
+distribution should follow the style in this document. This is not to
+try to stifle your creativity, but to make life easier for everybody who
+has to work with your code, and to aid those who are not quite sure how
+to do something.
+
+These conventions below apply to perl modules, web programs, and
+command-line programs, specifically, but also might apply to some
+degree to any Perl code written for use in RT.
+
+Note that these are all guidelines, not unbreakable rules. If you have
+a really good need to break one of the rules herein, however, then it is
+best to ask on the B<rt-devel> mailing list first.
+
+Note that with much of this document, it is not so much the Right Way as
+it is Our Way. We need to have conventions in order to make life easier
+for everyone. So don't gripe, and just follow it, because you didn't
+get a good grade in "Plays Well With Others" in kindergarten and you
+want to make up for it now.
+
+If you have any questions, please ask us on the B<rt-devel> mailing list:
+
+ http://www.bestpractical.com/rt/lists.html
+
+We don't always follow this guide. We are making changes throughout
+our code to be in line with it. But just because we didn't do
+it yet, that is no excuse. Do it anyway. :-)
+
+This document is subject to change at the whims of the core RT team.
+We hope to add any significant changes at the bottom of the document.
+
+
+=head1 CODING PRINCIPLES
+
+=head2 Perl Version
+
+We code everything to perl 5.6.1. Some features require advanced unicode
+features in perl 5.8.0. It is acceptable that unicode features work only for
+US-ASCII on perl 5.6.1.
+
+
+=head2 Documentation
+
+All modules will be documented using the POD examples in the module
+boilerplate. The function, purpose, use of the module will be
+explained, and each public API will be documented with name,
+description, inputs, outputs, side effects, etc.
+
+If an array or hash reference is returned, document the size of the
+array (including what each element is, as appropriate) and name each key
+in the hash. For complex data structures, map out the structure as
+appropriate (e.g., name each field returned for each column from a DB
+call; yes, this means you shouldn't use "SELECT *", which you shouldn't
+use anyway).
+
+Also document what kind of data returned values are. Is it an integer,
+a block of HTML, a boolean?
+
+All command-line program options will be documented using the
+boilerplate code for command-line programs, which doesn't yet exist.
+Each available function, switch, etc. should be documented, along
+with a statement of function, purpose, use of the program. Do not
+use the same options as another program, for a different purpose.
+
+All web templates should be documented with a statement of function,
+purpose, and use in a mason comment block.
+
+Any external documents, and documentation for command-line programs and
+modules, should be written in POD, where appropriate. From there, they
+can be translated to many formats with the various pod2* translators.
+Read the perlpod manpage before writing any POD, because although POD is
+not difficult, it is not what most people are used to. It is not a
+regular markup language; it is just a way to make easy documentation
+for translating to other formats. Read, and understand, the perlpod
+manpage, and ask us or someone else who knows if you have any questions.
+
+
+=head2 Version
+
+Our distribution versions use tuples, where the first number is the
+major revision, the second number is the version, and third
+number is the subversion. Odd-numbered versions are development
+versions. Examples:
+
+ 1.0.0 First release of RT 1
+ 1.0.1 Second release of RT 1.0
+ 1.0.10 etc.
+ 1.1.0 First development release of RT 1.2 (or 2.0)
+ 2.0.0 First release of RT 2
+
+Versions can be modified with a hyphen followed by some text, for
+special versions, or to give extra information. Examples:
+
+ 2.0.0-pre1 Notes that this is not final, but preview
+
+In perl 5.6.0, you can have versions like C<v2.0.0>, but this is not
+allowed in previous versions of perl. So to convert a tuple version
+string to a string to use with $VERSION, use a regular integer for
+the revision, and three digits for version and subversion. Examples:
+
+ 1.1.6 -> 1.001006
+ 2.0.0 -> 2.000000
+
+This way, perl can use the version strings in greater-than and
+less-than comparisons.
+
+
+=head2 Comments
+
+All code should be self-documenting as much as possible. Only include
+necessary comments. Use names like "$ticket_count", so you don't need to
+do something like:
+
+ # ticket count
+ my $tc = 0;
+
+Include any comments that are, or might be, necessary in order for
+someone else to understand the code. Sometimes a simple one-line
+comment is good to explain what the purpose of the following code is
+for. Sometimes each line needs to be commented because of a complex
+algorithm. Read Kernighan & Pike's I<Practice of Programming> about
+commenting. Good stuff, Maynard.
+
+
+=head2 Warnings and Strict
+
+All code must compile and run cleanly with "use strict" enabled and the
+perl "-w" (warnings) option on. If you must do something that -w or
+strict complains about, there are workarounds, but the chances that you
+really need to do it that way are remote.
+
+=head2 Lexical Variables
+
+Use only lexical variables, except for special global variables
+($VERSION, %ENV, @ISA, $!, etc.) or very special circumstances (see
+%HTML::Mason::Commands::session ). Global variables
+for regular use are never appropriate. When necessary, "declare"
+globals with "use vars" or "our()".
+
+A lexical variable is created with my(). A global variable is
+pre-existing (if it is a special variable), or it pops into existence
+when it is used. local() is used to tell perl to assign a temporary
+value to a variable. This should only be used with special variables,
+like $/, or in special circumstances. If you must assign to any global
+variable, consider whether or not you should use local().
+
+local() may also be used on elements of arrays and hashes, though there
+is seldom a need to do it, and you shouldn't.
+
+
+=head2 Exporting
+
+Do not export anything from a module by default. Feel free to put
+anything you want to in @EXPORT_OK, so users of your modules can
+explicitly ask for symbols (e.g., "use Something::Something qw(getFoo
+setFoo)"), but do not export them by default.
+
+
+=head2 Pass by Reference
+
+Arrays and hashes should be passed to and from functions by reference
+only. Note that a list and an array are NOT the same thing. This
+is perfectly fine:
+
+ return($user, $form, $constants);
+
+An exception might be a temporary array of discrete arguments:
+
+ my @return = ($user, $form);
+ push @return, $constants if $flag;
+ return @return;
+
+Although, usually, this is better (faster, easier to read, etc.):
+
+ if ($flag) {
+ return($user, $form, $constants);
+ } else {
+ return($user, $form);
+ }
+
+We need to talk about Class::ReturnValue here.
+
+
+=head2 Garbage Collection
+
+Perl does pretty good garbage collection for you. It will automatically
+clean up lexical variables that have gone out of scope and objects whose
+references have gone away. Normally you don't need to worry about
+cleaning up after yourself, if using lexicals.
+
+However, some glue code, code compiled in C and linked to Perl, might
+not automatically clean up for you. In such cases, clean up for
+yourself. If there is a method in that glue to dispose or destruct,
+then use it as appropriate.
+
+Also, if you have a long-running function that has a large data
+structure in it, it is polite to free up the memory as soon as you are
+done with it, if possible.
+
+ my $huge_data_structure = get_huge_data_structure();
+ do_something_with($huge_data_structure);
+ undef $huge_data_structure;
+
+=head2 DESTROY
+
+All object classes must provide a DESTROY method. If it won't do
+anything, provide it anyway:
+
+ sub DESTROY { }
+
+
+
+=head2 die() and exit()
+
+Don't do it. Do not die() or exit() from a web template or module. Do
+not call C<kill 9, $$>. Don't do it.
+
+In command-line programs, do as you please.
+
+
+=head2 shift and @_
+
+Do not use @_. Use shift. shift may take more lines, but Jesse thinks it
+leads to cleaner code.
+
+ my $var = shift; # right
+ my($var) = @_; # ick. no
+ sub foo { uc $_[0] } # icky. sometimes ok.
+
+
+ my($var1, $var2) = (shift, shift); # Um, no.
+
+ my $var1 = shift; # right
+ my $var2 = shift;
+
+
+
+=head2 Tests
+
+Modules should provide test code, with documentation on how to use
+it. Test::Inline allows tests to be embedded in code. Test::More makes it
+easy to create tests. Any code you write should have a testsuite.
+Any code you alter should have a test suite. If a patch comes in without
+tests, there is something wrong.
+
+When altering code, you must run the test harness before submitting a patch
+or committing code to the repository.
+
+"make regression" will extract inline tests, blow away the system database
+and run the test suite.
+
+"make regression-quiet" will do all that and not print the "ok" lines.
+
+
+
+=head2 STDIN/STDOUT
+
+Always report errors using $RT::Logger. It's a Log::Dispatch object.
+Unlike message meant for the user, log messages are not to be
+internationalized.
+
+There are several different levels ($RT::Logger methods) of logging:
+
+=over 4
+
+=item debug
+
+Used for messages only needed during system debugging.
+
+=item info
+
+Should be used to describe "system-critical" events which aren't errors.
+Examples: creating users, deleting users, creating tickets, creating queues,
+sending email (message id, time, recipients), recieving mail, changing
+passwords, changing access control, superuser logins)
+
+=item error
+
+Used for RT-generated failures during execution.
+
+=item crit
+
+Should be used for messages when an action can not be completed due to some
+error condition beyond our control.
+
+=back
+
+In the web UI and modules, never print directly to STDERR. Do not print
+directly to STDOUT, unless you need to print directly to the user's console.
+
+In command-line programs, feel free to print to STDERR and STDOUT as
+needed for direct console communication. But for actual error reporting,
+use the logging API.
+
+
+=head2 System Calls
+
+Always check return values from system calls, including open(),
+close(), mkdir(), or anything else that talks directly to the system.
+Perl built-in system calls return the error in $!; some functions in
+modules might return an error in $@ or some other way, so read the module's
+documentation if you don't know. Always do something, even if it is
+just calling $RT::Logger->warning(), when the return value is not what you'd expect.
+
+
+
+=head1 STYLE
+
+Much of the style section is taken from the perlsyle manpage. We make
+some changes to it here, but it wouldn't be a bad idea to read that
+document, too.
+
+=head2 Terminology
+
+=over 4
+
+=item RT the name
+
+"RT" is the name of the project. "RT" is, optionally, the
+specific name for the actual file distribution. That's it.
+
+While we sometimes use "RT2" or "RT3", that's shortand that's really
+not recommended. The name of the project is "RT".
+
+To specify a major version, use "RT 3.0".
+To specify a specific release, use "RT 3.0.12"
+
+=item function vs. sub(routine) vs. method
+
+Just because it is the Perl Way (not necessarily right for all
+languages, but the documented terminology in the perl documentation),
+"method" should be used only to refer to a subroutine that are object
+methods or class methods; that is, these are functions that are used
+with OOP that always take either an object or a class as the first
+argument. Regular subroutines, ones that are not object or class
+methods, are functions. Class methods that create and return an object
+are optionally called constructors.
+
+=item Users
+
+"users" are normally users of RT, the ones hitting the site; if using
+it in any other context, specify.
+"system users" are user
+names on the operating system. "database users" are the user names in
+the database server. None of these needs to be capitalized.
+
+=back
+
+
+=head2 Names
+
+Don't use single-character variables, except as iterator variables.
+
+Don't use two-character variables just to spite us over the above rule.
+
+Constants are in all caps; these are variables whose value will I<never>
+change during the course of the program.
+
+ $Minimum = 10; # wrong
+ $MAXIMUM = 50; # right
+
+Other variables are lowercase, with underscores separating the words.
+They words used should, in general, form a noun (usually singular),
+unless the variable is a flag used to denote some action that should be
+taken, in which case they should be verbs (or gerunds, as appropriate)
+describing that action.
+
+ $thisVar = 'foo'; # wrong
+ $this_var = 'foo'; # right
+ $work_hard = 1; # right, verb, boolean flag
+ $running_fast = 0; # right, gerund, boolean flag
+
+Arrays and hashes should be plural nouns, whether as regular arrays and
+hashes or array and hash references. Do not name references with "ref"
+or the data type in the name.
+
+ @stories = (1, 2, 3); # right
+ $comment_ref = [4, 5, 6]; # wrong
+ $comments = [4, 5, 6]; # right
+ $comment = $comments->[0]; # right
+
+Make the name descriptive. Don't use variables like "$sc" when you
+could call it "$story_count". See L<"Comments">.
+
+There are several variables in RT that are used throughout the code,
+that you should use in your code. Do not use these variable names for
+anything other than how they are normally used, and do not use any
+other variable names in their place. Some of these are:
+
+ $self # first named argument in object method
+
+Subroutines (except for special cases, like AUTOLOAD and simple accessors)
+begin with a verb, with words following to complete the action. Accessors
+don't start with "Get" if they're just the name of the attribute.
+
+Accessors which return an object should end with the suffix Obj.
+
+This section needs clarification for RT.
+
+Words begin with a capital letter. They
+should as clearly as possible describe the activity to be peformed, and
+the data to be returned.
+
+
+
+ Load(); # good
+ LoadByName(); # good
+ LoadById(); # good
+
+Subroutines beginning with C<_> are special: they are not to be used
+outside the current object. There is not to be enforced by the code
+itself, but by someone very big and very scary.
+
+For large for() loops, do not use $_, but name the variable.
+Do not use $_ (or assume it) except for when it is absolutely
+clear what is going on, or when it is required (such as with
+map() and grep()).
+
+ for (@list) {
+ print; # OK; everyone knows this one
+ print uc; # wrong; few people know this
+ print uc $_; # better
+ }
+
+Note that the special variable C<_> I<should> be used when possible.
+It is a placeholder that can be passed to stat() and the file test
+operators, that saves perl a trip to re-stat the file. In the
+example below, using C<$file> over for each file test, instead of
+C<_> for subsequent uses, is a performance hit. You should be
+careful that the last-tested file is what you think it is, though.
+
+ if (-d $file) { # $file is a directory
+ # ...
+ } elsif (-l _) { # $file is a symlink
+ # ...
+ }
+
+Package names begin with a capital letter in each word, followed by
+lower case letters (for the most part). Multiple words should be StudlyCapped.
+
+ RT::User # good
+ RT::Database::MySQL # proper name
+ RT::Display::Provider # good
+ RT::CustomField # not so good, but OK
+
+Plugin modules should begin with "RTx::", followed by the name
+of the plugin.
+
+=head1 Code formatting
+
+Use perltidy. Anything we say here is wrong if it conflicts with what
+perltidy does. Your perltidyrc should read:
+
+-lp -vt=2 -vtc=2 -nsfs -bar
+
+=head2 Indents and Blank Space
+
+All indents should be tabs. Set your tab stops whatever you want them
+to be; I use 8 spaces per tabs.
+
+No space before a semicolon that closes a statement.
+
+ foo(@bar) ; # wrong
+ foo(@bar); # right
+
+Line up corresponding items vertically.
+
+ my $foo = 1;
+ my $bar = 2;
+ my $xyzzy = 3;
+
+ open(FILE, $fh) or die $!;
+ open(FILE2, $fh2) or die $!;
+
+ $rot13 =~ tr[abcedfghijklmnopqrstuvwxyz]
+ [nopqrstuvwxyzabcdefghijklm];
+
+ # note we use a-mn-z instead of a-z,
+ # for readability
+ $rot13 =~ tr[a-mn-z]
+ [n-za-m];
+
+Put blank lines between groups of code that do different things. Put
+blank lines after your variable declarations. Put a blank line before a
+final return() statement. Put a blank line following a block (and
+before, with the exception of comment lines).
+
+An example:
+
+ # this is my function!
+ sub foo {
+ my $val = shift;
+ my $obj = new Constructor;
+ my($var1, $var2);
+
+ $obj->SetFoo($val);
+ $var1 = $obj->Foo();
+
+
+ return($val);
+ }
+
+ print 1;
+
+
+=head2 Parentheses
+
+For control structures, there is a space between the keyword and opening
+parenthesis. For functions, there is not.
+
+ for(@list) # wrong
+ for (@list) # right
+
+ my ($ref) # wrong
+ my($ref) # right
+
+Be careful about list vs. scalar context with parentheses!
+
+ my @array = ('a', 'b', 'c');
+ my($first_element) = @array; # a
+ my($first_element) = ('a', 'b', 'c'); # a
+ my $element_count = @array; # 3
+ my $last_element = ('a', 'b', 'c'); # c
+
+Always include parentheses after functions, even if there are no arguments.
+There are some exceptions, such as list operators (like print) and unary
+operators (like undef, delete, uc).
+
+There is no space inside the parentheses, unless it is needed for
+readability.
+
+ for ( map { [ $_, 1 ] } @list ) # OK
+ for ( @list ) # not really OK, not horrible
+
+On multi-line expressions, match up the closing parenthesis with either
+the opening statement, or the opening parenthesis, whichever works best.
+Examples:
+
+ @list = qw(
+ bar
+ baz
+ ); # right
+
+ if ($foo && $bar && $baz
+ && $buz && $xyzzy
+ ) {
+ print $foo;
+ }
+
+Whether or not there is space following a closing parenthesis is
+dependent on what it is that follows.
+
+ print foo(@bar), baz(@buz) if $xyzzy;
+
+Note also that parentheses around single-statement control expressions,
+as in C<if $xyzzy>, are optional (and discouraged) C<if> it is I<absolutely>
+clear -- to a programmer -- what is going on. There is absolutely no
+need for parentheses around C<$xyzzy> above, so leaving them out enhances
+readability. Use your best discretion. Better to include them, if
+there is any question.
+
+The same essentially goes for perl's built-in functions, when there is
+nothing confusing about what is going on (for example, there is only one
+function call in the statement, or the function call is separated by a
+flow control operator). User-supplied functions must always include
+parentheses.
+
+ print 1, 2, 3; # good
+ delete $hash{key} if isAnon($uid); # good
+
+
+However, if there is any possible confusion at all, then include the
+parentheses. Remember the words of Larry Wall in the perlstyle manpage:
+
+ When in doubt, parenthesize. At the very least it will
+ let some poor schmuck bounce on the % key in vi.
+
+ Even if you aren't in doubt, consider the mental welfare
+ of the person who has to maintain the code after you, and
+ who will probably put parens in the wrong place.
+
+So leave them out when it is absoutely clear to a programmer, but if
+there is any question, leave them in.
+
+
+=head2 Braces
+
+(This is about control braces, not hash/data structure braces.)
+
+There is always a space befor the opening brace.
+
+ while (<$fh>){ # wrong
+ while (<$fh>) { # right
+
+A one-line block may be put on one line, and the semicolon may be
+omitted.
+
+ for (@list) { print }
+
+Otherwise, finish each statement with a semicolon, put the keyword and
+opening curly on the first line, and the ending curly lined up with the
+keyword at the end.
+
+ for (@list) {
+ print;
+ smell();
+ }
+
+Generally, we prefer "uncuddled elses":
+
+ if ($foo) {
+ print;
+ }
+ else {
+ die;
+ }
+
+_If_ the if statement is very brief, sometimes "cuddling" the else makes code more readable. Feel free to cuddle them in that case:
+
+
+ if ($foo) {
+ print;
+ } else {
+ die;
+ }
+
+=head2 Operators
+
+Put space around most operators. The primary exception is the for
+aesthetics; e.g., sometimes the space around "**" is ommitted,
+and there is never a space before a ",", but always after.
+
+ print $x , $y; # wrong
+ print $x, $y; # right
+
+ $x = 2 >> 1; # good
+ $y = 2**2; # ok
+
+Note that "&&" and "||" have a higher precedence than "and" and "or".
+Other than that, they are exactly the same. It is best to use the lower
+precedence version for control, and the higher for testing/returning
+values. Examples:
+
+ $bool = $flag1 or $flag2; # WRONG (doesn't work)
+ $value = $foo || $bar; # right
+ open(FILE, $file) or die $!;
+
+ $true = foo($bar) && baz($buz);
+ foo($bar) and baz($buz);
+
+Note that "and" is seldom ever used, because the statement above is
+better written using "if":
+
+ baz($buz) if foo($bar);
+
+Most of the time, the confusion between and/&&, or/|| can be alleviated
+by using parentheses. If you want to leave off the parentheses then you
+I<must> use the proper operator. But if you use parentheses -- and
+normally, you should, if there is any question at all -- then it doesn't
+matter which you use. Use whichever is most readable and aesthetically
+pleasing to you at the time, and be consistent within your block of code.
+
+Break long lines AFTER operators, except for "and", "or", "&&", "||".
+Try to keep the two parts to a binary operator (an operator that
+has two operands) together when possible.
+
+ print "foo" . "bar" . "baz"
+ . "buz"; # wrong
+
+ print "foo" . "bar" . "baz" .
+ "buz"; # right
+
+ print $foo unless $x == 3 && $y ==
+ 4 && $z == 5; # wrong
+
+ print $foo unless $x == 3 && $y == 4
+ && $z == 5; # right
+
+
+=head2 Other
+
+Put space around a complex subscript inside the brackets or braces.
+
+ $foo{$bar{baz}{buz}}; # OK
+ $foo{ $bar{baz}{buz} }; # better
+
+In general, use single-quotes around literals, and double-quotes
+when the text needs to be interpolated.
+
+It is OK to omit quotes around names in braces and when using
+the => operator, but be careful not to use a name that doubles as
+a function; in that case, quote.
+
+ $what{'time'}{it}{is} = time();
+
+When making compound statements, put the primary action first.
+
+ open(FILE, $fh) or die $!; # right
+ die $! unless open(FILE, $fh); # wrong
+
+ print "Starting\n" if $verbose; # right
+ $verbose && print "Starting\n"; # wrong
+
+
+Use here-docs instead of repeated print statements.
+
+ print <<EOT;
+ This is a whole bunch of text.
+ I like it. I don't need to worry about messing
+ with lots of print statements and lining them up.
+ EOT
+
+Just remember that unless you put single quotes around your here-doc
+token (<<'EOT'), the text will be interpolated, so escape any "$" or "@"
+as needed.
+
+=head1 INTERNATIONALIZATION
+
+
+=head2 String extraction styleguide
+
+=over 4
+
+=item Web templates
+
+Templates should use the /l filtering component to call the localisation
+framework
+
+The string Foo!
+
+Should become <&|/l&>Foo!</&>
+
+All newlines should be removed from localized strings, to make it easy to
+grep the codebase for strings to be localized
+
+The string Foo
+ Bar
+ Baz
+
+Should become <&|/l&>Foo Bar Baz</&>
+
+
+Variable subsititutions should be moved to Locale::MakeText format
+
+The string Hello, <%$name %>
+
+should become <&|/l, $name &>Hello, [_1]</&>
+
+
+Multiple variables work just like single variables
+
+The string You found <%$num%> tickets in queue <%$queue%>
+
+should become <&|/l, $num, $queue &>You found [_1] tickets in queue [_2]</&>
+
+When subcomponents are called in the middle of a phrase, they need to be escaped
+too:
+
+The string <input type="submit" value="New ticket in">&nbsp<& /Elements/SelectNewTicketQueue&>
+
+should become <&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" value="New ticket in">&nbsp;[_1]</&>
+
+
+
+
+The string <& /Elements/TitleBoxStart, width=> "40%", titleright => "RT $RT::VERSION for $RT::rtname", title => 'Login' &>
+
+should become <& /Elements/TitleBoxStart,
+ width=> "40%",
+ titleright => loc("RT [_1] for [_2]",$RT::VERSION, $RT::rtname),
+ title => loc('Login'),
+ &>
+
+
+=item Library code
+
+
+
+Within RT's core code, every module has a localization handle available through the 'loc' method:
+
+The code return ( $id, "Queue created" );
+
+should become return ( $id, $self->loc("Queue created") );
+
+When returning or localizing a single string, the "extra" set of parenthesis () should be omitted.
+
+The code return ("Subject changed to ". $self->Data );
+
+should become return $self->loc( "Subject changed to [_1]", $self->Data );
+
+
+It is important not to localize the names of rights or statuses within RT's core, as there is logic that depends on them as string identifiers. The proper place to localize these values is when they're presented for display in the web or commandline interfaces.
+
+
+=back 4
+
+=head1 CODING PRCEDURE
+
+This is for new programs, modules, specific APIs, or anything else.
+
+Contact for core team is the slashcode-development mailing list.
+
+=over 4
+
+=item Present idea to core team
+
+We may know of a better way to approach the problem, or know of an
+existing way to deal with it, or know someone else is working on it.
+This is mostly informal, but a fairly complete explanation for the need
+and use of the code should be provided.
+
+
+=item Present complete specs to core team
+
+The complete proposed API to the core team should be submitted for
+approval and discussion. For web and command-line programs, present the
+functionality and interface (op codes, command-lin switches, etc.).
+
+The best way to do this is to take the documentation portion of the
+boilerplate and fill it in. You can make changes later if necessary,
+but fill it in as much as you can.
+
+
+=item Announce any changes to interface
+
+If the way it works or how it is called is going to change, notify the core
+team.
+
+
+=item Prepare for core review
+
+When you are done, the code will undergo a code review by a member of
+the core team, or someone picked by the core team. This is not to
+belittle you (that's just a nice side effect), it is to make sure that
+you understand your code, that we understand your code, that it won't
+break other code, that it follows the documentation and existing
+proposal. It is to check for possible optimizations or better ways of
+doing it.
+
+For members of the core team, one or more other members of the team will
+perform the review.
+
+Note that all code is expected to follow the coding principles and style
+guide contained in this document.
+
+
+=item Finish it up
+
+After the code is done (possibly going through multiple code reviews),
+if you do not have repository access, submit it to rt-<major-version>-bugs@fsck.com as a unified diff. From that point on, it'll be handled by someone with repository access.
+
+=back
+
+
+=head1 BUG REPORTS, PATCHES
+
+Use rt-<major-version>-bugs@fsck.com for I<any> bug that is not
+being fixed immediately. If it is not in RT, there
+is a good chance it will not be dealt with.
+
+Send patches to rt-<major-version>-bugs@fsck.com, too. Use C<diff
+-u> for patches.
+
+
+
+=head1 TO DO
+
+Talk about DBIx::SearchBuilder
+
+Talk about mason
+ component style
+ cascading style sheets
+
+Talk about adding a new translation
+
+Talk more about logging
+
+=head1 CHANGES
+
+ Adapted from Slash Styleguide by jesse - 20 Dec, 2002
+
+
+=head1 VERSION
+
+0.1
diff --git a/rt/lib/RT/System.pm b/rt/lib/RT/System.pm
new file mode 100644
index 0000000..bfa5a4e
--- /dev/null
+++ b/rt/lib/RT/System.pm
@@ -0,0 +1,165 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+RT::System
+
+=head1 DESCRIPTION
+
+RT::System is a simple global object used as a focal point for things
+that are system-wide.
+
+It works sort of like an RT::Record, except it's really a single object that has
+an id of "1" when instantiated.
+
+This gets used by the ACL system so that you can have rights for the scope "RT::System"
+
+In the future, there will probably be other API goodness encapsulated here.
+
+=cut
+
+
+package RT::System;
+use base qw /RT::Base/;
+use strict;
+
+use RT::ACL;
+use vars qw/ $RIGHTS/;
+
+# System rights are rights granted to the whole system
+# XXX TODO Can't localize these outside of having an object around.
+$RIGHTS = {
+ SuperUser => 'Do anything and everything', # loc_pair
+ AdminAllPersonalGroups =>
+ "Create, delete and modify the members of any user's personal groups"
+ , # loc_pair
+ AdminOwnPersonalGroups =>
+ 'Create, delete and modify the members of personal groups', # loc_pair
+ AdminUsers => 'Create, delete and modify users', # loc_pair
+ ModifySelf => "Modify one's own RT account", # loc_pair
+ DelegateRights =>
+ "Delegate specific rights which have been granted to you." # loc_pair
+};
+
+# Tell RT::ACE that this sort of object can get acls granted
+$RT::ACE::OBJECT_TYPES{'RT::System'} = 1;
+
+foreach my $right ( keys %{$RIGHTS} ) {
+ $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
+}
+
+
+=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 the rights do
+
+=begin testing
+
+my $s = RT::System->new($RT::SystemUser);
+my $rights = $s->AvailableRights;
+ok ($rights, "Rights defined");
+ok ($rights->{'AdminUsers'},"AdminUsers right found");
+ok ($rights->{'CreateTicket'},"CreateTicket right found");
+ok ($rights->{'AdminGroupMembership'},"ModifyGroupMembers right found");
+ok (!$rights->{'CasdasdsreateTicket'},"bogus right not found");
+
+
+
+=end testing
+
+
+=cut
+
+sub AvailableRights {
+ my $self = shift;
+
+ my $queue = RT::Queue->new($RT::SystemUser);
+ my $group = RT::Group->new($RT::SystemUser);
+
+ my $qr =$queue->AvailableRights();
+ my $gr = $group->AvailableRights();
+
+ # Build a merged list of all system wide rights, queue rights and group rights.
+ my %rights = (%{$RIGHTS}, %{$gr}, %{$qr});
+ return(\%rights);
+}
+
+
+=head2 new
+
+Create a new RT::System object. Really, you should be using $RT::System
+
+=cut
+
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless( $self, $class );
+
+
+ return ($self);
+}
+
+=head2 id
+
+Returns RT::System's id. It's 1.
+
+
+=begin testing
+
+use RT::System;
+my $sys = RT::System->new();
+is( $sys->Id, 1);
+is ($sys->id, 1);
+
+=end testing
+
+
+=cut
+
+*Id = \&id;
+
+sub id {
+ return (1);
+}
+
+=head2 Load
+
+Since this object is pretending to be an RT::Record, we need a load method.
+It does nothing
+
+=cut
+
+sub Load {
+ return (1);
+}
+
+eval "require RT::System_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/System_Vendor.pm});
+eval "require RT::System_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/System_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm
new file mode 100755
index 0000000..f73ea3e
--- /dev/null
+++ b/rt/lib/RT/Template.pm
@@ -0,0 +1,363 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Template
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Template;
+use RT::Record;
+use RT::Queue;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Templates');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'Queue'.
+ varchar(200) 'Name'.
+ varchar(255) 'Description'.
+ varchar(16) 'Type'.
+ varchar(16) 'Language'.
+ int(11) 'TranslationOf'.
+ blob 'Content'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Queue => '0',
+ Name => '',
+ Description => '',
+ Type => '',
+ Language => '',
+ TranslationOf => '0',
+ Content => '',
+
+ @_);
+ $self->SUPER::Create(
+ Queue => $args{'Queue'},
+ Name => $args{'Name'},
+ Description => $args{'Description'},
+ Type => $args{'Type'},
+ Language => $args{'Language'},
+ TranslationOf => $args{'TranslationOf'},
+ Content => $args{'Content'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Queue
+
+Returns the current value of Queue.
+(In the database, Queue is stored as int(11).)
+
+
+
+=item SetQueue VALUE
+
+
+Set Queue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Queue will be stored as a int(11).)
+
+
+=cut
+
+
+=item QueueObj
+
+Returns the Queue Object which has the id returned by Queue
+
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+ my $Queue = RT::Queue->new($self->CurrentUser);
+ $Queue->Load($self->__Value('Queue'));
+ return($Queue);
+}
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Description
+
+Returns the current value of Description.
+(In the database, Description is stored as varchar(255).)
+
+
+
+=item SetDescription VALUE
+
+
+Set Description to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Description will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(16).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item Language
+
+Returns the current value of Language.
+(In the database, Language is stored as varchar(16).)
+
+
+
+=item SetLanguage VALUE
+
+
+Set Language to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Language will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item TranslationOf
+
+Returns the current value of TranslationOf.
+(In the database, TranslationOf is stored as int(11).)
+
+
+
+=item SetTranslationOf VALUE
+
+
+Set TranslationOf to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TranslationOf will be stored as a int(11).)
+
+
+=cut
+
+
+=item Content
+
+Returns the current value of Content.
+(In the database, Content is stored as blob.)
+
+
+
+=item SetContent VALUE
+
+
+Set Content to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Content will be stored as a blob.)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Queue =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Description =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ Language =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ TranslationOf =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Content =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Template_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Template_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Template_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Template_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Template_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Template_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Template_Overlay, RT::Template_Vendor, RT::Template_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Template_Overlay.pm b/rt/lib/RT/Template_Overlay.pm
new file mode 100644
index 0000000..5950aa3
--- /dev/null
+++ b/rt/lib/RT/Template_Overlay.pm
@@ -0,0 +1,418 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
+
+=head1 NAME
+
+ RT::Template - RT's template object
+
+=head1 SYNOPSIS
+
+ use RT::Template;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::Template);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use Text::Template;
+use MIME::Entity;
+use MIME::Parser;
+use File::Temp qw /tempdir/;
+
+
+# {{{ sub _Accessible
+
+sub _Accessible {
+ my $self = shift;
+ my %Cols = (
+ id => 'read',
+ Name => 'read/write',
+ Description => 'read/write',
+ Type => 'read/write', #Type is one of Action or Message
+ Content => 'read/write',
+ Queue => 'read/write',
+ Creator => 'read/auto',
+ Created => 'read/auto',
+ LastUpdatedBy => 'read/auto',
+ LastUpdated => 'read/auto'
+ );
+ return $self->SUPER::_Accessible( @_, %Cols );
+}
+
+# }}}
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+
+ # use super::value or we get acl blocked
+ if ( ( defined $self->SUPER::_Value('Queue') )
+ && ( $self->SUPER::_Value('Queue') == 0 ) )
+ {
+ unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ModifyTemplate') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ else {
+
+ unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ return ( $self->SUPER::_Set(@_) );
+
+}
+
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+
+=begin testing
+
+my $t = RT::Template->new($RT::SystemUser);
+$t->Create(Name => "Foo", Queue => 1);
+my $t2 = RT::Template->new($RT::Nobody);
+$t2->Load($t->Id);
+ok($t2->QueueObj->id, "Got the template's queue objet");
+
+=end testing
+
+
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+
+ #If the current user doesn't have ACLs, don't let em at it.
+ #use super::value or we get acl blocked
+ if ( ( !defined $self->__Value('Queue') )
+ || ( $self->__Value('Queue') == 0 ) )
+ {
+ unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'ShowTemplate') ) {
+ return (undef);
+ }
+ }
+ else {
+ unless ( $self->CurrentUserHasQueueRight('ShowTemplate') ) {
+ return (undef);
+ }
+ }
+ return ( $self->__Value($field) );
+
+}
+
+# }}}
+
+# {{{ sub Load
+
+=head2 Load <identifer>
+
+Load a template, either by number or by name
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift;
+
+ if ( !$identifier ) {
+ return (undef);
+ }
+
+ if ( $identifier !~ /\D/ ) {
+ $self->SUPER::LoadById($identifier);
+ }
+ else {
+ $self->LoadByCol( 'Name', $identifier );
+
+ }
+}
+
+# }}}
+
+# {{{ sub LoadGlobalTemplate
+
+=head2 LoadGlobalTemplate NAME
+
+Load the global tempalte with the name NAME
+
+=cut
+
+sub LoadGlobalTemplate {
+ my $self = shift;
+ my $id = shift;
+
+ return ( $self->LoadQueueTemplate( Queue => 0, Name => $id ) );
+}
+
+# }}}
+
+# {{{ sub LoadQueueTemplate
+
+=head2 LoadQueueTemplate (Queue => QUEUEID, Name => NAME)
+
+Loads the Queue template named NAME for Queue QUEUE.
+
+=cut
+
+sub LoadQueueTemplate {
+ my $self = shift;
+ my %args = (
+ Queue => undef,
+ Name => undef,
+ @_
+ );
+
+ return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
+
+}
+
+# }}}
+
+# {{{ sub Create
+
+=head2 Create
+
+Takes a paramhash of Content, Queue, Name and Description.
+Name should be a unique string identifying this Template.
+Description and Content should be the template's title and content.
+Queue should be 0 for a global template and the queue # for a queue-specific
+template.
+
+Returns the Template's id # if the create was successful. Returns undef for
+unknown database failure.
+
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Content => undef,
+ Queue => 0,
+ Description => '[no description]',
+ Type => 'Action', #By default, template are 'Action' templates
+ Name => undef,
+ @_
+ );
+
+ if ( !$args{'Queue'} ) {
+ unless ( $self->CurrentUser->HasRight(Right =>'ModifyTemplate', Object => $RT::System) ) {
+ return (undef);
+ }
+ $args{'Queue'} = 0;
+ }
+ else {
+ my $QueueObj = new RT::Queue( $self->CurrentUser );
+ $QueueObj->Load( $args{'Queue'} ) || return ( 0, $self->loc('Invalid queue') );
+
+ unless ( $QueueObj->CurrentUserHasRight('ModifyTemplate') ) {
+ return (undef);
+ }
+ $args{'Queue'} = $QueueObj->Id;
+ }
+
+ my $result = $self->SUPER::Create(
+ Content => $args{'Content'},
+ Queue => $args{'Queue'},
+ Description => $args{'Description'},
+ Name => $args{'Name'}
+ );
+
+ return ($result);
+
+}
+
+# }}}
+
+# {{{ sub Delete
+
+=head2 Delete
+
+Delete this template.
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ return ( $self->SUPER::Delete(@_) );
+}
+
+# }}}
+
+# {{{ sub MIMEObj
+sub MIMEObj {
+ my $self = shift;
+ return ( $self->{'MIMEObj'} );
+}
+
+# }}}
+
+# {{{ sub Parse
+
+=item Parse
+
+ This routine performs Text::Template parsing on the template and then
+ imports the results into a MIME::Entity so we can really use it
+ It returns a tuple of (val, message)
+ If val is 0, the message contains an error message
+
+=cut
+
+sub Parse {
+ my $self = shift;
+
+ #We're passing in whatever we were passed. it's destined for _ParseContent
+ my $content = $self->_ParseContent(@_);
+
+ #Lets build our mime Entity
+
+ my $parser = MIME::Parser->new();
+
+ # Setup output directory for files. from RT::EmailParser::_SetupMIMEParser
+ if ( my $AttachmentDir =
+ eval { File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 ) } )
+ {
+
+ # Set up output directory for files:
+ $parser->output_dir("$AttachmentDir");
+ }
+ else {
+ $RT::Logger->error("Couldn't write attachments to temp dir on disk. using more memory and processor.");
+ # On some situations TMPDIR is non-writable. sad but true.
+ $parser->output_to_core(1);
+ $parser->tmp_to_core(1);
+ }
+
+ #If someone includes a message, don't extract it
+ $parser->extract_nested_messages(1);
+
+ # Set up the prefix for files with auto-generated names:
+ $parser->output_prefix("part");
+
+ # If content length is <= 50000 bytes, store each msg as in-core scalar;
+ # Else, write to a disk file (the default action):
+ $parser->output_to_core(50000);
+
+ ### Should we forgive normally-fatal errors?
+ $parser->ignore_errors(1);
+ $self->{'MIMEObj'} = eval { $parser->parse_data($content) };
+ my $error = ( $@ || $parser->last_error );
+
+ if ($error) {
+ $RT::Logger->error("$error");
+ return ( 0, $error );
+ }
+
+ # Unfold all headers
+ $self->{'MIMEObj'}->head->unfold();
+
+ return ( 1, $self->loc("Template parsed") );
+
+}
+
+# }}}
+
+# {{{ sub _ParseContent
+
+# Perform Template substitutions on the template
+
+sub _ParseContent {
+ my $self = shift;
+ my %args = (
+ Argument => undef,
+ TicketObj => undef,
+ TransactionObj => undef,
+ @_
+ );
+
+ no warnings 'redefine';
+ $T::Ticket = $args{'TicketObj'};
+ $T::Transaction = $args{'TransactionObj'};
+ $T::Argument = $args{'Argument'};
+ $T::Requestor = eval { $T::Ticket->Requestors->UserMembersObj->First->Name };
+ $T::rtname = $RT::rtname;
+ *T::loc = sub { $T::Ticket->loc(@_) };
+
+ # We need to untaint the content of the template, since we'll be working
+ # with it
+ my $content = $self->Content();
+ $content =~ s/^(.*)$/$1/;
+ my $template = Text::Template->new(
+ TYPE => 'STRING',
+ SOURCE => $content
+ );
+
+ my $retval = $template->fill_in( PACKAGE => 'T' );
+
+ # MIME::Parser has problems dealing with high-bit utf8 data.
+ Encode::_utf8_off($retval);
+ return ($retval);
+}
+
+# }}}
+
+# {{{ sub CurrentUserHasQueueRight
+
+=head2 CurrentUserHasQueueRight
+
+Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args.
+
+=cut
+
+sub CurrentUserHasQueueRight {
+ my $self = shift;
+ return ( $self->QueueObj->CurrentUserHasRight(@_) );
+}
+
+# }}}
+1;
diff --git a/rt/lib/RT/Templates.pm b/rt/lib/RT/Templates.pm
new file mode 100755
index 0000000..37db840
--- /dev/null
+++ b/rt/lib/RT/Templates.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Templates -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Templates
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Templates;
+
+use RT::SearchBuilder;
+use RT::Template;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Templates';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Template item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Template->new($self->CurrentUser));
+}
+
+ eval "require RT::Templates_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Templates_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Templates_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Templates_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Templates_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Templates_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Templates_Overlay, RT::Templates_Vendor, RT::Templates_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Templates_Overlay.pm b/rt/lib/RT/Templates_Overlay.pm
new file mode 100644
index 0000000..6bc992e
--- /dev/null
+++ b/rt/lib/RT/Templates_Overlay.pm
@@ -0,0 +1,141 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Templates - a collection of RT Template objects
+
+=head1 SYNOPSIS
+
+ use RT::Templates;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok (require RT::Templates);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+
+# {{{ sub _Init
+
+=head2 _Init
+
+ Returns RT::Templates specific init info like table and primary key names
+
+=cut
+
+sub _Init {
+
+ my $self = shift;
+ $self->{'table'} = "Templates";
+ $self->{'primary_key'} = "id";
+ return ($self->SUPER::_Init(@_));
+}
+# }}}
+
+# {{{ LimitToNotInQueue
+
+=head2 LimitToNotInQueue
+
+Takes a queue id # and limits the returned set of templates to those which
+aren't that queue's templates.
+
+=cut
+
+sub LimitToNotInQueue {
+ my $self = shift;
+ my $queue_id = shift;
+ $self->Limit(FIELD => 'Queue',
+ VALUE => "$queue_id",
+ OPERATOR => '!='
+ );
+}
+# }}}
+
+# {{{ LimitToGlobal
+
+=head2 LimitToGlobal
+
+Takes no arguments. Limits the returned set to "Global" templates
+which can be used with any queue.
+
+=cut
+
+sub LimitToGlobal {
+ my $self = shift;
+ my $queue_id = shift;
+ $self->Limit(FIELD => 'Queue',
+ VALUE => "0",
+ OPERATOR => '='
+ );
+}
+# }}}
+
+# {{{ LimitToQueue
+
+=head2 LimitToQueue
+
+Takes a queue id # and limits the returned set of templates to that queue's
+templates
+
+=cut
+
+sub LimitToQueue {
+ my $self = shift;
+ my $queue_id = shift;
+ $self->Limit(FIELD => 'Queue',
+ VALUE => "$queue_id",
+ OPERATOR => '='
+ );
+}
+# }}}
+
+# {{{ sub NewItem
+
+=head2 NewItem
+
+Returns a new empty Template object
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+
+ use RT::Template;
+ my $item = new RT::Template($self->CurrentUser);
+ return($item);
+}
+# }}}
+
+1;
+
diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm
new file mode 100755
index 0000000..2f075a2
--- /dev/null
+++ b/rt/lib/RT/Ticket.pm
@@ -0,0 +1,662 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Ticket
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Ticket;
+use RT::Record;
+use RT::Queue;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Tickets');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'EffectiveId'.
+ int(11) 'Queue'.
+ varchar(16) 'Type'.
+ int(11) 'IssueStatement'.
+ int(11) 'Resolution'.
+ int(11) 'Owner'.
+ varchar(200) 'Subject' defaults to '[no subject]'.
+ int(11) 'InitialPriority'.
+ int(11) 'FinalPriority'.
+ int(11) 'Priority'.
+ int(11) 'TimeEstimated'.
+ int(11) 'TimeWorked'.
+ varchar(10) 'Status'.
+ int(11) 'TimeLeft'.
+ datetime 'Told'.
+ datetime 'Starts'.
+ datetime 'Started'.
+ datetime 'Due'.
+ datetime 'Resolved'.
+ smallint(6) 'Disabled'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ EffectiveId => '0',
+ Queue => '0',
+ Type => '',
+ IssueStatement => '0',
+ Resolution => '0',
+ Owner => '0',
+ Subject => '[no subject]',
+ InitialPriority => '0',
+ FinalPriority => '0',
+ Priority => '0',
+ TimeEstimated => '0',
+ TimeWorked => '0',
+ Status => '',
+ TimeLeft => '0',
+ Told => '',
+ Starts => '',
+ Started => '',
+ Due => '',
+ Resolved => '',
+ Disabled => '0',
+
+ @_);
+ $self->SUPER::Create(
+ EffectiveId => $args{'EffectiveId'},
+ Queue => $args{'Queue'},
+ Type => $args{'Type'},
+ IssueStatement => $args{'IssueStatement'},
+ Resolution => $args{'Resolution'},
+ Owner => $args{'Owner'},
+ Subject => $args{'Subject'},
+ InitialPriority => $args{'InitialPriority'},
+ FinalPriority => $args{'FinalPriority'},
+ Priority => $args{'Priority'},
+ TimeEstimated => $args{'TimeEstimated'},
+ TimeWorked => $args{'TimeWorked'},
+ Status => $args{'Status'},
+ TimeLeft => $args{'TimeLeft'},
+ Told => $args{'Told'},
+ Starts => $args{'Starts'},
+ Started => $args{'Started'},
+ Due => $args{'Due'},
+ Resolved => $args{'Resolved'},
+ Disabled => $args{'Disabled'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item EffectiveId
+
+Returns the current value of EffectiveId.
+(In the database, EffectiveId is stored as int(11).)
+
+
+
+=item SetEffectiveId VALUE
+
+
+Set EffectiveId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, EffectiveId will be stored as a int(11).)
+
+
+=cut
+
+
+=item Queue
+
+Returns the current value of Queue.
+(In the database, Queue is stored as int(11).)
+
+
+
+=item SetQueue VALUE
+
+
+Set Queue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Queue will be stored as a int(11).)
+
+
+=cut
+
+
+=item QueueObj
+
+Returns the Queue Object which has the id returned by Queue
+
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+ my $Queue = RT::Queue->new($self->CurrentUser);
+ $Queue->Load($self->__Value('Queue'));
+ return($Queue);
+}
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(16).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item IssueStatement
+
+Returns the current value of IssueStatement.
+(In the database, IssueStatement is stored as int(11).)
+
+
+
+=item SetIssueStatement VALUE
+
+
+Set IssueStatement to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, IssueStatement will be stored as a int(11).)
+
+
+=cut
+
+
+=item Resolution
+
+Returns the current value of Resolution.
+(In the database, Resolution is stored as int(11).)
+
+
+
+=item SetResolution VALUE
+
+
+Set Resolution to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Resolution will be stored as a int(11).)
+
+
+=cut
+
+
+=item Owner
+
+Returns the current value of Owner.
+(In the database, Owner is stored as int(11).)
+
+
+
+=item SetOwner VALUE
+
+
+Set Owner to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Owner will be stored as a int(11).)
+
+
+=cut
+
+
+=item Subject
+
+Returns the current value of Subject.
+(In the database, Subject is stored as varchar(200).)
+
+
+
+=item SetSubject VALUE
+
+
+Set Subject to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Subject will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item InitialPriority
+
+Returns the current value of InitialPriority.
+(In the database, InitialPriority is stored as int(11).)
+
+
+
+=item SetInitialPriority VALUE
+
+
+Set InitialPriority to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, InitialPriority will be stored as a int(11).)
+
+
+=cut
+
+
+=item FinalPriority
+
+Returns the current value of FinalPriority.
+(In the database, FinalPriority is stored as int(11).)
+
+
+
+=item SetFinalPriority VALUE
+
+
+Set FinalPriority to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, FinalPriority will be stored as a int(11).)
+
+
+=cut
+
+
+=item Priority
+
+Returns the current value of Priority.
+(In the database, Priority is stored as int(11).)
+
+
+
+=item SetPriority VALUE
+
+
+Set Priority to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Priority will be stored as a int(11).)
+
+
+=cut
+
+
+=item TimeEstimated
+
+Returns the current value of TimeEstimated.
+(In the database, TimeEstimated is stored as int(11).)
+
+
+
+=item SetTimeEstimated VALUE
+
+
+Set TimeEstimated to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TimeEstimated will be stored as a int(11).)
+
+
+=cut
+
+
+=item TimeWorked
+
+Returns the current value of TimeWorked.
+(In the database, TimeWorked is stored as int(11).)
+
+
+
+=item SetTimeWorked VALUE
+
+
+Set TimeWorked to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TimeWorked will be stored as a int(11).)
+
+
+=cut
+
+
+=item Status
+
+Returns the current value of Status.
+(In the database, Status is stored as varchar(10).)
+
+
+
+=item SetStatus VALUE
+
+
+Set Status to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Status will be stored as a varchar(10).)
+
+
+=cut
+
+
+=item TimeLeft
+
+Returns the current value of TimeLeft.
+(In the database, TimeLeft is stored as int(11).)
+
+
+
+=item SetTimeLeft VALUE
+
+
+Set TimeLeft to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TimeLeft will be stored as a int(11).)
+
+
+=cut
+
+
+=item Told
+
+Returns the current value of Told.
+(In the database, Told is stored as datetime.)
+
+
+
+=item SetTold VALUE
+
+
+Set Told to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Told will be stored as a datetime.)
+
+
+=cut
+
+
+=item Starts
+
+Returns the current value of Starts.
+(In the database, Starts is stored as datetime.)
+
+
+
+=item SetStarts VALUE
+
+
+Set Starts to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Starts will be stored as a datetime.)
+
+
+=cut
+
+
+=item Started
+
+Returns the current value of Started.
+(In the database, Started is stored as datetime.)
+
+
+
+=item SetStarted VALUE
+
+
+Set Started to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Started will be stored as a datetime.)
+
+
+=cut
+
+
+=item Due
+
+Returns the current value of Due.
+(In the database, Due is stored as datetime.)
+
+
+
+=item SetDue VALUE
+
+
+Set Due to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Due will be stored as a datetime.)
+
+
+=cut
+
+
+=item Resolved
+
+Returns the current value of Resolved.
+(In the database, Resolved is stored as datetime.)
+
+
+
+=item SetResolved VALUE
+
+
+Set Resolved to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Resolved will be stored as a datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item Disabled
+
+Returns the current value of Disabled.
+(In the database, Disabled is stored as smallint(6).)
+
+
+
+=item SetDisabled VALUE
+
+
+Set Disabled to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Disabled will be stored as a smallint(6).)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ EffectiveId =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Queue =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ IssueStatement =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Resolution =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Owner =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Subject =>
+ {read => 1, write => 1, type => 'varchar(200)', default => '[no subject]'},
+ InitialPriority =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ FinalPriority =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Priority =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ TimeEstimated =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ TimeWorked =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Status =>
+ {read => 1, write => 1, type => 'varchar(10)', default => ''},
+ TimeLeft =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Told =>
+ {read => 1, write => 1, type => 'datetime', default => ''},
+ Starts =>
+ {read => 1, write => 1, type => 'datetime', default => ''},
+ Started =>
+ {read => 1, write => 1, type => 'datetime', default => ''},
+ Due =>
+ {read => 1, write => 1, type => 'datetime', default => ''},
+ Resolved =>
+ {read => 1, write => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ Disabled =>
+ {read => 1, write => 1, type => 'smallint(6)', default => '0'},
+
+ }
+};
+
+
+ eval "require RT::Ticket_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Ticket_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Ticket_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Ticket_Overlay, RT::Ticket_Vendor, RT::Ticket_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/TicketCustomFieldValue.pm b/rt/lib/RT/TicketCustomFieldValue.pm
new file mode 100644
index 0000000..862a5dc
--- /dev/null
+++ b/rt/lib/RT/TicketCustomFieldValue.pm
@@ -0,0 +1,286 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::TicketCustomFieldValue
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::TicketCustomFieldValue;
+use RT::Record;
+use RT::CustomField;
+use RT::Ticket;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('TicketCustomFieldValues');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'Ticket'.
+ int(11) 'CustomField'.
+ varchar(255) 'Content'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Ticket => '0',
+ CustomField => '0',
+ Content => '',
+
+ @_);
+ $self->SUPER::Create(
+ Ticket => $args{'Ticket'},
+ CustomField => $args{'CustomField'},
+ Content => $args{'Content'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Ticket
+
+Returns the current value of Ticket.
+(In the database, Ticket is stored as int(11).)
+
+
+
+=item SetTicket VALUE
+
+
+Set Ticket to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Ticket will be stored as a int(11).)
+
+
+=cut
+
+
+=item TicketObj
+
+Returns the Ticket Object which has the id returned by Ticket
+
+
+=cut
+
+sub TicketObj {
+ my $self = shift;
+ my $Ticket = RT::Ticket->new($self->CurrentUser);
+ $Ticket->Load($self->__Value('Ticket'));
+ return($Ticket);
+}
+
+=item CustomField
+
+Returns the current value of CustomField.
+(In the database, CustomField is stored as int(11).)
+
+
+
+=item SetCustomField VALUE
+
+
+Set CustomField to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, CustomField will be stored as a int(11).)
+
+
+=cut
+
+
+=item CustomFieldObj
+
+Returns the CustomField Object which has the id returned by CustomField
+
+
+=cut
+
+sub CustomFieldObj {
+ my $self = shift;
+ my $CustomField = RT::CustomField->new($self->CurrentUser);
+ $CustomField->Load($self->__Value('CustomField'));
+ return($CustomField);
+}
+
+=item Content
+
+Returns the current value of Content.
+(In the database, Content is stored as varchar(255).)
+
+
+
+=item SetContent VALUE
+
+
+Set Content to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Content will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Ticket =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ CustomField =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Content =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::TicketCustomFieldValue_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValue_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::TicketCustomFieldValue_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValue_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::TicketCustomFieldValue_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValue_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::TicketCustomFieldValue_Overlay, RT::TicketCustomFieldValue_Vendor, RT::TicketCustomFieldValue_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/TicketCustomFieldValue_Overlay.pm b/rt/lib/RT/TicketCustomFieldValue_Overlay.pm
new file mode 100644
index 0000000..c395eca
--- /dev/null
+++ b/rt/lib/RT/TicketCustomFieldValue_Overlay.pm
@@ -0,0 +1,52 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+
+
+=head2 LoadByTicketContentAndCustomField { Ticket => TICKET, CustomField => CUSTOMFIELD, Content => CONTENT }
+
+Loads a custom field value by Ticket, Content and which CustomField it's tied to
+
+=cut
+
+
+sub LoadByTicketContentAndCustomField {
+ my $self = shift;
+ my %args = ( Ticket => undef,
+ CustomField => undef,
+ Content => undef,
+ @_
+ );
+
+
+ $self->LoadByCols( Content => $args{'Content'},
+ CustomField => $args{'CustomField'},
+ Ticket => $args{'Ticket'});
+
+
+}
+
+1;
diff --git a/rt/lib/RT/TicketCustomFieldValues.pm b/rt/lib/RT/TicketCustomFieldValues.pm
new file mode 100644
index 0000000..f137f53
--- /dev/null
+++ b/rt/lib/RT/TicketCustomFieldValues.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::TicketCustomFieldValues -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::TicketCustomFieldValues
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::TicketCustomFieldValues;
+
+use RT::SearchBuilder;
+use RT::TicketCustomFieldValue;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'TicketCustomFieldValues';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::TicketCustomFieldValue item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::TicketCustomFieldValue->new($self->CurrentUser));
+}
+
+ eval "require RT::TicketCustomFieldValues_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValues_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::TicketCustomFieldValues_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValues_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::TicketCustomFieldValues_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/TicketCustomFieldValues_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::TicketCustomFieldValues_Overlay, RT::TicketCustomFieldValues_Vendor, RT::TicketCustomFieldValues_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/TicketCustomFieldValues_Overlay.pm b/rt/lib/RT/TicketCustomFieldValues_Overlay.pm
new file mode 100644
index 0000000..5777c37
--- /dev/null
+++ b/rt/lib/RT/TicketCustomFieldValues_Overlay.pm
@@ -0,0 +1,86 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub LimitToCustomField
+
+=head2 LimitToCustomField FIELD
+
+Limits the returned set to values for the custom field with Id FIELD
+
+=cut
+
+sub LimitToCustomField {
+ my $self = shift;
+ my $cf = shift;
+ return ($self->Limit( FIELD => 'CustomField',
+ VALUE => $cf,
+ OPERATOR => '='));
+
+}
+
+# }}}
+
+# {{{ sub LimitToTicket
+
+=head2 LimitToTicket TICKETID
+
+Limits the returned set to values for the ticket with Id TICKETID
+
+=cut
+
+sub LimitToTicket {
+ my $self = shift;
+ my $ticket = shift;
+ return ($self->Limit( FIELD => 'Ticket',
+ VALUE => $ticket,
+ OPERATOR => '='));
+
+}
+
+# }}}
+
+
+=sub HasEntry VALUE
+
+Returns true if this CustomFieldValues collection has an entry with content that eq VALUE
+
+=cut
+
+
+sub HasEntry {
+ my $self = shift;
+ my $value = shift;
+
+ #TODO: this could cache and optimize a fair bit.
+ foreach my $item (@{$self->ItemsArrayRef}) {
+ return(1) if ($item->Content eq $value);
+ }
+ return undef;
+
+}
+
+1;
+
diff --git a/rt/lib/RT/Ticket_Overlay.pm b/rt/lib/RT/Ticket_Overlay.pm
new file mode 100644
index 0000000..981df41
--- /dev/null
+++ b/rt/lib/RT/Ticket_Overlay.pm
@@ -0,0 +1,4095 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# {{{ Front Material
+
+=head1 SYNOPSIS
+
+ use RT::Ticket;
+ my $ticket = new RT::Ticket($CurrentUser);
+ $ticket->Load($ticket_id);
+
+=head1 DESCRIPTION
+
+This module lets you manipulate RT\'s ticket object.
+
+
+=head1 METHODS
+
+=begin testing
+
+use_ok ( RT::Queue);
+ok(my $testqueue = RT::Queue->new($RT::SystemUser));
+ok($testqueue->Create( Name => 'ticket tests'));
+ok($testqueue->Id != 0);
+use_ok(RT::CustomField);
+ok(my $testcf = RT::CustomField->new($RT::SystemUser));
+ok($testcf->Create( Name => 'selectmulti',
+ Queue => $testqueue->id,
+ Type => 'SelectMultiple'));
+ok($testcf->AddValue ( Name => 'Value1',
+ SortOrder => '1',
+ Description => 'A testing value'));
+ok($testcf->AddValue ( Name => 'Value2',
+ SortOrder => '2',
+ Description => 'Another testing value'));
+ok($testcf->AddValue ( Name => 'Value3',
+ SortOrder => '3',
+ Description => 'Yet Another testing value'));
+
+ok($testcf->Values->Count == 3);
+
+use_ok(RT::Ticket);
+
+my $u = RT::User->new($RT::SystemUser);
+$u->Load("root");
+ok ($u->Id, "Found the root user");
+ok(my $t = RT::Ticket->new($RT::SystemUser));
+ok(my ($id, $msg) = $t->Create( Queue => $testqueue->Id,
+ Subject => 'Testing',
+ Owner => $u->Id
+ ));
+ok($id != 0);
+ok ($t->OwnerObj->Id == $u->Id, "Root is the ticket owner");
+ok(my ($cfv, $cfm) =$t->AddCustomFieldValue(Field => $testcf->Id,
+ Value => 'Value1'));
+ok($cfv != 0, "Custom field creation didn't return an error: $cfm");
+ok($t->CustomFieldValues($testcf->Id)->Count == 1);
+ok($t->CustomFieldValues($testcf->Id)->First &&
+ $t->CustomFieldValues($testcf->Id)->First->Content eq 'Value1');;
+
+ok(my ($cfdv, $cfdm) = $t->DeleteCustomFieldValue(Field => $testcf->Id,
+ Value => 'Value1'));
+ok ($cfdv != 0, "Deleted a custom field value: $cfdm");
+ok($t->CustomFieldValues($testcf->Id)->Count == 0);
+
+ok(my $t2 = RT::Ticket->new($RT::SystemUser));
+ok($t2->Load($id));
+ok($t2->Subject eq 'Testing');
+ok($t2->QueueObj->Id eq $testqueue->id);
+ok($t2->OwnerObj->Id == $u->Id);
+
+my $t3 = RT::Ticket->new($RT::SystemUser);
+my ($id3, $msg3) = $t3->Create( Queue => $testqueue->Id,
+ Subject => 'Testing',
+ Owner => $u->Id);
+my ($cfv1, $cfm1) = $t->AddCustomFieldValue(Field => $testcf->Id,
+ Value => 'Value1');
+ok($cfv1 != 0, "Adding a custom field to ticket 1 is successful: $cfm");
+my ($cfv2, $cfm2) = $t3->AddCustomFieldValue(Field => $testcf->Id,
+ Value => 'Value2');
+ok($cfv2 != 0, "Adding a custom field to ticket 2 is successful: $cfm");
+my ($cfv3, $cfm3) = $t->AddCustomFieldValue(Field => $testcf->Id,
+ Value => 'Value3');
+ok($cfv3 != 0, "Adding a custom field to ticket 1 is successful: $cfm");
+ok($t->CustomFieldValues($testcf->Id)->Count == 2,
+ "This ticket has 2 custom field values");
+ok($t3->CustomFieldValues($testcf->Id)->Count == 1,
+ "This ticket has 1 custom field value");
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use RT::Queue;
+use RT::User;
+use RT::Record;
+use RT::Links;
+use RT::Date;
+use RT::CustomFields;
+use RT::TicketCustomFieldValues;
+use RT::Tickets;
+use RT::URI::fsck_com_rt;
+use RT::URI;
+
+=begin testing
+
+
+ok(require RT::Ticket, "Loading the RT::Ticket library");
+
+=end testing
+
+=cut
+
+# }}}
+
+# {{{ LINKTYPEMAP
+# A helper table for relationships mapping to make it easier
+# to build and parse links between tickets
+
+use vars '%LINKTYPEMAP';
+
+%LINKTYPEMAP = (
+ MemberOf => { Type => 'MemberOf',
+ Mode => 'Target', },
+ Members => { 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', },
+
+);
+
+# }}}
+
+# {{{ LINKDIRMAP
+# A helper table for relationships mapping to make it easier
+# to build and parse links between tickets
+
+use vars '%LINKDIRMAP';
+
+%LINKDIRMAP = (
+ MemberOf => { Base => 'MemberOf',
+ Target => 'HasMember', },
+ RefersTo => { Base => 'RefersTo',
+ Target => 'ReferredToBy', },
+ DependsOn => { Base => 'DependsOn',
+ Target => 'DependedOnBy', },
+
+);
+
+# }}}
+
+# {{{ sub Load
+
+=head2 Load
+
+Takes a single argument. This can be a ticket id, ticket alias or
+local ticket uri. If the ticket can't be loaded, returns undef.
+Otherwise, returns the ticket id.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $id = shift;
+
+ #TODO modify this routine to look at EffectiveId and do the recursive load
+ # thing. be careful to cache all the interim tickets we try so we don't loop forever.
+
+ #If it's a local URI, turn it into a ticket id
+ if ( $id =~ /^$RT::TicketBaseURI(\d+)$/ ) {
+ $id = $1;
+ }
+
+ #If it's a remote URI, we're going to punt for now
+ elsif ( $id =~ '://' ) {
+ return (undef);
+ }
+
+ #If we have an integer URI, load the ticket
+ if ( $id =~ /^\d+$/ ) {
+ my $ticketid = $self->LoadById($id);
+
+ unless ($ticketid) {
+ $RT::Logger->debug("$self tried to load a bogus ticket: $id\n");
+ return (undef);
+ }
+ }
+
+ #It's not a URI. It's not a numerical ticket ID. Punt!
+ else {
+ return (undef);
+ }
+
+ #If we're merged, resolve the merge.
+ if ( ( $self->EffectiveId ) and ( $self->EffectiveId != $self->Id ) ) {
+ return ( $self->Load( $self->EffectiveId ) );
+ }
+
+ #Ok. we're loaded. lets get outa here.
+ return ( $self->Id );
+
+}
+
+# }}}
+
+# {{{ sub LoadByURI
+
+=head2 LoadByURI
+
+Given a local ticket URI, loads the specified ticket.
+
+=cut
+
+sub LoadByURI {
+ my $self = shift;
+ my $uri = shift;
+
+ if ( $uri =~ /^$RT::TicketBaseURI(\d+)$/ ) {
+ my $id = $1;
+ return ( $self->Load($id) );
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub Create
+
+=head2 Create (ARGS)
+
+Arguments: ARGS is a hash of named parameters. Valid parameters are:
+
+ id
+ Queue - Either a Queue object or a Queue Name
+ Requestor - A reference to a list of RT::User objects, email addresses or RT user Names
+ Cc - A reference to a list of RT::User objects, email addresses or Names
+ AdminCc - A reference to a list of RT::User objects, email addresses or Names
+ Type -- The ticket\'s type. ignore this for now
+ Owner -- This ticket\'s owner. either an RT::User object or this user\'s id
+ Subject -- A string describing the subject of the ticket
+ InitialPriority -- an integer from 0 to 99
+ FinalPriority -- an integer from 0 to 99
+ Status -- any valid status (Defined in RT::Queue)
+ TimeEstimated -- an integer. estimated time for this task in minutes
+ TimeWorked -- an integer. time worked so far in minutes
+ TimeLeft -- an integer. time remaining in minutes
+ Starts -- an ISO date describing the ticket\'s start date and time in GMT
+ Due -- an ISO date describing the ticket\'s due date and time in GMT
+ MIMEObj -- a MIME::Entity object with the content of the initial ticket request.
+ CustomField-<n> -- a scalar or array of values for the customfield with the id <n>
+
+
+Returns: TICKETID, Transaction Object, Error Message
+
+
+=begin testing
+
+my $t = RT::Ticket->new($RT::SystemUser);
+
+ok( $t->Create(Queue => 'General', Due => '2002-05-21 00:00:00', ReferredToBy => 'http://www.cpan.org', RefersTo => 'http://fsck.com', Subject => 'This is a subject'), "Ticket Created");
+
+ok ( my $id = $t->Id, "Got ticket id");
+ok ($t->RefersTo->First->Target =~ /fsck.com/, "Got refers to");
+ok ($t->ReferredToBy->First->Base =~ /cpan.org/, "Got referredtoby");
+ok ($t->ResolvedObj->Unix == -1, "It hasn't been resolved - ". $t->ResolvedObj->Unix);
+
+=end testing
+
+=cut
+
+sub Create {
+ my $self = shift;
+
+ my %args = ( id => undef,
+ EffectiveId => undef,
+ Queue => undef,
+ Requestor => undef,
+ Cc => undef,
+ AdminCc => undef,
+ Type => 'ticket',
+ Owner => undef,
+ Subject => '',
+ InitialPriority => undef,
+ FinalPriority => undef,
+ Priority => undef,
+ Status => 'new',
+ TimeWorked => "0",
+ TimeLeft => 0,
+ TimeEstimated => 0,
+ Due => undef,
+ Starts => undef,
+ Started => undef,
+ Resolved => undef,
+ MIMEObj => undef,
+ _RecordTransaction => 1,
+
+
+
+ @_ );
+
+ my ( $ErrStr, $Owner, $resolved );
+ my (@non_fatal_errors);
+
+ my $QueueObj = RT::Queue->new($RT::SystemUser);
+
+
+ if ( ( defined( $args{'Queue'} ) ) && ( !ref( $args{'Queue'} ) ) ) {
+ $QueueObj->Load( $args{'Queue'} );
+ }
+ elsif ( ref( $args{'Queue'} ) eq 'RT::Queue' ) {
+ $QueueObj->Load( $args{'Queue'}->Id );
+ }
+ else {
+ $RT::Logger->debug( $args{'Queue'} . " not a recognised queue object.");
+ }
+;
+
+ #Can't create a ticket without a queue.
+ unless ( defined($QueueObj) && $QueueObj->Id ) {
+ $RT::Logger->debug("$self No queue given for ticket creation.");
+ return ( 0, 0, $self->loc('Could not create ticket. Queue not set') );
+ }
+
+ #Now that we have a queue, Check the ACLS
+ unless ( $self->CurrentUser->HasRight( Right => 'CreateTicket',
+ Object => $QueueObj )
+ ) {
+ return ( 0, 0,
+ $self->loc( "No permission to create tickets in the queue '[_1]'", $QueueObj->Name ) );
+ }
+
+ unless ( $QueueObj->IsValidStatus( $args{'Status'} ) ) {
+ return ( 0, 0, $self->loc('Invalid value for status') );
+ }
+
+
+ #Since we have a queue, we can set queue defaults
+ #Initial Priority
+
+ # If there's no queue default initial priority and it's not set, set it to 0
+ $args{'InitialPriority'} = ( $QueueObj->InitialPriority || 0 )
+ unless ( defined $args{'InitialPriority'} );
+
+ #Final priority
+
+ # If there's no queue default final priority and it's not set, set it to 0
+ $args{'FinalPriority'} = ( $QueueObj->FinalPriority || 0 )
+ unless ( defined $args{'FinalPriority'} );
+
+ # Priority may have changed from InitialPriority, for the case
+ # where we're importing tickets (eg, from an older RT version.)
+ my $priority = $args{'Priority'} || $args{'InitialPriority'};
+
+
+ # {{{ Dates
+ #TODO we should see what sort of due date we're getting, rather +
+ # than assuming it's in ISO format.
+
+ #Set the due date. if we didn't get fed one, use the queue default due in
+ my $Due = new RT::Date( $self->CurrentUser );
+
+ if ( $args{'Due'} ) {
+ $Due->Set( Format => 'ISO', Value => $args{'Due'} );
+ }
+ elsif ( $QueueObj->DefaultDueIn ) {
+ $Due->SetToNow;
+ $Due->AddDays( $QueueObj->DefaultDueIn );
+ }
+
+ my $Starts = new RT::Date( $self->CurrentUser );
+ if ( defined $args{'Starts'} ) {
+ $Starts->Set( Format => 'ISO', Value => $args{'Starts'} );
+ }
+
+ my $Started = new RT::Date( $self->CurrentUser );
+ if ( defined $args{'Started'} ) {
+ $Started->Set( Format => 'ISO', Value => $args{'Started'} );
+ }
+
+ my $Resolved = new RT::Date( $self->CurrentUser );
+ if ( defined $args{'Resolved'} ) {
+ $Resolved->Set( Format => 'ISO', Value => $args{'Resolved'} );
+ }
+
+
+ #If the status is an inactive status, set the resolved date
+ if ($QueueObj->IsInactiveStatus($args{'Status'}) && !$args{'Resolved'}) {
+ $RT::Logger->debug("Got a ".$args{'Status'} . "ticket with a resolved of ".$args{'Resolved'});
+ $Resolved->SetToNow;
+ }
+
+ # }}}
+
+ # {{{ Dealing with time fields
+
+ $args{'TimeEstimated'} = 0 unless defined $args{'TimeEstimated'};
+ $args{'TimeWorked'} = 0 unless defined $args{'TimeWorked'};
+ $args{'TimeLeft'} = 0 unless defined $args{'TimeLeft'};
+
+ # }}}
+
+ # {{{ Deal with setting the owner
+
+ if ( ref( $args{'Owner'} ) eq 'RT::User' ) {
+ $Owner = $args{'Owner'};
+ }
+
+ #If we've been handed something else, try to load the user.
+ elsif ( defined $args{'Owner'} ) {
+ $Owner = RT::User->new( $self->CurrentUser );
+ $Owner->Load( $args{'Owner'} );
+
+ }
+
+ #If we have a proposed owner and they don't have the right
+ #to own a ticket, scream about it and make them not the owner
+ if ( ( defined($Owner) )
+ and ( $Owner->Id )
+ and ( $Owner->Id != $RT::Nobody->Id )
+ and ( !$Owner->HasRight( Object => $QueueObj,
+ Right => 'OwnTicket' ) )
+ ) {
+
+ $RT::Logger->warning( "User "
+ . $Owner->Name . "("
+ . $Owner->id
+ . ") was proposed "
+ . "as a ticket owner but has no rights to own "
+ . "tickets in ".$QueueObj->Name );
+
+ push @non_fatal_errors, $self->loc("Invalid owner. Defaulting to 'nobody'.");
+
+ $Owner = undef;
+ }
+
+ #If we haven't been handed a valid owner, make it nobody.
+ unless ( defined($Owner) && $Owner->Id ) {
+ $Owner = new RT::User( $self->CurrentUser );
+ $Owner->Load( $RT::Nobody->Id );
+ }
+
+ # }}}
+
+ # We attempt to load or create each of the people who might have a role for this ticket
+ # _outside_ the transaction, so we don't get into ticket creation races
+ foreach my $type ( "Cc", "AdminCc", "Requestor" ) {
+ next unless (defined $args{$type});
+ foreach my $watcher ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) {
+ my $user = RT::User->new($RT::SystemUser);
+ $user->LoadOrCreateByEmail($watcher) if ($watcher && $watcher !~ /^\d+$/);
+ }
+ }
+
+
+ $RT::Handle->BeginTransaction();
+
+ my %params =( Queue => $QueueObj->Id,
+ Owner => $Owner->Id,
+ Subject => $args{'Subject'},
+ InitialPriority => $args{'InitialPriority'},
+ FinalPriority => $args{'FinalPriority'},
+ Priority => $priority,
+ Status => $args{'Status'},
+ TimeWorked => $args{'TimeWorked'},
+ TimeEstimated => $args{'TimeEstimated'},
+ TimeLeft => $args{'TimeLeft'},
+ Type => $args{'Type'},
+ Starts => $Starts->ISO,
+ Started => $Started->ISO,
+ Resolved => $Resolved->ISO,
+ Due => $Due->ISO );
+
+ # Parameters passed in during an import that we probably don't want to touch, otherwise
+ foreach my $attr qw(id Creator Created LastUpdated LastUpdatedBy) {
+ $params{$attr} = $args{$attr} if ($args{$attr});
+ }
+
+ # Delete null integer parameters
+ foreach my $attr qw(TimeWorked TimeLeft TimeEstimated InitialPriority FinalPriority) {
+ delete $params{$attr} unless (exists $params{$attr} && $params{$attr});
+ }
+
+
+ my $id = $self->SUPER::Create( %params);
+ unless ($id) {
+ $RT::Logger->crit( "Couldn't create a ticket");
+ $RT::Handle->Rollback();
+ return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error") );
+ }
+
+ #Set the ticket's effective ID now that we've created it.
+ my ( $val, $msg ) = $self->__Set( Field => 'EffectiveId', Value => ($args{'EffectiveId'} || $id ) );
+
+ unless ($val) {
+ $RT::Logger->crit("$self ->Create couldn't set EffectiveId: $msg\n");
+ $RT::Handle->Rollback();
+ return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error") );
+ }
+
+ my $create_groups_ret = $self->_CreateTicketGroups();
+ unless ($create_groups_ret) {
+ $RT::Logger->crit( "Couldn't create ticket groups for ticket "
+ . $self->Id
+ . ". aborting Ticket creation." );
+ $RT::Handle->Rollback();
+ return ( 0, 0,
+ $self->loc( "Ticket could not be created due to an internal error") );
+ }
+
+ # Set the owner in the Groups table
+ # We denormalize it into the Ticket table too because doing otherwise would
+ # kill performance, bigtime. It gets kept in lockstep thanks to the magic of transactionalization
+
+ $self->OwnerGroup->_AddMember( PrincipalId => $Owner->PrincipalId , InsideTransaction => 1);
+
+ # {{{ Deal with setting up watchers
+
+
+ foreach my $type ( "Cc", "AdminCc", "Requestor" ) {
+ next unless (defined $args{$type});
+ foreach my $watcher ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) {
+
+ # If there is an empty entry in the list, let's get out of here.
+ next unless $watcher;
+
+ # we reason that all-digits number must be a principal id, not email
+ # this is the only way to can add
+ my $field = 'Email';
+ $field = 'PrincipalId' if $watcher =~ /^\d+$/;
+
+ my ( $wval, $wmsg );
+
+ if ( $type eq 'AdminCc' ) {
+
+ # Note that we're using AddWatcher, rather than _AddWatcher, as we
+ # actually _want_ that ACL check. Otherwise, random ticket creators
+ # could make themselves adminccs and maybe get ticket rights. that would
+ # be poor
+ ( $wval, $wmsg ) = $self->AddWatcher( Type => $type,
+ $field => $watcher,
+ Silent => 1 );
+ }
+ else {
+ ( $wval, $wmsg ) = $self->_AddWatcher( Type => $type,
+ $field => $watcher,
+ Silent => 1 );
+ }
+
+ push @non_fatal_errors, $wmsg unless ($wval);
+ }
+ }
+
+ # }}}
+ # {{{ Deal with setting up links
+
+
+ foreach my $type ( keys %LINKTYPEMAP ) {
+ next unless (defined $args{$type});
+ foreach my $link (
+ ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
+ {
+ my ( $wval, $wmsg ) = $self->AddLink(
+ Type => $LINKTYPEMAP{$type}->{'Type'},
+ $LINKTYPEMAP{$type}->{'Mode'} => $link,
+ Silent => 1
+ );
+
+ push @non_fatal_errors, $wmsg unless ($wval);
+ }
+ }
+
+ # }}}
+
+ # {{{ Add all the custom fields
+
+ foreach my $arg ( keys %args ) {
+ next unless ( $arg =~ /^CustomField-(\d+)$/i );
+ my $cfid = $1;
+ foreach
+ my $value ( ref( $args{$arg} ) ? @{ $args{$arg} } : ( $args{$arg} ) ) {
+ next unless (length($value));
+ $self->_AddCustomFieldValue( Field => $cfid,
+ Value => $value,
+ RecordTransaction => 0
+ );
+ }
+ }
+ # }}}
+
+ if ( $args{'_RecordTransaction'} ) {
+ # {{{ Add a transaction for the create
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => "Create",
+ TimeTaken => 0,
+ MIMEObj => $args{'MIMEObj'}
+ );
+
+
+ if ( $self->Id && $Trans ) {
+ $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
+ $ErrStr = join ( "\n", $ErrStr, @non_fatal_errors );
+
+ $RT::Logger->info("Ticket ".$self->Id. " created in queue '".$QueueObj->Name."' by ".$self->CurrentUser->Name);
+ }
+ else {
+ $RT::Handle->Rollback();
+
+ # TODO where does this get errstr from?
+ $RT::Logger->error("Ticket couldn't be created: $ErrStr");
+ return ( 0, 0, $self->loc( "Ticket could not be created due to an internal error"));
+ }
+
+ $RT::Handle->Commit();
+ return ( $self->Id, $TransObj->Id, $ErrStr );
+ # }}}
+ }
+ else {
+
+ # Not going to record a transaction
+ $RT::Handle->Commit();
+ $ErrStr = $self->loc( "Ticket [_1] created in queue '[_2]'", $self->Id, $QueueObj->Name );
+ $ErrStr = join ( "\n", $ErrStr, @non_fatal_errors );
+ return ( $self->Id, $0, $ErrStr );
+
+ }
+}
+
+
+# }}}
+
+# {{{ sub CreateFromEmailMessage
+
+
+=head2 CreateFromEmailMessage { Message, Queue, ExtractActorFromHeaders }
+
+This code replaces what was once a large part of the email gateway.
+It takes an email message as a parameter, parses out the sender, subject
+and a MIME object. It then creates a ticket based on those attributes
+
+=cut
+
+sub CreateFromEmailMessage {
+ my $self = shift;
+ my %args = ( Message => undef,
+ Queue => undef,
+ ExtractActorFromSender => undef,
+ @_ );
+
+
+ # Pull out requestor
+
+ # Pull out Cc?
+
+ #
+
+
+}
+
+# }}}
+
+
+# {{{ CreateFrom822
+
+=head2 FORMAT
+
+CreateTickets uses the template as a template for an ordered set of tickets
+to create. The basic format is as follows:
+
+
+ ===Create-Ticket: identifier
+ Param: Value
+ Param2: Value
+ Param3: Value
+ Content: Blah
+ blah
+ blah
+ ENDOFCONTENT
+=head2 Acceptable fields
+
+A complete list of acceptable fields for this beastie:
+
+
+ * Queue => Name or id# of a queue
+ Subject => A text string
+ Status => A valid status. defaults to 'new'
+
+ Due => Dates can be specified in seconds since the epoch
+ to be handled literally or in a semi-free textual
+ format which RT will attempt to parse.
+ Starts =>
+ Started =>
+ Resolved =>
+ Owner => Username or id of an RT user who can and should own
+ this ticket
+ + Requestor => Email address
+ + Cc => Email address
+ + AdminCc => Email address
+ TimeWorked =>
+ TimeEstimated =>
+ TimeLeft =>
+ InitialPriority =>
+ FinalPriority =>
+ Type =>
+ + DependsOn =>
+ + DependedOnBy =>
+ + RefersTo =>
+ + ReferredToBy =>
+ + Members =>
+ + MemberOf =>
+ Content => content. Can extend to multiple lines. Everything
+ within a template after a Content: header is treated
+ as content until we hit a line containing only
+ ENDOFCONTENT
+ ContentType => the content-type of the Content field
+ CustomField-<id#> => custom field value
+
+Fields marked with an * are required.
+
+Fields marked with a + man have multiple values, simply
+by repeating the fieldname on a new line with an additional value.
+
+
+When parsed, field names are converted to lowercase and have -s stripped.
+Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
+be treated as the same thing.
+
+
+=begin testing
+
+use_ok(RT::Ticket);
+
+=end testing
+
+
+=cut
+
+sub CreateFrom822 {
+ my $self = shift;
+ my $content = shift;
+
+
+
+ my %args = $self->_Parse822HeadersForAttributes($content);
+
+ # Now we have a %args to work with.
+ # Make sure we have at least the minimum set of
+ # reasonable data and do our thang
+ my $ticket = RT::Ticket->new($RT::SystemUser);
+
+ my %ticketargs = (
+ Queue => $args{'queue'},
+ Subject => $args{'subject'},
+ Status => $args{'status'},
+ Due => $args{'due'},
+ Starts => $args{'starts'},
+ Started => $args{'started'},
+ Resolved => $args{'resolved'},
+ Owner => $args{'owner'},
+ Requestor => $args{'requestor'},
+ Cc => $args{'cc'},
+ AdminCc => $args{'admincc'},
+ TimeWorked => $args{'timeworked'},
+ TimeEstimated => $args{'timeestimated'},
+ TimeLeft => $args{'timeleft'},
+ InitialPriority => $args{'initialpriority'},
+ FinalPriority => $args{'finalpriority'},
+ Type => $args{'type'},
+ DependsOn => $args{'dependson'},
+ DependedOnBy => $args{'dependedonby'},
+ RefersTo => $args{'refersto'},
+ ReferredToBy => $args{'referredtoby'},
+ Members => $args{'members'},
+ MemberOf => $args{'memberof'},
+ MIMEObj => $args{'mimeobj'}
+ );
+
+ # Add custom field entries to %ticketargs.
+ # TODO: allow named custom fields
+ map {
+ /^customfield-(\d+)$/
+ && ( $ticketargs{ "CustomField-" . $1 } = $args{$_} );
+ } keys(%args);
+
+ my ( $id, $transid, $msg ) = $ticket->Create(%ticketargs);
+ unless ($id) {
+ $RT::Logger->error( "Couldn't create a related ticket for "
+ . $self->TicketObj->Id . " "
+ . $msg );
+ }
+
+ return (1);
+}
+
+# }}}
+
+# {{{ UpdateFrom822
+
+=head2 UpdateFrom822 $MESSAGE
+
+Takes an RFC822 format message as a string and uses it to make a bunch of changes to a ticket.
+Returns an um. ask me again when the code exists
+
+
+=begin testing
+
+my $simple_update = <<EOF;
+Subject: target
+AddRequestor: jesse\@example.com
+EOF
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($id,$msg) =$ticket->Create(Subject => 'first', Queue => 'general');
+ok($ticket->Id, "Created the test ticket - ".$id ." - ".$msg);
+$ticket->UpdateFrom822($simple_update);
+is($ticket->Subject, 'target', "changed the subject");
+my $jesse = RT::User->new($RT::SystemUser);
+$jesse->LoadByEmail('jesse@example.com');
+ok ($jesse->Id, "There's a user for jesse");
+ok($ticket->Requestors->HasMember( $jesse->PrincipalObj), "It has the jesse principal object as a requestor ");
+
+=end testing
+
+
+=cut
+
+sub UpdateFrom822 {
+ my $self = shift;
+ my $content = shift;
+ my %args = $self->_Parse822HeadersForAttributes($content);
+
+
+ my %ticketargs = (
+ Queue => $args{'queue'},
+ Subject => $args{'subject'},
+ Status => $args{'status'},
+ Due => $args{'due'},
+ Starts => $args{'starts'},
+ Started => $args{'started'},
+ Resolved => $args{'resolved'},
+ Owner => $args{'owner'},
+ Requestor => $args{'requestor'},
+ Cc => $args{'cc'},
+ AdminCc => $args{'admincc'},
+ TimeWorked => $args{'timeworked'},
+ TimeEstimated => $args{'timeestimated'},
+ TimeLeft => $args{'timeleft'},
+ InitialPriority => $args{'initialpriority'},
+ Priority => $args{'priority'},
+ FinalPriority => $args{'finalpriority'},
+ Type => $args{'type'},
+ DependsOn => $args{'dependson'},
+ DependedOnBy => $args{'dependedonby'},
+ RefersTo => $args{'refersto'},
+ ReferredToBy => $args{'referredtoby'},
+ Members => $args{'members'},
+ MemberOf => $args{'memberof'},
+ MIMEObj => $args{'mimeobj'}
+ );
+
+ foreach my $type qw(Requestor Cc Admincc) {
+
+ foreach my $action ( 'Add', 'Del', '' ) {
+
+ my $lctag = lc($action) . lc($type);
+ foreach my $list ( $args{$lctag}, $args{ $lctag . 's' } ) {
+
+ foreach my $entry ( ref($list) ? @{$list} : ($list) ) {
+ push @{$ticketargs{ $action . $type }} , split ( /\s*,\s*/, $entry );
+ }
+
+ }
+
+ # Todo: if we're given an explicit list, transmute it into a list of adds/deletes
+
+ }
+ }
+
+ # Add custom field entries to %ticketargs.
+ # TODO: allow named custom fields
+ map {
+ /^customfield-(\d+)$/
+ && ( $ticketargs{ "CustomField-" . $1 } = $args{$_} );
+ } keys(%args);
+
+# for each ticket we've been told to update, iterate through the set of
+# rfc822 headers and perform that update to the ticket.
+
+
+ # {{{ Set basic fields
+ my @attribs = qw(
+ Subject
+ FinalPriority
+ Priority
+ TimeEstimated
+ TimeWorked
+ TimeLeft
+ Status
+ Queue
+ Type
+ );
+
+
+ # Resolve the queue from a name to a numeric id.
+ if ( $ticketargs{'Queue'} and ( $ticketargs{'Queue'} !~ /^(\d+)$/ ) ) {
+ my $tempqueue = RT::Queue->new($RT::SystemUser);
+ $tempqueue->Load( $ticketargs{'Queue'} );
+ $ticketargs{'Queue'} = $tempqueue->Id() if ( $tempqueue->id );
+ }
+
+ # die "updaterecordobject is a webui thingy";
+ my @results;
+
+ foreach my $attribute (@attribs) {
+ my $value = $ticketargs{$attribute};
+
+ if ( $value ne $self->$attribute() ) {
+
+ my $method = "Set$attribute";
+ my ( $code, $msg ) = $self->$method($value);
+
+ push @results, $self->loc($attribute) . ': ' . $msg;
+
+ }
+ }
+
+ # We special case owner changing, so we can use ForceOwnerChange
+ if ( $ticketargs{'Owner'} && ( $self->Owner != $ticketargs{'Owner'} ) ) {
+ my $ChownType = "Give";
+ $ChownType = "Force" if ( $ticketargs{'ForceOwnerChange'} );
+
+ my ( $val, $msg ) = $self->SetOwner( $ticketargs{'Owner'}, $ChownType );
+ push ( @results, $msg );
+ }
+
+ # }}}
+# Deal with setting watchers
+
+
+# Acceptable arguments:
+# Requestor
+# Requestors
+# AddRequestor
+# AddRequestors
+# DelRequestor
+
+ foreach my $type qw(Requestor Cc AdminCc) {
+
+ # If we've been given a number of delresses to del, do it.
+ foreach my $address (@{$ticketargs{'Del'.$type}}) {
+ my ($id, $msg) = $self->DeleteWatcher( Type => $type, Email => $address);
+ push (@results, $msg) ;
+ }
+
+ # If we've been given a number of addresses to add, do it.
+ foreach my $address (@{$ticketargs{'Add'.$type}}) {
+ $RT::Logger->debug("Adding $address as a $type");
+ my ($id, $msg) = $self->AddWatcher( Type => $type, Email => $address);
+ push (@results, $msg) ;
+
+ }
+
+
+}
+
+
+}
+# }}}
+
+# {{{ _Parse822HeadersForAttributes Content
+
+=head2 _Parse822HeadersForAttributes Content
+
+Takes an RFC822 style message and parses its attributes into a hash.
+
+=cut
+
+sub _Parse822HeadersForAttributes {
+ my $self = shift;
+ my $content = shift;
+ my %args;
+
+ my @lines = ( split ( /\n/, $content ) );
+ while ( defined( my $line = shift @lines ) ) {
+ if ( $line =~ /^(.*?):(?:\s+(.*))?$/ ) {
+ my $value = $2;
+ my $tag = lc($1);
+
+ $tag =~ s/-//g;
+ if ( defined( $args{$tag} ) )
+ { #if we're about to get a second value, make it an array
+ $args{$tag} = [ $args{$tag} ];
+ }
+ if ( ref( $args{$tag} ) )
+ { #If it's an array, we want to push the value
+ push @{ $args{$tag} }, $value;
+ }
+ else { #if there's nothing there, just set the value
+ $args{$tag} = $value;
+ }
+ } elsif ($line =~ /^$/) {
+
+ #TODO: this won't work, since "" isn't of the form "foo:value"
+
+ while ( defined( my $l = shift @lines ) ) {
+ push @{ $args{'content'} }, $l;
+ }
+ }
+
+ }
+
+ foreach my $date qw(due starts started resolved) {
+ my $dateobj = RT::Date->new($RT::SystemUser);
+ if ( $args{$date} =~ /^\d+$/ ) {
+ $dateobj->Set( Format => 'unix', Value => $args{$date} );
+ }
+ else {
+ $dateobj->Set( Format => 'unknown', Value => $args{$date} );
+ }
+ $args{$date} = $dateobj->ISO;
+ }
+ $args{'mimeobj'} = MIME::Entity->new();
+ $args{'mimeobj'}->build(
+ Type => ( $args{'contenttype'} || 'text/plain' ),
+ Data => ($args{'content'} || '')
+ );
+
+ return (%args);
+}
+
+# }}}
+
+# {{{ sub Import
+
+=head2 Import PARAMHASH
+
+Import a ticket.
+Doesn\'t create a transaction.
+Doesn\'t supply queue defaults, etc.
+
+Returns: TICKETID
+
+=cut
+
+sub Import {
+ my $self = shift;
+ my ( $ErrStr, $QueueObj, $Owner );
+
+ my %args = (
+ id => undef,
+ EffectiveId => undef,
+ Queue => undef,
+ Requestor => undef,
+ Type => 'ticket',
+ Owner => $RT::Nobody->Id,
+ Subject => '[no subject]',
+ InitialPriority => undef,
+ FinalPriority => undef,
+ Status => 'new',
+ TimeWorked => "0",
+ Due => undef,
+ Created => undef,
+ Updated => undef,
+ Resolved => undef,
+ Told => undef,
+ @_
+ );
+
+ if ( ( defined( $args{'Queue'} ) ) && ( !ref( $args{'Queue'} ) ) ) {
+ $QueueObj = RT::Queue->new($RT::SystemUser);
+ $QueueObj->Load( $args{'Queue'} );
+
+ #TODO error check this and return 0 if it\'s not loading properly +++
+ }
+ elsif ( ref( $args{'Queue'} ) eq 'RT::Queue' ) {
+ $QueueObj = RT::Queue->new($RT::SystemUser);
+ $QueueObj->Load( $args{'Queue'}->Id );
+ }
+ else {
+ $RT::Logger->debug(
+ "$self " . $args{'Queue'} . " not a recognised queue object." );
+ }
+
+ #Can't create a ticket without a queue.
+ unless ( defined($QueueObj) and $QueueObj->Id ) {
+ $RT::Logger->debug("$self No queue given for ticket creation.");
+ return ( 0, $self->loc('Could not create ticket. Queue not set') );
+ }
+
+ #Now that we have a queue, Check the ACLS
+ unless (
+ $self->CurrentUser->HasRight(
+ Right => 'CreateTicket',
+ Object => $QueueObj
+ )
+ )
+ {
+ return ( 0,
+ $self->loc("No permission to create tickets in the queue '[_1]'"
+ , $QueueObj->Name));
+ }
+
+ # {{{ Deal with setting the owner
+
+ # Attempt to take user object, user name or user id.
+ # Assign to nobody if lookup fails.
+ if ( defined( $args{'Owner'} ) ) {
+ if ( ref( $args{'Owner'} ) ) {
+ $Owner = $args{'Owner'};
+ }
+ else {
+ $Owner = new RT::User( $self->CurrentUser );
+ $Owner->Load( $args{'Owner'} );
+ if ( !defined( $Owner->id ) ) {
+ $Owner->Load( $RT::Nobody->id );
+ }
+ }
+ }
+
+ #If we have a proposed owner and they don't have the right
+ #to own a ticket, scream about it and make them not the owner
+ if (
+ ( defined($Owner) )
+ and ( $Owner->Id != $RT::Nobody->Id )
+ and (
+ !$Owner->HasRight(
+ Object => $QueueObj,
+ Right => 'OwnTicket'
+ )
+ )
+ )
+ {
+
+ $RT::Logger->warning( "$self user "
+ . $Owner->Name . "("
+ . $Owner->id
+ . ") was proposed "
+ . "as a ticket owner but has no rights to own "
+ . "tickets in '"
+ . $QueueObj->Name . "'\n" );
+
+ $Owner = undef;
+ }
+
+ #If we haven't been handed a valid owner, make it nobody.
+ unless ( defined($Owner) ) {
+ $Owner = new RT::User( $self->CurrentUser );
+ $Owner->Load( $RT::Nobody->UserObj->Id );
+ }
+
+ # }}}
+
+ unless ( $self->ValidateStatus( $args{'Status'} ) ) {
+ return ( 0, $self->loc("'[_1]' is an invalid value for status", $args{'Status'}) );
+ }
+
+ $self->{'_AccessibleCache'}{Created} = { 'read' => 1, 'write' => 1 };
+ $self->{'_AccessibleCache'}{Creator} = { 'read' => 1, 'auto' => 1 };
+ $self->{'_AccessibleCache'}{LastUpdated} = { 'read' => 1, 'write' => 1 };
+ $self->{'_AccessibleCache'}{LastUpdatedBy} = { 'read' => 1, 'auto' => 1 };
+
+ # If we're coming in with an id, set that now.
+ my $EffectiveId = undef;
+ if ( $args{'id'} ) {
+ $EffectiveId = $args{'id'};
+
+ }
+
+ my $id = $self->SUPER::Create(
+ id => $args{'id'},
+ EffectiveId => $EffectiveId,
+ Queue => $QueueObj->Id,
+ Owner => $Owner->Id,
+ Subject => $args{'Subject'}, # loc
+ InitialPriority => $args{'InitialPriority'}, # loc
+ FinalPriority => $args{'FinalPriority'}, # loc
+ Priority => $args{'InitialPriority'}, # loc
+ Status => $args{'Status'}, # loc
+ TimeWorked => $args{'TimeWorked'}, # loc
+ Type => $args{'Type'}, # loc
+ Created => $args{'Created'}, # loc
+ Told => $args{'Told'}, # loc
+ LastUpdated => $args{'Updated'}, # loc
+ Resolved => $args{'Resolved'}, # loc
+ Due => $args{'Due'}, # loc
+ );
+
+ # If the ticket didn't have an id
+ # Set the ticket's effective ID now that we've created it.
+ if ( $args{'id'} ) {
+ $self->Load( $args{'id'} );
+ }
+ else {
+ my ( $val, $msg ) =
+ $self->__Set( Field => 'EffectiveId', Value => $id );
+
+ unless ($val) {
+ $RT::Logger->err(
+ $self . "->Import couldn't set EffectiveId: $msg\n" );
+ }
+ }
+
+ my $watcher;
+ foreach $watcher ( @{ $args{'Cc'} } ) {
+ $self->_AddWatcher( Type => 'Cc', Person => $watcher, Silent => 1 );
+ }
+ foreach $watcher ( @{ $args{'AdminCc'} } ) {
+ $self->_AddWatcher( Type => 'AdminCc', Person => $watcher,
+ Silent => 1 );
+ }
+ foreach $watcher ( @{ $args{'Requestor'} } ) {
+ $self->_AddWatcher( Type => 'Requestor', Person => $watcher,
+ Silent => 1 );
+ }
+
+ return ( $self->Id, $ErrStr );
+}
+
+# }}}
+
+
+# {{{ Routines dealing with watchers.
+
+# {{{ _CreateTicketGroups
+
+=head2 _CreateTicketGroups
+
+Create the ticket groups and relationships for this ticket.
+This routine expects to be called from Ticket->Create _inside of a transaction_
+
+It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
+
+It will return true on success and undef on failure.
+
+=begin testing
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($id, $msg) = $ticket->Create(Subject => "Foo",
+ Owner => $RT::SystemUser->Id,
+ Status => 'open',
+ Requestor => ['jesse@example.com'],
+ Queue => '1'
+ );
+ok ($id, "Ticket $id was created");
+ok(my $group = RT::Group->new($RT::SystemUser));
+ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Requestor'));
+ok ($group->Id, "Found the requestors object for this ticket");
+
+ok(my $jesse = RT::User->new($RT::SystemUser), "Creating a jesse rt::user");
+$jesse->LoadByEmail('jesse@example.com');
+ok($jesse->Id, "Found the jesse rt user");
+
+
+ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $jesse->PrincipalId), "The ticket actually has jesse at fsck.com as a requestor");
+ok ((my $add_id, $add_msg) = $ticket->AddWatcher(Type => 'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
+ok ($add_id, "Add succeeded: ($add_msg)");
+ok(my $bob = RT::User->new($RT::SystemUser), "Creating a bob rt::user");
+$bob->LoadByEmail('bob@fsck.com');
+ok($bob->Id, "Found the bob rt user");
+ok ($ticket->IsWatcher(Type => 'Requestor', PrincipalId => $bob->PrincipalId), "The ticket actually has bob at fsck.com as a requestor");;
+ok ((my $add_id, $add_msg) = $ticket->DeleteWatcher(Type =>'Requestor', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
+ok (!$ticket->IsWatcher(Type => 'Requestor', Principal => $bob->PrincipalId), "The ticket no longer has bob at fsck.com as a requestor");;
+
+
+$group = RT::Group->new($RT::SystemUser);
+ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Cc'));
+ok ($group->Id, "Found the cc object for this ticket");
+$group = RT::Group->new($RT::SystemUser);
+ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'AdminCc'));
+ok ($group->Id, "Found the AdminCc object for this ticket");
+$group = RT::Group->new($RT::SystemUser);
+ok($group->LoadTicketRoleGroup(Ticket => $id, Type=> 'Owner'));
+ok ($group->Id, "Found the Owner object for this ticket");
+ok($group->HasMember($RT::SystemUser->UserObj->PrincipalObj), "the owner group has the member 'RT_System'");
+
+=end testing
+
+=cut
+
+
+sub _CreateTicketGroups {
+ my $self = shift;
+
+ my @types = qw(Requestor Owner Cc AdminCc);
+
+ foreach my $type (@types) {
+ my $type_obj = RT::Group->new($self->CurrentUser);
+ my ($id, $msg) = $type_obj->CreateRoleGroup(Domain => 'RT::Ticket-Role',
+ Instance => $self->Id,
+ Type => $type);
+ unless ($id) {
+ $RT::Logger->error("Couldn't create a ticket group of type '$type' for ticket ".
+ $self->Id.": ".$msg);
+ return(undef);
+ }
+ }
+ return(1);
+
+}
+
+# }}}
+
+# {{{ sub OwnerGroup
+
+=head2 OwnerGroup
+
+A constructor which returns an RT::Group object containing the owner of this ticket.
+
+=cut
+
+sub OwnerGroup {
+ my $self = shift;
+ my $owner_obj = RT::Group->new($self->CurrentUser);
+ $owner_obj->LoadTicketRoleGroup( Ticket => $self->Id, Type => 'Owner');
+ return ($owner_obj);
+}
+
+# }}}
+
+
+# {{{ sub AddWatcher
+
+=head2 AddWatcher
+
+AddWatcher takes a parameter hash. The keys are as follows:
+
+Type One of Requestor, Cc, AdminCc
+
+PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
+
+Email The email address of the new watcher. If a user with this
+ email address can't be found, a new nonprivileged user will be created.
+
+If the watcher you\'re trying to set has an RT account, set the Owner paremeter to their User Id. Otherwise, set the Email parameter to their Email address.
+
+=cut
+
+sub AddWatcher {
+ my $self = shift;
+ my %args = (
+ Type => undef,
+ PrincipalId => undef,
+ Email => undef,
+ @_
+ );
+
+ # {{{ Check ACLS
+ #If the watcher we're trying to add is for the current user
+ if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
+ # If it's an AdminCc and they don't have
+ # 'WatchAsAdminCc' or 'ModifyTicket', bail
+ if ( $args{'Type'} eq 'AdminCc' ) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+
+ # If it's a Requestor or Cc and they don't have
+ # 'Watch' or 'ModifyTicket', bail
+ elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('Watch') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+ else {
+ $RT::Logger->warn( "$self -> AddWatcher got passed a bogus type");
+ return ( 0, $self->loc('Error in parameters to Ticket->AddWatcher') );
+ }
+ }
+
+ # If the watcher isn't the current user
+ # and the current user doesn't have 'ModifyTicket'
+ # bail
+ else {
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+
+ # }}}
+
+ return ( $self->_AddWatcher(%args) );
+}
+
+#This contains the meat of AddWatcher. but can be called from a routine like
+# Create, which doesn't need the additional acl check
+sub _AddWatcher {
+ my $self = shift;
+ my %args = (
+ Type => undef,
+ Silent => undef,
+ PrincipalId => undef,
+ Email => undef,
+ @_
+ );
+
+
+ my $principal = RT::Principal->new($self->CurrentUser);
+ if ($args{'Email'}) {
+ my $user = RT::User->new($RT::SystemUser);
+ my ($pid, $msg) = $user->LoadOrCreateByEmail($args{'Email'});
+ if ($pid) {
+ $args{'PrincipalId'} = $pid;
+ }
+ }
+ if ($args{'PrincipalId'}) {
+ $principal->Load($args{'PrincipalId'});
+ }
+
+
+ # If we can't find this watcher, we need to bail.
+ unless ($principal->Id) {
+ $RT::Logger->error("Could not load create a user with the email address '".$args{'Email'}. "' to add as a watcher for ticket ".$self->Id);
+ return(0, $self->loc("Could not find or create that user"));
+ }
+
+
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadTicketRoleGroup(Type => $args{'Type'}, Ticket => $self->Id);
+ unless ($group->id) {
+ return(0,$self->loc("Group not found"));
+ }
+
+ if ( $group->HasMember( $principal)) {
+
+ return ( 0, $self->loc('That principal is already a [_1] for this ticket', $self->loc($args{'Type'})) );
+ }
+
+
+ my ( $m_id, $m_msg ) = $group->_AddMember( PrincipalId => $principal->Id,
+ InsideTransaction => 1 );
+ unless ($m_id) {
+ $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id."\n".$m_msg);
+
+ return ( 0, $self->loc('Could not make that principal a [_1] for this ticket', $self->loc($args{'Type'})) );
+ }
+
+ unless ( $args{'Silent'} ) {
+ $self->_NewTransaction(
+ Type => 'AddWatcher',
+ NewValue => $principal->Id,
+ Field => $args{'Type'}
+ );
+ }
+
+ return ( 1, $self->loc('Added principal as a [_1] for this ticket', $self->loc($args{'Type'})) );
+}
+
+# }}}
+
+
+# {{{ sub DeleteWatcher
+
+=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
+
+
+Deletes a Ticket watcher. Takes two arguments:
+
+Type (one of Requestor,Cc,AdminCc)
+
+and one of
+
+PrincipalId (an RT::Principal Id of the watcher you want to remove)
+ OR
+Email (the email address of an existing wathcer)
+
+
+=cut
+
+
+sub DeleteWatcher {
+ my $self = shift;
+
+ my %args = ( Type => undef,
+ PrincipalId => undef,
+ Email => undef,
+ @_ );
+
+ unless ($args{'PrincipalId'} || $args{'Email'} ) {
+ return(0, $self->loc("No principal specified"));
+ }
+ my $principal = RT::Principal->new($self->CurrentUser);
+ if ($args{'PrincipalId'} ) {
+
+ $principal->Load($args{'PrincipalId'});
+ } else {
+ my $user = RT::User->new($self->CurrentUser);
+ $user->LoadByEmail($args{'Email'});
+ $principal->Load($user->Id);
+ }
+ # If we can't find this watcher, we need to bail.
+ unless ($principal->Id) {
+ return(0, $self->loc("Could not find that principal"));
+ }
+
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadTicketRoleGroup(Type => $args{'Type'}, Ticket => $self->Id);
+ unless ($group->id) {
+ return(0,$self->loc("Group not found"));
+ }
+
+ # {{{ Check ACLS
+ #If the watcher we're trying to add is for the current user
+ if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
+ # If it's an AdminCc and they don't have
+ # 'WatchAsAdminCc' or 'ModifyTicket', bail
+ if ( $args{'Type'} eq 'AdminCc' ) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+
+ # If it's a Requestor or Cc and they don't have
+ # 'Watch' or 'ModifyTicket', bail
+ elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ or $self->CurrentUserHasRight('Watch') ) {
+ return ( 0, $self->loc('Permission Denied'))
+ }
+ }
+ else {
+ $RT::Logger->warn( "$self -> DeleteWatcher got passed a bogus type");
+ return ( 0, $self->loc('Error in parameters to Ticket->DelWatcher') );
+ }
+ }
+
+ # If the watcher isn't the current user
+ # and the current user doesn't have 'ModifyTicket' bail
+ else {
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+
+ # }}}
+
+
+ # see if this user is already a watcher.
+
+ unless ( $group->HasMember($principal)) {
+ return ( 0,
+ $self->loc('That principal is not a [_1] for this ticket', $args{'Type'}) );
+ }
+
+ my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
+ unless ($m_id) {
+ $RT::Logger->error("Failed to delete ".$principal->Id.
+ " as a member of group ".$group->Id."\n".$m_msg);
+
+ return ( 0, $self->loc('Could not remove that principal as a [_1] for this ticket', $args{'Type'}) );
+ }
+
+ unless ( $args{'Silent'} ) {
+ $self->_NewTransaction(
+ Type => 'DelWatcher',
+ OldValue => $principal->Id,
+ Field => $args{'Type'}
+ );
+ }
+
+ return ( 1, $self->loc("[_1] is no longer a [_2] for this ticket.", $principal->Object->Name, $args{'Type'} ));
+}
+
+
+
+
+# }}}
+
+
+# {{{ a set of [foo]AsString subs that will return the various sorts of watchers for a ticket/queue as a comma delineated string
+
+=head2 RequestorAddresses
+
+ B<Returns> String: All Ticket Requestor email addresses as a string.
+
+=cut
+
+sub RequestorAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return undef;
+ }
+
+ return ( $self->Requestors->MemberEmailAddressesAsString );
+}
+
+
+=head2 AdminCcAddresses
+
+returns String: All Ticket AdminCc email addresses as a string
+
+=cut
+
+sub AdminCcAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return undef;
+ }
+
+ return ( $self->AdminCc->MemberEmailAddressesAsString )
+
+}
+
+=head2 CcAddresses
+
+returns String: All Ticket Ccs as a string of email addresses
+
+=cut
+
+sub CcAddresses {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return undef;
+ }
+
+ return ( $self->Cc->MemberEmailAddressesAsString);
+
+}
+
+# }}}
+
+# {{{ Routines that return RT::Watchers objects of Requestors, Ccs and AdminCcs
+
+# {{{ sub Requestors
+
+=head2 Requestors
+
+Takes nothing.
+Returns this ticket's Requestors as an RT::Group object
+
+=cut
+
+sub Requestors {
+ my $self = shift;
+
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ $group->LoadTicketRoleGroup(Type => 'Requestor', Ticket => $self->Id);
+ }
+ return ($group);
+
+}
+
+# }}}
+
+# {{{ sub Cc
+
+=head2 Cc
+
+Takes nothing.
+Returns an RT::Group object which contains this ticket's Ccs.
+If the user doesn't have "ShowTicket" permission, returns an empty group
+
+=cut
+
+sub Cc {
+ my $self = shift;
+
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ $group->LoadTicketRoleGroup(Type => 'Cc', Ticket => $self->Id);
+ }
+ return ($group);
+
+}
+
+# }}}
+
+# {{{ sub AdminCc
+
+=head2 AdminCc
+
+Takes nothing.
+Returns an RT::Group object which contains this ticket's AdminCcs.
+If the user doesn't have "ShowTicket" permission, returns an empty group
+
+=cut
+
+sub AdminCc {
+ my $self = shift;
+
+ my $group = RT::Group->new($self->CurrentUser);
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ $group->LoadTicketRoleGroup(Type => 'AdminCc', Ticket => $self->Id);
+ }
+ return ($group);
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ IsWatcher,IsRequestor,IsCc, IsAdminCc
+
+# {{{ sub IsWatcher
+# a generic routine to be called by IsRequestor, IsCc and IsAdminCc
+
+=head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL }
+
+Takes a param hash with the attributes Type and either PrincipalId or Email
+
+Type is one of Requestor, Cc, AdminCc and Owner
+
+PrincipalId is an RT::Principal id, and Email is an email address.
+
+Returns true if the specified principal (or the one corresponding to the
+specified address) is a member of the group Type for this ticket.
+
+=cut
+
+sub IsWatcher {
+ my $self = shift;
+
+ my %args = ( Type => 'Requestor',
+ PrincipalId => undef,
+ Email => undef,
+ @_
+ );
+
+ # Load the relevant group.
+ my $group = RT::Group->new($self->CurrentUser);
+ $group->LoadTicketRoleGroup(Type => $args{'Type'}, Ticket => $self->id);
+
+ # Find the relevant principal.
+ my $principal = RT::Principal->new($self->CurrentUser);
+ if (!$args{PrincipalId} && $args{Email}) {
+ # Look up the specified user.
+ my $user = RT::User->new($self->CurrentUser);
+ $user->LoadByEmail($args{Email});
+ if ($user->Id) {
+ $args{PrincipalId} = $user->PrincipalId;
+ }
+ else {
+ # A non-existent user can't be a group member.
+ return 0;
+ }
+ }
+ $principal->Load($args{'PrincipalId'});
+
+ # Ask if it has the member in question
+ return ($group->HasMember($principal));
+}
+
+# }}}
+
+# {{{ sub IsRequestor
+
+=head2 IsRequestor PRINCIPAL_ID
+
+ Takes an RT::Principal id
+ Returns true if the principal is a requestor of the current ticket.
+
+
+=cut
+
+sub IsRequestor {
+ my $self = shift;
+ my $person = shift;
+
+ return ( $self->IsWatcher( Type => 'Requestor', PrincipalId => $person ) );
+
+};
+
+# }}}
+
+# {{{ sub IsCc
+
+=head2 IsCc PRINCIPAL_ID
+
+ Takes an RT::Principal id.
+ Returns true if the principal is a requestor of the current ticket.
+
+
+=cut
+
+sub IsCc {
+ my $self = shift;
+ my $cc = shift;
+
+ return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
+
+}
+
+# }}}
+
+# {{{ sub IsAdminCc
+
+=head2 IsAdminCc PRINCIPAL_ID
+
+ Takes an RT::Principal id.
+ Returns true if the principal is a requestor of the current ticket.
+
+=cut
+
+sub IsAdminCc {
+ my $self = shift;
+ my $person = shift;
+
+ return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
+
+}
+
+# }}}
+
+# {{{ sub IsOwner
+
+=head2 IsOwner
+
+ Takes an RT::User object. Returns true if that user is this ticket's owner.
+returns undef otherwise
+
+=cut
+
+sub IsOwner {
+ my $self = shift;
+ my $person = shift;
+
+ # no ACL check since this is used in acl decisions
+ # unless ($self->CurrentUserHasRight('ShowTicket')) {
+ # return(undef);
+ # }
+
+ #Tickets won't yet have owners when they're being created.
+ unless ( $self->OwnerObj->id ) {
+ return (undef);
+ }
+
+ if ( $person->id == $self->OwnerObj->id ) {
+ return (1);
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with queues
+
+# {{{ sub ValidateQueue
+
+sub ValidateQueue {
+ my $self = shift;
+ my $Value = shift;
+
+ if ( !$Value ) {
+ $RT::Logger->warning( " RT:::Queue::ValidateQueue called with a null value. this isn't ok.");
+ return (1);
+ }
+
+ my $QueueObj = RT::Queue->new( $self->CurrentUser );
+ my $id = $QueueObj->Load($Value);
+
+ if ($id) {
+ return (1);
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub SetQueue
+
+sub SetQueue {
+ my $self = shift;
+ my $NewQueue = shift;
+
+ #Redundant. ACL gets checked in _Set;
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ my $NewQueueObj = RT::Queue->new( $self->CurrentUser );
+ $NewQueueObj->Load($NewQueue);
+
+ unless ( $NewQueueObj->Id() ) {
+ return ( 0, $self->loc("That queue does not exist") );
+ }
+
+ if ( $NewQueueObj->Id == $self->QueueObj->Id ) {
+ return ( 0, $self->loc('That is the same value') );
+ }
+ unless (
+ $self->CurrentUser->HasRight(
+ Right => 'CreateTicket',
+ Object => $NewQueueObj
+ )
+ )
+ {
+ return ( 0, $self->loc("You may not create requests in that queue.") );
+ }
+
+ unless (
+ $self->OwnerObj->HasRight(
+ Right => 'OwnTicket',
+ Object => $NewQueueObj
+ )
+ )
+ {
+ $self->Untake();
+ }
+
+ return ( $self->_Set( Field => 'Queue', Value => $NewQueueObj->Id() ) );
+
+}
+
+# }}}
+
+# {{{ sub QueueObj
+
+=head2 QueueObj
+
+Takes nothing. returns this ticket's queue object
+
+=cut
+
+sub QueueObj {
+ my $self = shift;
+
+ my $queue_obj = RT::Queue->new( $self->CurrentUser );
+
+ #We call __Value so that we can avoid the ACL decision and some deep recursion
+ my ($result) = $queue_obj->Load( $self->__Value('Queue') );
+ return ($queue_obj);
+}
+
+# }}}
+
+# }}}
+
+# {{{ Date printing routines
+
+# {{{ sub DueObj
+
+=head2 DueObj
+
+ Returns an RT::Date object containing this ticket's due date
+
+=cut
+
+sub DueObj {
+ my $self = shift;
+
+ my $time = new RT::Date( $self->CurrentUser );
+
+ # -1 is RT::Date slang for never
+ if ( $self->Due ) {
+ $time->Set( Format => 'sql', Value => $self->Due );
+ }
+ else {
+ $time->Set( Format => 'unix', Value => -1 );
+ }
+
+ return $time;
+}
+
+# }}}
+
+# {{{ sub DueAsString
+
+=head2 DueAsString
+
+Returns this ticket's due date as a human readable string
+
+=cut
+
+sub DueAsString {
+ my $self = shift;
+ return $self->DueObj->AsString();
+}
+
+# }}}
+
+# {{{ sub ResolvedObj
+
+=head2 ResolvedObj
+
+ Returns an RT::Date object of this ticket's 'resolved' time.
+
+=cut
+
+sub ResolvedObj {
+ my $self = shift;
+
+ my $time = new RT::Date( $self->CurrentUser );
+ $time->Set( Format => 'sql', Value => $self->Resolved );
+ return $time;
+}
+
+# }}}
+
+# {{{ sub SetStarted
+
+=head2 SetStarted
+
+Takes a date in ISO format or undef
+Returns a transaction id and a message
+The client calls "Start" to note that the project was started on the date in $date.
+A null date means "now"
+
+=cut
+
+sub SetStarted {
+ my $self = shift;
+ my $time = shift || 0;
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, self->loc("Permission Denied") );
+ }
+
+ #We create a date object to catch date weirdness
+ my $time_obj = new RT::Date( $self->CurrentUser() );
+ if ( $time != 0 ) {
+ $time_obj->Set( Format => 'ISO', Value => $time );
+ }
+ else {
+ $time_obj->SetToNow();
+ }
+
+ #Now that we're starting, open this ticket
+ #TODO do we really want to force this as policy? it should be a scrip
+
+ #We need $TicketAsSystem, in case the current user doesn't have
+ #ShowTicket
+ #
+ my $TicketAsSystem = new RT::Ticket($RT::SystemUser);
+ $TicketAsSystem->Load( $self->Id );
+ if ( $TicketAsSystem->Status eq 'new' ) {
+ $TicketAsSystem->Open();
+ }
+
+ return ( $self->_Set( Field => 'Started', Value => $time_obj->ISO ) );
+
+}
+
+# }}}
+
+# {{{ sub StartedObj
+
+=head2 StartedObj
+
+ Returns an RT::Date object which contains this ticket's
+'Started' time.
+
+=cut
+
+sub StartedObj {
+ my $self = shift;
+
+ my $time = new RT::Date( $self->CurrentUser );
+ $time->Set( Format => 'sql', Value => $self->Started );
+ return $time;
+}
+
+# }}}
+
+# {{{ sub StartsObj
+
+=head2 StartsObj
+
+ Returns an RT::Date object which contains this ticket's
+'Starts' time.
+
+=cut
+
+sub StartsObj {
+ my $self = shift;
+
+ my $time = new RT::Date( $self->CurrentUser );
+ $time->Set( Format => 'sql', Value => $self->Starts );
+ return $time;
+}
+
+# }}}
+
+# {{{ sub ToldObj
+
+=head2 ToldObj
+
+ Returns an RT::Date object which contains this ticket's
+'Told' time.
+
+=cut
+
+sub ToldObj {
+ my $self = shift;
+
+ my $time = new RT::Date( $self->CurrentUser );
+ $time->Set( Format => 'sql', Value => $self->Told );
+ return $time;
+}
+
+# }}}
+
+# {{{ sub ToldAsString
+
+=head2 ToldAsString
+
+A convenience method that returns ToldObj->AsString
+
+TODO: This should be deprecated
+
+=cut
+
+sub ToldAsString {
+ my $self = shift;
+ if ( $self->Told ) {
+ return $self->ToldObj->AsString();
+ }
+ else {
+ return ("Never");
+ }
+}
+
+# }}}
+
+# {{{ sub TimeWorkedAsString
+
+=head2 TimeWorkedAsString
+
+Returns the amount of time worked on this ticket as a Text String
+
+=cut
+
+sub TimeWorkedAsString {
+ my $self = shift;
+ return "0" unless $self->TimeWorked;
+
+ #This is not really a date object, but if we diff a number of seconds
+ #vs the epoch, we'll get a nice description of time worked.
+
+ my $worked = new RT::Date( $self->CurrentUser );
+
+ #return the #of minutes worked turned into seconds and written as
+ # a simple text string
+
+ return ( $worked->DurationAsString( $self->TimeWorked * 60 ) );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with correspondence/comments
+
+# {{{ sub Comment
+
+=head2 Comment
+
+Comment on this ticket.
+Takes a hashref with the following attributes:
+If MIMEObj is undefined, Content will be used to build a MIME::Entity for this
+commentl
+
+MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content.
+
+=cut
+
+## Please see file perltidy.ERR
+sub Comment {
+ my $self = shift;
+
+ my %args = ( CcMessageTo => undef,
+ BccMessageTo => undef,
+ MIMEObj => undef,
+ Content => undef,
+ TimeTaken => 0,
+ @_ );
+
+ unless ( ( $self->CurrentUserHasRight('CommentOnTicket') )
+ or ( $self->CurrentUserHasRight('ModifyTicket') ) ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ unless ( $args{'MIMEObj'} ) {
+ if ( $args{'Content'} ) {
+ use MIME::Entity;
+ $args{'MIMEObj'} = MIME::Entity->build(
+ Data => ( ref $args{'Content'} ? $args{'Content'} : [ $args{'Content'} ] )
+ );
+ }
+ else {
+
+ return ( 0, $self->loc("No correspondence attached") );
+ }
+ }
+
+ RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8
+
+ # If we've been passed in CcMessageTo and BccMessageTo fields,
+ # add them to the mime object for passing on to the transaction handler
+ # The "NotifyOtherRecipients" scripAction will look for RT--Send-Cc: and
+ # RT-Send-Bcc: headers
+
+ $args{'MIMEObj'}->head->add( 'RT-Send-Cc',
+ RT::User::CanonicalizeEmailAddress(undef, $args{'CcMessageTo'}) )
+ if defined $args{'CcMessageTo'};
+ $args{'MIMEObj'}->head->add( 'RT-Send-Bcc',
+ RT::User::CanonicalizeEmailAddress(undef, $args{'BccMessageTo'}) )
+ if defined $args{'BccMessageTo'};
+
+ #Record the correspondence (write the transaction)
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => 'Comment',
+ Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ),
+ TimeTaken => $args{'TimeTaken'},
+ MIMEObj => $args{'MIMEObj'}
+ );
+
+ return ( $Trans, $self->loc("The comment has been recorded") );
+}
+
+# }}}
+
+# {{{ sub Correspond
+
+=head2 Correspond
+
+Correspond on this ticket.
+Takes a hashref with the following attributes:
+
+
+MIMEObj, TimeTaken, CcMessageTo, BccMessageTo, Content
+
+if there's no MIMEObj, Content is used to build a MIME::Entity object
+
+
+=cut
+
+sub Correspond {
+ my $self = shift;
+ my %args = ( CcMessageTo => undef,
+ BccMessageTo => undef,
+ MIMEObj => undef,
+ Content => undef,
+ TimeTaken => 0,
+ @_ );
+
+ unless ( ( $self->CurrentUserHasRight('ReplyToTicket') )
+ or ( $self->CurrentUserHasRight('ModifyTicket') ) ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ unless ( $args{'MIMEObj'} ) {
+ if ( $args{'Content'} ) {
+ use MIME::Entity;
+ $args{'MIMEObj'} = MIME::Entity->build(
+ Data => ( ref $args{'Content'} ? $args{'Content'} : [ $args{'Content'} ] )
+ );
+
+ }
+ else {
+
+ return ( 0, $self->loc("No correspondence attached") );
+ }
+ }
+
+ RT::I18N::SetMIMEEntityToUTF8($args{'MIMEObj'}); # convert text parts into utf-8
+
+ # If we've been passed in CcMessageTo and BccMessageTo fields,
+ # add them to the mime object for passing on to the transaction handler
+ # The "NotifyOtherRecipients" scripAction will look for RT-Send-Cc: and RT-Send-Bcc:
+ # headers
+
+ $args{'MIMEObj'}->head->add( 'RT-Send-Cc',
+ RT::User::CanonicalizeEmailAddress(undef, $args{'CcMessageTo'}) )
+ if defined $args{'CcMessageTo'};
+ $args{'MIMEObj'}->head->add( 'RT-Send-Bcc',
+ RT::User::CanonicalizeEmailAddress(undef, $args{'BccMessageTo'}) )
+ if defined $args{'BccMessageTo'};
+
+ #Record the correspondence (write the transaction)
+ my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction(
+ Type => 'Correspond',
+ Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ),
+ TimeTaken => $args{'TimeTaken'},
+ MIMEObj => $args{'MIMEObj'} );
+
+ unless ($Trans) {
+ $RT::Logger->err( "$self couldn't init a transaction $msg");
+ return ( $Trans, $self->loc("correspondence (probably) not sent"), $args{'MIMEObj'} );
+ }
+
+ #Set the last told date to now if this isn't mail from the requestor.
+ #TODO: Note that this will wrongly ack mail from any non-requestor as a "told"
+
+ unless ( $TransObj->IsInbound ) {
+ $self->_SetTold;
+ }
+
+ return ( $Trans, $self->loc("correspondence sent") );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with Links and Relations between tickets
+
+# {{{ Link Collections
+
+# {{{ sub Members
+
+=head2 Members
+
+ This returns an RT::Links object which references all the tickets
+which are 'MembersOf' this ticket
+
+=cut
+
+sub Members {
+ my $self = shift;
+ return ( $self->_Links( 'Target', 'MemberOf' ) );
+}
+
+# }}}
+
+# {{{ sub MemberOf
+
+=head2 MemberOf
+
+ This returns an RT::Links object which references all the tickets that this
+ticket is a 'MemberOf'
+
+=cut
+
+sub MemberOf {
+ my $self = shift;
+ return ( $self->_Links( 'Base', 'MemberOf' ) );
+}
+
+# }}}
+
+# {{{ RefersTo
+
+=head2 RefersTo
+
+ This returns an RT::Links object which shows all references for which this ticket is a base
+
+=cut
+
+sub RefersTo {
+ my $self = shift;
+ return ( $self->_Links( 'Base', 'RefersTo' ) );
+}
+
+# }}}
+
+# {{{ ReferredToBy
+
+=head2 ReferredToBy
+
+ This returns an RT::Links object which shows all references for which this ticket is a target
+
+=cut
+
+sub ReferredToBy {
+ my $self = shift;
+ return ( $self->_Links( 'Target', 'RefersTo' ) );
+}
+
+# }}}
+
+# {{{ DependedOnBy
+
+=head2 DependedOnBy
+
+ This returns an RT::Links object which references all the tickets that depend on this one
+
+=cut
+
+sub DependedOnBy {
+ my $self = shift;
+ return ( $self->_Links( 'Target', 'DependsOn' ) );
+}
+
+# }}}
+
+
+
+=head2 HasUnresolvedDependencies
+
+ Takes a paramhash of Type (default to '__any'). Returns true if
+$self->UnresolvedDependencies returns an object with one or more members
+of that type. Returns false otherwise
+
+
+=begin testing
+
+my $t1 = RT::Ticket->new($RT::SystemUser);
+my ($id, $trans, $msg) = $t1->Create(Subject => 'DepTest1', Queue => 'general');
+ok($id, "Created dep test 1 - $msg");
+
+my $t2 = RT::Ticket->new($RT::SystemUser);
+my ($id2, $trans, $msg2) = $t2->Create(Subject => 'DepTest2', Queue => 'general');
+ok($id2, "Created dep test 2 - $msg2");
+my $t3 = RT::Ticket->new($RT::SystemUser);
+my ($id3, $trans, $msg3) = $t3->Create(Subject => 'DepTest3', Queue => 'general', Type => 'approval');
+ok($id3, "Created dep test 3 - $msg3");
+
+ok ($t1->AddLink( Type => 'DependsOn', Target => $t2->id));
+ok ($t1->AddLink( Type => 'DependsOn', Target => $t3->id));
+
+ok ($t1->HasUnresolvedDependencies, "Ticket ".$t1->Id." has unresolved deps");
+ok (!$t1->HasUnresolvedDependencies( Type => 'blah' ), "Ticket ".$t1->Id." has no unresolved blahs");
+ok ($t1->HasUnresolvedDependencies( Type => 'approval' ), "Ticket ".$t1->Id." has unresolved approvals");
+ok (!$t2->HasUnresolvedDependencies, "Ticket ".$t2->Id." has no unresolved deps");
+my ($rid, $rmsg)= $t1->Resolve();
+ok(!$rid, $rmsg);
+ok($t2->Resolve);
+($rid, $rmsg)= $t1->Resolve();
+ok(!$rid, $rmsg);
+ok($t3->Resolve);
+($rid, $rmsg)= $t1->Resolve();
+ok($rid, $rmsg);
+
+
+=end testing
+
+=cut
+
+sub HasUnresolvedDependencies {
+ my $self = shift;
+ my %args = (
+ Type => undef,
+ @_
+ );
+
+ my $deps = $self->UnresolvedDependencies;
+
+ if ($args{Type}) {
+ $deps->Limit( FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $args{Type});
+ }
+ else {
+ $deps->IgnoreType;
+ }
+
+ if ($deps->Count > 0) {
+ return 1;
+ }
+ else {
+ return (undef);
+ }
+}
+
+
+# {{{ UnresolvedDependencies
+
+=head2 UnresolvedDependencies
+
+Returns an RT::Tickets object of tickets which this ticket depends on
+and which have a status of new, open or stalled. (That list comes from
+RT::Queue->ActiveStatusArray
+
+=cut
+
+
+sub UnresolvedDependencies {
+ my $self = shift;
+ my $deps = RT::Tickets->new($self->CurrentUser);
+
+ my @live_statuses = RT::Queue->ActiveStatusArray();
+ foreach my $status (@live_statuses) {
+ $deps->LimitStatus(VALUE => $status);
+ }
+ $deps->LimitDependedOnBy($self->Id);
+
+ return($deps);
+
+}
+
+# }}}
+
+# {{{ AllDependedOnBy
+
+=head2 AllDependedOnBy
+
+Returns an array of RT::Ticket objects which (directly or indirectly)
+depends on this ticket; takes an optional 'Type' argument in the param
+hash, which will limit returned tickets to that type, as well as cause
+tickets with that type to serve as 'leaf' nodes that stops the recursive
+dependency search.
+
+=cut
+
+sub AllDependedOnBy {
+ my $self = shift;
+ my $dep = $self->DependedOnBy;
+ my %args = (
+ Type => undef,
+ _found => {},
+ _top => 1,
+ @_
+ );
+
+ while (my $link = $dep->Next()) {
+ next unless ($link->BaseURI->IsLocal());
+ next if $args{_found}{$link->BaseObj->Id};
+
+ if (!$args{Type}) {
+ $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
+ $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
+ }
+ elsif ($link->BaseObj->Type eq $args{Type}) {
+ $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
+ }
+ else {
+ $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
+ }
+ }
+
+ if ($args{_top}) {
+ return map { $args{_found}{$_} } sort keys %{$args{_found}};
+ }
+ else {
+ return 1;
+ }
+}
+
+# }}}
+
+# {{{ DependsOn
+
+=head2 DependsOn
+
+ This returns an RT::Links object which references all the tickets that this ticket depends on
+
+=cut
+
+sub DependsOn {
+ my $self = shift;
+ return ( $self->_Links( 'Base', 'DependsOn' ) );
+}
+
+# }}}
+
+
+
+
+# {{{ sub _Links
+
+sub _Links {
+ my $self = shift;
+
+ #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
+ #tobias meant by $f
+ my $field = shift;
+ my $type = shift || "";
+
+ unless ( $self->{"$field$type"} ) {
+ $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ # Maybe this ticket is a merged ticket
+ my $Tickets = new RT::Tickets( $self->CurrentUser );
+ # at least to myself
+ $self->{"$field$type"}->Limit( FIELD => $field,
+ VALUE => $self->URI,
+ ENTRYAGGREGATOR => 'OR' );
+ $Tickets->Limit( FIELD => 'EffectiveId',
+ VALUE => $self->EffectiveId );
+ while (my $Ticket = $Tickets->Next) {
+ $self->{"$field$type"}->Limit( FIELD => $field,
+ VALUE => $Ticket->URI,
+ ENTRYAGGREGATOR => 'OR' );
+ }
+ $self->{"$field$type"}->Limit( FIELD => 'Type',
+ VALUE => $type )
+ if ($type);
+ }
+ }
+ return ( $self->{"$field$type"} );
+}
+
+# }}}
+
+# }}}
+
+# {{{ sub DeleteLink
+
+=head2 DeleteLink
+
+Delete a link. takes a paramhash of Base, Target and Type.
+Either Base or Target must be null. The null value will
+be replaced with this ticket\'s id
+
+=cut
+
+sub DeleteLink {
+ my $self = shift;
+ my %args = (
+ Base => undef,
+ Target => undef,
+ Type => undef,
+ @_
+ );
+
+ #check acls
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ $RT::Logger->debug("No permission to delete links\n");
+ return ( 0, $self->loc('Permission Denied'))
+
+ }
+
+ #we want one of base and target. we don't care which
+ #but we only want _one_
+
+ my $direction;
+ my $remote_link;
+
+ if ( $args{'Base'} and $args{'Target'} ) {
+ $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target\n");
+ return ( 0, $self->loc("Can't specifiy both base and target") );
+ }
+ elsif ( $args{'Base'} ) {
+ $args{'Target'} = $self->URI();
+ $remote_link = $args{'Base'};
+ $direction = 'Target';
+ }
+ elsif ( $args{'Target'} ) {
+ $args{'Base'} = $self->URI();
+ $remote_link = $args{'Target'};
+ $direction='Base';
+ }
+ else {
+ $RT::Logger->debug("$self: Base or Target must be specified\n");
+ return ( 0, $self->loc('Either base or target must be specified') );
+ }
+
+ my $link = new RT::Link( $self->CurrentUser );
+ $RT::Logger->debug( "Trying to load link: " . $args{'Base'} . " " . $args{'Type'} . " " . $args{'Target'} . "\n" );
+
+
+ $link->LoadByParams( Base=> $args{'Base'}, Type=> $args{'Type'}, Target=> $args{'Target'} );
+ #it's a real link.
+ if ( $link->id ) {
+
+ my $linkid = $link->id;
+ $link->Delete();
+
+ my $TransString = "Ticket $args{'Base'} no longer $args{Type} ticket $args{'Target'}.";
+ my $remote_uri = RT::URI->new( $RT::SystemUser );
+ $remote_uri->FromURI( $remote_link );
+
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => 'DeleteLink',
+ Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
+ OldValue => $remote_uri->URI || $remote_link,
+ TimeTaken => 0
+ );
+
+ return ( $Trans, $self->loc("Link deleted ([_1])", $TransString));
+ }
+
+ #if it's not a link we can find
+ else {
+ $RT::Logger->debug("Couldn't find that link\n");
+ return ( 0, $self->loc("Link not found") );
+ }
+}
+
+# }}}
+
+# {{{ sub AddLink
+
+=head2 AddLink
+
+Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket.
+
+
+=cut
+
+sub AddLink {
+ my $self = shift;
+ my %args = ( Target => '',
+ Base => '',
+ Type => '',
+ Silent => undef,
+ @_ );
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ # Remote_link is the URI of the object that is not this ticket
+ my $remote_link;
+ my $direction;
+
+ if ( $args{'Base'} and $args{'Target'} ) {
+ $RT::Logger->debug(
+"$self tried to delete a link. both base and target were specified\n" );
+ return ( 0, $self->loc("Can't specifiy both base and target") );
+ }
+ elsif ( $args{'Base'} ) {
+ $args{'Target'} = $self->URI();
+ $remote_link = $args{'Base'};
+ $direction = 'Target';
+ }
+ elsif ( $args{'Target'} ) {
+ $args{'Base'} = $self->URI();
+ $remote_link = $args{'Target'};
+ $direction='Base';
+ }
+ else {
+ return ( 0, $self->loc('Either base or target must be specified') );
+ }
+
+ # If the base isn't a URI, make it a URI.
+ # If the target isn't a URI, make it a URI.
+
+ # {{{ Check if the link already exists - we don't want duplicates
+ use RT::Link;
+ my $old_link = RT::Link->new( $self->CurrentUser );
+ $old_link->LoadByParams( Base => $args{'Base'},
+ Type => $args{'Type'},
+ Target => $args{'Target'} );
+ if ( $old_link->Id ) {
+ $RT::Logger->debug("$self Somebody tried to duplicate a link");
+ return ( $old_link->id, $self->loc("Link already exists"), 0 );
+ }
+
+ # }}}
+
+ # Storing the link in the DB.
+ my $link = RT::Link->new( $self->CurrentUser );
+ my ($linkid) = $link->Create( Target => $args{Target},
+ Base => $args{Base},
+ Type => $args{Type} );
+
+ unless ($linkid) {
+ return ( 0, $self->loc("Link could not be created") );
+ }
+
+ my $TransString =
+ "Ticket $args{'Base'} $args{Type} ticket $args{'Target'}.";
+
+ # Don't write the transaction if we're doing this on create
+ if ( $args{'Silent'} ) {
+ return ( 1, $self->loc( "Link created ([_1])", $TransString ) );
+ }
+ else {
+ my $remote_uri = RT::URI->new( $RT::SystemUser );
+ $remote_uri->FromURI( $remote_link );
+
+ #Write the transaction
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => 'AddLink',
+ Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
+ NewValue => $remote_uri->URI || $remote_link,
+ TimeTaken => 0 );
+ return ( $Trans, $self->loc( "Link created ([_1])", $TransString ) );
+ }
+
+}
+
+# }}}
+
+# {{{ sub URI
+
+=head2 URI
+
+Returns this ticket's URI
+
+=cut
+
+sub URI {
+ my $self = shift;
+ my $uri = RT::URI::fsck_com_rt->new($self->CurrentUser);
+ return($uri->URIForObject($self));
+}
+
+# }}}
+
+# {{{ sub MergeInto
+
+=head2 MergeInto
+MergeInto take the id of the ticket to merge this ticket into.
+
+=cut
+
+sub MergeInto {
+ my $self = shift;
+ my $MergeInto = shift;
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ # Load up the new ticket.
+ my $NewTicket = RT::Ticket->new($RT::SystemUser);
+ $NewTicket->Load($MergeInto);
+
+ # make sure it exists.
+ unless ( defined $NewTicket->Id ) {
+ return ( 0, $self->loc("New ticket doesn't exist") );
+ }
+
+ # Make sure the current user can modify the new ticket.
+ unless ( $NewTicket->CurrentUserHasRight('ModifyTicket') ) {
+ $RT::Logger->debug("failed...");
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ $RT::Logger->debug(
+ "checking if the new ticket has the same id and effective id...");
+ unless ( $NewTicket->id == $NewTicket->EffectiveId ) {
+ $RT::Logger->err( "$self trying to merge into "
+ . $NewTicket->Id
+ . " which is itself merged.\n" );
+ return ( 0,
+ $self->loc("Can't merge into a merged ticket. You should never get this error") );
+ }
+
+ # We use EffectiveId here even though it duplicates information from
+ # the links table becasue of the massive performance hit we'd take
+ # by trying to do a seperate database query for merge info everytime
+ # loaded a ticket.
+
+ #update this ticket's effective id to the new ticket's id.
+ my ( $id_val, $id_msg ) = $self->__Set(
+ Field => 'EffectiveId',
+ Value => $NewTicket->Id()
+ );
+
+ unless ($id_val) {
+ $RT::Logger->error(
+ "Couldn't set effective ID for " . $self->Id . ": $id_msg" );
+ return ( 0, $self->loc("Merge failed. Couldn't set EffectiveId") );
+ }
+
+ my ( $status_val, $status_msg ) = $self->__Set( Field => 'Status', Value => 'resolved');
+
+ unless ($status_val) {
+ $RT::Logger->error( $self->loc("[_1] couldn't set status to resolved. RT's Database may be inconsistent.", $self) );
+ }
+
+
+ # update all the links that point to that old ticket
+ my $old_links_to = RT::Links->new($self->CurrentUser);
+ $old_links_to->Limit(FIELD => 'Target', VALUE => $self->URI);
+
+ while (my $link = $old_links_to->Next) {
+ if ($link->Base eq $NewTicket->URI) {
+ $link->Delete;
+ } else {
+ $link->SetTarget($NewTicket->URI);
+ }
+
+ }
+
+ my $old_links_from = RT::Links->new($self->CurrentUser);
+ $old_links_from->Limit(FIELD => 'Base', VALUE => $self->URI);
+
+ while (my $link = $old_links_from->Next) {
+ if ($link->Target eq $NewTicket->URI) {
+ $link->Delete;
+ } else {
+ $link->SetBase($NewTicket->URI);
+ }
+
+ }
+
+
+ #add all of this ticket's watchers to that ticket.
+ my $requestors = $self->Requestors->MembersObj;
+ while (my $watcher = $requestors->Next) {
+ $NewTicket->_AddWatcher( Type => 'Requestor',
+ Silent => 1,
+ PrincipalId => $watcher->MemberId);
+ }
+
+ my $Ccs = $self->Cc->MembersObj;
+ while (my $watcher = $Ccs->Next) {
+ $NewTicket->_AddWatcher( Type => 'Cc',
+ Silent => 1,
+ PrincipalId => $watcher->MemberId);
+ }
+
+ my $AdminCcs = $self->AdminCc->MembersObj;
+ while (my $watcher = $AdminCcs->Next) {
+ $NewTicket->_AddWatcher( Type => 'AdminCc',
+ Silent => 1,
+ PrincipalId => $watcher->MemberId);
+ }
+
+
+ #find all of the tickets that were merged into this ticket.
+ my $old_mergees = new RT::Tickets( $self->CurrentUser );
+ $old_mergees->Limit(
+ FIELD => 'EffectiveId',
+ OPERATOR => '=',
+ VALUE => $self->Id
+ );
+
+ # update their EffectiveId fields to the new ticket's id
+ while ( my $ticket = $old_mergees->Next() ) {
+ my ( $val, $msg ) = $ticket->__Set(
+ Field => 'EffectiveId',
+ Value => $NewTicket->Id()
+ );
+ }
+
+ #make a new link: this ticket is merged into that other ticket.
+ $self->AddLink( Type => 'MergedInto', Target => $NewTicket->Id());
+
+ $NewTicket->_SetLastUpdated;
+
+ return ( 1, $self->loc("Merge Successful") );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with ownership
+
+# {{{ sub OwnerObj
+
+=head2 OwnerObj
+
+Takes nothing and returns an RT::User object of
+this ticket's owner
+
+=cut
+
+sub OwnerObj {
+ my $self = shift;
+
+ #If this gets ACLed, we lose on a rights check in User.pm and
+ #get deep recursion. if we need ACLs here, we need
+ #an equiv without ACLs
+
+ my $owner = new RT::User( $self->CurrentUser );
+ $owner->Load( $self->__Value('Owner') );
+
+ #Return the owner object
+ return ($owner);
+}
+
+# }}}
+
+# {{{ sub OwnerAsString
+
+=head2 OwnerAsString
+
+Returns the owner's email address
+
+=cut
+
+sub OwnerAsString {
+ my $self = shift;
+ return ( $self->OwnerObj->EmailAddress );
+
+}
+
+# }}}
+
+# {{{ sub SetOwner
+
+=head2 SetOwner
+
+Takes two arguments:
+ the Id or Name of the owner
+and (optionally) the type of the SetOwner Transaction. It defaults
+to 'Give'. 'Steal' is also a valid option.
+
+=begin testing
+
+my $root = RT::User->new($RT::SystemUser);
+$root->Load('root');
+ok ($root->Id, "Loaded the root user");
+my $t = RT::Ticket->new($RT::SystemUser);
+$t->Load(1);
+$t->SetOwner('root');
+ok ($t->OwnerObj->Name eq 'root' , "Root owns the ticket");
+$t->Steal();
+ok ($t->OwnerObj->id eq $RT::SystemUser->id , "SystemUser owns the ticket");
+my $txns = RT::Transactions->new($RT::SystemUser);
+$txns->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$txns->Limit(FIELD => 'Ticket', VALUE => '1');
+my $steal = $txns->First;
+ok($steal->OldValue == $root->Id , "Stolen from root");
+ok($steal->NewValue == $RT::SystemUser->Id , "Stolen by the systemuser");
+
+=end testing
+
+=cut
+
+sub SetOwner {
+ my $self = shift;
+ my $NewOwner = shift;
+ my $Type = shift || "Give";
+
+ # must have ModifyTicket rights
+ # or TakeTicket/StealTicket and $NewOwner is self
+ # see if it's a take
+ if ( $self->OwnerObj->Id == $RT::Nobody->Id ) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ || $self->CurrentUserHasRight('TakeTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+
+ # see if it's a steal
+ elsif ( $self->OwnerObj->Id != $RT::Nobody->Id
+ && $self->OwnerObj->Id != $self->CurrentUser->id ) {
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket')
+ || $self->CurrentUserHasRight('StealTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+ else {
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ }
+ my $NewOwnerObj = RT::User->new( $self->CurrentUser );
+ my $OldOwnerObj = $self->OwnerObj;
+
+ $NewOwnerObj->Load($NewOwner);
+ if ( !$NewOwnerObj->Id ) {
+ return ( 0, $self->loc("That user does not exist") );
+ }
+
+ #If thie ticket has an owner and it's not the current user
+
+ if ( ( $Type ne 'Steal' )
+ and ( $Type ne 'Force' )
+ and #If we're not stealing
+ ( $self->OwnerObj->Id != $RT::Nobody->Id ) and #and the owner is set
+ ( $self->CurrentUser->Id ne $self->OwnerObj->Id() )
+ ) { #and it's not us
+ return ( 0,
+ $self->loc(
+"You can only reassign tickets that you own or that are unowned" ) );
+ }
+
+ #If we've specified a new owner and that user can't modify the ticket
+ elsif ( ( $NewOwnerObj->Id )
+ and ( !$NewOwnerObj->HasRight( Right => 'OwnTicket',
+ Object => $self ) )
+ ) {
+ return ( 0, $self->loc("That user may not own tickets in that queue") );
+ }
+
+ #If the ticket has an owner and it's the new owner, we don't need
+ #To do anything
+ elsif ( ( $self->OwnerObj )
+ and ( $NewOwnerObj->Id eq $self->OwnerObj->Id ) ) {
+ return ( 0, $self->loc("That user already owns that ticket") );
+ }
+
+ $RT::Handle->BeginTransaction();
+
+ # Delete the owner in the owner group, then add a new one
+ # TODO: is this safe? it's not how we really want the API to work
+ # for most things, but it's fast.
+ my ( $del_id, $del_msg ) = $self->OwnerGroup->MembersObj->First->Delete();
+ unless ($del_id) {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc("Could not change owner. ") . $del_msg );
+ }
+
+ my ( $add_id, $add_msg ) = $self->OwnerGroup->_AddMember(
+ PrincipalId => $NewOwnerObj->PrincipalId,
+ InsideTransaction => 1 );
+ unless ($add_id) {
+ $RT::Handle->Rollback();
+ return ( 0, $self->loc("Could not change owner. ") . $add_msg );
+ }
+
+ # We call set twice with slightly different arguments, so
+ # as to not have an SQL transaction span two RT transactions
+
+ my ( $val, $msg ) = $self->_Set(
+ Field => 'Owner',
+ RecordTransaction => 0,
+ Value => $NewOwnerObj->Id,
+ TimeTaken => 0,
+ TransactionType => $Type,
+ CheckACL => 0, # don't check acl
+ );
+
+ unless ($val) {
+ $RT::Handle->Rollback;
+ return ( 0, $self->loc("Could not change owner. ") . $msg );
+ }
+
+ $RT::Handle->Commit();
+
+ my ( $trans, $msg, undef ) = $self->_NewTransaction(
+ Type => $Type,
+ Field => 'Owner',
+ NewValue => $NewOwnerObj->Id,
+ OldValue => $OldOwnerObj->Id,
+ TimeTaken => 0 );
+
+ if ($trans) {
+ $msg = $self->loc( "Owner changed from [_1] to [_2]",
+ $OldOwnerObj->Name, $NewOwnerObj->Name );
+
+ # TODO: make sure the trans committed properly
+ }
+ return ( $trans, $msg );
+
+}
+
+# }}}
+
+# {{{ sub Take
+
+=head2 Take
+
+A convenince method to set the ticket's owner to the current user
+
+=cut
+
+sub Take {
+ my $self = shift;
+ return ( $self->SetOwner( $self->CurrentUser->Id, 'Take' ) );
+}
+
+# }}}
+
+# {{{ sub Untake
+
+=head2 Untake
+
+Convenience method to set the owner to 'nobody' if the current user is the owner.
+
+=cut
+
+sub Untake {
+ my $self = shift;
+ return ( $self->SetOwner( $RT::Nobody->UserObj->Id, 'Untake' ) );
+}
+
+# }}}
+
+# {{{ sub Steal
+
+=head2 Steal
+
+A convenience method to change the owner of the current ticket to the
+current user. Even if it's owned by another user.
+
+=cut
+
+sub Steal {
+ my $self = shift;
+
+ if ( $self->IsOwner( $self->CurrentUser ) ) {
+ return ( 0, $self->loc("You already own this ticket") );
+ }
+ else {
+ return ( $self->SetOwner( $self->CurrentUser->Id, 'Steal' ) );
+
+ }
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with status
+
+# {{{ sub ValidateStatus
+
+=head2 ValidateStatus STATUS
+
+Takes a string. Returns true if that status is a valid status for this ticket.
+Returns false otherwise.
+
+=cut
+
+sub ValidateStatus {
+ my $self = shift;
+ my $status = shift;
+
+ #Make sure the status passed in is valid
+ unless ( $self->QueueObj->IsValidStatus($status) ) {
+ return (undef);
+ }
+
+ return (1);
+
+}
+
+# }}}
+
+# {{{ sub SetStatus
+
+=head2 SetStatus STATUS
+
+Set this ticket\'s status. STATUS can be one of: new, open, stalled, resolved, rejected or deleted.
+
+Alternatively, you can pass in a list of named parameters (Status => STATUS, Force => FORCE). If FORCE is true, ignore unresolved dependencies and force a status change.
+
+=begin testing
+
+my $tt = RT::Ticket->new($RT::SystemUser);
+my ($id, $tid, $msg)= $tt->Create(Queue => 'general',
+ Subject => 'test');
+ok($id, $msg);
+ok($tt->Status eq 'new', "New ticket is created as new");
+
+($id, $msg) = $tt->SetStatus('open');
+ok($id, $msg);
+ok ($msg =~ /open/i, "Status message is correct");
+($id, $msg) = $tt->SetStatus('resolved');
+ok($id, $msg);
+ok ($msg =~ /resolved/i, "Status message is correct");
+($id, $msg) = $tt->SetStatus('resolved');
+ok(!$id,$msg);
+
+
+=end testing
+
+
+=cut
+
+sub SetStatus {
+ my $self = shift;
+ my %args;
+
+ if (@_ == 1) {
+ $args{Status} = shift;
+ }
+ else {
+ %args = (@_);
+ }
+
+ #Check ACL
+ if ( $args{Status} eq 'deleted') {
+ unless ($self->CurrentUserHasRight('DeleteTicket')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ unless ($self->CurrentUserHasRight('ModifyTicket')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+
+ if (!$args{Force} && ($args{'Status'} eq 'resolved') && $self->HasUnresolvedDependencies) {
+ return (0, $self->loc('That ticket has unresolved dependencies'));
+ }
+
+ my $now = RT::Date->new( $self->CurrentUser );
+ $now->SetToNow();
+
+ #If we're changing the status from new, record that we've started
+ if ( ( $self->Status =~ /new/ ) && ( $args{Status} ne 'new' ) ) {
+
+ #Set the Started time to "now"
+ $self->_Set( Field => 'Started',
+ Value => $now->ISO,
+ RecordTransaction => 0 );
+ }
+
+ if ( $args{Status} =~ /^(resolved|rejected|dead)$/ ) {
+
+ #When we resolve a ticket, set the 'Resolved' attribute to now.
+ $self->_Set( Field => 'Resolved',
+ Value => $now->ISO,
+ RecordTransaction => 0 );
+ }
+
+ #Actually update the status
+ my ($val, $msg)= $self->_Set( Field => 'Status',
+ Value => $args{Status},
+ TimeTaken => 0,
+ TransactionType => 'Status' );
+
+ return($val,$msg);
+}
+
+# }}}
+
+# {{{ sub Kill
+
+=head2 Kill
+
+Takes no arguments. Marks this ticket for garbage collection
+
+=cut
+
+sub Kill {
+ my $self = shift;
+ $RT::Logger->crit("'Kill' is deprecated. use 'Delete' instead.");
+ return $self->Delete;
+}
+
+sub Delete {
+ my $self = shift;
+ return ( $self->SetStatus('deleted') );
+
+ # TODO: garbage collection
+}
+
+# }}}
+
+# {{{ sub Stall
+
+=head2 Stall
+
+Sets this ticket's status to stalled
+
+=cut
+
+sub Stall {
+ my $self = shift;
+ return ( $self->SetStatus('stalled') );
+}
+
+# }}}
+
+# {{{ sub Reject
+
+=head2 Reject
+
+Sets this ticket's status to rejected
+
+=cut
+
+sub Reject {
+ my $self = shift;
+ return ( $self->SetStatus('rejected') );
+}
+
+# }}}
+
+# {{{ sub Open
+
+=head2 Open
+
+Sets this ticket\'s status to Open
+
+=cut
+
+sub Open {
+ my $self = shift;
+ return ( $self->SetStatus('open') );
+}
+
+# }}}
+
+# {{{ sub Resolve
+
+=head2 Resolve
+
+Sets this ticket\'s status to Resolved
+
+=cut
+
+sub Resolve {
+ my $self = shift;
+ return ( $self->SetStatus('resolved') );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with custom fields
+
+
+# {{{ FirstCustomFieldValue
+
+=item FirstCustomFieldValue FIELD
+
+Return the content of the first value of CustomField FIELD for this ticket
+Takes a field id or name
+
+=cut
+
+sub FirstCustomFieldValue {
+ my $self = shift;
+ my $field = shift;
+ my $values = $self->CustomFieldValues($field);
+ if ($values->First) {
+ return $values->First->Content;
+ } else {
+ return undef;
+ }
+
+}
+
+
+
+# {{{ CustomFieldValues
+
+=item CustomFieldValues FIELD
+
+Return a TicketCustomFieldValues object of all values of CustomField FIELD for this ticket.
+Takes a field id or name.
+
+
+=cut
+
+sub CustomFieldValues {
+ my $self = shift;
+ my $field = shift;
+
+ my $cf = RT::CustomField->new($self->CurrentUser);
+
+ if ($field =~ /^\d+$/) {
+ $cf->LoadById($field);
+ } else {
+ $cf->LoadByNameAndQueue(Name => $field, Queue => $self->QueueObj->Id);
+ }
+ my $cf_values = RT::TicketCustomFieldValues->new( $self->CurrentUser );
+ $cf_values->LimitToCustomField($cf->id);
+ $cf_values->LimitToTicket($self->Id());
+ $cf_values->OrderBy( FIELD => 'id' );
+
+ # @values is a CustomFieldValues object;
+ return ($cf_values);
+}
+
+# }}}
+
+# {{{ AddCustomFieldValue
+
+=item AddCustomFieldValue { Field => FIELD, Value => VALUE }
+
+VALUE should be a string.
+FIELD can be a CustomField object OR a CustomField ID.
+
+
+Adds VALUE as a value of CustomField FIELD. If this is a single-value custom field,
+deletes the old value.
+If VALUE isn't a valid value for the custom field, returns
+(0, 'Error message' ) otherwise, returns (1, 'Success Message')
+
+=cut
+
+sub AddCustomFieldValue {
+ my $self = shift;
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ $self->_AddCustomFieldValue(@_);
+}
+
+sub _AddCustomFieldValue {
+ my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ RecordTransaction => 1,
+ @_
+ );
+
+ my $cf = RT::CustomField->new( $self->CurrentUser );
+ if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
+ $cf->Load( $args{'Field'}->id );
+ }
+ else {
+ $cf->Load( $args{'Field'} );
+ }
+
+ unless ( $cf->Id ) {
+ return ( 0, $self->loc("Custom field [_1] not found", $args{'Field'}) );
+ }
+
+ # Load up a TicketCustomFieldValues object for this custom field and this ticket
+ my $values = $cf->ValuesForTicket( $self->id );
+
+ unless ( $cf->ValidateValue( $args{'Value'} ) ) {
+ return ( 0, $self->loc("Invalid value for custom field") );
+ }
+
+ # If the custom field only accepts a single value, delete the existing
+ # value and record a "changed from foo to bar" transaction
+ if ( $cf->SingleValue ) {
+
+ # We need to whack any old values here. In most cases, the custom field should
+ # only have one value to delete. In the pathalogical case, this custom field
+ # used to be a multiple and we have many values to whack....
+ my $cf_values = $values->Count;
+
+ if ( $cf_values > 1 ) {
+ my $i = 0; #We want to delete all but the last one, so we can then
+ # execute the same code to "change" the value from old to new
+ while ( my $value = $values->Next ) {
+ $i++;
+ if ( $i < $cf_values ) {
+ my $old_value = $value->Content;
+ my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $value->Content);
+ unless ($val) {
+ return (0,$msg);
+ }
+ my ( $TransactionId, $Msg, $TransactionObj ) =
+ $self->_NewTransaction(
+ Type => 'CustomField',
+ Field => $cf->Id,
+ OldValue => $old_value
+ );
+ }
+ }
+ }
+
+ my $old_value;
+ if (my $value = $cf->ValuesForTicket( $self->Id )->First) {
+ $old_value = $value->Content();
+ return (1) if $old_value eq $args{'Value'};
+ }
+
+ my ( $new_value_id, $value_msg ) = $cf->AddValueForTicket(
+ Ticket => $self->Id,
+ Content => $args{'Value'}
+ );
+
+ unless ($new_value_id) {
+ return ( 0,
+ $self->loc("Could not add new custom field value for ticket. [_1] ",
+ ,$value_msg) );
+ }
+
+ my $new_value = RT::TicketCustomFieldValue->new( $self->CurrentUser );
+ $new_value->Load($new_value_id);
+
+ # now that adding the new value was successful, delete the old one
+ if ($old_value) {
+ my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $old_value);
+ unless ($val) {
+ return (0,$msg);
+ }
+ }
+
+ if ($args{'RecordTransaction'}) {
+ my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+ Type => 'CustomField',
+ Field => $cf->Id,
+ OldValue => $old_value,
+ NewValue => $new_value->Content
+ );
+ }
+
+ if ( $old_value eq '' ) {
+ return ( 1, $self->loc("[_1] [_2] added", $cf->Name, $new_value->Content) );
+ }
+ elsif ( $new_value->Content eq '' ) {
+ return ( 1, $self->loc("[_1] [_2] deleted", $cf->Name, $old_value) );
+ }
+ else {
+ return ( 1, $self->loc("[_1] [_2] changed to [_3]", $cf->Name, $old_value, $new_value->Content ) );
+ }
+
+ }
+
+ # otherwise, just add a new value and record "new value added"
+ else {
+ my ( $new_value_id ) = $cf->AddValueForTicket(
+ Ticket => $self->Id,
+ Content => $args{'Value'}
+ );
+
+ unless ($new_value_id) {
+ return ( 0,
+ $self->loc("Could not add new custom field value for ticket. "));
+ }
+ if ( $args{'RecordTransaction'} ) {
+ my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+ Type => 'CustomField',
+ Field => $cf->Id,
+ NewValue => $args{'Value'}
+ );
+ unless ($TransactionId) {
+ return ( 0,
+ $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
+ }
+ }
+ return ( 1, $self->loc("[_1] added as a value for [_2]",$args{'Value'}, $cf->Name));
+ }
+
+}
+
+# }}}
+
+# {{{ DeleteCustomFieldValue
+
+=item DeleteCustomFieldValue { Field => FIELD, Value => VALUE }
+
+Deletes VALUE as a value of CustomField FIELD.
+
+VALUE can be a string, a CustomFieldValue or a TicketCustomFieldValue.
+
+If VALUE isn't a valid value for the custom field, returns
+(0, 'Error message' ) otherwise, returns (1, 'Success Message')
+
+=cut
+
+sub DeleteCustomFieldValue {
+ my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ @_);
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ my $cf = RT::CustomField->new( $self->CurrentUser );
+ if ( UNIVERSAL::isa( $args{'Field'}, "RT::CustomField" ) ) {
+ $cf->LoadById( $args{'Field'}->id );
+ }
+ else {
+ $cf->LoadById( $args{'Field'} );
+ }
+
+ unless ( $cf->Id ) {
+ return ( 0, $self->loc("Custom field not found") );
+ }
+
+
+ my ($val, $msg) = $cf->DeleteValueForTicket(Ticket => $self->Id, Content => $args{'Value'});
+ unless ($val) {
+ return (0,$msg);
+ }
+ my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
+ Type => 'CustomField',
+ Field => $cf->Id,
+ OldValue => $args{'Value'}
+ );
+ unless($TransactionId) {
+ return(0, $self->loc("Couldn't create a transaction: [_1]", $Msg));
+ }
+
+ return($TransactionId, $self->loc("[_1] is no longer a value for custom field [_2]", $args{'Value'}, $cf->Name));
+}
+
+# }}}
+
+# }}}
+
+# {{{ Actions + Routines dealing with transactions
+
+# {{{ sub SetTold and _SetTold
+
+=head2 SetTold ISO [TIMETAKEN]
+
+Updates the told and records a transaction
+
+=cut
+
+sub SetTold {
+ my $self = shift;
+ my $told;
+ $told = shift if (@_);
+ my $timetaken = shift || 0;
+
+ unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ my $datetold = new RT::Date( $self->CurrentUser );
+ if ($told) {
+ $datetold->Set( Format => 'iso',
+ Value => $told );
+ }
+ else {
+ $datetold->SetToNow();
+ }
+
+ return ( $self->_Set( Field => 'Told',
+ Value => $datetold->ISO,
+ TimeTaken => $timetaken,
+ TransactionType => 'Told' ) );
+}
+
+=head2 _SetTold
+
+Updates the told without a transaction or acl check. Useful when we're sending replies.
+
+=cut
+
+sub _SetTold {
+ my $self = shift;
+
+ my $now = new RT::Date( $self->CurrentUser );
+ $now->SetToNow();
+
+ #use __Set to get no ACLs ;)
+ return ( $self->__Set( Field => 'Told',
+ Value => $now->ISO ) );
+}
+
+# }}}
+
+# {{{ sub Transactions
+
+=head2 Transactions
+
+ Returns an RT::Transactions object of all transactions on this ticket
+
+=cut
+
+sub Transactions {
+ my $self = shift;
+
+ use RT::Transactions;
+ my $transactions = RT::Transactions->new( $self->CurrentUser );
+
+ #If the user has no rights, return an empty object
+ if ( $self->CurrentUserHasRight('ShowTicket') ) {
+ my $tickets = $transactions->NewAlias('Tickets');
+ $transactions->Join(
+ ALIAS1 => 'main',
+ FIELD1 => 'Ticket',
+ ALIAS2 => $tickets,
+ FIELD2 => 'id'
+ );
+ $transactions->Limit(
+ ALIAS => $tickets,
+ FIELD => 'EffectiveId',
+ VALUE => $self->id()
+ );
+
+ # if the user may not see comments do not return them
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ $transactions->Limit(
+ FIELD => 'Type',
+ OPERATOR => '!=',
+ VALUE => "Comment"
+ );
+ }
+ }
+
+ return ($transactions);
+}
+
+# }}}
+
+# {{{ sub _NewTransaction
+
+sub _NewTransaction {
+ my $self = shift;
+ my %args = (
+ TimeTaken => 0,
+ Type => undef,
+ OldValue => undef,
+ NewValue => undef,
+ Data => undef,
+ Field => undef,
+ MIMEObj => undef,
+ @_
+ );
+
+ require RT::Transaction;
+ my $trans = new RT::Transaction( $self->CurrentUser );
+ my ( $transaction, $msg ) = $trans->Create(
+ Ticket => $self->Id,
+ TimeTaken => $args{'TimeTaken'},
+ Type => $args{'Type'},
+ Data => $args{'Data'},
+ Field => $args{'Field'},
+ NewValue => $args{'NewValue'},
+ OldValue => $args{'OldValue'},
+ MIMEObj => $args{'MIMEObj'}
+ );
+
+
+ $self->Load($self->Id);
+
+ $RT::Logger->warning($msg) unless $transaction;
+
+ $self->_SetLastUpdated;
+
+ if ( defined $args{'TimeTaken'} ) {
+ $self->_UpdateTimeTaken( $args{'TimeTaken'} );
+ }
+ if ( $RT::UseTransactionBatch and $transaction ) {
+ push @{$self->{_TransactionBatch}}, $trans;
+ }
+ return ( $transaction, $msg, $trans );
+}
+
+# }}}
+
+=head2 TransactionBatch
+
+ Returns an array reference of all transactions created on this ticket during
+ this ticket object's lifetime, or undef if there were none.
+
+ Only works when the $RT::UseTransactionBatch config variable is set to true.
+
+=cut
+
+sub TransactionBatch {
+ my $self = shift;
+ return $self->{_TransactionBatch};
+}
+
+sub DESTROY {
+ my $self = shift;
+
+ # The following line eliminates reentrancy.
+ # It protects against the fact that perl doesn't deal gracefully
+ # when an object's refcount is changed in its destructor.
+ return if $self->{_Destroyed}++;
+
+ my $batch = $self->TransactionBatch or return;
+ require RT::Scrips;
+ RT::Scrips->new($RT::SystemUser)->Apply(
+ Stage => 'TransactionBatch',
+ TicketObj => $self,
+ TransactionObj => $batch->[0],
+ );
+}
+
+# }}}
+
+# {{{ PRIVATE UTILITY METHODS. Mostly needed so Ticket can be a DBIx::Record
+
+# {{{ sub _ClassAccessible
+
+sub _ClassAccessible {
+ {
+ EffectiveId => { 'read' => 1, 'write' => 1, 'public' => 1 },
+ Queue => { 'read' => 1, 'write' => 1 },
+ Requestors => { 'read' => 1, 'write' => 1 },
+ Owner => { 'read' => 1, 'write' => 1 },
+ Subject => { 'read' => 1, 'write' => 1 },
+ InitialPriority => { 'read' => 1, 'write' => 1 },
+ FinalPriority => { 'read' => 1, 'write' => 1 },
+ Priority => { 'read' => 1, 'write' => 1 },
+ Status => { 'read' => 1, 'write' => 1 },
+ TimeEstimated => { 'read' => 1, 'write' => 1 },
+ TimeWorked => { 'read' => 1, 'write' => 1 },
+ TimeLeft => { 'read' => 1, 'write' => 1 },
+ Created => { 'read' => 1, 'auto' => 1 },
+ Creator => { 'read' => 1, 'auto' => 1 },
+ Told => { 'read' => 1, 'write' => 1 },
+ Resolved => { 'read' => 1 },
+ Type => { 'read' => 1 },
+ Starts => { 'read' => 1, 'write' => 1 },
+ Started => { 'read' => 1, 'write' => 1 },
+ Due => { 'read' => 1, 'write' => 1 },
+ Creator => { 'read' => 1, 'auto' => 1 },
+ Created => { 'read' => 1, 'auto' => 1 },
+ LastUpdatedBy => { 'read' => 1, 'auto' => 1 },
+ LastUpdated => { 'read' => 1, 'auto' => 1 }
+ };
+
+}
+
+# }}}
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+
+ my %args = ( Field => undef,
+ Value => undef,
+ TimeTaken => 0,
+ RecordTransaction => 1,
+ UpdateTicket => 1,
+ CheckACL => 1,
+ TransactionType => 'Set',
+ @_ );
+
+ if ($args{'CheckACL'}) {
+ unless ( $self->CurrentUserHasRight('ModifyTicket')) {
+ return ( 0, $self->loc("Permission Denied"));
+ }
+ }
+
+ unless ($args{'UpdateTicket'} || $args{'RecordTransaction'}) {
+ $RT::Logger->error("Ticket->_Set called without a mandate to record an update or update the ticket");
+ return(0, $self->loc("Internal Error"));
+ }
+
+ #if the user is trying to modify the record
+
+ #Take care of the old value we really don't want to get in an ACL loop.
+ # so ask the super::_Value
+ my $Old = $self->SUPER::_Value("$args{'Field'}");
+
+ my ($ret, $msg);
+ if ( $args{'UpdateTicket'} ) {
+
+ #Set the new value
+ ( $ret, $msg ) = $self->SUPER::_Set( Field => $args{'Field'},
+ Value => $args{'Value'} );
+
+ #If we can't actually set the field to the value, don't record
+ # a transaction. instead, get out of here.
+ if ( $ret == 0 ) { return ( 0, $msg ); }
+ }
+
+ if ( $args{'RecordTransaction'} == 1 ) {
+
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => $args{'TransactionType'},
+ Field => $args{'Field'},
+ NewValue => $args{'Value'},
+ OldValue => $Old,
+ TimeTaken => $args{'TimeTaken'},
+ );
+ return ( $Trans, scalar $TransObj->Description );
+ }
+ else {
+ return ( $ret, $msg );
+ }
+}
+
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+ #if the field is public, return it.
+ if ( $self->_Accessible( $field, 'public' ) ) {
+
+ #$RT::Logger->debug("Skipping ACL check for $field\n");
+ return ( $self->SUPER::_Value($field) );
+
+ }
+
+ #If the current user doesn't have ACLs, don't let em at it.
+
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return (undef);
+ }
+ return ( $self->SUPER::_Value($field) );
+
+}
+
+# }}}
+
+# {{{ sub _UpdateTimeTaken
+
+=head2 _UpdateTimeTaken
+
+This routine will increment the timeworked counter. it should
+only be called from _NewTransaction
+
+=cut
+
+sub _UpdateTimeTaken {
+ my $self = shift;
+ my $Minutes = shift;
+ my ($Total);
+
+ $Total = $self->SUPER::_Value("TimeWorked");
+ $Total = ( $Total || 0 ) + ( $Minutes || 0 );
+ $self->SUPER::_Set(
+ Field => "TimeWorked",
+ Value => $Total
+ );
+
+ return ($Total);
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with ACCESS CONTROL
+
+# {{{ sub CurrentUserHasRight
+
+=head2 CurrentUserHasRight
+
+ Takes the textual name of a Ticket scoped right (from RT::ACE) and returns
+1 if the user has that right. It returns 0 if the user doesn't have that right.
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+
+ return (
+ $self->HasRight(
+ Principal => $self->CurrentUser->UserObj(),
+ Right => "$right"
+ )
+ );
+
+}
+
+# }}}
+
+# {{{ sub HasRight
+
+=head2 HasRight
+
+ Takes a paramhash with the attributes 'Right' and 'Principal'
+ 'Right' is a ticket-scoped textual right from RT::ACE
+ 'Principal' is an RT::User object
+
+ Returns 1 if the principal has the right. Returns undef if not.
+
+=cut
+
+sub HasRight {
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ Principal => undef,
+ @_
+ );
+
+ unless ( ( defined $args{'Principal'} ) and ( ref( $args{'Principal'} ) ) )
+ {
+ $RT::Logger->warning("Principal attrib undefined for Ticket::HasRight");
+ }
+
+ return (
+ $args{'Principal'}->HasRight(
+ Object => $self,
+ Right => $args{'Right'}
+ )
+ );
+}
+
+# }}}
+
+# }}}
+
+1;
+
+=head1 AUTHOR
+
+Jesse Vincent, jesse@bestpractical.com
+
+=head1 SEE ALSO
+
+RT
+
+=cut
+
diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm
new file mode 100755
index 0000000..b6b3491
--- /dev/null
+++ b/rt/lib/RT/Tickets.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Tickets -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Tickets
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Tickets;
+
+use RT::SearchBuilder;
+use RT::Ticket;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Tickets';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Ticket item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Ticket->new($self->CurrentUser));
+}
+
+ eval "require RT::Tickets_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Tickets_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Tickets_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Tickets_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Tickets_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Tickets_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Tickets_Overlay, RT::Tickets_Vendor, RT::Tickets_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Tickets_Overlay.pm b/rt/lib/RT/Tickets_Overlay.pm
new file mode 100644
index 0000000..55777b0
--- /dev/null
+++ b/rt/lib/RT/Tickets_Overlay.pm
@@ -0,0 +1,2140 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Major Changes:
+
+# - Decimated ProcessRestrictions and broke it into multiple
+# functions joined by a LUT
+# - Semi-Generic SQL stuff moved to another file
+
+# Known Issues: FIXME!
+
+# - ClearRestrictions and Reinitialization is messy and unclear. The
+# only good way to do it is to create a new RT::Tickets object.
+
+=head1 NAME
+
+ RT::Tickets - A collection of Ticket objects
+
+
+=head1 SYNOPSIS
+
+ use RT::Tickets;
+ my $tickets = new RT::Tickets($CurrentUser);
+
+=head1 DESCRIPTION
+
+ A collection of RT::Tickets.
+
+=head1 METHODS
+
+=begin testing
+
+ok (require RT::Tickets);
+
+=end testing
+
+=cut
+use strict;
+no warnings qw(redefine);
+use vars qw(@SORTFIELDS);
+
+
+# Configuration Tables:
+
+# FIELDS is a mapping of searchable Field name, to Type, and other
+# metadata.
+
+my %FIELDS =
+ ( Status => ['ENUM'],
+ Queue => ['ENUM' => 'Queue',],
+ Type => ['ENUM',],
+ Creator => ['ENUM' => 'User',],
+ LastUpdatedBy => ['ENUM' => 'User',],
+ Owner => ['ENUM' => 'User',],
+ EffectiveId => ['INT',],
+ id => ['INT',],
+ InitialPriority => ['INT',],
+ FinalPriority => ['INT',],
+ Priority => ['INT',],
+ TimeLeft => ['INT',],
+ TimeWorked => ['INT',],
+ MemberOf => ['LINK' => To => 'MemberOf', ],
+ DependsOn => ['LINK' => To => 'DependsOn',],
+ RefersTo => ['LINK' => To => 'RefersTo',],
+ HasMember => ['LINK' => From => 'MemberOf',],
+ DependentOn => ['LINK' => From => 'DependsOn',],
+ ReferredToBy => ['LINK' => From => 'RefersTo',],
+# HasDepender => ['LINK',],
+# RelatedTo => ['LINK',],
+ Told => ['DATE' => 'Told',],
+ Starts => ['DATE' => 'Starts',],
+ Started => ['DATE' => 'Started',],
+ Due => ['DATE' => 'Due',],
+ Resolved => ['DATE' => 'Resolved',],
+ LastUpdated => ['DATE' => 'LastUpdated',],
+ Created => ['DATE' => 'Created',],
+ Subject => ['STRING',],
+ Type => ['STRING',],
+ Content => ['TRANSFIELD',],
+ ContentType => ['TRANSFIELD',],
+ Filename => ['TRANSFIELD',],
+ TransactionDate => ['TRANSDATE',],
+ Requestor => ['WATCHERFIELD' => 'Requestor',],
+ CC => ['WATCHERFIELD' => 'Cc',],
+ AdminCC => ['WATCHERFIELD' => 'AdminCC',],
+ Watcher => ['WATCHERFIELD'],
+ LinkedTo => ['LINKFIELD',],
+ CustomFieldValue =>['CUSTOMFIELD',],
+ CF => ['CUSTOMFIELD',],
+ );
+
+# Mapping of Field Type to Function
+my %dispatch =
+ ( ENUM => \&_EnumLimit,
+ INT => \&_IntLimit,
+ LINK => \&_LinkLimit,
+ DATE => \&_DateLimit,
+ STRING => \&_StringLimit,
+ TRANSFIELD => \&_TransLimit,
+ TRANSDATE => \&_TransDateLimit,
+ WATCHERFIELD => \&_WatcherLimit,
+ LINKFIELD => \&_LinkFieldLimit,
+ CUSTOMFIELD => \&_CustomFieldLimit,
+ );
+my %can_bundle =
+ ( WATCHERFIELD => "yeps",
+ );
+
+# Default EntryAggregator per type
+# if you specify OP, you must specify all valid OPs
+my %DefaultEA = (
+ INT => 'AND',
+ ENUM => { '=' => 'OR',
+ '!='=> 'AND'
+ },
+ DATE => { '=' => 'OR',
+ '>='=> 'AND',
+ '<='=> 'AND',
+ '>' => 'AND',
+ '<' => 'AND'
+ },
+ STRING => { '=' => 'OR',
+ '!='=> 'AND',
+ 'LIKE'=> 'AND',
+ 'NOT LIKE' => 'AND'
+ },
+ TRANSFIELD => 'AND',
+ TRANSDATE => 'AND',
+ LINK => 'OR',
+ LINKFIELD => 'AND',
+ TARGET => 'AND',
+ BASE => 'AND',
+ WATCHERFIELD => { '=' => 'OR',
+ '!='=> 'AND',
+ 'LIKE'=> 'OR',
+ 'NOT LIKE' => 'AND'
+ },
+
+ CUSTOMFIELD => 'OR',
+ );
+
+
+# Helper functions for passing the above lexically scoped tables above
+# into Tickets_Overlay_SQL.
+sub FIELDS { return \%FIELDS }
+sub dispatch { return \%dispatch }
+sub can_bundle { return \%can_bundle }
+
+# Bring in the clowns.
+require RT::Tickets_Overlay_SQL;
+
+# {{{ sub SortFields
+
+@SORTFIELDS = qw(id Status
+ Queue Subject
+ Owner Created Due Starts Started
+ Told
+ Resolved LastUpdated Priority TimeWorked TimeLeft);
+
+=head2 SortFields
+
+Returns the list of fields that lists of tickets can easily be sorted by
+
+=cut
+
+sub SortFields {
+ my $self = shift;
+ return(@SORTFIELDS);
+}
+
+
+# }}}
+
+
+# BEGIN SQL STUFF *********************************
+
+=head1 Limit Helper Routines
+
+These routines are the targets of a dispatch table depending on the
+type of field. They all share the same signature:
+
+ my ($self,$field,$op,$value,@rest) = @_;
+
+The values in @rest should be suitable for passing directly to
+DBIx::SearchBuilder::Limit.
+
+Essentially they are an expanded/broken out (and much simplified)
+version of what ProcessRestrictions used to do. They're also much
+more clearly delineated by the TYPE of field being processed.
+
+=head2 _EnumLimit
+
+Handle Fields which are limited to certain values, and potentially
+need to be looked up from another class.
+
+This subroutine actually handles two different kinds of fields. For
+some the user is responsible for limiting the values. (i.e. Status,
+Type).
+
+For others, the value specified by the user will be looked by via
+specified class.
+
+Meta Data:
+ name of class to lookup in (Optional)
+
+=cut
+
+sub _EnumLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ # SQL::Statement changes != to <>. (Can we remove this now?)
+ $op = "!=" if $op eq "<>";
+
+ die "Invalid Operation: $op for $field"
+ unless $op eq "=" or $op eq "!=";
+
+ my $meta = $FIELDS{$field};
+ if (defined $meta->[1]) {
+ my $class = "RT::" . $meta->[1];
+ my $o = $class->new($sb->CurrentUser);
+ $o->Load( $value );
+ $value = $o->Id;
+ }
+ $sb->_SQLLimit( FIELD => $field,,
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
+}
+
+=head2 _IntLimit
+
+Handle fields where the values are limited to integers. (For example,
+Priority, TimeWorked.)
+
+Meta Data:
+ None
+
+=cut
+
+sub _IntLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ die "Invalid Operator $op for $field"
+ unless $op =~ /^(=|!=|>|<|>=|<=)$/;
+
+ $sb->_SQLLimit(
+ FIELD => $field,
+ VALUE => $value,
+ OPERATOR => $op,
+ @rest,
+ );
+}
+
+
+=head2 _LinkLimit
+
+Handle fields which deal with links between tickets. (MemberOf, DependsOn)
+
+Meta Data:
+ 1: Direction (From,To)
+ 2: Relationship Type (MemberOf, DependsOn,RefersTo)
+
+=cut
+
+sub _LinkLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ die "Op must be ="
+ unless $op eq "=";
+
+ my $meta = $FIELDS{$field};
+ die "Incorrect Meta Data for $field"
+ unless (defined $meta->[1] and defined $meta->[2]);
+
+ my $LinkAlias = $sb->NewAlias ('Links');
+
+ $sb->_OpenParen();
+
+ $sb->_SQLLimit(
+ ALIAS => $LinkAlias,
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $meta->[2],
+ @rest,
+ );
+
+ if ($meta->[1] eq "To") {
+ my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalTarget" : "Target" );
+
+ $sb->_SQLLimit(
+ ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $value ,
+ );
+
+ #If we're searching on target, join the base to ticket.id
+ $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'},
+ ALIAS2 => $LinkAlias, FIELD2 => 'LocalBase');
+
+ } elsif ( $meta->[1] eq "From" ) {
+ my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalBase" : "Base" );
+
+ $sb->_SQLLimit(
+ ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $value ,
+ );
+
+ #If we're searching on base, join the target to ticket.id
+ $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'},
+ ALIAS2 => $LinkAlias, FIELD2 => 'LocalTarget');
+
+ } else {
+ die "Invalid link direction '$meta->[1]' for $field\n";
+ }
+
+ $sb->_CloseParen();
+
+}
+
+=head2 _DateLimit
+
+Handle date fields. (Created, LastTold..)
+
+Meta Data:
+ 1: type of relationship. (Probably not necessary.)
+
+=cut
+
+sub _DateLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ die "Invalid Date Op: $op"
+ unless $op =~ /^(=|>|<|>=|<=)$/;
+
+ my $meta = $FIELDS{$field};
+ die "Incorrect Meta Data for $field"
+ unless (defined $meta->[1]);
+
+ require Time::ParseDate;
+ use POSIX 'strftime';
+
+ # FIXME: Replace me with RT::Date( Type => 'unknown' ...)
+ my $time = Time::ParseDate::parsedate( $value,
+ UK => $RT::DateDayBeforeMonth,
+ PREFER_PAST => $RT::AmbiguousDayInPast,
+ PREFER_FUTURE => !($RT::AmbiguousDayInPast),
+ FUZZY => 1
+ );
+
+ if ($op eq "=") {
+ # if we're specifying =, that means we want everything on a
+ # particular single day. in the database, we need to check for >
+ # and < the edges of that day.
+
+ my $daystart = strftime("%Y-%m-%d %H:%M",
+ gmtime($time - ( $time % 86400 )));
+ my $dayend = strftime("%Y-%m-%d %H:%M",
+ gmtime($time + ( 86399 - $time % 86400 )));
+
+ $sb-> _OpenParen;
+
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => ">=",
+ VALUE => $daystart,
+ @rest,
+ );
+
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => "<=",
+ VALUE => $dayend,
+ @rest,
+ ENTRYAGGREGATOR => 'AND',
+ );
+
+ $sb-> _CloseParen;
+
+ } else {
+ $value = strftime("%Y-%m-%d %H:%M", gmtime($time));
+ $sb->_SQLLimit(
+ FIELD => $meta->[1],
+ OPERATOR => $op,
+ VALUE => $value,
+ @rest,
+ );
+ }
+}
+
+=head2 _StringLimit
+
+Handle simple fields which are just strings. (Subject,Type)
+
+Meta Data:
+ None
+
+=cut
+
+sub _StringLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ # FIXME:
+ # Valid Operators:
+ # =, !=, LIKE, NOT LIKE
+
+ $sb->_SQLLimit(
+ FIELD => $field,
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest,
+ );
+}
+
+=head2 _TransDateLimit
+
+Handle fields limiting based on Transaction Date.
+
+The inpupt value must be in a format parseable by Time::ParseDate
+
+Meta Data:
+ None
+
+=cut
+
+sub _TransDateLimit {
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ # See the comments for TransLimit, they apply here too
+
+ $sb->{_sql_transalias} = $sb->NewAlias ('Transactions')
+ unless defined $sb->{_sql_transalias};
+ $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments')
+ unless defined $sb->{_sql_trattachalias};
+
+ $sb->_OpenParen;
+
+ # Join Transactions To Attachments
+ $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
+ ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
+
+ # Join Transactions to Tickets
+ $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
+ ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
+
+ my $d = new RT::Date( $sb->CurrentUser );
+ $d->Set( Format => 'ISO', Value => $value);
+ $value = $d->ISO;
+
+ #Search for the right field
+ $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias},
+ FIELD => 'Created',
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest
+ );
+
+ $sb->_CloseParen;
+}
+
+=head2 _TransLimit
+
+Limit based on the Content of a transaction or the ContentType.
+
+Meta Data:
+ none
+
+=cut
+
+sub _TransLimit {
+ # Content, ContentType, Filename
+
+ # If only this was this simple. We've got to do something
+ # complicated here:
+
+ #Basically, we want to make sure that the limits apply to
+ #the same attachment, rather than just another attachment
+ #for the same ticket, no matter how many clauses we lump
+ #on. We put them in TicketAliases so that they get nuked
+ #when we redo the join.
+
+ # In the SQL, we might have
+ # (( Content = foo ) or ( Content = bar AND Content = baz ))
+ # The AND group should share the same Alias.
+
+ # Actually, maybe it doesn't matter. We use the same alias and it
+ # works itself out? (er.. different.)
+
+ # Steal more from _ProcessRestrictions
+
+ # FIXME: Maybe look at the previous FooLimit call, and if it was a
+ # TransLimit and EntryAggregator == AND, reuse the Aliases?
+
+ # Or better - store the aliases on a per subclause basis - since
+ # those are going to be the things we want to relate to each other,
+ # anyway.
+
+ # maybe we should not allow certain kinds of aggregation of these
+ # clauses and do a psuedo regex instead? - the problem is getting
+ # them all into the same subclause when you have (A op B op C) - the
+ # way they get parsed in the tree they're in different subclauses.
+
+ my ($sb,$field,$op,$value,@rest) = @_;
+
+ $sb->{_sql_transalias} = $sb->NewAlias ('Transactions')
+ unless defined $sb->{_sql_transalias};
+ $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments')
+ unless defined $sb->{_sql_trattachalias};
+
+ $sb->_OpenParen;
+
+ # Join Transactions To Attachments
+ $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId',
+ ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id');
+
+ # Join Transactions to Tickets
+ $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH!
+ ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket');
+
+ #Search for the right field
+ $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias},
+ FIELD => $field,
+ OPERATOR => $op,
+ VALUE => $value,
+ CASESENSITIVE => 0,
+ @rest
+ );
+
+ $sb->_CloseParen;
+
+}
+
+=head2 _WatcherLimit
+
+Handle watcher limits. (Requestor, CC, etc..)
+
+Meta Data:
+ 1: Field to query on
+
+=cut
+
+sub _WatcherLimit {
+ my ($self,$field,$op,$value,@rest) = @_;
+ my %rest = @rest;
+
+ $self->_OpenParen;
+
+ my $groups = $self->NewAlias('Groups');
+ my $groupmembers = $self->NewAlias('CachedGroupMembers');
+ my $users = $self->NewAlias('Users');
+
+
+ #Find user watchers
+# my $subclause = undef;
+# my $aggregator = 'OR';
+# if ($restriction->{'OPERATOR'} =~ /!|NOT/i ){
+# $subclause = 'AndEmailIsNot';
+# $aggregator = 'AND';
+# }
+
+ if (ref $field) { # gross hack
+ my @bundle = @$field;
+ $self->_OpenParen;
+ for my $chunk (@bundle) {
+ ($field,$op,$value,@rest) = @$chunk;
+ $self->_SQLLimit(ALIAS => $users,
+ FIELD => $rest{SUBKEY} || 'EmailAddress',
+ VALUE => $value,
+ OPERATOR => $op,
+ CASESENSITIVE => 0,
+ @rest,
+ );
+ }
+ $self->_CloseParen;
+ } else {
+ $self->_SQLLimit(ALIAS => $users,
+ FIELD => $rest{SUBKEY} || 'EmailAddress',
+ VALUE => $value,
+ OPERATOR => $op,
+ CASESENSITIVE => 0,
+ @rest,
+ );
+ }
+
+ # {{{ Tie to groups for tickets we care about
+ $self->_SQLLimit(ALIAS => $groups,
+ FIELD => 'Domain',
+ VALUE => 'RT::Ticket-Role',
+ ENTRYAGGREGATOR => 'AND');
+
+ $self->Join(ALIAS1 => $groups, FIELD1 => 'Instance',
+ ALIAS2 => 'main', FIELD2 => 'id');
+ # }}}
+
+ # If we care about which sort of watcher
+ my $meta = $FIELDS{$field};
+ my $type = ( defined $meta->[1] ? $meta->[1] : undef );
+
+ if ( $type ) {
+ $self->_SQLLimit(ALIAS => $groups,
+ FIELD => 'Type',
+ VALUE => $type,
+ ENTRYAGGREGATOR => 'AND');
+ }
+
+ $self->Join (ALIAS1 => $groups, FIELD1 => 'id',
+ ALIAS2 => $groupmembers, FIELD2 => 'GroupId');
+
+ $self->Join( ALIAS1 => $groupmembers, FIELD1 => 'MemberId',
+ ALIAS2 => $users, FIELD2 => 'id');
+
+ $self->_CloseParen;
+
+}
+
+sub _LinkFieldLimit {
+ my $restriction;
+ my $self;
+ my $LinkAlias;
+ my %args;
+ if ($restriction->{'TYPE'}) {
+ $self->SUPER::Limit(ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => $restriction->{'TYPE'} );
+ }
+
+ #If we're trying to limit it to things that are target of
+ if ($restriction->{'TARGET'}) {
+ # If the TARGET is an integer that means that we want to look at
+ # the LocalTarget field. otherwise, we want to look at the
+ # "Target" field
+ my ($matchfield);
+ if ($restriction->{'TARGET'} =~/^(\d+)$/) {
+ $matchfield = "LocalTarget";
+ } else {
+ $matchfield = "Target";
+ }
+ $self->SUPER::Limit(ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $restriction->{'TARGET'} );
+ #If we're searching on target, join the base to ticket.id
+ $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
+ ALIAS2 => $LinkAlias,
+ FIELD2 => 'LocalBase');
+ }
+ #If we're trying to limit it to things that are base of
+ elsif ($restriction->{'BASE'}) {
+ # If we're trying to match a numeric link, we want to look at
+ # LocalBase, otherwise we want to look at "Base"
+ my ($matchfield);
+ if ($restriction->{'BASE'} =~/^(\d+)$/) {
+ $matchfield = "LocalBase";
+ } else {
+ $matchfield = "Base";
+ }
+
+ $self->SUPER::Limit(ALIAS => $LinkAlias,
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => $matchfield,
+ OPERATOR => '=',
+ VALUE => $restriction->{'BASE'} );
+ #If we're searching on base, join the target to ticket.id
+ $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
+ ALIAS2 => $LinkAlias,
+ FIELD2 => 'LocalTarget')
+ }
+}
+
+
+=head2 KeywordLimit
+
+Limit based on Keywords
+
+Meta Data:
+ none
+
+=cut
+
+sub _CustomFieldLimit {
+ my ($self,$_field,$op,$value,@rest) = @_;
+
+ my %rest = @rest;
+ my $field = $rest{SUBKEY} || die "No field specified";
+
+ # For our sanity, we can only limit on one queue at a time
+ my $queue = undef;
+ # Ugh. This will not do well for things with underscores in them
+
+ use RT::CustomFields;
+ my $CF = RT::CustomFields->new( $self->CurrentUser );
+ #$CF->Load( $cfid} );
+
+ my $q;
+ if ($field =~ /^(.+?)\.{(.+)}$/) {
+ my $q = RT::Queue->new($self->CurrentUser);
+ $q->Load($1);
+ $field = $2;
+ $CF->LimitToQueue( $q->Id );
+ $queue = $q->Id;
+ } else {
+ $field = $1 if $field =~ /^{(.+)}$/; # trim { }
+ $CF->LimitToGlobal;
+ }
+ $CF->FindAllRows;
+
+ my $cfid = 0;
+
+ while ( my $CustomField = $CF->Next ) {
+ if ($CustomField->Name eq $field) {
+ $cfid = $CustomField->Id;
+ last;
+ }
+ }
+ die "No custom field named $field found\n"
+ unless $cfid;
+
+# use RT::CustomFields;
+# my $CF = RT::CustomField->new( $self->CurrentUser );
+# $CF->Load( $cfid );
+
+
+ my $null_columns_ok;
+
+ my $TicketCFs;
+ # Perform one Join per CustomField
+ if ($self->{_sql_keywordalias}{$cfid}) {
+ $TicketCFs = $self->{_sql_keywordalias}{$cfid};
+ } else {
+ $TicketCFs = $self->{_sql_keywordalias}{$cfid} =
+ $self->Join( TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'TicketCustomFieldValues',
+ FIELD2 => 'Ticket' );
+ }
+
+ $self->_OpenParen;
+
+ $self->_SQLLimit( ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => $op,
+ VALUE => $value,
+ QUOTEVALUE => 1,
+ @rest );
+
+ if ( $op =~ /^IS$/i
+ or ( $op eq '!=' ) ) {
+ $null_columns_ok = 1;
+ }
+
+ #If we're trying to find tickets where the keyword isn't somethng,
+ #also check ones where it _IS_ null
+
+ if ( $op eq '!=' ) {
+ $self->_SQLLimit( ALIAS => $TicketCFs,
+ FIELD => 'Content',
+ OPERATOR => 'IS',
+ VALUE => 'NULL',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'OR', );
+ }
+
+ $self->_SQLLimit( LEFTJOIN => $TicketCFs,
+ FIELD => 'CustomField',
+ VALUE => $cfid,
+ ENTRYAGGREGATOR => 'OR' );
+
+
+
+ $self->_CloseParen;
+
+}
+
+
+# End Helper Functions
+
+# End of SQL Stuff -------------------------------------------------
+
+# {{{ Limit the result set based on content
+
+# {{{ sub Limit
+
+=head2 Limit
+
+Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION
+Generally best called from LimitFoo methods
+
+=cut
+sub Limit {
+ my $self = shift;
+ my %args = ( FIELD => undef,
+ OPERATOR => '=',
+ VALUE => undef,
+ DESCRIPTION => undef,
+ @_
+ );
+ $args{'DESCRIPTION'} = $self->loc(
+ "[_1] [_2] [_3]", $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'}
+ ) if (!defined $args{'DESCRIPTION'}) ;
+
+ my $index = $self->_NextIndex;
+
+ #make the TicketRestrictions hash the equivalent of whatever we just passed in;
+
+ %{$self->{'TicketRestrictions'}{$index}} = %args;
+
+ $self->{'RecalcTicketLimits'} = 1;
+
+ # If we're looking at the effective id, we don't want to append the other clause
+ # which limits us to tickets where id = effective id
+ if ($args{'FIELD'} eq 'EffectiveId') {
+ $self->{'looking_at_effective_id'} = 1;
+ }
+
+ if ($args{'FIELD'} eq 'Type') {
+ $self->{'looking_at_type'} = 1;
+ }
+
+ return ($index);
+}
+
+# }}}
+
+
+
+
+=head2 FreezeLimits
+
+Returns a frozen string suitable for handing back to ThawLimits.
+
+=cut
+# {{{ sub FreezeLimits
+
+sub FreezeLimits {
+ my $self = shift;
+ require FreezeThaw;
+ return (FreezeThaw::freeze($self->{'TicketRestrictions'},
+ $self->{'restriction_index'}
+ ));
+}
+
+# }}}
+
+=head2 ThawLimits
+
+Take a frozen Limits string generated by FreezeLimits and make this tickets
+object have that set of limits.
+
+=cut
+# {{{ sub ThawLimits
+
+sub ThawLimits {
+ my $self = shift;
+ my $in = shift;
+
+ #if we don't have $in, get outta here.
+ return undef unless ($in);
+
+ $self->{'RecalcTicketLimits'} = 1;
+
+ require FreezeThaw;
+
+ #We don't need to die if the thaw fails.
+
+ eval {
+ ($self->{'TicketRestrictions'},
+ $self->{'restriction_index'}
+ ) = FreezeThaw::thaw($in);
+ }
+
+}
+
+# }}}
+
+# {{{ Limit by enum or foreign key
+
+# {{{ sub LimitQueue
+
+=head2 LimitQueue
+
+LimitQueue takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of = or !=. (It defaults to =).
+VALUE is a queue id or Name.
+
+
+=cut
+
+sub LimitQueue {
+ my $self = shift;
+ my %args = (VALUE => undef,
+ OPERATOR => '=',
+ @_);
+
+ #TODO VALUE should also take queue names and queue objects
+ #TODO FIXME why are we canonicalizing to name, not id, robrt?
+ if ($args{VALUE} =~ /^\d+$/) {
+ my $queue = new RT::Queue($self->CurrentUser);
+ $queue->Load($args{'VALUE'});
+ $args{VALUE} = $queue->Name;
+ }
+
+ # What if they pass in an Id? Check for isNum() and convert to
+ # string.
+
+ #TODO check for a valid queue here
+
+ $self->Limit (FIELD => 'Queue',
+ VALUE => $args{VALUE},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE},
+ ),
+ );
+
+}
+# }}}
+
+# {{{ sub LimitStatus
+
+=head2 LimitStatus
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of = or !=.
+VALUE is a status.
+
+=cut
+
+sub LimitStatus {
+ my $self = shift;
+ my %args = ( OPERATOR => '=',
+ @_);
+ $self->Limit (FIELD => 'Status',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Status'), $args{'OPERATOR'}, $self->loc($args{'VALUE'})
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub IgnoreType
+
+=head2 IgnoreType
+
+If called, this search will not automatically limit the set of results found
+to tickets of type "Ticket". Tickets of other types, such as "project" and
+"approval" will be found.
+
+=cut
+
+sub IgnoreType {
+ my $self = shift;
+
+ # Instead of faking a Limit that later gets ignored, fake up the
+ # fact that we're already looking at type, so that the check in
+ # Tickets_Overlay_SQL/FromSQL goes down the right branch
+
+ # $self->LimitType(VALUE => '__any');
+ $self->{looking_at_type} = 1;
+}
+
+# }}}
+
+# {{{ sub LimitType
+
+=head2 LimitType
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of = or !=, it defaults to "=".
+VALUE is a string to search for in the type of the ticket.
+
+
+
+=cut
+
+sub LimitType {
+ my $self = shift;
+ my %args = (OPERATOR => '=',
+ VALUE => undef,
+ @_);
+ $self->Limit (FIELD => 'Type',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'},
+ ),
+ );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Limit by string field
+
+# {{{ sub LimitSubject
+
+=head2 LimitSubject
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of = or !=.
+VALUE is a string to search for in the subject of the ticket.
+
+=cut
+
+sub LimitSubject {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'Subject',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Limit based on ticket numerical attributes
+# Things that can be > < = !=
+
+# {{{ sub LimitId
+
+=head2 LimitId
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a ticket Id to search for
+
+=cut
+
+sub LimitId {
+ my $self = shift;
+ my %args = (OPERATOR => '=',
+ @_);
+
+ $self->Limit (FIELD => 'id',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitPriority
+
+=head2 LimitPriority
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a value to match the ticket\'s priority against
+
+=cut
+
+sub LimitPriority {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'Priority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Priority'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitInitialPriority
+
+=head2 LimitInitialPriority
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a value to match the ticket\'s initial priority against
+
+
+=cut
+
+sub LimitInitialPriority {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'InitialPriority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Initial Priority'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitFinalPriority
+
+=head2 LimitFinalPriority
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a value to match the ticket\'s final priority against
+
+=cut
+
+sub LimitFinalPriority {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'FinalPriority',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Final Priority'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitTimeWorked
+
+=head2 LimitTimeWorked
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a value to match the ticket's TimeWorked attribute
+
+=cut
+
+sub LimitTimeWorked {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'TimeWorked',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Time worked'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitTimeLeft
+
+=head2 LimitTimeLeft
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, >, < or !=.
+VALUE is a value to match the ticket's TimeLeft attribute
+
+=cut
+
+sub LimitTimeLeft {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'TimeLeft',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Time left'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# }}}
+
+# {{{ Limiting based on attachment attributes
+
+# {{{ sub LimitContent
+
+=head2 LimitContent
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, LIKE, NOT LIKE or !=.
+VALUE is a string to search for in the body of the ticket
+
+=cut
+sub LimitContent {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'Content',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Ticket content'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+
+# {{{ sub LimitFilename
+
+=head2 LimitFilename
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, LIKE, NOT LIKE or !=.
+VALUE is a string to search for in the body of the ticket
+
+=cut
+sub LimitFilename {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'Filename',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Attachment filename'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+# }}}
+# {{{ sub LimitContentType
+
+=head2 LimitContentType
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of =, LIKE, NOT LIKE or !=.
+VALUE is a content type to search ticket attachments for
+
+=cut
+
+sub LimitContentType {
+ my $self = shift;
+ my %args = (@_);
+ $self->Limit (FIELD => 'ContentType',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Ticket content type'), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+# }}}
+
+# }}}
+
+# {{{ Limiting based on people
+
+# {{{ sub LimitOwner
+
+=head2 LimitOwner
+
+Takes a paramhash with the fields OPERATOR and VALUE.
+OPERATOR is one of = or !=.
+VALUE is a user id.
+
+=cut
+
+sub LimitOwner {
+ my $self = shift;
+ my %args = ( OPERATOR => '=',
+ @_);
+
+ my $owner = new RT::User($self->CurrentUser);
+ $owner->Load($args{'VALUE'});
+ # FIXME: check for a valid $owner
+ $self->Limit (FIELD => 'Owner',
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ DESCRIPTION => join(
+ ' ', $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(),
+ ),
+ );
+
+}
+
+# }}}
+
+# {{{ Limiting watchers
+
+# {{{ sub LimitWatcher
+
+
+=head2 LimitWatcher
+
+ Takes a paramhash with the fields OPERATOR, TYPE and VALUE.
+ OPERATOR is one of =, LIKE, NOT LIKE or !=.
+ VALUE is a value to match the ticket\'s watcher email addresses against
+ TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them
+
+=begin testing
+
+my $t1 = RT::Ticket->new($RT::SystemUser);
+$t1->Create(Queue => 'general', Subject => "LimitWatchers test", Requestors => \['requestor1@example.com']);
+
+=end testing
+
+=cut
+
+sub LimitWatcher {
+ my $self = shift;
+ my %args = ( OPERATOR => '=',
+ VALUE => undef,
+ TYPE => undef,
+ @_);
+
+
+ #build us up a description
+ my ($watcher_type, $desc);
+ if ($args{'TYPE'}) {
+ $watcher_type = $args{'TYPE'};
+ }
+ else {
+ $watcher_type = "Watcher";
+ }
+
+ $self->Limit (FIELD => $watcher_type,
+ VALUE => $args{'VALUE'},
+ OPERATOR => $args{'OPERATOR'},
+ TYPE => $args{'TYPE'},
+ DESCRIPTION => join(
+ ' ', $self->loc($watcher_type), $args{'OPERATOR'}, $args{'VALUE'},
+ ),
+ );
+}
+
+
+sub LimitRequestor {
+ my $self = shift;
+ my %args = (@_);
+ my ($package, $filename, $line) = caller;
+ $RT::Logger->error("Tickets->LimitRequestor is deprecated. please rewrite call at $package - $filename: $line");
+ $self->LimitWatcher(TYPE => 'Requestor', @_);
+
+}
+
+# }}}
+
+
+# }}}
+
+# }}}
+
+# {{{ Limiting based on links
+
+# {{{ LimitLinkedTo
+
+=head2 LimitLinkedTo
+
+LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET
+TYPE limits the sort of relationship we want to search on
+
+TYPE = { RefersTo, MemberOf, DependsOn }
+
+TARGET is the id or URI of the TARGET of the link
+(TARGET used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as TARGET
+
+=cut
+
+sub LimitLinkedTo {
+ my $self = shift;
+ my %args = (
+ TICKET => undef,
+ TARGET => undef,
+ TYPE => undef,
+ @_);
+
+ $self->Limit(
+ FIELD => 'LinkedTo',
+ BASE => undef,
+ TARGET => ($args{'TARGET'} || $args{'TICKET'}),
+ TYPE => $args{'TYPE'},
+ DESCRIPTION => $self->loc(
+ "Tickets [_1] by [_2]", $self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'})
+ ),
+ );
+}
+
+
+# }}}
+
+# {{{ LimitLinkedFrom
+
+=head2 LimitLinkedFrom
+
+LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE
+TYPE limits the sort of relationship we want to search on
+
+
+BASE is the id or URI of the BASE of the link
+(BASE used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as BASE
+
+
+=cut
+
+sub LimitLinkedFrom {
+ my $self = shift;
+ my %args = ( BASE => undef,
+ TICKET => undef,
+ TYPE => undef,
+ @_);
+
+ # translate RT2 From/To naming to RT3 TicketSQL naming
+ my %fromToMap = qw(DependsOn DependentOn
+ MemberOf HasMember
+ RefersTo ReferredToBy);
+
+ my $type = $args{'TYPE'};
+ $type = $fromToMap{$type} if exists($fromToMap{$type});
+
+ $self->Limit( FIELD => 'LinkedTo',
+ TARGET => undef,
+ BASE => ($args{'BASE'} || $args{'TICKET'}),
+ TYPE => $type,
+ DESCRIPTION => $self->loc(
+ "Tickets [_1] [_2]", $self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'})
+ ),
+ );
+}
+
+
+# }}}
+
+# {{{ LimitMemberOf
+sub LimitMemberOf {
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedTo ( TARGET=> "$ticket_id",
+ TYPE => 'MemberOf',
+ );
+
+}
+# }}}
+
+# {{{ LimitHasMember
+sub LimitHasMember {
+ my $self = shift;
+ my $ticket_id =shift;
+ $self->LimitLinkedFrom ( BASE => "$ticket_id",
+ TYPE => 'HasMember',
+ );
+
+}
+# }}}
+
+# {{{ LimitDependsOn
+
+sub LimitDependsOn {
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedTo ( TARGET => "$ticket_id",
+ TYPE => 'DependsOn',
+ );
+
+}
+
+# }}}
+
+# {{{ LimitDependedOnBy
+
+sub LimitDependedOnBy {
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedFrom ( BASE => "$ticket_id",
+ TYPE => 'DependentOn',
+ );
+
+}
+
+# }}}
+
+
+# {{{ LimitRefersTo
+
+sub LimitRefersTo {
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedTo ( TARGET => "$ticket_id",
+ TYPE => 'RefersTo',
+ );
+
+}
+
+# }}}
+
+# {{{ LimitReferredToBy
+
+sub LimitReferredToBy {
+ my $self = shift;
+ my $ticket_id = shift;
+ $self->LimitLinkedFrom ( BASE=> "$ticket_id",
+ TYPE => 'ReferredTo',
+ );
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ limit based on ticket date attribtes
+
+# {{{ sub LimitDate
+
+=head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate)
+
+Takes a paramhash with the fields FIELD OPERATOR and VALUE.
+
+OPERATOR is one of > or <
+VALUE is a date and time in ISO format in GMT
+FIELD is one of Starts, Started, Told, Created, Resolved, LastUpdated
+
+There are also helper functions of the form LimitFIELD that eliminate
+the need to pass in a FIELD argument.
+
+=cut
+
+sub LimitDate {
+ my $self = shift;
+ my %args = (
+ FIELD => undef,
+ VALUE => undef,
+ OPERATOR => undef,
+
+ @_);
+
+ #Set the description if we didn't get handed it above
+ unless ($args{'DESCRIPTION'} ) {
+ $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
+ }
+
+ $self->Limit (%args);
+
+}
+
+# }}}
+
+
+
+
+sub LimitCreated {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Created', @_);
+}
+sub LimitDue {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Due', @_);
+
+}
+sub LimitStarts {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Starts', @_);
+
+}
+sub LimitStarted {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Started', @_);
+}
+sub LimitResolved {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Resolved', @_);
+}
+sub LimitTold {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'Told', @_);
+}
+sub LimitLastUpdated {
+ my $self = shift;
+ $self->LimitDate( FIELD => 'LastUpdated', @_);
+}
+#
+# {{{ sub LimitTransactionDate
+
+=head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate)
+
+Takes a paramhash with the fields FIELD OPERATOR and VALUE.
+
+OPERATOR is one of > or <
+VALUE is a date and time in ISO format in GMT
+
+
+=cut
+
+sub LimitTransactionDate {
+ my $self = shift;
+ my %args = (
+ FIELD => 'TransactionDate',
+ VALUE => undef,
+ OPERATOR => undef,
+
+ @_);
+
+ # <20021217042756.GK28744@pallas.fsck.com>
+ # "Kill It" - Jesse.
+
+ #Set the description if we didn't get handed it above
+ unless ($args{'DESCRIPTION'} ) {
+ $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
+ }
+
+ $self->Limit (%args);
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ Limit based on custom fields
+# {{{ sub LimitCustomField
+
+=head2 LimitCustomField
+
+Takes a paramhash of key/value pairs with the following keys:
+
+=over 4
+
+=item CUSTOMFIELD - CustomField name or id. If a name is passed, an additional
+parameter QUEUE may also be passed to distinguish the custom field.
+
+=item OPERATOR - The usual Limit operators
+
+=item VALUE - The value to compare against
+
+=back
+
+=cut
+
+sub LimitCustomField {
+ my $self = shift;
+ my %args = ( VALUE => undef,
+ CUSTOMFIELD => undef,
+ OPERATOR => '=',
+ DESCRIPTION => undef,
+ FIELD => 'CustomFieldValue',
+ QUOTEVALUE => 1,
+ @_ );
+
+ use RT::CustomFields;
+ my $CF = RT::CustomField->new( $self->CurrentUser );
+ if ( $args{CUSTOMFIELD} =~ /^\d+$/) {
+ $CF->Load( $args{CUSTOMFIELD} );
+ }
+ else {
+ $CF->LoadByNameAndQueue( Name => $args{CUSTOMFIELD}, Queue => $args{QUEUE} );
+ $args{CUSTOMFIELD} = $CF->Id;
+ }
+
+ #If we are looking to compare with a null value.
+ if ( $args{'OPERATOR'} =~ /^is$/i ) {
+ $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has no value.", $CF->Name);
+ }
+ elsif ( $args{'OPERATOR'} =~ /^is not$/i ) {
+ $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has a value.", $CF->Name);
+ }
+
+ # if we're not looking to compare with a null value
+ else {
+ $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] [_2] [_3]", $CF->Name , $args{OPERATOR} , $args{VALUE});
+ }
+
+ my $q = "";
+ if ($CF->Queue) {
+ my $qo = new RT::Queue( $self->CurrentUser );
+ $qo->load( $CF->Queue );
+ $q = $qo->Name;
+ }
+
+ my @rest;
+ @rest = ( ENTRYAGGREGATOR => 'AND' )
+ if ($CF->Type eq 'SelectMultiple');
+
+ $self->Limit( VALUE => $args{VALUE},
+ FIELD => "CF.".( $q
+ ? $q . ".{" . $CF->Name . "}"
+ : $CF->Name
+ ),
+ OPERATOR => $args{OPERATOR},
+ CUSTOMFIELD => 1,
+ @rest,
+ );
+
+
+ $self->{'RecalcTicketLimits'} = 1;
+}
+
+# }}}
+# }}}
+
+
+# {{{ sub _NextIndex
+
+=head2 _NextIndex
+
+Keep track of the counter for the array of restrictions
+
+=cut
+
+sub _NextIndex {
+ my $self = shift;
+ return ($self->{'restriction_index'}++);
+}
+# }}}
+
+# }}}
+
+# {{{ Core bits to make this a DBIx::SearchBuilder object
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = "Tickets";
+ $self->{'RecalcTicketLimits'} = 1;
+ $self->{'looking_at_effective_id'} = 0;
+ $self->{'looking_at_type'} = 0;
+ $self->{'restriction_index'} =1;
+ $self->{'primary_key'} = "id";
+ delete $self->{'items_array'};
+ delete $self->{'item_map'};
+ $self->SUPER::_Init(@_);
+
+ $self->_InitSQL;
+
+}
+# }}}
+
+# {{{ sub Count
+sub Count {
+ my $self = shift;
+ $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
+ return($self->SUPER::Count());
+}
+# }}}
+
+# {{{ sub CountAll
+sub CountAll {
+ my $self = shift;
+ $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
+ return($self->SUPER::CountAll());
+}
+# }}}
+
+
+# {{{ sub ItemsArrayRef
+
+=head2 ItemsArrayRef
+
+Returns a reference to the set of all items found in this search
+
+=cut
+
+sub ItemsArrayRef {
+ my $self = shift;
+ my @items;
+
+ unless ( $self->{'items_array'} ) {
+
+ my $placeholder = $self->_ItemsCounter;
+ $self->GotoFirstItem();
+ while ( my $item = $self->Next ) {
+ push ( @{ $self->{'items_array'} }, $item );
+ }
+ $self->GotoItem($placeholder);
+ }
+ return ( $self->{'items_array'} );
+}
+# }}}
+
+# {{{ sub Next
+sub Next {
+ my $self = shift;
+
+ $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 );
+
+ my $Ticket = $self->SUPER::Next();
+ if ((defined($Ticket)) and (ref($Ticket))) {
+
+ #Make sure we _never_ show deleted tickets
+ #TODO we should be doing this in the where clause.
+ #but you can't do multiple clauses on the same field just yet :/
+
+ if ($Ticket->__Value('Status') eq 'deleted') {
+ return($self->Next());
+ }
+ # Since Ticket could be granted with more rights instead
+ # of being revoked, it's ok if queue rights allow
+ # ShowTicket. It seems need another query, but we have
+ # rights cache in Principal::HasRight.
+ elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket') ||
+ $Ticket->CurrentUserHasRight('ShowTicket')) {
+ return($Ticket);
+ }
+
+ #If the user doesn't have the right to show this ticket
+ else {
+ return($self->Next());
+ }
+ }
+ #if there never was any ticket
+ else {
+ return(undef);
+ }
+
+}
+# }}}
+
+# }}}
+
+# {{{ Deal with storing and restoring restrictions
+
+# {{{ sub LoadRestrictions
+
+=head2 LoadRestrictions
+
+LoadRestrictions takes a string which can fully populate the TicketRestrictons hash.
+TODO It is not yet implemented
+
+=cut
+
+# }}}
+
+# {{{ sub DescribeRestrictions
+
+=head2 DescribeRestrictions
+
+takes nothing.
+Returns a hash keyed by restriction id.
+Each element of the hash is currently a one element hash that contains DESCRIPTION which
+is a description of the purpose of that TicketRestriction
+
+=cut
+
+sub DescribeRestrictions {
+ my $self = shift;
+
+ my ($row, %listing);
+
+ foreach $row (keys %{$self->{'TicketRestrictions'}}) {
+ $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
+ }
+ return (%listing);
+}
+# }}}
+
+# {{{ sub RestrictionValues
+
+=head2 RestrictionValues FIELD
+
+Takes a restriction field and returns a list of values this field is restricted
+to.
+
+=cut
+
+sub RestrictionValues {
+ my $self = shift;
+ my $field = shift;
+ map $self->{'TicketRestrictions'}{$_}{'VALUE'},
+ grep {
+ $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field
+ && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "="
+ }
+ keys %{$self->{'TicketRestrictions'}};
+}
+
+# }}}
+
+# {{{ sub ClearRestrictions
+
+=head2 ClearRestrictions
+
+Removes all restrictions irretrievably
+
+=cut
+
+sub ClearRestrictions {
+ my $self = shift;
+ delete $self->{'TicketRestrictions'};
+ $self->{'looking_at_effective_id'} = 0;
+ $self->{'looking_at_type'} = 0;
+ $self->{'RecalcTicketLimits'} =1;
+}
+
+# }}}
+
+# {{{ sub DeleteRestriction
+
+=head2 DeleteRestriction
+
+Takes the row Id of a restriction (From DescribeRestrictions' output, for example.
+Removes that restriction from the session's limits.
+
+=cut
+
+
+sub DeleteRestriction {
+ my $self = shift;
+ my $row = shift;
+ delete $self->{'TicketRestrictions'}{$row};
+
+ $self->{'RecalcTicketLimits'} = 1;
+ #make the underlying easysearch object forget all its preconceptions
+}
+
+# }}}
+
+# {{{ sub _RestrictionsToClauses
+
+# Convert a set of oldstyle SB Restrictions to Clauses for RQL
+
+sub _RestrictionsToClauses {
+ my $self = shift;
+
+ my $row;
+ my %clause;
+ foreach $row (keys %{$self->{'TicketRestrictions'}}) {
+ my $restriction = $self->{'TicketRestrictions'}{$row};
+ #use Data::Dumper;
+ #print Dumper($restriction),"\n";
+
+ # We need to reimplement the subclause aggregation that SearchBuilder does.
+ # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main',
+ # Then SB AND's the different Subclauses together.
+
+ # So, we want to group things into Subclauses, convert them to
+ # SQL, and then join them with the appropriate DefaultEA.
+ # Then join each subclause group with AND.
+
+ my $field = $restriction->{'FIELD'};
+ my $realfield = $field; # CustomFields fake up a fieldname, so
+ # we need to figure that out
+
+ # One special case
+ # Rewrite LinkedTo meta field to the real field
+ if ($field =~ /LinkedTo/) {
+ $realfield = $field = $restriction->{'TYPE'};
+ }
+
+ # Two special case
+ # CustomFields have a different real field
+ if ($field =~ /^CF\./) {
+ $realfield = "CF"
+ }
+
+ die "I don't know about $field yet"
+ unless (exists $FIELDS{$realfield} or $restriction->{CUSTOMFIELD});
+
+ my $type = $FIELDS{$realfield}->[0];
+ my $op = $restriction->{'OPERATOR'};
+
+ my $value = ( grep { defined }
+ map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET))[0];
+
+ # this performs the moral equivalent of defined or/dor/C<//>,
+ # without the short circuiting.You need to use a 'defined or'
+ # type thing instead of just checking for truth values, because
+ # VALUE could be 0.(i.e. "false")
+
+ # You could also use this, but I find it less aesthetic:
+ # (although it does short circuit)
+ #( defined $restriction->{'VALUE'}? $restriction->{VALUE} :
+ # defined $restriction->{'TICKET'} ?
+ # $restriction->{TICKET} :
+ # defined $restriction->{'BASE'} ?
+ # $restriction->{BASE} :
+ # defined $restriction->{'TARGET'} ?
+ # $restriction->{TARGET} )
+
+ my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND";
+ if ( ref $ea ) {
+ die "Invalid operator $op for $field ($type)"
+ unless exists $ea->{$op};
+ $ea = $ea->{$op};
+ }
+
+ # Each CustomField should be put into a different Clause so they
+ # are ANDed together.
+ if ($restriction->{CUSTOMFIELD}) {
+ $realfield = $field;
+ }
+
+ exists $clause{$realfield} or $clause{$realfield} = [];
+ # Escape Quotes
+ $field =~ s!(['"])!\\$1!g;
+ $value =~ s!(['"])!\\$1!g;
+ my $data = [ $ea, $type, $field, $op, $value ];
+
+ # here is where we store extra data, say if it's a keyword or
+ # something. (I.e. "TYPE SPECIFIC STUFF")
+
+ #print Dumper($data);
+ push @{$clause{$realfield}}, $data;
+ }
+ return \%clause;
+}
+
+# }}}
+
+# {{{ sub _ProcessRestrictions
+
+=head2 _ProcessRestrictions PARAMHASH
+
+# The new _ProcessRestrictions is somewhat dependent on the SQL stuff,
+# but isn't quite generic enough to move into Tickets_Overlay_SQL.
+
+=cut
+
+sub _ProcessRestrictions {
+ my $self = shift;
+
+ #Blow away ticket aliases since we'll need to regenerate them for
+ #a new search
+ delete $self->{'TicketAliases'};
+ delete $self->{'items_array'};
+ delete $self->{'item_map'};
+ delete $self->{'raw_rows'};
+ delete $self->{'rows'};
+ delete $self->{'count_all'};
+
+ my $sql = $self->{_sql_query}; # Violating the _SQL namespace
+ if (!$sql||$self->{'RecalcTicketLimits'}) {
+ # "Restrictions to Clauses Branch\n";
+ my $clauseRef = eval { $self->_RestrictionsToClauses; };
+ if ($@) {
+ $RT::Logger->error( "RestrictionsToClauses: " . $@ );
+ $self->FromSQL("");
+ } else {
+ $sql = $self->ClausesToSQL($clauseRef);
+ $self->FromSQL($sql);
+ }
+ }
+
+
+ $self->{'RecalcTicketLimits'} = 0;
+
+}
+
+=head2 _BuildItemMap
+
+ # Build up a map of first/last/next/prev items, so that we can display search nav quickly
+
+=cut
+
+sub _BuildItemMap {
+ my $self = shift;
+
+ my $items = $self->ItemsArrayRef;
+ my $prev = 0 ;
+
+ delete $self->{'item_map'};
+ if ($items->[0]) {
+ $self->{'item_map'}->{'first'} = $items->[0]->EffectiveId;
+ while (my $item = shift @$items ) {
+ my $id = $item->EffectiveId;
+ $self->{'item_map'}->{$id}->{'defined'} = 1;
+ $self->{'item_map'}->{$id}->{prev} = $prev;
+ $self->{'item_map'}->{$id}->{next} = $items->[0]->EffectiveId if ($items->[0]);
+ $prev = $id;
+ }
+ $self->{'item_map'}->{'last'} = $prev;
+ }
+}
+
+
+=head2 ItemMap
+
+Returns an a map of all items found by this search. The map is of the form
+
+$ItemMap->{'first'} = first ticketid found
+$ItemMap->{'last'} = last ticketid found
+$ItemMap->{$id}->{prev} = the tikcet id found before $id
+$ItemMap->{$id}->{next} = the tikcet id found after $id
+
+=cut
+
+sub ItemMap {
+ my $self = shift;
+ $self->_BuildItemMap() unless ($self->{'item_map'});
+ return ($self->{'item_map'});
+}
+
+
+
+
+=cut
+
+}
+
+
+
+# }}}
+
+# }}}
+
+=head2 PrepForSerialization
+
+You don't want to serialize a big tickets object, as the {items} hash will be instantly invalid _and_ eat lots of space
+
+=cut
+
+
+sub PrepForSerialization {
+ my $self = shift;
+ delete $self->{'items'};
+ $self->RedoSearch();
+}
+
+1;
+
diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm
new file mode 100644
index 0000000..629e6da
--- /dev/null
+++ b/rt/lib/RT/Tickets_Overlay_SQL.pm
@@ -0,0 +1,447 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+use strict;
+use warnings;
+
+# Import configuration data from the lexcial scope of __PACKAGE__ (or
+# at least where those two Subroutines are defined.)
+
+my %FIELDS = %{FIELDS()};
+my %dispatch = %{dispatch()};
+my %can_bundle = %{can_bundle()};
+
+# Lower Case version of FIELDS, for case insensitivity
+my %lcfields = map { ( lc($_) => $_ ) } (keys %FIELDS);
+
+sub _InitSQL {
+ my $self = shift;
+
+ # How many of these do we actually still use?
+
+ # Private Member Variales (which should get cleaned)
+ $self->{'_sql_linksc'} = 0;
+ $self->{'_sql_watchersc'} = 0;
+ $self->{'_sql_keywordsc'} = 0;
+ $self->{'_sql_subclause'} = "a";
+ $self->{'_sql_first'} = 0;
+ $self->{'_sql_opstack'} = [''];
+ $self->{'_sql_transalias'} = undef;
+ $self->{'_sql_trattachalias'} = undef;
+ $self->{'_sql_keywordalias'} = undef;
+ $self->{'_sql_depth'} = 0;
+ $self->{'_sql_localdepth'} = 0;
+ $self->{'_sql_query'} = '';
+ $self->{'_sql_looking_at'} = {};
+
+}
+
+sub _SQLLimit {
+ # All SQL stuff goes into one SB subclause so we can deal with all
+ # the aggregation
+ my $this = shift;
+ $this->SUPER::Limit(@_,
+ SUBCLAUSE => 'ticketsql');
+}
+
+# Helpers
+sub _OpenParen {
+ $_[0]->SUPER::_OpenParen( 'ticketsql' );
+}
+sub _CloseParen {
+ $_[0]->SUPER::_CloseParen( 'ticketsql' );
+}
+
+=head1 SQL Functions
+
+=cut
+
+sub _match {
+ # Case insensitive equality
+ my ($y,$x) = @_;
+ return 1 if $x =~ /^$y$/i;
+ # return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv?
+ return 0;
+}
+
+=head2 Robert's Simple SQL Parser
+
+Documentation In Progress
+
+The Parser/Tokenizer is a relatively simple state machine that scans through a SQL WHERE clause type string extracting a token at a time (where a token is:
+
+ VALUE -> quoted string or number
+ AGGREGator -> AND or OR
+ KEYWORD -> quoted string or single word
+ OPerator -> =,!=,LIKE,etc..
+ PARENthesis -> open or close.
+
+And that stream of tokens is passed through the "machine" in order to build up a structure that looks like:
+
+ KEY OP VALUE
+ AND KEY OP VALUE
+ OR KEY OP VALUE
+
+That also deals with parenthesis for nesting. (The parentheses are
+just handed off the SearchBuilder)
+
+=cut
+
+use Regexp::Common qw /delimited/;
+
+# States
+use constant VALUE => 1;
+use constant AGGREG => 2;
+use constant OP => 4;
+use constant PAREN => 8;
+use constant KEYWORD => 16;
+my @tokens = qw[VALUE AGGREG OP PAREN KEYWORD];
+
+my $re_aggreg = qr[(?i:AND|OR)];
+my $re_value = qr[$RE{delimited}{-delim=>qq{\'\"}}|\d+];
+my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+];
+my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short
+my $re_paren = qr'\(|\)';
+
+sub _close_bundle
+{
+ my ($self, @bundle) = @_;
+ return unless @bundle;
+ if (@bundle == 1) {
+ $bundle[0]->{dispatch}->(
+ $self,
+ $bundle[0]->{key},
+ $bundle[0]->{op},
+ $bundle[0]->{val},
+ SUBCLAUSE => "",
+ ENTRYAGGREGATOR => $bundle[0]->{ea},
+ SUBKEY => $bundle[0]->{subkey},
+ );
+ } else {
+ my @args;
+ for my $chunk (@bundle) {
+ push @args, [
+ $chunk->{key},
+ $chunk->{op},
+ $chunk->{val},
+ SUBCLAUSE => "",
+ ENTRYAGGREGATOR => $chunk->{ea},
+ SUBKEY => $chunk->{subkey},
+ ];
+ }
+ $bundle[0]->{dispatch}->(
+ $self, \@args,
+ );
+ }
+}
+
+sub _parser {
+ my ($self,$string) = @_;
+ my $want = KEYWORD | PAREN;
+ my $last = undef;
+
+ my $depth = 0;
+ my @bundle;
+
+ my ($ea,$key,$op,$value) = ("","","","");
+
+ # order of matches in the RE is important.. op should come early,
+ # because it has spaces in it. otherwise "NOT LIKE" might be parsed
+ # as a keyword or value.
+
+ while ($string =~ /(
+ $re_aggreg
+ |$re_op
+ |$re_keyword
+ |$re_value
+ |$re_paren
+ )/igx ) {
+ my $val = $1;
+ my $current = 0;
+
+ # Highest priority is last
+ $current = OP if _match($re_op,$val);
+ $current = VALUE if _match($re_value,$val);
+ $current = KEYWORD if _match($re_keyword,$val) && ($want & KEYWORD);
+ $current = AGGREG if _match($re_aggreg,$val);
+ $current = PAREN if _match($re_paren,$val);
+
+ unless ($current && $want & $current) {
+ # Error
+ # FIXME: I will only print out the highest $want value
+ die "Error near ->$val<- expecting a ", $tokens[((log $want)/(log 2))], " in $string\n";
+ }
+
+ # State Machine:
+
+ # Parens are highest priority
+ if ($current & PAREN) {
+ if ($val eq "(") {
+ $self->_close_bundle(@bundle); @bundle = ();
+ $depth++;
+ $self->_OpenParen;
+
+ } else {
+ $self->_close_bundle(@bundle); @bundle = ();
+ $depth--;
+ $self->_CloseParen;
+ }
+
+ $want = KEYWORD | PAREN | AGGREG;
+ }
+ elsif ( $current & AGGREG ) {
+ $ea = $val;
+ $want = KEYWORD | PAREN;
+ }
+ elsif ( $current & KEYWORD ) {
+ $key = $val;
+ $want = OP;
+ }
+ elsif ( $current & OP ) {
+ $op = $val;
+ $want = VALUE;
+ }
+ elsif ( $current & VALUE ) {
+ $value = $val;
+
+ # Remove surrounding quotes from $key, $val
+ # (in future, simplify as for($key,$val) { action on $_ })
+ if ($key =~ /$RE{delimited}{-delim=>qq{\'\"}}/) {
+ substr($key,0,1) = "";
+ substr($key,-1,1) = "";
+ }
+ if ($val =~ /$RE{delimited}{-delim=>qq{\'\"}}/) {
+ substr($val,0,1) = "";
+ substr($val,-1,1) = "";
+ }
+ # Unescape escaped characters
+ $key =~ s!\\(.)!$1!g;
+ $val =~ s!\\(.)!$1!g;
+ # print "$ea Key=[$key] op=[$op] val=[$val]\n";
+
+
+ my $subkey;
+ if ($key =~ /^(.+?)\.(.+)$/) {
+ $key = $1;
+ $subkey = $2;
+ }
+
+ my $class;
+ if (exists $lcfields{lc $key}) {
+ $key = $lcfields{lc $key};
+ $class = $FIELDS{$key}->[0];
+ }
+ # no longer have a default, since CF's are now a real class, not fallthrough
+ # fixme: "default class" is not Generic.
+
+
+ die "Unknown field: $key" unless $class;
+
+ $self->{_sql_localdepth} = 0;
+ die "No such dispatch method: $class"
+ unless exists $dispatch{$class};
+ my $sub = $dispatch{$class} || die;;
+ if ($can_bundle{$class} &&
+ (!@bundle ||
+ ($bundle[-1]->{dispatch} == $sub &&
+ $bundle[-1]->{key} eq $key &&
+ $bundle[-1]->{subkey} eq $subkey)))
+ {
+ push @bundle, {
+ dispatch => $sub,
+ key => $key,
+ op => $op,
+ val => $val,
+ ea => $ea || "",
+ subkey => $subkey,
+ };
+ } else {
+ $self->_close_bundle(@bundle); @bundle = ();
+ $sub->(
+ $self,
+ $key,
+ $op,
+ $val,
+ SUBCLAUSE => "", # don't need anymore
+ ENTRYAGGREGATOR => $ea || "",
+ SUBKEY => $subkey,
+ );
+ }
+
+ $self->{_sql_looking_at}{lc $key} = 1;
+
+ ($ea,$key,$op,$value) = ("","","","");
+
+ $want = PAREN | AGGREG;
+ } else {
+ die "I'm lost";
+ }
+
+ $last = $current;
+ } # while
+
+ $self->_close_bundle(@bundle); @bundle = ();
+
+ die "Incomplete query"
+ unless (($want | PAREN) || ($want | KEYWORD));
+
+ die "Incomplete Query"
+ unless ($last && ($last | PAREN) || ($last || VALUE));
+
+ # This will never happen, because the parser will complain
+ die "Mismatched parentheses"
+ unless $depth == 0;
+
+}
+
+
+=head2 ClausesToSQL
+
+=cut
+
+sub ClausesToSQL {
+ my $self = shift;
+ my $clauses = shift;
+ my @sql;
+
+ for my $f (keys %{$clauses}) {
+ my $sql;
+ my $first = 1;
+
+ # Build SQL from the data hash
+ for my $data ( @{ $clauses->{$f} } ) {
+ $sql .= $data->[0] unless $first; $first=0;
+ $sql .= " '". $data->[2] . "' ";
+ $sql .= $data->[3] . " ";
+ $sql .= "'". $data->[4] . "' ";
+ }
+
+ push @sql, " ( " . $sql . " ) ";
+ }
+
+ return join("AND",@sql);
+}
+
+=head2 FromSQL
+
+Convert a RT-SQL string into a set of SearchBuilder restrictions.
+
+Returns (1, 'Status message') on success and (0, 'Error Message') on
+failure.
+
+=cut
+
+sub FromSQL {
+ my ($self,$query) = @_;
+
+ $self->CleanSlate;
+ $self->_InitSQL();
+ return (1,"No Query") unless $query;
+
+ $self->{_sql_query} = $query;
+ eval { $self->_parser( $query ); };
+ $RT::Logger->error( $@ ) if $@;
+ return(0,$@) if $@;
+
+ # We only want to look at EffectiveId's (mostly) for these searches.
+ unless (exists $self->{_sql_looking_at}{'effectiveid'}) {
+ $self->SUPER::Limit( FIELD => 'EffectiveId',
+ ENTRYAGGREGATOR => 'AND',
+ OPERATOR => '=',
+ QUOTEVALUE => 0,
+ VALUE => 'main.id'
+ ); #TODO, we shouldn't be hard #coding the tablename to main.
+ }
+ # FIXME: Need to bring this logic back in
+
+ # if ($self->_isLimited && (! $self->{'looking_at_effective_id'})) {
+ # $self->SUPER::Limit( FIELD => 'EffectiveId',
+ # OPERATOR => '=',
+ # QUOTEVALUE => 0,
+ # VALUE => 'main.id'); #TODO, we shouldn't be hard coding the tablename to main.
+ # }
+ # --- This is hardcoded above. This comment block can probably go.
+ # Or, we need to reimplement the looking_at_effective_id toggle.
+
+ # Unless we've explicitly asked to look at a specific Type, we need
+ # to limit to it.
+ unless ($self->{looking_at_type}) {
+ $self->SUPER::Limit( FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => 'ticket');
+ }
+
+ # We never ever want to show deleted tickets
+ $self->SUPER::Limit(FIELD => 'Status' , OPERATOR => '!=', VALUE => 'deleted');
+
+
+ # set SB's dirty flag
+ $self->{'must_redo_search'} = 1;
+ $self->{'RecalcTicketLimits'} = 0;
+
+ return (1,"Good Query");
+
+}
+
+
+1;
+
+=pod
+
+=head2 Exceptions
+
+Most of the RT code does not use Exceptions (die/eval) but it is used
+in the TicketSQL code for simplicity and historical reasons. Lest you
+be worried that the dies will trigger user visible errors, all are
+trapped via evals.
+
+99% of the dies fall in subroutines called via FromSQL and then parse.
+(This includes all of the _FooLimit routines in Tickets_Overlay.pm.)
+The other 1% or so are via _ProcessRestrictions.
+
+All dies are trapped by eval {}s, and will be logged at the 'error'
+log level. The general failure mode is to not display any tickets.
+
+=head2 General Flow
+
+Legacy Layer:
+
+ Legacy LimitFoo routines build up a RestrictionsHash
+
+ _ProcessRestrictions converts the Restrictions to Clauses
+ ([key,op,val,rest]).
+
+ Clauses are converted to RT-SQL (TicketSQL)
+
+New RT-SQL Layer:
+
+ FromSQL calls the parser
+
+ The parser calls the _FooLimit routines to do DBIx::SearchBuilder
+ limits.
+
+And then the normal SearchBuilder/Ticket routines are used for
+display/navigation.
+
+=cut
+
diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm
new file mode 100755
index 0000000..ca491a6
--- /dev/null
+++ b/rt/lib/RT/Transaction.pm
@@ -0,0 +1,364 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::Transaction
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::Transaction;
+use RT::Record;
+use RT::Ticket;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Transactions');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ int(11) 'EffectiveTicket'.
+ int(11) 'Ticket'.
+ int(11) 'TimeTaken'.
+ varchar(20) 'Type'.
+ varchar(40) 'Field'.
+ varchar(255) 'OldValue'.
+ varchar(255) 'NewValue'.
+ varchar(100) 'Data'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ EffectiveTicket => '0',
+ Ticket => '0',
+ TimeTaken => '0',
+ Type => '',
+ Field => '',
+ OldValue => '',
+ NewValue => '',
+ Data => '',
+
+ @_);
+ $self->SUPER::Create(
+ EffectiveTicket => $args{'EffectiveTicket'},
+ Ticket => $args{'Ticket'},
+ TimeTaken => $args{'TimeTaken'},
+ Type => $args{'Type'},
+ Field => $args{'Field'},
+ OldValue => $args{'OldValue'},
+ NewValue => $args{'NewValue'},
+ Data => $args{'Data'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item EffectiveTicket
+
+Returns the current value of EffectiveTicket.
+(In the database, EffectiveTicket is stored as int(11).)
+
+
+
+=item SetEffectiveTicket VALUE
+
+
+Set EffectiveTicket to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, EffectiveTicket will be stored as a int(11).)
+
+
+=cut
+
+
+=item Ticket
+
+Returns the current value of Ticket.
+(In the database, Ticket is stored as int(11).)
+
+
+
+=item SetTicket VALUE
+
+
+Set Ticket to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Ticket will be stored as a int(11).)
+
+
+=cut
+
+
+=item TicketObj
+
+Returns the Ticket Object which has the id returned by Ticket
+
+
+=cut
+
+sub TicketObj {
+ my $self = shift;
+ my $Ticket = RT::Ticket->new($self->CurrentUser);
+ $Ticket->Load($self->__Value('Ticket'));
+ return($Ticket);
+}
+
+=item TimeTaken
+
+Returns the current value of TimeTaken.
+(In the database, TimeTaken is stored as int(11).)
+
+
+
+=item SetTimeTaken VALUE
+
+
+Set TimeTaken to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, TimeTaken will be stored as a int(11).)
+
+
+=cut
+
+
+=item Type
+
+Returns the current value of Type.
+(In the database, Type is stored as varchar(20).)
+
+
+
+=item SetType VALUE
+
+
+Set Type to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Type will be stored as a varchar(20).)
+
+
+=cut
+
+
+=item Field
+
+Returns the current value of Field.
+(In the database, Field is stored as varchar(40).)
+
+
+
+=item SetField VALUE
+
+
+Set Field to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Field will be stored as a varchar(40).)
+
+
+=cut
+
+
+=item OldValue
+
+Returns the current value of OldValue.
+(In the database, OldValue is stored as varchar(255).)
+
+
+
+=item SetOldValue VALUE
+
+
+Set OldValue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, OldValue will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item NewValue
+
+Returns the current value of NewValue.
+(In the database, NewValue is stored as varchar(255).)
+
+
+
+=item SetNewValue VALUE
+
+
+Set NewValue to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NewValue will be stored as a varchar(255).)
+
+
+=cut
+
+
+=item Data
+
+Returns the current value of Data.
+(In the database, Data is stored as varchar(100).)
+
+
+
+=item SetData VALUE
+
+
+Set Data to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Data will be stored as a varchar(100).)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ EffectiveTicket =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Ticket =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ TimeTaken =>
+ {read => 1, write => 1, type => 'int(11)', default => '0'},
+ Type =>
+ {read => 1, write => 1, type => 'varchar(20)', default => ''},
+ Field =>
+ {read => 1, write => 1, type => 'varchar(40)', default => ''},
+ OldValue =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ NewValue =>
+ {read => 1, write => 1, type => 'varchar(255)', default => ''},
+ Data =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::Transaction_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transaction_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Transaction_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transaction_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Transaction_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transaction_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Transaction_Overlay, RT::Transaction_Vendor, RT::Transaction_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Transaction_Overlay.pm b/rt/lib/RT/Transaction_Overlay.pm
new file mode 100644
index 0000000..9c9a2fd
--- /dev/null
+++ b/rt/lib/RT/Transaction_Overlay.pm
@@ -0,0 +1,796 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Transaction - RT\'s transaction object
+
+=head1 SYNOPSIS
+
+ use RT::Transaction;
+
+
+=head1 DESCRIPTION
+
+
+Each RT::Transaction describes an atomic change to a ticket object
+or an update to an RT::Ticket object.
+It can have arbitrary MIME attachments.
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::Transaction);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use vars qw( %_BriefDescriptions );
+
+use RT::Attachments;
+
+# {{{ sub Create
+
+=head2 Create
+
+Create a new transaction.
+
+This routine should _never_ be called anything other Than RT::Ticket. It should not be called
+from client code. Ever. Not ever. If you do this, we will hunt you down. and break your kneecaps.
+Then the unpleasant stuff will start.
+
+TODO: Document what gets passed to this
+
+=cut
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ id => undef,
+ TimeTaken => 0,
+ Ticket => 0,
+ Type => 'undefined',
+ Data => '',
+ Field => undef,
+ OldValue => undef,
+ NewValue => undef,
+ MIMEObj => undef,
+ ActivateScrips => 1,
+ @_
+ );
+
+ #if we didn't specify a ticket, we need to bail
+ unless ( $args{'Ticket'} ) {
+ return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify a ticket id"));
+ }
+
+
+
+ #lets create our transaction
+ my %params = (Ticket => $args{'Ticket'},
+ Type => $args{'Type'},
+ Data => $args{'Data'},
+ Field => $args{'Field'},
+ OldValue => $args{'OldValue'},
+ NewValue => $args{'NewValue'},
+ Created => $args{'Created'}
+ );
+
+ # Parameters passed in during an import that we probably don't want to touch, otherwise
+ foreach my $attr qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy) {
+ $params{$attr} = $args{$attr} if ($args{$attr});
+ }
+
+ my $id = $self->SUPER::Create(%params);
+ $self->Load($id);
+ $self->_Attach( $args{'MIMEObj'} )
+ if defined $args{'MIMEObj'};
+
+ #Provide a way to turn off scrips if we need to
+ if ( $args{'ActivateScrips'} ) {
+ require RT::Scrips;
+ RT::Scrips->new($RT::SystemUser)->Apply(
+ Stage => 'TransactionCreate',
+ Type => $args{'Type'},
+ Ticket => $args{'Ticket'},
+ Transaction => $self->id,
+ );
+ }
+
+ return ( $id, $self->loc("Transaction Created") );
+}
+
+# }}}
+
+# {{{ sub Delete
+
+sub Delete {
+ my $self = shift;
+ return ( 0,
+ $self->loc('Deleting this object could break referential integrity') );
+}
+
+# }}}
+
+# {{{ Routines dealing with Attachments
+
+# {{{ sub Message
+
+=head2 Message
+
+ Returns the RT::Attachments Object which contains the "top-level"object
+ attachment for this transaction
+
+=cut
+
+sub Message {
+
+ my $self = shift;
+
+ if ( !defined( $self->{'message'} ) ) {
+
+ $self->{'message'} = new RT::Attachments( $self->CurrentUser );
+ $self->{'message'}->Limit(
+ FIELD => 'TransactionId',
+ VALUE => $self->Id
+ );
+
+ $self->{'message'}->ChildrenOf(0);
+ }
+ return ( $self->{'message'} );
+}
+
+# }}}
+
+# {{{ sub Content
+
+=head2 Content PARAMHASH
+
+If this transaction has attached mime objects, returns the first text/plain part.
+Otherwise, returns undef.
+
+Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message
+at $args{'Wrap'}. $args{'Wrap'} defaults to 70.
+
+
+=cut
+
+sub Content {
+ my $self = shift;
+ my %args = (
+ Quote => 0,
+ Wrap => 70,
+ @_
+ );
+
+ my $content;
+ my $content_obj = $self->ContentObj;
+ if ($content_obj) {
+ $content = $content_obj->Content;
+ }
+
+ # If all else fails, return a message that we couldn't find any content
+ else {
+ $content = $self->loc('This transaction appears to have no content');
+ }
+
+ if ( $args{'Quote'} ) {
+
+ # Remove quoted signature.
+ $content =~ s/\n-- \n(.*)$//s;
+
+ # What's the longest line like?
+ my $max = 0;
+ foreach ( split ( /\n/, $content ) ) {
+ $max = length if ( length > $max );
+ }
+
+ if ( $max > 76 ) {
+ require Text::Wrapper;
+ my $wrapper = new Text::Wrapper(
+ columns => $args{'Wrap'},
+ body_start => ( $max > 70 * 3 ? ' ' : '' ),
+ par_start => ''
+ );
+ $content = $wrapper->wrap($content);
+ }
+
+ $content = '['
+ . $self->CreatorObj->Name() . ' - '
+ . $self->CreatedAsString() . "]:\n\n" . $content . "\n\n";
+ $content =~ s/^/> /gm;
+
+ }
+
+ return ($content);
+}
+
+# }}}
+
+# {{{ ContentObj
+
+=head2 ContentObj
+
+Returns the RT::Attachment object which contains the content for this Transaction
+
+=cut
+
+
+
+sub ContentObj {
+
+ my $self = shift;
+
+ # If we don\'t have any content, return undef now.
+ unless ( $self->Attachments->First ) {
+ return (undef);
+ }
+
+ # Get the set of toplevel attachments to this transaction.
+ my $Attachment = $self->Attachments->First();
+
+ # If it's a message or a plain part, just return the
+ # body.
+ if ( $Attachment->ContentType() =~ '^(text/plain$|message/)' ) {
+ return ($Attachment);
+ }
+
+ # If it's a multipart object, first try returning the first
+ # text/plain part.
+
+ elsif ( $Attachment->ContentType() =~ '^multipart/' ) {
+ my $plain_parts = $Attachment->Children();
+ $plain_parts->ContentType( VALUE => 'text/plain' );
+
+ # If we actully found a part, return its content
+ if ( $plain_parts->First && $plain_parts->First->Content ne '' ) {
+ return ( $plain_parts->First );
+ }
+
+ # If that fails, return the first text/plain or message/ part
+ # which has some content.
+
+ else {
+ my $all_parts = $Attachment->Children();
+ while ( my $part = $all_parts->Next ) {
+ if (( $part->ContentType() =~ '^(text/plain$|message/)' ) && $part->Content() ) {
+ return ($part);
+ }
+ }
+ }
+
+ }
+
+ # We found no content. suck
+ return (undef);
+}
+
+# }}}
+
+# {{{ sub Subject
+
+=head2 Subject
+
+If this transaction has attached mime objects, returns the first one's subject
+Otherwise, returns null
+
+=cut
+
+sub Subject {
+ my $self = shift;
+ if ( $self->Attachments->First ) {
+ return ( $self->Attachments->First->Subject );
+ }
+ else {
+ return (undef);
+ }
+}
+
+# }}}
+
+# {{{ sub Attachments
+
+=head2 Attachments
+
+ Returns all the RT::Attachment objects which are attached
+to this transaction. Takes an optional parameter, which is
+a ContentType that Attachments should be restricted to.
+
+=cut
+
+sub Attachments {
+ my $self = shift;
+
+ unless ( $self->{'attachments'} ) {
+ $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
+
+ #If it's a comment, return an empty object if they don't have the right to see it
+ if ( $self->Type eq 'Comment' ) {
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ return ( $self->{'attachments'} );
+ }
+ }
+
+ #if they ain't got rights to see, return an empty object
+ else {
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return ( $self->{'attachments'} );
+ }
+ }
+
+ $self->{'attachments'}->Limit( FIELD => 'TransactionId',
+ VALUE => $self->Id );
+
+ # Get the self->{'attachments'} in the order they're put into
+ # the database. Arguably, we should be returning a tree
+ # of self->{'attachments'}, not a set...but no current app seems to need
+ # it.
+
+ $self->{'attachments'}->OrderBy( ALIAS => 'main',
+ FIELD => 'id',
+ ORDER => 'asc' );
+
+ }
+ return ( $self->{'attachments'} );
+
+}
+
+# }}}
+
+# {{{ sub _Attach
+
+=head2 _Attach
+
+A private method used to attach a mime object to this transaction.
+
+=cut
+
+sub _Attach {
+ my $self = shift;
+ my $MIMEObject = shift;
+
+ if ( !defined($MIMEObject) ) {
+ $RT::Logger->error(
+"$self _Attach: We can't attach a mime object if you don't give us one.\n"
+ );
+ return ( 0, $self->loc("[_1]: no attachment specified", $self) );
+ }
+
+ my $Attachment = new RT::Attachment( $self->CurrentUser );
+ $Attachment->Create(
+ TransactionId => $self->Id,
+ Attachment => $MIMEObject
+ );
+ return ( $Attachment, $self->loc("Attachment created") );
+
+}
+
+# }}}
+
+# }}}
+
+# {{{ Routines dealing with Transaction Attributes
+
+# {{{ sub Description
+
+=head2 Description
+
+Returns a text string which describes this transaction
+
+=cut
+
+sub Description {
+ my $self = shift;
+
+ #Check those ACLs
+ #If it's a comment, we need to be extra special careful
+ if ( $self->__Value('Type') eq 'Comment' ) {
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ return ( $self->loc("Permission Denied") );
+ }
+ }
+
+ #if they ain't got rights to see, don't let em
+ else {
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return ($self->loc("Permission Denied") );
+ }
+ }
+
+ if ( !defined( $self->Type ) ) {
+ return ( $self->loc("No transaction type specified"));
+ }
+
+ return ( $self->loc("[_1] by [_2]",$self->BriefDescription , $self->CreatorObj->Name ));
+}
+
+# }}}
+
+# {{{ sub BriefDescription
+
+=head2 BriefDescription
+
+Returns a text string which briefly describes this transaction
+
+=cut
+
+sub BriefDescription {
+ my $self = shift;
+
+
+ #Check those ACLs
+ #If it's a comment, we need to be extra special careful
+ if ( $self->__Value('Type') eq 'Comment' ) {
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ return ( $self->loc("Permission Denied") );
+ }
+ }
+
+ #if they ain't got rights to see, don't let em
+ else {
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return ( $self->loc("Permission Denied") );
+ }
+ }
+
+ my $type = $self->Type; #cache this, rather than calling it 30 times
+
+ if ( !defined( $type ) ) {
+ return $self->loc("No transaction type specified");
+ }
+
+ if ( $type eq 'Create' ) {
+ return ($self->loc("Ticket created"));
+ }
+ elsif ( $type =~ /Status/ ) {
+ if ( $self->Field eq 'Status' ) {
+ if ( $self->NewValue eq 'deleted' ) {
+ return ($self->loc("Ticket deleted"));
+ }
+ else {
+ return ( $self->loc("Status changed from [_1] to [_2]", $self->loc($self->OldValue), $self->loc($self->NewValue) ));
+
+ }
+ }
+
+ # Generic:
+ my $no_value = $self->loc("(no value)");
+ return ( $self->loc( "[_1] changed from [_2] to [_3]", $self->Field , ( $self->OldValue || $no_value ) , $self->NewValue ));
+ }
+
+ if (my $code = $_BriefDescriptions{$type}) {
+ return $code->($self);
+ }
+
+ return $self->loc( "Default: [_1]/[_2] changed from [_3] to [_4]", $type, $self->Field, $self->OldValue, $self->NewValue );
+}
+
+%_BriefDescriptions = (
+ Correspond => sub {
+ my $self = shift;
+ return $self->loc("Correspondence added");
+ },
+ Comment => sub {
+ my $self = shift;
+ return $self->loc("Comments added");
+ },
+ CustomField => sub {
+ my $self = shift;
+ my $field = $self->loc('CustomField');
+
+ if ( $self->Field ) {
+ my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->Load( $self->Field );
+ $field = $cf->Name();
+ }
+
+ if ( $self->OldValue eq '' ) {
+ return ( $self->loc("[_1] [_2] added", $field, $self->NewValue) );
+ }
+ elsif ( $self->NewValue eq '' ) {
+ return ( $self->loc("[_1] [_2] deleted", $field, $self->OldValue) );
+
+ }
+ else {
+ return $self->loc("[_1] [_2] changed to [_3]", $field, $self->OldValue, $self->NewValue );
+ }
+ },
+ Untake => sub {
+ my $self = shift;
+ return $self->loc("Untaken");
+ },
+ Take => sub {
+ my $self = shift;
+ return $self->loc("Taken");
+ },
+ Force => sub {
+ my $self = shift;
+ my $Old = RT::User->new( $self->CurrentUser );
+ $Old->Load( $self->OldValue );
+ my $New = RT::User->new( $self->CurrentUser );
+ $New->Load( $self->NewValue );
+
+ return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
+ },
+ Steal => sub {
+ my $self = shift;
+ my $Old = RT::User->new( $self->CurrentUser );
+ $Old->Load( $self->OldValue );
+ return $self->loc("Stolen from [_1] ", $Old->Name);
+ },
+ Give => sub {
+ my $self = shift;
+ my $New = RT::User->new( $self->CurrentUser );
+ $New->Load( $self->NewValue );
+ return $self->loc( "Given to [_1]", $New->Name );
+ },
+ AddWatcher => sub {
+ my $self = shift;
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($self->NewValue);
+ return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
+ },
+ DelWatcher => sub {
+ my $self = shift;
+ my $principal = RT::Principal->new($self->CurrentUser);
+ $principal->Load($self->OldValue);
+ return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
+ },
+ Subject => sub {
+ my $self = shift;
+ return $self->loc( "Subject changed to [_1]", $self->Data );
+ },
+ AddLink => sub {
+ my $self = shift;
+ my $value;
+ if ( $self->NewValue ) {
+ my $URI = RT::URI->new( $self->CurrentUser );
+ $URI->FromURI( $self->NewValue );
+ if ( $URI->Resolver ) {
+ $value = $URI->Resolver->AsString;
+ }
+ else {
+ $value = $self->NewValue;
+ }
+ if ( $self->Field eq 'DependsOn' ) {
+ return $self->loc( "Dependency on [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'DependedOnBy' ) {
+ return $self->loc( "Dependency by [_1] added", $value );
+
+ }
+ elsif ( $self->Field eq 'RefersTo' ) {
+ return $self->loc( "Reference to [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'ReferredToBy' ) {
+ return $self->loc( "Reference by [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'MemberOf' ) {
+ return $self->loc( "Membership in [_1] added", $value );
+ }
+ elsif ( $self->Field eq 'HasMember' ) {
+ return $self->loc( "Member [_1] added", $value );
+ }
+ }
+ else {
+ return ( $self->Data );
+ }
+ },
+ DeleteLink => sub {
+ my $self = shift;
+ my $value;
+ if ( $self->OldValue ) {
+ my $URI = RT::URI->new( $self->CurrentUser );
+ $URI->FromURI( $self->OldValue );
+ if ( $URI->Resolver ) {
+ $value = $URI->Resolver->AsString;
+ }
+ else {
+ $value = $self->OldValue;
+ }
+
+ if ( $self->Field eq 'DependsOn' ) {
+ return $self->loc( "Dependency on [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'DependedOnBy' ) {
+ return $self->loc( "Dependency by [_1] deleted", $value );
+
+ }
+ elsif ( $self->Field eq 'RefersTo' ) {
+ return $self->loc( "Reference to [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'ReferredToBy' ) {
+ return $self->loc( "Reference by [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'MemberOf' ) {
+ return $self->loc( "Membership in [_1] deleted", $value );
+ }
+ elsif ( $self->Field eq 'HasMember' ) {
+ return $self->loc( "Member [_1] deleted", $value );
+ }
+ }
+ else {
+ return ( $self->Data );
+ }
+ },
+ Set => sub {
+ my $self = shift;
+ if ( $self->Field eq 'Queue' ) {
+ my $q1 = new RT::Queue( $self->CurrentUser );
+ $q1->Load( $self->OldValue );
+ my $q2 = new RT::Queue( $self->CurrentUser );
+ $q2->Load( $self->NewValue );
+ return $self->loc("[_1] changed from [_2] to [_3]", $self->Field , $q1->Name , $q2->Name);
+ }
+
+ # Write the date/time change at local time:
+ elsif ($self->Field =~ /Due|Starts|Started|Told/) {
+ my $t1 = new RT::Date($self->CurrentUser);
+ $t1->Set(Format => 'ISO', Value => $self->NewValue);
+ my $t2 = new RT::Date($self->CurrentUser);
+ $t2->Set(Format => 'ISO', Value => $self->OldValue);
+ return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $t2->AsString, $t1->AsString );
+ }
+ else {
+ return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $self->OldValue, $self->NewValue );
+ }
+ },
+ PurgeTransaction => sub {
+ my $self = shift;
+ return $self->loc("Transaction [_1] purged", $self->Data);
+ },
+);
+
+# }}}
+
+# {{{ Utility methods
+
+# {{{ sub IsInbound
+
+=head2 IsInbound
+
+Returns true if the creator of the transaction is a requestor of the ticket.
+Returns false otherwise
+
+=cut
+
+sub IsInbound {
+ my $self = shift;
+ return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
+}
+
+# }}}
+
+# }}}
+
+sub _ClassAccessible {
+ {
+
+ id => { read => 1, type => 'int(11)', default => '' },
+ EffectiveTicket =>
+ { read => 1, write => 1, type => 'int(11)', default => '' },
+ Ticket =>
+ { read => 1, public => 1, type => 'int(11)', default => '' },
+ TimeTaken => { read => 1, type => 'int(11)', default => '' },
+ Type => { read => 1, type => 'varchar(20)', default => '' },
+ Field => { read => 1, type => 'varchar(40)', default => '' },
+ OldValue => { read => 1, type => 'varchar(255)', default => '' },
+ NewValue => { read => 1, type => 'varchar(255)', default => '' },
+ Data => { read => 1, type => 'varchar(100)', default => '' },
+ Creator => { read => 1, auto => 1, type => 'int(11)', default => '' },
+ Created =>
+ { read => 1, auto => 1, type => 'datetime', default => '' },
+
+ }
+};
+
+# }}}
+
+# }}}
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+ return ( 0, $self->loc('Transactions are immutable') );
+}
+
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+ #if the field is public, return it.
+ if ( $self->_Accessible( $field, 'public' ) ) {
+ return ( $self->__Value($field) );
+
+ }
+
+ #If it's a comment, we need to be extra special careful
+ if ( $self->__Value('Type') eq 'Comment' ) {
+ unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
+ return (undef);
+ }
+ }
+
+ #if they ain't got rights to see, don't let em
+ else {
+ unless ( $self->CurrentUserHasRight('ShowTicket') ) {
+ return (undef);
+ }
+ }
+
+ return ( $self->__Value($field) );
+
+}
+
+# }}}
+
+# {{{ sub CurrentUserHasRight
+
+=head2 CurrentUserHasRight RIGHT
+
+Calls $self->CurrentUser->HasQueueRight for the right passed in here.
+passed in here.
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+ return (
+ $self->CurrentUser->HasRight(
+ Right => "$right",
+ Object => $self->TicketObj
+ )
+ );
+}
+
+# }}}
+
+# Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
+sub _CacheConfig {
+ {
+ 'cache_p' => 1,
+ 'fast_update_p' => 1,
+ 'cache_for_sec' => 180,
+ }
+}
+1;
diff --git a/rt/lib/RT/Transactions.pm b/rt/lib/RT/Transactions.pm
new file mode 100755
index 0000000..23a475a
--- /dev/null
+++ b/rt/lib/RT/Transactions.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Transactions -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Transactions
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Transactions;
+
+use RT::SearchBuilder;
+use RT::Transaction;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Transactions';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::Transaction item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::Transaction->new($self->CurrentUser));
+}
+
+ eval "require RT::Transactions_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transactions_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Transactions_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transactions_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Transactions_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Transactions_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Transactions_Overlay, RT::Transactions_Vendor, RT::Transactions_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Transactions_Overlay.pm b/rt/lib/RT/Transactions_Overlay.pm
new file mode 100644
index 0000000..3a7d4c1
--- /dev/null
+++ b/rt/lib/RT/Transactions_Overlay.pm
@@ -0,0 +1,86 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Transactions - a collection of RT Transaction objects
+
+=head1 SYNOPSIS
+
+ use RT::Transactions;
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok (require RT::Transactions);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+
+ $self->{'table'} = "Transactions";
+ $self->{'primary_key'} = "id";
+
+ # By default, order by the date of the transaction, rather than ID.
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'Created',
+ ORDER => 'ASC');
+
+ return ( $self->SUPER::_Init(@_));
+}
+# }}}
+
+=head2 example methods
+
+ Queue RT::Queue or Queue Id
+ Ticket RT::Ticket or Ticket Id
+
+
+LimitDate
+
+Type TRANSTYPE
+Field STRING
+OldValue OLDVAL
+NewValue NEWVAL
+Data DATA
+TimeTaken
+Actor USEROBJ/USERID
+ContentMatches STRING
+
+=cut
+
+
+1;
+
diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm
new file mode 100644
index 0000000..337a356
--- /dev/null
+++ b/rt/lib/RT/URI.pm
@@ -0,0 +1,249 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::URI;;
+
+use strict;
+use vars qw/@ISA/;
+@ISA = qw(RT::Base);
+
+use RT::URI::base;
+use Carp;
+
+=head1 NAME
+
+RT::URI
+
+=head1 DESCRIPTION
+
+This class provides a base class for URIs, such as those handled
+by RT::Link objects.
+
+=head1 API
+
+
+
+=cut
+
+
+
+
+=head2 new
+
+Create a new RT::URI object.
+
+=cut
+
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless( $self, $class );
+
+ $self->CurrentUser(@_);
+
+ return ($self);
+}
+
+
+
+# {{{ FromObject
+
+=head2 FromObject <Object>
+
+Given a local object, such as an RT::Ticket or an RT::FM::Article, this routine will return a URI for
+the local object
+
+=cut
+
+sub FromObject {
+ my $self = shift;
+ my $obj = shift;
+
+ return undef unless $obj->can('URI');
+ return $self->FromURI($obj->URI);
+}
+
+# }}}
+
+# {{{ FromURI
+
+=head2 FromURI <URI>
+
+Returns a local object id for this content. You are expected to know
+what sort of object this is the Id of
+
+Returns true if everything is ok, otherwise false
+
+=cut
+
+sub FromURI {
+ my $self = shift;
+ my $uri = shift;
+
+ return undef unless ($uri);
+
+ my $scheme;
+ # Special case: integers passed in as URIs must be ticket ids
+ if ($uri =~ /^(\d+)$/) {
+ $scheme = "fsck.com-rt";
+ } elsif ($uri =~ /^((?:\w|\.|-)+?):/) {
+ $scheme = $1;
+ }
+ else {
+ $RT::Logger->warning("$self Could not determine a URI scheme for $uri");
+ return (undef);
+ }
+
+ # load up a resolver object for this scheme
+ $self->_GetResolver($scheme);
+
+ unless ($self->Resolver->ParseURI($uri)) {
+ $RT::Logger->warning("Resolver ".ref($self->Resolver)." could not parse $uri");
+ $self->{resolver} = undef; # clear resolver
+ return (undef);
+ }
+
+return(1);
+
+}
+
+# }}}
+
+# {{{ _GetResolver
+
+=private _GetResolver <scheme>
+
+Gets an RT URI resolver for the scheme <scheme>.
+Falls back to a null resolver. RT::URI::base.
+
+=cut
+
+sub _GetResolver {
+ my $self = shift;
+ my $scheme = shift;
+
+ $scheme =~ s/(\.|-)/_/g;
+ my $resolver;
+
+
+ eval "
+ require RT::URI::$scheme;
+ \$resolver = RT::URI::$scheme->new(\$self->CurrentUser);
+ ";
+
+ if ($resolver) {
+ $self->{'resolver'} = $resolver;
+ } else {
+ $self->{'resolver'} = RT::URI::base->new($self->CurrentUser);
+ }
+
+}
+
+# }}}
+
+# {{{ Scheme
+
+=head2 Scheme
+
+Returns a local object id for this content. You are expected to know what sort of object this is the Id
+of
+
+=cut
+
+sub Scheme {
+ my $self = shift;
+ return ($self->Resolver->Scheme);
+
+}
+# }}}
+# {{{ URI
+
+=head2 URI
+
+Returns a local object id for this content. You are expected to know what sort of object this is the Id
+of
+
+=cut
+
+sub URI {
+ my $self = shift;
+ return ($self->Resolver->URI);
+
+}
+# }}}
+
+# {{{ Object
+
+=head2 Object
+
+Returns a local object for this content. This will usually be an RT::Ticket or somesuch
+
+=cut
+
+
+sub Object {
+ my $self = shift;
+ return($self->Resolver->Object);
+
+}
+
+
+# }}}
+
+# {{{ IsLocal
+
+=head2 IsLocal
+
+Returns a local object for this content. This will usually be an RT::Ticket or somesuch
+
+=cut
+
+sub IsLocal {
+ my $self = shift;
+ return $self->Resolver->IsLocal;
+}
+
+
+# }}}
+
+
+=head Resolver
+
+Returns this URI's URI resolver object
+
+=cut
+
+
+sub Resolver {
+ my $self =shift;
+ return ($self->{'resolver'});
+}
+
+eval "require RT::URI_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI_Vendor.pm});
+eval "require RT::URI_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI_Local.pm});
+
+1;
diff --git a/rt/lib/RT/URI/base.pm b/rt/lib/RT/URI/base.pm
new file mode 100644
index 0000000..a599f3a
--- /dev/null
+++ b/rt/lib/RT/URI/base.pm
@@ -0,0 +1,129 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::URI::base;
+
+use strict;
+use base qw(RT::Base);
+
+=head1 NAME
+
+RT::URI::base
+
+=head1 DESCRIPTION
+
+A baseclass (and fallback) RT::URI handler. Every URI handler needs to
+handle the API presented here
+
+=cut
+
+
+=head1 API
+
+=head2 new
+
+Create a new URI handler
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ bless( $self, $class );
+ $self->CurrentUser(@_);
+ return ($self);
+}
+
+sub ParseObject {
+ my $self = shift;
+ my $obj = shift;
+ $self->{'uri'} = "unknown-object:".ref($obj);
+
+
+}
+
+
+
+sub ParseURI {
+ my $self = shift;
+ my $uri = shift;
+
+ if ($uri =~ /^(.*?):/) {
+ $self->{'scheme'} = $1;
+ }
+ $self->{'uri'} = $uri;
+
+
+}
+
+
+sub Object {
+ my $self = shift;
+ return undef;
+
+}
+
+sub URI {
+ my $self = shift;
+ return($self->{'uri'});
+}
+
+sub Scheme {
+ my $self = shift;
+ return($self->{'scheme'});
+
+}
+
+sub HREF {
+ my $self = shift;
+ return($self->{'href'} || $self->{'uri'});
+}
+
+sub IsLocal {
+ my $self = shift;
+ return undef;
+}
+
+=head2 AsString
+
+Return a "pretty" string representing the URI object.
+
+This is meant to be used like this:
+
+ % $re = $uri->Resolver;
+ <A HREF="<% $re->HREF %>"><% $re->AsString %></A>
+
+=cut
+
+sub AsString {
+ my $self = shift;
+ return $self->URI;
+}
+
+eval "require RT::URI::base_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Vendor.pm});
+eval "require RT::URI::base_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Local.pm});
+
+1;
diff --git a/rt/lib/RT/URI/freeside.pm b/rt/lib/RT/URI/freeside.pm
new file mode 100644
index 0000000..bfb514d
--- /dev/null
+++ b/rt/lib/RT/URI/freeside.pm
@@ -0,0 +1,188 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 2004 Kristian Hoffmann <khoff@fire2wire.com>
+# Based on the original RT::URI::base and RT::URI::fsck_com_rt.
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::URI::freeside;
+
+use RT::URI::base;
+use strict;
+use vars qw(@ISA);
+
+@ISA = qw/RT::URI::base/;
+
+
+=head1 NAME
+
+RT::URI::base
+
+=head1 DESCRIPTION
+
+URI handler for freeside URIs. See http://www.sisd.com/freeside/ for
+more information on freeside.
+
+=cut
+
+
+sub FreesideURIPrefix {
+
+ my $self = shift;
+ return($self->Scheme . '://freeside');
+
+}
+
+sub FreesideURILabel {
+
+ my $self = shift;
+
+ return(undef) unless (exists($self->{'fstable'}) and
+ exists($self->{'fspkey'}));
+
+ my $label;
+ my ($table, $pkey) = ($self->{'fstable'}, $self->{'fspkey'});
+
+ eval {
+ use FS::UID qw(dbh);
+ use FS::Record qw(qsearchs qsearch dbdef);
+ eval "use FS::$table;";
+ use FS::cust_svc;
+
+ my $dbdef = dbdef or die "No dbdef";
+ my $pkeyfield = $dbdef->table($table)->primary_key
+ or die "No primary key for table $table";
+
+ my $rec = qsearchs($table, { $pkeyfield => $pkey })
+ or die "Record with $pkeyfield == $pkey does not exist in table $table";
+
+ if ($table =~ /^svc_/) {
+ if ($rec->can('cust_svc')) {
+ my $cust_svc = $rec->cust_svc or die '$rec->cust_svc failed';
+ my ($svc, $tag, $svcdb) = $cust_svc->label;
+ $label = "Freeside service ${svc}: ${tag}";
+ }
+ } elsif ($table eq 'cust_main') {
+ my ($last, $first, $company) = map { $rec->getfield($_) }
+ qw(last first company);
+ $label = "Freeside customer ${last}, ${first}";
+ $label .= ($company ne '') ? " with ${company}" : '';
+ } else {
+ $label = "Freeside ${table}, ${pkeyfield} == ${pkey}";
+ }
+
+ #... other cases
+
+ };
+
+ if ($label and !$@) {
+ return($label);
+ } else {
+ return(undef);
+ }
+
+
+}
+
+sub ParseURI {
+ my $self = shift;
+ my $uri = shift;
+ my ($table, $pkey);
+
+ my $uriprefix = $self->FreesideURIPrefix;
+ if ($uri =~ /^$uriprefix\/(\w+)\/(\d+)$/) {
+ $table = $1;
+ $pkey = $2;
+ $self->{'scheme'} = $self->Scheme;
+ } else {
+ return(undef);
+ }
+
+ $self->{'uri'} = "${uriprefix}/${table}/${pkey}";
+ $self->{'fstable'} = $table;
+ $self->{'fspkey'} = $pkey;
+
+ my $p;
+
+ eval {
+ use FS::UID qw(dbh);
+ use FS::CGI qw(popurl);
+
+ if (dbh) {
+ $p = popurl(3);
+ }
+
+ };
+
+ if ($@ or (!$p)) {
+ $self->{'href'} = $self->{'uri'};
+ } else {
+ $self->{'href'} = "${p}view/${table}.cgi?${pkey}";
+ }
+
+ $self->{'uri'};
+
+}
+
+sub Scheme {
+ my $self = shift;
+ return('freeside');
+
+}
+
+sub HREF {
+ my $self = shift;
+ return($self->{'href'} || $self->{'uri'});
+}
+
+sub IsLocal {
+ my $self = shift;
+ return undef;
+}
+
+=head2 AsString
+
+Return a "pretty" string representing the URI object.
+
+This is meant to be used like this:
+
+ % $re = $uri->Resolver;
+ <A HREF="<% $re->HREF %>"><% $re->AsString %></A>
+
+=cut
+
+sub AsString {
+ my $self = shift;
+ my $prettystring;
+ if ($prettystring = $self->FreesideURILabel) {
+ return $prettystring;
+ } else {
+ return $self->URI;
+ }
+}
+
+eval "require RT::URI::base_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Vendor.pm});
+eval "require RT::URI::base_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/base_Local.pm});
+
+1;
diff --git a/rt/lib/RT/URI/fsck_com_rt.pm b/rt/lib/RT/URI/fsck_com_rt.pm
new file mode 100644
index 0000000..4035776
--- /dev/null
+++ b/rt/lib/RT/URI/fsck_com_rt.pm
@@ -0,0 +1,246 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+package RT::URI::fsck_com_rt;
+
+use RT::Ticket;
+
+use RT::URI::base;
+
+use strict;
+use vars qw(@ISA);
+@ISA = qw/RT::URI::base/;
+
+
+
+
+=head2 LocalURIPrefix
+
+Returns the prefix for a local ticket URI
+
+=begin testing
+
+use_ok("RT::URI::fsck_com_rt");
+my $uri = RT::URI::fsck_com_rt->new($RT::SystemUser);
+
+ok(ref($uri));
+
+use Data::Dumper;
+
+
+ok (UNIVERSAL::isa($uri,RT::URI::fsck_com_rt), "It's an RT::URI::fsck_com_rt");
+
+ok ($uri->isa('RT::URI::base'), "It's an RT::URI::base");
+ok ($uri->isa('RT::Base'), "It's an RT::Base");
+
+is ($uri->LocalURIPrefix , 'fsck.com-rt://example.com/ticket/');
+
+=end testing
+
+
+
+=cut
+
+sub LocalURIPrefix {
+ my $self = shift;
+ my $prefix = $self->Scheme. "://$RT::Organization/ticket/";
+ return ($prefix);
+}
+
+
+
+
+
+=head2 URIForObject RT::Ticket
+
+Returns the RT URI for a local RT::Ticket object
+
+=begin testing
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+$ticket->Load(1);
+my $uri = RT::URI::fsck_com_rt->new($ticket->CurrentUser);
+is($uri->LocalURIPrefix . "1" , $uri->URIForObject($ticket));
+
+=end testing
+
+=cut
+
+sub URIForObject {
+
+ my $self = shift;
+
+ my $obj = shift;
+ return ($self->LocalURIPrefix. $obj->Id);
+}
+
+
+=head2 ParseObject $TicketObj
+
+When handed an RT::Ticekt object, figure out its URI
+
+
+=cut
+
+
+
+=head2 ParseURI URI
+
+When handed an fsck.com-rt: URI, figures out things like whether its a local ticket
+and what its ID is
+
+=cut
+
+
+sub ParseURI {
+ my $self = shift;
+ my $uri = shift;
+
+ my $ticket;
+
+ if ($uri =~ /^(\d+)$/) {
+ $ticket = RT::Ticket->new($self->CurrentUser);
+ $ticket->Load($uri);
+ $self->{'uri'} = $ticket->URI;
+ }
+ else {
+ $self->{'uri'} = $uri;
+ }
+
+
+
+ #If it's a local URI, load the ticket object and return its URI
+ if ( $self->IsLocal) {
+
+ my $local_uri_prefix = $self->LocalURIPrefix;
+ if ($self->{'uri'} =~ /^$local_uri_prefix(\d+)$/i) {
+ my $id = $1;
+
+
+ $ticket = RT::Ticket->new( $self->CurrentUser );
+ $ticket->Load($id);
+
+ #If we couldn't find a ticket, return undef.
+ unless ( defined $ticket->Id ) {
+ return undef;
+ }
+ } else {
+ return undef;
+ }
+ }
+
+ $self->{'object'} = $ticket;
+ if ( UNIVERSAL::can( $ticket, 'Id' ) ) {
+ return ( $ticket->Id );
+ }
+ else {
+ return undef;
+ }
+}
+
+=head2 IsLocal
+
+Returns true if this URI is for a local ticket.
+Returns undef otherwise.
+
+
+
+=cut
+
+sub IsLocal {
+ my $self = shift;
+ my $local_uri_prefix = $self->LocalURIPrefix;
+ if ($self->{'uri'} =~ /^$local_uri_prefix/i) {
+ return 1;
+ }
+ else {
+ return undef;
+ }
+}
+
+
+
+=head2 Object
+
+Returns the object for this URI, if it's local. Otherwise returns undef.
+
+=cut
+
+sub Object {
+ my $self = shift;
+ return ($self->{'object'});
+
+}
+
+=head2 Scheme
+
+Return the URI scheme for RT tickets
+
+=cut
+
+
+sub Scheme {
+ my $self = shift;
+ return "fsck.com-rt";
+}
+
+=head2 HREF
+
+If this is a local ticket, return an HTTP url to it.
+Otherwise, return its URI
+
+=cut
+
+
+sub HREF {
+ my $self = shift;
+ if ($self->IsLocal && $self->Object) {
+ return ( $RT::WebURL . "Ticket/Display.html?id=".$self->Object->Id);
+ }
+ else {
+ return ($self->URI);
+ }
+}
+
+=head2 AsString
+
+Returns either a localized string 'ticket #23' or the full URI if the object is not local
+
+=cut
+
+sub AsString {
+ my $self = shift;
+ if ($self->IsLocal && $self->Object) {
+ return $self->loc("ticket #[_1]", $self->Object->Id);
+ }
+ else {
+ return $self->URI;
+ }
+}
+
+eval "require RT::URI::fsck_com_rt_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/fsck_com_rt_Vendor.pm});
+eval "require RT::URI::fsck_com_rt_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/URI/fsck_com_rt_Local.pm});
+
+1;
diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm
new file mode 100755
index 0000000..cbc10f5
--- /dev/null
+++ b/rt/lib/RT/User.pm
@@ -0,0 +1,854 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+RT::User
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package RT::User;
+use RT::Record;
+
+
+use vars qw( @ISA );
+@ISA= qw( RT::Record );
+
+sub _Init {
+ my $self = shift;
+
+ $self->Table('Users');
+ $self->SUPER::_Init(@_);
+}
+
+
+
+
+
+=item Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+ varchar(200) 'Name'.
+ varchar(40) 'Password'.
+ blob 'Comments'.
+ blob 'Signature'.
+ varchar(120) 'EmailAddress'.
+ blob 'FreeformContactInfo'.
+ varchar(200) 'Organization'.
+ varchar(120) 'RealName'.
+ varchar(16) 'NickName'.
+ varchar(16) 'Lang'.
+ varchar(16) 'EmailEncoding'.
+ varchar(16) 'WebEncoding'.
+ varchar(100) 'ExternalContactInfoId'.
+ varchar(30) 'ContactInfoSystem'.
+ varchar(100) 'ExternalAuthId'.
+ varchar(30) 'AuthSystem'.
+ varchar(16) 'Gecos'.
+ varchar(30) 'HomePhone'.
+ varchar(30) 'WorkPhone'.
+ varchar(30) 'MobilePhone'.
+ varchar(30) 'PagerPhone'.
+ varchar(200) 'Address1'.
+ varchar(200) 'Address2'.
+ varchar(100) 'City'.
+ varchar(100) 'State'.
+ varchar(16) 'Zip'.
+ varchar(50) 'Country'.
+ varchar(50) 'Timezone'.
+ text 'PGPKey'.
+
+=cut
+
+
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Name => '',
+ Password => '',
+ Comments => '',
+ Signature => '',
+ EmailAddress => '',
+ FreeformContactInfo => '',
+ Organization => '',
+ RealName => '',
+ NickName => '',
+ Lang => '',
+ EmailEncoding => '',
+ WebEncoding => '',
+ ExternalContactInfoId => '',
+ ContactInfoSystem => '',
+ ExternalAuthId => '',
+ AuthSystem => '',
+ Gecos => '',
+ HomePhone => '',
+ WorkPhone => '',
+ MobilePhone => '',
+ PagerPhone => '',
+ Address1 => '',
+ Address2 => '',
+ City => '',
+ State => '',
+ Zip => '',
+ Country => '',
+ Timezone => '',
+ PGPKey => '',
+
+ @_);
+ $self->SUPER::Create(
+ Name => $args{'Name'},
+ Password => $args{'Password'},
+ Comments => $args{'Comments'},
+ Signature => $args{'Signature'},
+ EmailAddress => $args{'EmailAddress'},
+ FreeformContactInfo => $args{'FreeformContactInfo'},
+ Organization => $args{'Organization'},
+ RealName => $args{'RealName'},
+ NickName => $args{'NickName'},
+ Lang => $args{'Lang'},
+ EmailEncoding => $args{'EmailEncoding'},
+ WebEncoding => $args{'WebEncoding'},
+ ExternalContactInfoId => $args{'ExternalContactInfoId'},
+ ContactInfoSystem => $args{'ContactInfoSystem'},
+ ExternalAuthId => $args{'ExternalAuthId'},
+ AuthSystem => $args{'AuthSystem'},
+ Gecos => $args{'Gecos'},
+ HomePhone => $args{'HomePhone'},
+ WorkPhone => $args{'WorkPhone'},
+ MobilePhone => $args{'MobilePhone'},
+ PagerPhone => $args{'PagerPhone'},
+ Address1 => $args{'Address1'},
+ Address2 => $args{'Address2'},
+ City => $args{'City'},
+ State => $args{'State'},
+ Zip => $args{'Zip'},
+ Country => $args{'Country'},
+ Timezone => $args{'Timezone'},
+ PGPKey => $args{'PGPKey'},
+);
+
+}
+
+
+
+=item id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=item Name
+
+Returns the current value of Name.
+(In the database, Name is stored as varchar(200).)
+
+
+
+=item SetName VALUE
+
+
+Set Name to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Name will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Password
+
+Returns the current value of Password.
+(In the database, Password is stored as varchar(40).)
+
+
+
+=item SetPassword VALUE
+
+
+Set Password to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Password will be stored as a varchar(40).)
+
+
+=cut
+
+
+=item Comments
+
+Returns the current value of Comments.
+(In the database, Comments is stored as blob.)
+
+
+
+=item SetComments VALUE
+
+
+Set Comments to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Comments will be stored as a blob.)
+
+
+=cut
+
+
+=item Signature
+
+Returns the current value of Signature.
+(In the database, Signature is stored as blob.)
+
+
+
+=item SetSignature VALUE
+
+
+Set Signature to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Signature will be stored as a blob.)
+
+
+=cut
+
+
+=item EmailAddress
+
+Returns the current value of EmailAddress.
+(In the database, EmailAddress is stored as varchar(120).)
+
+
+
+=item SetEmailAddress VALUE
+
+
+Set EmailAddress to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, EmailAddress will be stored as a varchar(120).)
+
+
+=cut
+
+
+=item FreeformContactInfo
+
+Returns the current value of FreeformContactInfo.
+(In the database, FreeformContactInfo is stored as blob.)
+
+
+
+=item SetFreeformContactInfo VALUE
+
+
+Set FreeformContactInfo to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, FreeformContactInfo will be stored as a blob.)
+
+
+=cut
+
+
+=item Organization
+
+Returns the current value of Organization.
+(In the database, Organization is stored as varchar(200).)
+
+
+
+=item SetOrganization VALUE
+
+
+Set Organization to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Organization will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item RealName
+
+Returns the current value of RealName.
+(In the database, RealName is stored as varchar(120).)
+
+
+
+=item SetRealName VALUE
+
+
+Set RealName to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, RealName will be stored as a varchar(120).)
+
+
+=cut
+
+
+=item NickName
+
+Returns the current value of NickName.
+(In the database, NickName is stored as varchar(16).)
+
+
+
+=item SetNickName VALUE
+
+
+Set NickName to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, NickName will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item Lang
+
+Returns the current value of Lang.
+(In the database, Lang is stored as varchar(16).)
+
+
+
+=item SetLang VALUE
+
+
+Set Lang to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Lang will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item EmailEncoding
+
+Returns the current value of EmailEncoding.
+(In the database, EmailEncoding is stored as varchar(16).)
+
+
+
+=item SetEmailEncoding VALUE
+
+
+Set EmailEncoding to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, EmailEncoding will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item WebEncoding
+
+Returns the current value of WebEncoding.
+(In the database, WebEncoding is stored as varchar(16).)
+
+
+
+=item SetWebEncoding VALUE
+
+
+Set WebEncoding to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, WebEncoding will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item ExternalContactInfoId
+
+Returns the current value of ExternalContactInfoId.
+(In the database, ExternalContactInfoId is stored as varchar(100).)
+
+
+
+=item SetExternalContactInfoId VALUE
+
+
+Set ExternalContactInfoId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ExternalContactInfoId will be stored as a varchar(100).)
+
+
+=cut
+
+
+=item ContactInfoSystem
+
+Returns the current value of ContactInfoSystem.
+(In the database, ContactInfoSystem is stored as varchar(30).)
+
+
+
+=item SetContactInfoSystem VALUE
+
+
+Set ContactInfoSystem to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ContactInfoSystem will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item ExternalAuthId
+
+Returns the current value of ExternalAuthId.
+(In the database, ExternalAuthId is stored as varchar(100).)
+
+
+
+=item SetExternalAuthId VALUE
+
+
+Set ExternalAuthId to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, ExternalAuthId will be stored as a varchar(100).)
+
+
+=cut
+
+
+=item AuthSystem
+
+Returns the current value of AuthSystem.
+(In the database, AuthSystem is stored as varchar(30).)
+
+
+
+=item SetAuthSystem VALUE
+
+
+Set AuthSystem to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, AuthSystem will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item Gecos
+
+Returns the current value of Gecos.
+(In the database, Gecos is stored as varchar(16).)
+
+
+
+=item SetGecos VALUE
+
+
+Set Gecos to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Gecos will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item HomePhone
+
+Returns the current value of HomePhone.
+(In the database, HomePhone is stored as varchar(30).)
+
+
+
+=item SetHomePhone VALUE
+
+
+Set HomePhone to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, HomePhone will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item WorkPhone
+
+Returns the current value of WorkPhone.
+(In the database, WorkPhone is stored as varchar(30).)
+
+
+
+=item SetWorkPhone VALUE
+
+
+Set WorkPhone to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, WorkPhone will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item MobilePhone
+
+Returns the current value of MobilePhone.
+(In the database, MobilePhone is stored as varchar(30).)
+
+
+
+=item SetMobilePhone VALUE
+
+
+Set MobilePhone to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, MobilePhone will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item PagerPhone
+
+Returns the current value of PagerPhone.
+(In the database, PagerPhone is stored as varchar(30).)
+
+
+
+=item SetPagerPhone VALUE
+
+
+Set PagerPhone to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PagerPhone will be stored as a varchar(30).)
+
+
+=cut
+
+
+=item Address1
+
+Returns the current value of Address1.
+(In the database, Address1 is stored as varchar(200).)
+
+
+
+=item SetAddress1 VALUE
+
+
+Set Address1 to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Address1 will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item Address2
+
+Returns the current value of Address2.
+(In the database, Address2 is stored as varchar(200).)
+
+
+
+=item SetAddress2 VALUE
+
+
+Set Address2 to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Address2 will be stored as a varchar(200).)
+
+
+=cut
+
+
+=item City
+
+Returns the current value of City.
+(In the database, City is stored as varchar(100).)
+
+
+
+=item SetCity VALUE
+
+
+Set City to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, City will be stored as a varchar(100).)
+
+
+=cut
+
+
+=item State
+
+Returns the current value of State.
+(In the database, State is stored as varchar(100).)
+
+
+
+=item SetState VALUE
+
+
+Set State to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, State will be stored as a varchar(100).)
+
+
+=cut
+
+
+=item Zip
+
+Returns the current value of Zip.
+(In the database, Zip is stored as varchar(16).)
+
+
+
+=item SetZip VALUE
+
+
+Set Zip to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Zip will be stored as a varchar(16).)
+
+
+=cut
+
+
+=item Country
+
+Returns the current value of Country.
+(In the database, Country is stored as varchar(50).)
+
+
+
+=item SetCountry VALUE
+
+
+Set Country to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Country will be stored as a varchar(50).)
+
+
+=cut
+
+
+=item Timezone
+
+Returns the current value of Timezone.
+(In the database, Timezone is stored as varchar(50).)
+
+
+
+=item SetTimezone VALUE
+
+
+Set Timezone to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, Timezone will be stored as a varchar(50).)
+
+
+=cut
+
+
+=item PGPKey
+
+Returns the current value of PGPKey.
+(In the database, PGPKey is stored as text.)
+
+
+
+=item SetPGPKey VALUE
+
+
+Set PGPKey to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, PGPKey will be stored as a text.)
+
+
+=cut
+
+
+=item Creator
+
+Returns the current value of Creator.
+(In the database, Creator is stored as int(11).)
+
+
+=cut
+
+
+=item Created
+
+Returns the current value of Created.
+(In the database, Created is stored as datetime.)
+
+
+=cut
+
+
+=item LastUpdatedBy
+
+Returns the current value of LastUpdatedBy.
+(In the database, LastUpdatedBy is stored as int(11).)
+
+
+=cut
+
+
+=item LastUpdated
+
+Returns the current value of LastUpdated.
+(In the database, LastUpdated is stored as datetime.)
+
+
+=cut
+
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Password =>
+ {read => 1, write => 1, type => 'varchar(40)', default => ''},
+ Comments =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ Signature =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ EmailAddress =>
+ {read => 1, write => 1, type => 'varchar(120)', default => ''},
+ FreeformContactInfo =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ Organization =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ RealName =>
+ {read => 1, write => 1, type => 'varchar(120)', default => ''},
+ NickName =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ Lang =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ EmailEncoding =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ WebEncoding =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ ExternalContactInfoId =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ ContactInfoSystem =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ ExternalAuthId =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ AuthSystem =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ Gecos =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ HomePhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ WorkPhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ MobilePhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ PagerPhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ Address1 =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Address2 =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ City =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ State =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ Zip =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ Country =>
+ {read => 1, write => 1, type => 'varchar(50)', default => ''},
+ Timezone =>
+ {read => 1, write => 1, type => 'varchar(50)', default => ''},
+ PGPKey =>
+ {read => 1, write => 1, type => 'text', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => '0'},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+ eval "require RT::User_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/User_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::User_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/User_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::User_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/User_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::User_Overlay, RT::User_Vendor, RT::User_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/User_Overlay.pm b/rt/lib/RT/User_Overlay.pm
new file mode 100644
index 0000000..ba322cd
--- /dev/null
+++ b/rt/lib/RT/User_Overlay.pm
@@ -0,0 +1,1594 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::User - RT User object
+
+=head1 SYNOPSIS
+
+ use RT::User;
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::User);
+
+=end testing
+
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+use vars qw(%_USERS_KEY_CACHE);
+
+%_USERS_KEY_CACHE = ();
+
+use Digest::MD5;
+use RT::Principals;
+use RT::ACE;
+
+
+# {{{ sub _Accessible
+
+
+sub _ClassAccessible {
+ {
+
+ id =>
+ {read => 1, type => 'int(11)', default => ''},
+ Name =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(120)', default => ''},
+ Password =>
+ { write => 1, type => 'varchar(40)', default => ''},
+ Comments =>
+ {read => 1, write => 1, admin => 1, type => 'blob', default => ''},
+ Signature =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ EmailAddress =>
+ {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
+ FreeformContactInfo =>
+ {read => 1, write => 1, type => 'blob', default => ''},
+ Organization =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(200)', default => ''},
+ RealName =>
+ {read => 1, write => 1, public => 1, type => 'varchar(120)', default => ''},
+ NickName =>
+ {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
+ Lang =>
+ {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
+ EmailEncoding =>
+ {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
+ WebEncoding =>
+ {read => 1, write => 1, public => 1, type => 'varchar(16)', default => ''},
+ ExternalContactInfoId =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
+ ContactInfoSystem =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(30)', default => ''},
+ ExternalAuthId =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(100)', default => ''},
+ AuthSystem =>
+ {read => 1, write => 1, public => 1, admin => 1,type => 'varchar(30)', default => ''},
+ Gecos =>
+ {read => 1, write => 1, public => 1, admin => 1, type => 'varchar(16)', default => ''},
+
+ PGPKey => {
+ {read => 1, write => 1, public => 1, admin => 1, type => 'text', default => ''},
+ },
+ HomePhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ WorkPhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ MobilePhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ PagerPhone =>
+ {read => 1, write => 1, type => 'varchar(30)', default => ''},
+ Address1 =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ Address2 =>
+ {read => 1, write => 1, type => 'varchar(200)', default => ''},
+ City =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ State =>
+ {read => 1, write => 1, type => 'varchar(100)', default => ''},
+ Zip =>
+ {read => 1, write => 1, type => 'varchar(16)', default => ''},
+ Country =>
+ {read => 1, write => 1, type => 'varchar(50)', default => ''},
+ Creator =>
+ {read => 1, auto => 1, type => 'int(11)', default => ''},
+ Created =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+ LastUpdatedBy =>
+ {read => 1, auto => 1, type => 'int(11)', default => ''},
+ LastUpdated =>
+ {read => 1, auto => 1, type => 'datetime', default => ''},
+
+ }
+};
+
+
+# }}}
+
+# {{{ sub Create
+
+=head2 Create { PARAMHASH }
+
+
+=begin testing
+
+# Make sure we can create a user
+
+my $u1 = RT::User->new($RT::SystemUser);
+is(ref($u1), 'RT::User');
+my ($id, $msg) = $u1->Create(Name => 'CreateTest1', EmailAddress => 'create-test-1@example.com');
+ok ($id, "Creating user CreateTest1 - " . $msg );
+
+# Make sure we can't create a second user with the same name
+my $u2 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u2->Create(Name => 'CreateTest1', EmailAddress => 'create-test-2@example.com');
+ok (!$id, $msg);
+
+
+# Make sure we can't create a second user with the same EmailAddress address
+my $u3 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u3->Create(Name => 'CreateTest2', EmailAddress => 'create-test-1@example.com');
+ok (!$id, $msg);
+
+# Make sure we can create a user with no EmailAddress address
+my $u4 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u4->Create(Name => 'CreateTest3');
+ok ($id, $msg);
+
+# make sure we can create a second user with no EmailAddress address
+my $u5 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u5->Create(Name => 'CreateTest4');
+ok ($id, $msg);
+
+# make sure we can create a user with a blank EmailAddress address
+my $u6 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u6->Create(Name => 'CreateTest6', EmailAddress => '');
+ok ($id, $msg);
+# make sure we can create a second user with a blankEmailAddress address
+my $u7 = RT::User->new($RT::SystemUser);
+($id, $msg) = $u7->Create(Name => 'CreateTest7', EmailAddress => '');
+ok ($id, $msg);
+
+# Can we change the email address away from from "";
+($id,$msg) = $u7->SetEmailAddress('foo@bar');
+ok ($id, $msg);
+# can we change the address back to "";
+($id,$msg) = $u7->SetEmailAddress('');
+ok ($id, $msg);
+is ($u7->EmailAddress, '');
+
+
+=end testing
+
+=cut
+
+
+sub Create {
+ my $self = shift;
+ my %args = (
+ Privileged => 0,
+ Disabled => 0,
+ EmailAddress => '',
+ @_ # get the real argumentlist
+ );
+
+ #Check the ACL
+ unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
+ return ( 0, $self->loc('No permission to create users') );
+ }
+
+ $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'});
+ # if the user doesn't have a name defined, set it to the email address
+ $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'});
+
+
+
+ # Privileged is no longer a column in users
+ my $privileged = $args{'Privileged'};
+ delete $args{'Privileged'};
+
+
+ if ($args{'CryptedPassword'} ) {
+ $args{'Password'} = $args{'CryptedPassword'};
+ delete $args{'CryptedPassword'};
+ }
+ elsif ( !$args{'Password'} ) {
+ $args{'Password'} = '*NO-PASSWORD*';
+ }
+ elsif ( length( $args{'Password'} ) < $RT::MinimumPasswordLength ) {
+ return ( 0, $self->loc("Password too short") );
+ }
+
+ else {
+ $args{'Password'} = $self->_GeneratePassword($args{'Password'});
+ }
+
+ #TODO Specify some sensible defaults.
+
+ unless ( $args{'Name'} ) {
+ use Data::Dumper;
+ $RT::Logger->crit(Dumper \%args);
+ return ( 0, $self->loc("Must specify 'Name' attribute") );
+ }
+
+ #SANITY CHECK THE NAME AND ABORT IF IT'S TAKEN
+ if ($RT::SystemUser) { #This only works if RT::SystemUser has been defined
+ my $TempUser = RT::User->new($RT::SystemUser);
+ $TempUser->Load( $args{'Name'} );
+ return ( 0, $self->loc('Name in use') ) if ( $TempUser->Id );
+
+ return ( 0, $self->loc('Email address in use') )
+ unless ( $self->ValidateEmailAddress( $args{'EmailAddress'} ) );
+ }
+ else {
+ $RT::Logger->warning( "$self couldn't check for pre-existing users");
+ }
+
+
+ $RT::Handle->BeginTransaction();
+ # Groups deal with principal ids, rather than user ids.
+ # When creating this user, set up a principal Id for it.
+ my $principal = RT::Principal->new($self->CurrentUser);
+ my $principal_id = $principal->Create(PrincipalType => 'User',
+ Disabled => $args{'Disabled'},
+ ObjectId => '0');
+ $principal->__Set(Field => 'ObjectId', Value => $principal_id);
+ # If we couldn't create a principal Id, get the fuck out.
+ unless ($principal_id) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
+ return ( 0, $self->loc('Could not create user') );
+ }
+
+ delete $args{'Disabled'};
+
+ $self->SUPER::Create(id => $principal_id , %args);
+ my $id = $self->Id;
+
+ #If the create failed.
+ unless ($id) {
+ $RT::Handle->Rollback();
+ $RT::Logger->error("Could not create a new user - " .join('-'. %args));
+
+ return ( 0, $self->loc('Could not create user') );
+ }
+
+
+ #TODO post 2.0
+ #if ($args{'SendWelcomeMessage'}) {
+ # #TODO: Check if the email exists and looks valid
+ # #TODO: Send the user a "welcome message"
+ #}
+
+
+
+ my $aclstash = RT::Group->new($self->CurrentUser);
+ my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
+
+ unless ($stash_id) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("Couldn't stash the user in groumembers");
+ return ( 0, $self->loc('Could not create user') );
+ }
+
+ $RT::Handle->Commit;
+
+ #$RT::Logger->debug("Adding the user as a member of everyone");
+ my $everyone = RT::Group->new($self->CurrentUser);
+ $everyone->LoadSystemInternalGroup('Everyone');
+ $everyone->AddMember($self->PrincipalId);
+
+ if ($privileged) {
+ my $priv = RT::Group->new($self->CurrentUser);
+ #$RT::Logger->debug("Making ".$self->Id." a privileged user");
+ $priv->LoadSystemInternalGroup('Privileged');
+ $priv->AddMember($self->PrincipalId);
+ } else {
+ my $unpriv = RT::Group->new($self->CurrentUser);
+ #$RT::Logger->debug("Making ".$self->Id." an unprivileged user");
+ $unpriv->LoadSystemInternalGroup('Unprivileged');
+ $unpriv->AddMember($self->PrincipalId);
+ }
+
+
+ # $RT::Logger->debug("Finished creating the user");
+ return ( $id, $self->loc('User created') );
+}
+
+# }}}
+
+
+
+# {{{ SetPrivileged
+
+=head2 SetPrivileged BOOL
+
+If passed a true value, makes this user a member of the "Privileged" PseudoGroup.
+Otherwise, makes this user a member of the "Unprivileged" pseudogroup.
+
+Returns a standard RT tuple of (val, msg);
+
+=begin testing
+
+
+ok(my $user = RT::User->new($RT::SystemUser));
+ok($user->Load('root'), "Loaded user 'root'");
+ok($user->Privileged, "User 'root' is privileged");
+ok(my ($v,$m) = $user->SetPrivileged(0));
+ok ($v ==1, "Set unprivileged suceeded ($m)");
+ok(!$user->Privileged, "User 'root' is no longer privileged");
+ok(my ($v2,$m2) = $user->SetPrivileged(1));
+ok ($v2 ==1, "Set privileged suceeded ($m2");
+ok($user->Privileged, "User 'root' is privileged again");
+
+=end testing
+
+=cut
+
+sub SetPrivileged {
+ my $self = shift;
+ my $val = shift;
+
+ my $priv = RT::Group->new($self->CurrentUser);
+ $priv->LoadSystemInternalGroup('Privileged');
+
+ unless ($priv->Id) {
+ $RT::Logger->crit("Could not find Privileged pseudogroup");
+ return(0,$self->loc("Failed to find 'Privileged' users pseudogroup."));
+ }
+
+ my $unpriv = RT::Group->new($self->CurrentUser);
+ $unpriv->LoadSystemInternalGroup('Unprivileged');
+ unless ($unpriv->Id) {
+ $RT::Logger->crit("Could not find unprivileged pseudogroup");
+ return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup"));
+ }
+
+ if ($val) {
+ if ($priv->HasMember($self->PrincipalObj)) {
+ #$RT::Logger->debug("That user is already privileged");
+ return (0,$self->loc("That user is already privileged"));
+ }
+ if ($unpriv->HasMember($self->PrincipalObj)) {
+ $unpriv->DeleteMember($self->PrincipalId);
+ } else {
+ # if we had layered transactions, life would be good
+ # sadly, we have to just go ahead, even if something
+ # bogus happened
+ $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
+ "unprivileged. something is drastically wrong.");
+ }
+ my ($status, $msg) = $priv->AddMember($self->PrincipalId);
+ if ($status) {
+ return (1, $self->loc("That user is now privileged"));
+ } else {
+ return (0, $msg);
+ }
+ }
+ else {
+ if ($unpriv->HasMember($self->PrincipalObj)) {
+ #$RT::Logger->debug("That user is already unprivileged");
+ return (0,$self->loc("That user is already unprivileged"));
+ }
+ if ($priv->HasMember($self->PrincipalObj)) {
+ $priv->DeleteMember($self->PrincipalId);
+ } else {
+ # if we had layered transactions, life would be good
+ # sadly, we have to just go ahead, even if something
+ # bogus happened
+ $RT::Logger->crit("User ".$self->Id." is neither privileged nor ".
+ "unprivileged. something is drastically wrong.");
+ }
+ my ($status, $msg) = $unpriv->AddMember($self->PrincipalId);
+ if ($status) {
+ return (1, $self->loc("That user is now unprivileged"));
+ } else {
+ return (0, $msg);
+ }
+ }
+}
+
+# }}}
+
+# {{{ Privileged
+
+=head2 Privileged
+
+Returns true if this user is privileged. Returns undef otherwise.
+
+=cut
+
+sub Privileged {
+ my $self = shift;
+ my $priv = RT::Group->new($self->CurrentUser);
+ $priv->LoadSystemInternalGroup('Privileged');
+ if ($priv->HasMember($self->PrincipalObj)) {
+ return(1);
+ }
+ else {
+ return(undef);
+ }
+}
+
+# }}}
+
+# {{{ sub _BootstrapCreate
+
+#create a user without validating _any_ data.
+
+#To be used only on database init.
+# We can't localize here because it's before we _have_ a loc framework
+
+sub _BootstrapCreate {
+ my $self = shift;
+ my %args = (@_);
+
+ $args{'Password'} = '*NO-PASSWORD*';
+
+
+ $RT::Handle->BeginTransaction();
+
+ # Groups deal with principal ids, rather than user ids.
+ # When creating this user, set up a principal Id for it.
+ my $principal = RT::Principal->new($self->CurrentUser);
+ my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0');
+ $principal->__Set(Field => 'ObjectId', Value => $principal_id);
+
+ # If we couldn't create a principal Id, get the fuck out.
+ unless ($principal_id) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K");
+ return ( 0, 'Could not create user' );
+ }
+ $self->SUPER::Create(id => $principal_id, %args);
+ my $id = $self->Id;
+ #If the create failed.
+ unless ($id) {
+ $RT::Handle->Rollback();
+ return ( 0, 'Could not create user' ) ; #never loc this
+ }
+
+ my $aclstash = RT::Group->new($self->CurrentUser);
+ my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal);
+
+ unless ($stash_id) {
+ $RT::Handle->Rollback();
+ $RT::Logger->crit("Couldn't stash the user in groupmembers");
+ return ( 0, $self->loc('Could not create user') );
+ }
+
+
+ $RT::Handle->Commit();
+
+ return ( $id, 'User created' );
+}
+
+# }}}
+
+# {{{ sub Delete
+
+sub Delete {
+ my $self = shift;
+
+ return ( 0, $self->loc('Deleting this object would violate referential integrity') );
+
+}
+
+# }}}
+
+# {{{ sub Load
+
+=head2 Load
+
+Load a user object from the database. Takes a single argument.
+If the argument is numerical, load by the column 'id'. Otherwise, load by
+the "Name" column which is the user's textual username.
+
+=cut
+
+sub Load {
+ my $self = shift;
+ my $identifier = shift || return undef;
+
+ #if it's an int, load by id. otherwise, load by name.
+ if ( $identifier !~ /\D/ ) {
+ $self->SUPER::LoadById($identifier);
+ }
+ else {
+ $self->LoadByCol( "Name", $identifier );
+ }
+}
+
+# }}}
+
+# {{{ sub LoadByEmail
+
+=head2 LoadByEmail
+
+Tries to load this user object from the database by the user's email address.
+
+
+=cut
+
+sub LoadByEmail {
+ my $self = shift;
+ my $address = shift;
+
+ # Never load an empty address as an email address.
+ unless ($address) {
+ return (undef);
+ }
+
+ $address = $self->CanonicalizeEmailAddress($address);
+
+ #$RT::Logger->debug("Trying to load an email address: $address\n");
+ return $self->LoadByCol( "EmailAddress", $address );
+}
+
+# }}}
+
+# {{{ LoadOrCreateByEmail
+
+=head2 LoadOrCreateByEmail ADDRESS
+
+Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with
+the provided email address. and loads them.
+
+Returns a tuple of the user's id and a status message.
+0 will be returned in place of the user's id in case of failure.
+
+=cut
+
+sub LoadOrCreateByEmail {
+ my $self = shift;
+ my $email = shift;
+
+ my ($val, $message);
+
+ $self->LoadByEmail($email);
+ $message = $self->loc('User loaded');
+ unless ($self->Id) {
+ ( $val, $message ) = $self->Create(
+ Name => $email,
+ EmailAddress => $email,
+ RealName => $email,
+ Privileged => 0,
+ Comments => 'Autocreated when added as a watcher');
+ unless ($val) {
+ # Deal with the race condition of two account creations at once
+ $self->LoadByEmail($email);
+ unless ($self->Id) {
+ sleep 5;
+ $self->LoadByEmail($email);
+ }
+ if ($self->Id) {
+ $RT::Logger->error("Recovered from creation failure due to race condition");
+ $message = $self->loc("User loaded");
+ }
+ else {
+ $RT::Logger->crit("Failed to create user ".$email .": " .$message);
+ }
+ }
+ }
+
+ if ($self->Id) {
+ return($self->Id, $message);
+ }
+ else {
+ return(0, $message);
+ }
+
+
+ }
+
+# }}}
+
+# {{{ sub ValidateEmailAddress
+
+=head2 ValidateEmailAddress ADDRESS
+
+Returns true if the email address entered is not in use by another user or is
+undef or ''. Returns false if it's in use.
+
+=cut
+
+sub ValidateEmailAddress {
+ my $self = shift;
+ my $Value = shift;
+
+ # if the email address is null, it's always valid
+ return (1) if ( !$Value || $Value eq "" );
+
+ my $TempUser = RT::User->new($RT::SystemUser);
+ $TempUser->LoadByEmail($Value);
+
+ if ( $TempUser->id && ( $TempUser->id != $self->id ) )
+ { # if we found a user with that address
+ # it's invalid to set this user's address to it
+ return (undef);
+ }
+ else { #it's a valid email address
+ return (1);
+ }
+}
+
+# }}}
+
+# {{{ sub CanonicalizeEmailAddress
+
+
+
+=item CanonicalizeEmailAddress ADDRESS
+
+# CanonicalizeEmailAddress converts email addresses into canonical form.
+# it takes one email address in and returns the proper canonical
+# form. You can dump whatever your proper local config is in here
+
+=cut
+
+sub CanonicalizeEmailAddress {
+ my $self = shift;
+ my $email = shift;
+ # Example: the following rule would treat all email
+ # coming from a subdomain as coming from second level domain
+ # foo.com
+ if ($RT::CanonicalizeEmailAddressMatch && $RT::CanonicalizeEmailAddressReplace ) {
+ $email =~ s/$RT::CanonicalizeEmailAddressMatch/$RT::CanonicalizeEmailAddressReplace/gi;
+ }
+ return ($email);
+}
+
+
+# }}}
+
+
+# {{{ Password related functions
+
+# {{{ sub SetRandomPassword
+
+=head2 SetRandomPassword
+
+Takes no arguments. Returns a status code and a new password or an error message.
+If the status is 1, the second value returned is the new password.
+If the status is anything else, the new value returned is the error code.
+
+=cut
+
+sub SetRandomPassword {
+ my $self = shift;
+
+ unless ( $self->CurrentUserCanModify('Password') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ my $pass = $self->GenerateRandomPassword( 6, 8 );
+
+ # If we have "notify user on
+
+ my ( $val, $msg ) = $self->SetPassword($pass);
+
+ #If we got an error return the error.
+ return ( 0, $msg ) unless ($val);
+
+ #Otherwise, we changed the password, lets return it.
+ return ( 1, $pass );
+
+}
+
+# }}}
+
+# {{{ sub ResetPassword
+
+=head2 ResetPassword
+
+Returns status, [ERROR or new password]. Resets this user\'s password to
+a randomly generated pronouncable password and emails them, using a
+global template called "RT_PasswordChange", which can be overridden
+with global templates "RT_PasswordChange_Privileged" or "RT_PasswordChange_NonPrivileged"
+for privileged and Non-privileged users respectively.
+
+=cut
+
+sub ResetPassword {
+ my $self = shift;
+
+ unless ( $self->CurrentUserCanModify('Password') ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ my ( $status, $pass ) = $self->SetRandomPassword();
+
+ unless ($status) {
+ return ( 0, "$pass" );
+ }
+
+ my $template = RT::Template->new( $self->CurrentUser );
+
+ if ( $self->Privileged ) {
+ $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
+ }
+ else {
+ $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
+ }
+
+ unless ( $template->Id ) {
+ $template->LoadGlobalTemplate('RT_PasswordChange');
+ }
+
+ unless ( $template->Id ) {
+ $RT::Logger->crit( "$self tried to send "
+ . $self->Name
+ . " a password reminder "
+ . "but couldn't find a password change template" );
+ }
+
+ my $notification = RT::Action::SendPasswordEmail->new(
+ TemplateObj => $template,
+ Argument => $pass
+ );
+
+ $notification->SetTo( $self->EmailAddress );
+
+ my ($ret);
+ $ret = $notification->Prepare();
+ if ($ret) {
+ $ret = $notification->Commit();
+ }
+
+ if ($ret) {
+ return ( 1, $self->loc('New password notification sent') );
+ }
+ else {
+ return ( 0, $self->loc('Notification could not be sent') );
+ }
+
+}
+
+# }}}
+
+# {{{ sub GenerateRandomPassword
+
+=head2 GenerateRandomPassword MIN_LEN and MAX_LEN
+
+Returns a random password between MIN_LEN and MAX_LEN characters long.
+
+=cut
+
+sub GenerateRandomPassword {
+ my $self = shift;
+ my $min_length = shift;
+ my $max_length = shift;
+
+ #This code derived from mpw.pl, a bit of code with a sordid history
+ # Its notes:
+
+ # Perl cleaned up a bit by Jesse Vincent 1/14/2001.
+ # Converted to perl from C by Marc Horowitz, 1/20/2000.
+ # Converted to C from Multics PL/I by Bill Sommerfeld, 4/21/86.
+ # Original PL/I version provided by Jerry Saltzer.
+
+ my ( $frequency, $start_freq, $total_sum, $row_sums );
+
+ #When munging characters, we need to know where to start counting letters from
+ my $a = ord('a');
+
+ # frequency of English digraphs (from D Edwards 1/27/66)
+ $frequency = [
+ [
+ 4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167,
+ 2, 14, 0, 83, 76, 127, 7, 25, 8, 1, 9, 1
+ ], # aa - az
+ [
+ 13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0,
+ 11, 0, 0, 15, 4, 2, 13, 0, 0, 0, 15, 0
+ ], # ba - bz
+ [
+ 32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0,
+ 50, 3, 0, 10, 0, 28, 11, 0, 0, 0, 3, 0
+ ], # ca - cz
+ [
+ 40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6,
+ 16, 4, 0, 21, 18, 53, 19, 5, 15, 0, 3, 0
+ ], # da - dz
+ [
+ 84, 20, 55, 125, 51, 40, 19, 16, 50, 1,
+ 4, 55, 54, 146, 35, 37, 6, 191, 149, 65,
+ 9, 26, 21, 12, 5, 0
+ ], # ea - ez
+ [
+ 19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0,
+ 51, 0, 0, 26, 8, 47, 6, 3, 3, 0, 2, 0
+ ], # fa - fz
+ [
+ 20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4,
+ 21, 1, 1, 20, 9, 21, 9, 0, 5, 0, 1, 0
+ ], # ga - gz
+ [
+ 101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2,
+ 44, 1, 0, 3, 10, 18, 6, 0, 5, 0, 3, 0
+ ], # ha - hz
+ [
+ 40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202,
+ 56, 12, 1, 46, 79, 117, 1, 22, 0, 4, 0, 3
+ ], # ia - iz
+ [
+ 3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0
+ ], # ja - jz
+ [
+ 1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 6, 2, 1, 0, 2, 0, 1, 0
+ ], # ka - kz
+ [
+ 44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2,
+ 25, 1, 1, 2, 16, 23, 9, 0, 1, 0, 33, 0
+ ], # la - lz
+ [
+ 52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1,
+ 17, 18, 1, 2, 12, 3, 8, 0, 1, 0, 2, 0
+ ], # ma - mz
+ [
+ 42, 10, 47, 122, 63, 19, 106, 12, 30, 1,
+ 6, 6, 9, 7, 54, 7, 1, 7, 44, 124,
+ 6, 1, 15, 0, 12, 0
+ ], # na - nz
+ [
+ 7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134,
+ 13, 23, 0, 91, 23, 42, 55, 16, 28, 0, 4, 1
+ ], # oa - oz
+ [
+ 19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0,
+ 27, 9, 0, 33, 14, 7, 6, 0, 0, 0, 0, 0
+ ], # pa - pz
+ [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0
+ ], # qa - qz
+ [
+ 83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16,
+ 60, 4, 0, 24, 37, 55, 6, 11, 4, 0, 28, 0
+ ], # ra - rz
+ [
+ 65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12,
+ 56, 17, 6, 9, 48, 116, 35, 1, 28, 0, 4, 0
+ ], # sa - sz
+ [
+ 57, 22, 3, 1, 76, 5, 2, 330, 126, 1,
+ 0, 14, 10, 6, 79, 7, 0, 49, 50, 56,
+ 21, 2, 27, 0, 24, 0
+ ], # ta - tz
+ [
+ 11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31,
+ 1, 15, 0, 47, 39, 31, 0, 3, 0, 0, 0, 0
+ ], # ua - uz
+ [
+ 7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0,
+ 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0
+ ], # va - vz
+ [
+ 36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8,
+ 15, 0, 0, 0, 4, 2, 0, 0, 1, 0, 0, 0
+ ], # wa - wz
+ [
+ 1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0,
+ 1, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0
+ ], # xa - xz
+ [
+ 14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5,
+ 17, 3, 0, 4, 16, 30, 0, 0, 5, 0, 0, 0
+ ], # ya - yz
+ [
+ 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+ ]; # za - zz
+
+ #We need to know the totals for each row
+ $row_sums = [
+ map {
+ my $sum = 0;
+ map { $sum += $_ } @$_;
+ $sum;
+ } @$frequency
+ ];
+
+ #Frequency with which a given letter starts a word.
+ $start_freq = [
+ 1299, 425, 725, 271, 375, 470, 93, 223, 1009, 24,
+ 20, 355, 379, 319, 823, 618, 21, 317, 962, 1991,
+ 271, 104, 516, 6, 16, 14
+ ];
+
+ $total_sum = 0;
+ map { $total_sum += $_ } @$start_freq;
+
+ my $length = $min_length + int( rand( $max_length - $min_length ) );
+
+ my $char = $self->_GenerateRandomNextChar( $total_sum, $start_freq );
+ my @word = ( $char + $a );
+ for ( 2 .. $length ) {
+ $char =
+ $self->_GenerateRandomNextChar( $row_sums->[$char],
+ $frequency->[$char] );
+ push ( @word, $char + $a );
+ }
+
+ #Return the password
+ return pack( "C*", @word );
+
+}
+
+#A private helper function for RandomPassword
+# Takes a row summary and a frequency chart for the next character to be searched
+sub _GenerateRandomNextChar {
+ my $self = shift;
+ my ( $all, $freq ) = @_;
+ my ( $pos, $i );
+
+ for ( $pos = int( rand($all) ), $i = 0 ;
+ $pos >= $freq->[$i] ;
+ $pos -= $freq->[$i], $i++ )
+ {
+ }
+
+ return ($i);
+}
+
+# }}}
+
+# {{{ sub SetPassword
+
+=head2 SetPassword
+
+Takes a string. Checks the string's length and sets this user's password
+to that string.
+
+=cut
+
+sub SetPassword {
+ my $self = shift;
+ my $password = shift;
+
+ unless ( $self->CurrentUserCanModify('Password') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ if ( !$password ) {
+ return ( 0, $self->loc("No password set") );
+ }
+ elsif ( length($password) < $RT::MinimumPasswordLength ) {
+ return ( 0, $self->loc("Password too short") );
+ }
+ else {
+ $password = $self->_GeneratePassword($password);
+ return ( $self->SUPER::SetPassword( $password));
+ }
+
+}
+
+=head2 _GeneratePassword PASSWORD
+
+returns an MD5 hash of the password passed in, in base64 encoding.
+
+=cut
+
+sub _GeneratePassword {
+ my $self = shift;
+ my $password = shift;
+
+ my $md5 = Digest::MD5->new();
+ $md5->add($password);
+ return ($md5->b64digest);
+
+}
+
+# }}}
+
+# {{{ sub IsPassword
+
+=head2 IsPassword
+
+Returns true if the passed in value is this user's password.
+Returns undef otherwise.
+
+=cut
+
+sub IsPassword {
+ my $self = shift;
+ my $value = shift;
+
+ #TODO there isn't any apparent way to legitimately ACL this
+
+ # RT does not allow null passwords
+ if ( ( !defined($value) ) or ( $value eq '' ) ) {
+ return (undef);
+ }
+
+ if ( $self->PrincipalObj->Disabled ) {
+ $RT::Logger->info(
+ "Disabled user " . $self->Name . " tried to log in" );
+ return (undef);
+ }
+
+ if ( ($self->__Value('Password') eq '') ||
+ ($self->__Value('Password') eq undef) ) {
+ return(undef);
+ }
+
+ # generate an md5 password
+ if ($self->_GeneratePassword($value) eq $self->__Value('Password')) {
+ return(1);
+ }
+
+ # if it's a historical password we say ok.
+
+ if ( $self->__Value('Password') eq crypt( $value, $self->__Value('Password') ) ) {
+ return (1);
+ }
+
+ # no password check has succeeded. get out
+
+ return (undef);
+}
+
+# }}}
+
+# }}}
+
+# {{{ sub SetDisabled
+
+=head2 Sub SetDisabled
+
+Toggles the user's disabled flag.
+If this flag is
+set, all password checks for this user will fail. All ACL checks for this
+user will fail. The user will appear in no user listings.
+
+=cut
+
+# }}}
+
+sub SetDisabled {
+ my $self = shift;
+ unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
+ return (0, $self->loc('Permission Denied'));
+ }
+ return $self->PrincipalObj->SetDisabled(@_);
+}
+
+sub Disabled {
+ my $self = shift;
+ return $self->PrincipalObj->Disabled(@_);
+}
+
+
+# {{{ Principal related routines
+
+=head2 PrincipalObj
+
+Returns the principal object for this user. returns an empty RT::Principal
+if there's no principal object matching this user.
+The response is cached. PrincipalObj should never ever change.
+
+=begin testing
+
+ok(my $u = RT::User->new($RT::SystemUser));
+ok($u->Load(1), "Loaded the first user");
+ok($u->PrincipalObj->ObjectId == 1, "user 1 is the first principal");
+ok($u->PrincipalObj->PrincipalType eq 'User' , "Principal 1 is a user, not a group");
+
+=end testing
+
+=cut
+
+
+sub PrincipalObj {
+ my $self = shift;
+ unless ($self->{'PrincipalObj'} &&
+ ($self->{'PrincipalObj'}->ObjectId == $self->Id) &&
+ ($self->{'PrincipalObj'}->PrincipalType eq 'User')) {
+
+ $self->{'PrincipalObj'} = RT::Principal->new($self->CurrentUser);
+ $self->{'PrincipalObj'}->LoadByCols('ObjectId' => $self->Id,
+ 'PrincipalType' => 'User') ;
+ }
+ return($self->{'PrincipalObj'});
+}
+
+
+=head2 PrincipalId
+
+Returns this user's PrincipalId
+
+=cut
+
+sub PrincipalId {
+ my $self = shift;
+ return $self->Id;
+}
+
+# }}}
+
+
+
+# {{{ sub HasGroupRight
+
+=head2 HasGroupRight
+
+Takes a paramhash which can contain
+these items:
+ GroupObj => RT::Group or Group => integer
+ Right => 'Right'
+
+
+Returns 1 if this user has the right specified in the paramhash for the Group
+passed in.
+
+Returns undef if they don't.
+
+=cut
+
+sub HasGroupRight {
+ my $self = shift;
+ my %args = (
+ GroupObj => undef,
+ Group => undef,
+ Right => undef,
+ @_
+ );
+
+
+ if ( defined $args{'Group'} ) {
+ $args{'GroupObj'} = RT::Group->new( $self->CurrentUser );
+ $args{'GroupObj'}->Load( $args{'Group'} );
+ }
+
+ # {{{ Validate and load up the GroupId
+ unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) {
+ return undef;
+ }
+
+ # }}}
+
+
+ # Figure out whether a user has the right we're asking about.
+ my $retval = $self->HasRight(
+ Object => $args{'GroupObj'},
+ Right => $args{'Right'},
+ );
+
+ return ($retval);
+
+
+}
+
+# }}}
+
+# {{{ sub Rights testing
+
+=head2 Rights testing
+
+
+=begin testing
+
+my $root = RT::User->new($RT::SystemUser);
+$root->Load('root');
+ok($root->Id, "Found the root user");
+my $rootq = RT::Queue->new($root);
+$rootq->Load(1);
+ok($rootq->Id, "Loaded the first queue");
+
+ok ($rootq->CurrentUser->HasRight(Right=> 'CreateTicket', Object => $rootq), "Root can create tickets");
+
+my $new_user = RT::User->new($RT::SystemUser);
+my ($id, $msg) = $new_user->Create(Name => 'ACLTest');
+
+ok ($id, "Created a new user for acl test $msg");
+
+my $q = RT::Queue->new($new_user);
+$q->Load(1);
+ok($q->Id, "Loaded the first queue");
+
+
+ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "Some random user doesn't have the right to create tickets");
+ok (my ($gval, $gmsg) = $new_user->PrincipalObj->GrantRight( Right => 'CreateTicket', Object => $q), "Granted the random user the right to create tickets");
+ok ($gval, "Grant succeeded - $gmsg");
+
+
+ok ($q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can create tickets after we grant him the right");
+ok (my ($gval, $gmsg) = $new_user->PrincipalObj->RevokeRight( Right => 'CreateTicket', Object => $q), "revoked the random user the right to create tickets");
+ok ($gval, "Revocation succeeded - $gmsg");
+ok (!$q->CurrentUser->HasRight(Right => 'CreateTicket', Object => $q), "The user can't create tickets anymore");
+
+
+
+
+
+# Create a ticket in the queue
+my $new_tick = RT::Ticket->new($RT::SystemUser);
+my ($tickid, $tickmsg) = $new_tick->Create(Subject=> 'ACL Test', Queue => 'General');
+ok($tickid, "Created ticket: $tickid");
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+# Create a new group
+my $group = RT::Group->new($RT::SystemUser);
+$group->CreateUserDefinedGroup(Name => 'ACLTest');
+ok($group->Id, "Created a new group Ok");
+# Grant a group the right to modify tickets in a queue
+ok(my ($gv,$gm) = $group->PrincipalObj->GrantRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
+ok($gv,"Grant succeeed - $gm");
+# Add the user to the group
+ok( my ($aid, $amsg) = $group->AddMember($new_user->PrincipalId), "Added the member to the group");
+ok ($aid, "Member added to group: $amsg");
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can modify the ticket with group membership");
+
+
+# Remove the user from the group
+ok( my ($did, $dmsg) = $group->DeleteMember($new_user->PrincipalId), "Deleted the member from the group");
+ok ($did,"Deleted the group member: $dmsg");
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+
+
+my $q_as_system = RT::Queue->new($RT::SystemUser);
+$q_as_system->Load(1);
+ok($q_as_system->Id, "Loaded the first queue");
+
+# Create a ticket in the queue
+my $new_tick2 = RT::Ticket->new($RT::SystemUser);
+my ($tick2id, $tickmsg) = $new_tick2->Create(Subject=> 'ACL Test 2', Queue =>$q_as_system->Id);
+ok($tick2id, "Created ticket: $tick2id");
+ok($new_tick2->QueueObj->id eq $q_as_system->Id, "Created a new ticket in queue 1");
+
+
+# make sure that the user can't do this without subgroup membership
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+
+# Create a subgroup
+my $subgroup = RT::Group->new($RT::SystemUser);
+$subgroup->CreateUserDefinedGroup(Name => 'Subgrouptest');
+ok($subgroup->Id, "Created a new group ".$subgroup->Id."Ok");
+#Add the subgroup as a subgroup of the group
+my ($said, $samsg) = $group->AddMember($subgroup->PrincipalId);
+ok ($said, "Added the subgroup as a member of the group");
+# Add the user to a subgroup of the group
+
+my ($usaid, $usamsg) = $subgroup->AddMember($new_user->PrincipalId);
+ok($usaid,"Added the user ".$new_user->Id."to the subgroup");
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket with subgroup membership");
+
+# {{{ Deal with making sure that members of subgroups of a disabled group don't have rights
+
+my ($id, $msg);
+ ($id, $msg) = $group->SetDisabled(1);
+ ok ($id,$msg);
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$group->Id. " is disabled");
+ ($id, $msg) = $group->SetDisabled(0);
+ok($id,$msg);
+# Test what happens when we disable the group the user is a member of directly
+
+($id, $msg) = $subgroup->SetDisabled(1);
+ ok ($id,$msg);
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket when the group ".$subgroup->Id. " is disabled");
+ ($id, $msg) = $subgroup->SetDisabled(0);
+ ok ($id,$msg);
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket without group membership");
+
+# }}}
+
+
+my ($usrid, $usrmsg) = $subgroup->DeleteMember($new_user->PrincipalId);
+ok($usrid,"removed the user from the group - $usrmsg");
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+
+#revoke the right to modify tickets in a queue
+ok(($gv,$gm) = $group->PrincipalObj->RevokeRight( Object => $q, Right => 'ModifyTicket'),"Granted the group the right to modify tickets");
+ok($gv,"revoke succeeed - $gm");
+
+# {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _queue_ level
+
+# Grant queue admin cc the right to modify ticket in the queue
+ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $q_as_system, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
+ok($qv, "Granted the right successfully - $qm");
+
+# Add the user as a queue admincc
+ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
+ok ($add_id, "the user is now a queue admincc - $add_msg");
+
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
+# Remove the user from the role group
+ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
+
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+
+# }}}
+
+# {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
+
+# Add the user as a ticket admincc
+ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
+ok ($add_id, "the user is now a queue admincc - $add_msg");
+
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
+
+# Remove the user from the role group
+ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
+
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+
+
+# Revoke the right to modify ticket in the queue
+ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $q_as_system, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
+ok($rqv, "Revoked the right successfully - $rqm");
+
+# }}}
+
+
+
+# {{{ Test the user's right to modify a ticket as a _queue_ admincc for a right granted at the _system_ level
+
+# Before we start Make sure the user does not have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without it being granted");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without it being granted");
+
+# Grant queue admin cc the right to modify ticket in the queue
+ok(my ($qv,$qm) = $q_as_system->AdminCc->PrincipalObj->GrantRight( Object => $RT::System, Right => 'ModifyTicket'),"Granted the queue adminccs the right to modify tickets");
+ok($qv, "Granted the right successfully - $qm");
+
+# Make sure the user can't modify the ticket before they're added as a watcher
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue without being an admincc");
+
+# Add the user as a queue admincc
+ok ((my $add_id, $add_msg) = $q_as_system->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
+ok ($add_id, "the user is now a queue admincc - $add_msg");
+
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
+ok ($new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can modify tickets in the queue as an admincc");
+# Remove the user from the role group
+ok ((my $del_id, $del_msg) = $q_as_system->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
+
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without group membership");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can't modify tickets in the queue without group membership");
+
+# }}}
+
+# {{{ Test the user's right to modify a ticket as a _ticket_ admincc with the right granted at the _queue_ level
+
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can not modify the ticket without being an admincc");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
+
+
+# Add the user as a ticket admincc
+ok ((my $uadd_id, $uadd_msg) = $new_tick2->AddWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Added the new user as a queue admincc");
+ok ($add_id, "the user is now a queue admincc - $add_msg");
+
+# Make sure the user does have the right to modify tickets in the queue
+ok ($new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can modify the ticket as an admincc");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj being only a ticket admincc");
+
+# Remove the user from the role group
+ok ((my $del_id, $del_msg) = $new_tick2->DeleteWatcher(Type => 'AdminCc', PrincipalId => $new_user->PrincipalId) , "Deleted the new user as a queue admincc");
+
+# Make sure the user doesn't have the right to modify tickets in the queue
+ok (!$new_user->HasRight( Object => $new_tick2, Right => 'ModifyTicket'), "User can't modify the ticket without being an admincc");
+ok (!$new_user->HasRight( Object => $new_tick2->QueueObj, Right => 'ModifyTicket'), "User can not modify tickets in the queue obj without being an admincc");
+
+
+# Revoke the right to modify ticket in the queue
+ok(my ($rqv,$rqm) = $q_as_system->AdminCc->PrincipalObj->RevokeRight( Object => $RT::System, Right => 'ModifyTicket'),"Revokeed the queue adminccs the right to modify tickets");
+ok($rqv, "Revoked the right successfully - $rqm");
+
+# }}}
+
+
+
+
+# Grant "privileged users" the system right to create users
+# Create a privileged user.
+# have that user create another user
+# Revoke the right for privileged users to create users
+# have the privileged user try to create another user and fail the ACL check
+
+=end testing
+
+=cut
+
+# }}}
+
+
+# {{{ sub HasRight
+
+=head2 sub HasRight
+
+Shim around PrincipalObj->HasRight. See RT::Principal
+
+=cut
+
+sub HasRight {
+
+ my $self = shift;
+ return $self->PrincipalObj->HasRight(@_);
+}
+
+# }}}
+
+# {{{ sub CurrentUserCanModify
+
+=head2 CurrentUserCanModify RIGHT
+
+If the user has rights for this object, either because
+he has 'AdminUsers' or (if he\'s trying to edit himself and the right isn\'t an
+admin right) 'ModifySelf', return 1. otherwise, return undef.
+
+=cut
+
+sub CurrentUserCanModify {
+ my $self = shift;
+ my $right = shift;
+
+ if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) {
+ return (1);
+ }
+
+ #If the field is marked as an "administrators only" field,
+ # don\'t let the user touch it.
+ elsif ( $self->_Accessible( $right, 'admin' ) ) {
+ return (undef);
+ }
+
+ #If the current user is trying to modify themselves
+ elsif ( ( $self->id == $self->CurrentUser->id )
+ and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) )
+ {
+ return (1);
+ }
+
+ #If we don\'t have a good reason to grant them rights to modify
+ # by now, they lose
+ else {
+ return (undef);
+ }
+
+}
+
+# }}}
+
+# {{{ sub CurrentUserHasRight
+
+=head2 CurrentUserHasRight
+
+ Takes a single argument. returns 1 if $Self->CurrentUser
+ has the requested right. returns undef otherwise
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+
+ return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) );
+}
+
+# }}}
+
+# {{{ sub _Set
+
+sub _Set {
+ my $self = shift;
+
+ my %args = (
+ Field => undef,
+ Value => undef,
+ @_
+ );
+
+ # Nobody is allowed to futz with RT_System or Nobody
+
+ if ( ($self->Id == $RT::SystemUser->Id ) ||
+ ($self->Id == $RT::Nobody->Id)) {
+ return ( 0, $self->loc("Can not modify system users") );
+ }
+ unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) {
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ #Set the new value
+ my ( $ret, $msg ) = $self->SUPER::_Set(
+ Field => $args{'Field'},
+ Value => $args{'Value'}
+ );
+
+ return ( $ret, $msg );
+}
+
+# }}}
+
+# {{{ sub _Value
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+
+ my $self = shift;
+ my $field = shift;
+
+ #If the current user doesn't have ACLs, don't let em at it.
+
+ my @PublicFields = qw( Name EmailAddress Organization Disabled
+ RealName NickName Gecos ExternalAuthId
+ AuthSystem ExternalContactInfoId
+ ContactInfoSystem );
+
+ #if the field is public, return it.
+ if ( $self->_Accessible( $field, 'public' ) ) {
+ return ( $self->SUPER::_Value($field) );
+
+ }
+
+ #If the user wants to see their own values, let them
+ # TODO figure ouyt a better way to deal with this
+ elsif ( $self->CurrentUser->Id == $self->Id ) {
+ return ( $self->SUPER::_Value($field) );
+ }
+
+ #If the user has the admin users right, return the field
+ elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
+ return ( $self->SUPER::_Value($field) );
+ }
+ else {
+ return (undef);
+ }
+
+}
+
+# }}}
+
+
+1;
+
+
diff --git a/rt/lib/RT/Users.pm b/rt/lib/RT/Users.pm
new file mode 100755
index 0000000..d58f696
--- /dev/null
+++ b/rt/lib/RT/Users.pm
@@ -0,0 +1,115 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+
+
+=head1 NAME
+
+ RT::Users -- Class Description
+
+=head1 SYNOPSIS
+
+ use RT::Users
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package RT::Users;
+
+use RT::SearchBuilder;
+use RT::User;
+
+use vars qw( @ISA );
+@ISA= qw(RT::SearchBuilder);
+
+
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Users';
+ $self->{'primary_key'} = 'id';
+
+
+ return ( $self->SUPER::_Init(@_) );
+}
+
+
+=item NewItem
+
+Returns an empty new RT::User item
+
+=cut
+
+sub NewItem {
+ my $self = shift;
+ return(RT::User->new($self->CurrentUser));
+}
+
+ eval "require RT::Users_Overlay";
+ if ($@ && $@ !~ qr{^Can't locate RT/Users_Overlay.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Users_Vendor";
+ if ($@ && $@ !~ qr{^Can't locate RT/Users_Vendor.pm}) {
+ die $@;
+ };
+
+ eval "require RT::Users_Local";
+ if ($@ && $@ !~ qr{^Can't locate RT/Users_Local.pm}) {
+ die $@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows "overlay" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+RT::Users_Overlay, RT::Users_Vendor, RT::Users_Local
+
+=cut
+
+
+1;
diff --git a/rt/lib/RT/Users_Overlay.pm b/rt/lib/RT/Users_Overlay.pm
new file mode 100644
index 0000000..430e6d7
--- /dev/null
+++ b/rt/lib/RT/Users_Overlay.pm
@@ -0,0 +1,310 @@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+=head1 NAME
+
+ RT::Users - Collection of RT::User objects
+
+=head1 SYNOPSIS
+
+ use RT::Users;
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=begin testing
+
+ok(require RT::Users);
+
+=end testing
+
+=cut
+
+use strict;
+no warnings qw(redefine);
+
+# {{{ sub _Init
+sub _Init {
+ my $self = shift;
+ $self->{'table'} = 'Users';
+ $self->{'primary_key'} = 'id';
+
+
+
+ my @result = $self->SUPER::_Init(@_);
+ # By default, order by name
+ $self->OrderBy( ALIAS => 'main',
+ FIELD => 'Name',
+ ORDER => 'ASC' );
+
+ $self->{'princalias'} = $self->NewAlias('Principals');
+
+ $self->Join( ALIAS1 => 'main',
+ FIELD1 => 'id',
+ ALIAS2 => $self->{'princalias'},
+ FIELD2 => 'id' );
+
+ $self->Limit( ALIAS => $self->{'princalias'},
+ FIELD => 'PrincipalType',
+ OPERATOR => '=',
+ VALUE => 'User' );
+ return (@result);
+}
+
+# }}}
+
+=head2 PrincipalsAlias
+
+Returns the string that represents this Users object's primary "Principals" alias.
+
+
+=cut
+
+sub PrincipalsAlias {
+ my $self = shift;
+ return($self->{'princalias'});
+
+}
+
+
+# {{{ sub _DoSearch
+
+=head2 _DoSearch
+
+ A subclass of DBIx::SearchBuilder::_DoSearch that makes sure that _Disabled rows never get seen unless
+we're explicitly trying to see them.
+
+=cut
+
+sub _DoSearch {
+ my $self = shift;
+
+ #unless we really want to find disabled rows, make sure we\'re only finding enabled ones.
+ unless ( $self->{'find_disabled_rows'} ) {
+ $self->LimitToEnabled();
+ }
+ return ( $self->SUPER::_DoSearch(@_) );
+
+}
+
+# }}}
+# {{{ sub LimitToEnabled
+
+=head2 LimitToEnabled
+
+Only find items that haven\'t been disabled
+
+=cut
+
+sub LimitToEnabled {
+ my $self = shift;
+
+ $self->Limit( ALIAS => $self->{'princalias'},
+ FIELD => 'Disabled',
+ VALUE => '0',
+ OPERATOR => '=' );
+}
+
+# }}}
+
+# {{{ LimitToEmail
+
+=head2 LimitToEmail
+
+Takes one argument. an email address. limits the returned set to
+that email address
+
+=cut
+
+sub LimitToEmail {
+ my $self = shift;
+ my $addr = shift;
+ $self->Limit( FIELD => 'EmailAddress', VALUE => "$addr" );
+}
+
+# }}}
+
+# {{{ MemberOfGroup
+
+=head2 MemberOfGroup PRINCIPAL_ID
+
+takes one argument, a group's principal id. Limits the returned set
+to members of a given group
+
+=cut
+
+sub MemberOfGroup {
+ my $self = shift;
+ my $group = shift;
+
+ return $self->loc("No group specified") if ( !defined $group );
+
+ my $groupalias = $self->NewAlias('CachedGroupMembers');
+
+ # Join the principal to the groups table
+ $self->Join( ALIAS1 => $self->{'princalias'},
+ FIELD1 => 'id',
+ ALIAS2 => $groupalias,
+ FIELD2 => 'MemberId' );
+
+ $self->Limit( ALIAS => "$groupalias",
+ FIELD => 'GroupId',
+ VALUE => "$group",
+ OPERATOR => "=" );
+}
+
+# }}}
+
+# {{{ LimitToPrivileged
+
+=head2 LimitToPrivileged
+
+Limits to users who can be made members of ACLs and groups
+
+=cut
+
+sub LimitToPrivileged {
+ my $self = shift;
+
+ my $priv = RT::Group->new( $self->CurrentUser );
+ $priv->LoadSystemInternalGroup('Privileged');
+ unless ( $priv->Id ) {
+ $RT::Logger->crit("Couldn't find a privileged users group");
+ }
+ $self->MemberOfGroup( $priv->PrincipalId );
+}
+
+# }}}
+
+# {{{ WhoHaveRight
+
+=head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef }
+
+=begin testing
+
+ok(my $users = RT::Users->new($RT::SystemUser));
+$users->WhoHaveRight(Object =>$RT::System, Right =>'SuperUser');
+ok($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
+# TODO: this wants more testing
+
+
+=end testing
+
+
+find all users who the right Right for this group, either individually
+or as members of groups
+
+
+
+
+
+=cut
+
+sub WhoHaveRight {
+ my $self = shift;
+ my %args = ( Right => undef,
+ Object => => undef,
+ IncludeSystemRights => undef,
+ IncludeSuperusers => undef,
+ IncludeSubgroupMembers => 1,
+ @_ );
+
+ if (defined $args{'ObjectType'} || defined $args{'ObjectId'}) {
+ $RT::Logger->crit("$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
+ return(undef);
+ }
+ my @privgroups;
+ my $Groups = RT::Groups->new($RT::SystemUser);
+ $Groups->WithRight(Right=> $args{'Right'},
+ Object => $args{'Object'},
+ IncludeSystemRights => $args{'IncludeSystemRights'},
+ IncludeSuperusers => $args{'IncludeSuperusers'});
+ while (my $Group = $Groups->Next()) {
+ push @privgroups, $Group->Id();
+ }
+
+
+ if (@privgroups) {
+ $self->WhoBelongToGroups(Groups => \@privgroups,
+ IncludeSubgroupMembers => $args{'IncludeSubgroupMembers'});
+ }
+ else {
+ # We don't have any group that matches -- make it impossible.
+ $self->Limit( FIELD => 'Id', VALUE => 'IS', OPERATOR => 'NULL' );
+ }
+}
+
+# }}}
+
+# {{{ WhoBelongToGroups
+
+=head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
+
+=cut
+
+sub WhoBelongToGroups {
+ my $self = shift;
+ my %args = ( Groups => undef,
+ IncludeSubgroupMembers => 1,
+ @_ );
+
+ # Unprivileged users can't be granted real system rights.
+ # is this really the right thing to be saying?
+ $self->LimitToPrivileged();
+
+ my $userprinc = $self->{'princalias'};
+ my $cgm;
+
+ # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups
+ # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
+ # if we don't we'll find only 'direct' members of the group in question
+
+ if ( $args{'IncludeSubgroupMembers'} ) {
+ $cgm = $self->NewAlias('CachedGroupMembers');
+ }
+ else {
+ $cgm = $self->NewAlias('GroupMembers');
+ }
+
+ # {{{ Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
+ $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId',
+ ALIAS2 => $userprinc, FIELD2 => 'id' );
+ # }}}
+
+ # my $and_check_groups = "($cgm.GroupId = NULL";
+ foreach my $groupid (@{$args{'Groups'}}) {
+ $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR')
+
+ #$and_check_groups .= " OR $cgm.GroupId = $groupid";
+ }
+ #$and_check_groups .= ")";
+
+ #$self->_AddSubClause("WhichGroup", $and_check_groups);
+}
+# }}}
+
+
+1;
diff --git a/rt/lib/t/00smoke.t b/rt/lib/t/00smoke.t
new file mode 100644
index 0000000..4f36bb3
--- /dev/null
+++ b/rt/lib/t/00smoke.t
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use Test::More qw(no_plan);
+
+use lib "/opt/rt3/lib";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+use File::Find;
+File::Find::find({wanted => \&wanted}, '.');
+sub wanted { /^*\.pm\z/s && ok(require $_, "Requiring '$_'"); }
+
+
diff --git a/rt/lib/t/00smoke.t.in b/rt/lib/t/00smoke.t.in
new file mode 100644
index 0000000..11f0a9c
--- /dev/null
+++ b/rt/lib/t/00smoke.t.in
@@ -0,0 +1,14 @@
+#!@PERL@
+
+use Test::More qw(no_plan);
+
+use lib "@RT_LIB_PATH@";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+use File::Find;
+File::Find::find({wanted => \&wanted}, '.');
+sub wanted { /^*\.pm\z/s && ok(require $_, "Requiring '$_'"); }
+
+
diff --git a/rt/lib/t/01harness.t b/rt/lib/t/01harness.t
new file mode 100644
index 0000000..98c28d2
--- /dev/null
+++ b/rt/lib/t/01harness.t
@@ -0,0 +1,12 @@
+#!/usr/bin/perl
+
+use Test::More qw(no_plan);
+
+use lib "/opt/rt3/lib";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+my $test = shift @ARGV;
+require $test;
+
diff --git a/rt/lib/t/01harness.t.in b/rt/lib/t/01harness.t.in
new file mode 100644
index 0000000..d132330
--- /dev/null
+++ b/rt/lib/t/01harness.t.in
@@ -0,0 +1,12 @@
+#!@PERL@
+
+use Test::More qw(no_plan);
+
+use lib "@RT_LIB_PATH@";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+my $test = shift @ARGV;
+require $test;
+
diff --git a/rt/lib/t/02regression.t b/rt/lib/t/02regression.t
new file mode 100644
index 0000000..4504cc7
--- /dev/null
+++ b/rt/lib/t/02regression.t
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+use Test::More qw(no_plan);
+
+use lib "/opt/rt3/lib";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+# Create a new queue
+use_ok(RT::Queue);
+my $q = RT::Queue->new($RT::SystemUser);
+
+$q->Load('regression');
+if ($q->id != 0) {
+ die "Regression tests not starting with a clean DB. Bailing";
+}
+
+my ($id, $msg) = $q->Create( Name => 'Regression',
+ Description => 'A regression test queue',
+ CorrespondAddress => 'correspond@a',
+ CommentAddress => 'comment@a');
+
+isnt($id, 0, "Queue was created sucessfully - $msg");
+
+my $q2 = RT::Queue->new($RT::SystemUser);
+
+ok($q2->Load($id));
+is($q2->id, $id, "Sucessfully loaded the queue again");
+is($q2->Name, 'Regression');
+is($q2->Description, 'A regression test queue');
+is($q2->CorrespondAddress, 'correspond@a');
+is($q2->CommentAddress, 'comment@a');
+
+
+use File::Find;
+File::Find::find({wanted => \&wanted_autogen}, 'lib/t/autogen');
+sub wanted_autogen { /^autogen.*\.t\z/s && require $_; }
+
+File::Find::find({wanted => \&wanted_regression}, 'lib/t/regression');
+sub wanted_regression { /^*\.t\z/s && require $_; }
+
+require "/opt/rt3/lib/t/03web.pl";
+require "/opt/rt3/lib/t/04_send_email.pl";
diff --git a/rt/lib/t/02regression.t.in b/rt/lib/t/02regression.t.in
new file mode 100644
index 0000000..8c050ff
--- /dev/null
+++ b/rt/lib/t/02regression.t.in
@@ -0,0 +1,44 @@
+#!@PERL@
+
+use Test::More qw(no_plan);
+
+use lib "@RT_LIB_PATH@";
+use RT;
+ok(RT::LoadConfig);
+ok(RT::Init, "Basic initialization and DB connectivity");
+
+# Create a new queue
+use_ok(RT::Queue);
+my $q = RT::Queue->new($RT::SystemUser);
+
+$q->Load('regression');
+if ($q->id != 0) {
+ die "Regression tests not starting with a clean DB. Bailing";
+}
+
+my ($id, $msg) = $q->Create( Name => 'Regression',
+ Description => 'A regression test queue',
+ CorrespondAddress => 'correspond@a',
+ CommentAddress => 'comment@a');
+
+isnt($id, 0, "Queue was created sucessfully - $msg");
+
+my $q2 = RT::Queue->new($RT::SystemUser);
+
+ok($q2->Load($id));
+is($q2->id, $id, "Sucessfully loaded the queue again");
+is($q2->Name, 'Regression');
+is($q2->Description, 'A regression test queue');
+is($q2->CorrespondAddress, 'correspond@a');
+is($q2->CommentAddress, 'comment@a');
+
+
+use File::Find;
+File::Find::find({wanted => \&wanted_autogen}, 'lib/t/autogen');
+sub wanted_autogen { /^autogen.*\.t\z/s && require $_; }
+
+File::Find::find({wanted => \&wanted_regression}, 'lib/t/regression');
+sub wanted_regression { /^*\.t\z/s && require $_; }
+
+require "@RT_LIB_PATH@/t/03web.pl";
+require "@RT_LIB_PATH@/t/04_send_email.pl";
diff --git a/rt/lib/t/03web.pl b/rt/lib/t/03web.pl
new file mode 100644
index 0000000..94ad3e9
--- /dev/null
+++ b/rt/lib/t/03web.pl
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+
+use strict;
+use WWW::Mechanize;
+use HTTP::Request::Common;
+use HTTP::Cookies;
+use LWP;
+use Encode;
+
+my $cookie_jar = HTTP::Cookies->new;
+my $agent = WWW::Mechanize->new();
+
+# give the agent a place to stash the cookies
+
+$agent->cookie_jar($cookie_jar);
+
+
+# get the top page
+my $url = "http://localhost".$RT::WebPath."/";
+$agent->get($url);
+
+is ($agent->{'status'}, 200, "Loaded a page");
+
+
+# {{{ test a login
+
+# follow the link marked "Login"
+
+ok($agent->{form}->find_input('user'));
+
+ok($agent->{form}->find_input('pass'));
+ok ($agent->{'content'} =~ /username:/i);
+$agent->field( 'user' => 'root' );
+$agent->field( 'pass' => 'password' );
+# the field isn't named, so we have to click link 0
+$agent->click(0);
+is($agent->{'status'}, 200, "Fetched the page ok");
+ok( $agent->{'content'} =~ /Logout/i, "Found a logout link");
+
+
+
+$agent->get($url."Ticket/Create.html?Queue=1");
+is ($agent->{'status'}, 200, "Loaded Create.html");
+$agent->form(3);
+# Start with a string containing characters in latin1
+my $string = "I18N Web Testing æøå";
+Encode::from_to($string, 'iso-8859-1', 'utf8');
+$agent->field('Subject' => "Foo");
+$agent->field('Content' => $string);
+ok($agent->submit(), "Created new ticket with $string");
+
+ok( $agent->{'content'} =~ qr{$string} , "Found the content");
+
+$agent->get($url."Ticket/Create.html?Queue=1");
+is ($agent->{'status'}, 200, "Loaded Create.html");
+$agent->form(3);
+# Start with a string containing characters in latin1
+my $string = "I18N Web Testing æøå";
+Encode::from_to($string, 'iso-8859-1', 'utf8');
+$agent->field('Subject' => $string);
+$agent->field('Content' => "BAR");
+ok($agent->submit(), "Created new ticket with $string");
+
+ok( $agent->{'content'} =~ qr{$string} , "Found the content");
+
+
+
+# }}}
+
+
+
+use File::Find;
+find ( \&wanted , 'html/');
+
+sub wanted {
+ -f && /\.html$/ && $_ !~ /Logout.html$/ && test_get($File::Find::name);
+}
+
+sub test_get {
+ my $file = shift;
+
+
+ $file =~ s#^html/##;
+ ok ($agent->get("$url/$file", "GET $url/$file"));
+ is ($agent->{'status'}, 200, "Loaded $file");
+ ok( $agent->{'content'} =~ /Logout/i, "Found a logout link on $file ");
+ ok( $agent->{'content'} !~ /Not logged in/i, "Still logged in for $file");
+ ok( $agent->{'content'} !~ /System error/i, "Didn't get a Mason compilation error on $file");
+
+}
+
+# }}}
+
+1;
diff --git a/rt/lib/t/03web.pl.in b/rt/lib/t/03web.pl.in
new file mode 100644
index 0000000..4fe2d3e
--- /dev/null
+++ b/rt/lib/t/03web.pl.in
@@ -0,0 +1,94 @@
+#!@PERL@
+
+use strict;
+use WWW::Mechanize;
+use HTTP::Request::Common;
+use HTTP::Cookies;
+use LWP;
+use Encode;
+
+my $cookie_jar = HTTP::Cookies->new;
+my $agent = WWW::Mechanize->new();
+
+# give the agent a place to stash the cookies
+
+$agent->cookie_jar($cookie_jar);
+
+
+# get the top page
+my $url = "http://localhost".$RT::WebPath."/";
+$agent->get($url);
+
+is ($agent->{'status'}, 200, "Loaded a page");
+
+
+# {{{ test a login
+
+# follow the link marked "Login"
+
+ok($agent->{form}->find_input('user'));
+
+ok($agent->{form}->find_input('pass'));
+ok ($agent->{'content'} =~ /username:/i);
+$agent->field( 'user' => 'root' );
+$agent->field( 'pass' => 'password' );
+# the field isn't named, so we have to click link 0
+$agent->click(0);
+is($agent->{'status'}, 200, "Fetched the page ok");
+ok( $agent->{'content'} =~ /Logout/i, "Found a logout link");
+
+
+
+$agent->get($url."Ticket/Create.html?Queue=1");
+is ($agent->{'status'}, 200, "Loaded Create.html");
+$agent->form(3);
+# Start with a string containing characters in latin1
+my $string = "I18N Web Testing æøå";
+Encode::from_to($string, 'iso-8859-1', 'utf8');
+$agent->field('Subject' => "Foo");
+$agent->field('Content' => $string);
+ok($agent->submit(), "Created new ticket with $string");
+
+ok( $agent->{'content'} =~ qr{$string} , "Found the content");
+
+$agent->get($url."Ticket/Create.html?Queue=1");
+is ($agent->{'status'}, 200, "Loaded Create.html");
+$agent->form(3);
+# Start with a string containing characters in latin1
+my $string = "I18N Web Testing æøå";
+Encode::from_to($string, 'iso-8859-1', 'utf8');
+$agent->field('Subject' => $string);
+$agent->field('Content' => "BAR");
+ok($agent->submit(), "Created new ticket with $string");
+
+ok( $agent->{'content'} =~ qr{$string} , "Found the content");
+
+
+
+# }}}
+
+
+
+use File::Find;
+find ( \&wanted , 'html/');
+
+sub wanted {
+ -f && /\.html$/ && $_ !~ /Logout.html$/ && test_get($File::Find::name);
+}
+
+sub test_get {
+ my $file = shift;
+
+
+ $file =~ s#^html/##;
+ ok ($agent->get("$url/$file", "GET $url/$file"));
+ is ($agent->{'status'}, 200, "Loaded $file");
+ ok( $agent->{'content'} =~ /Logout/i, "Found a logout link on $file ");
+ ok( $agent->{'content'} !~ /Not logged in/i, "Still logged in for $file");
+ ok( $agent->{'content'} !~ /System error/i, "Didn't get a Mason compilation error on $file");
+
+}
+
+# }}}
+
+1;
diff --git a/rt/lib/t/04_send_email.pl b/rt/lib/t/04_send_email.pl
new file mode 100644
index 0000000..c384eed
--- /dev/null
+++ b/rt/lib/t/04_send_email.pl
@@ -0,0 +1,481 @@
+#!/usr/bin/perl -w
+
+use strict;
+use RT::EmailParser;
+use RT::Tickets;
+use RT::Action::SendEmail;
+
+my @_outgoing_messages;
+my @scrips_fired;
+
+#We're not testing acls here.
+my $everyone = RT::Group->new($RT::SystemUser);
+$everyone->LoadSystemInternalGroup('Everyone');
+$everyone->PrincipalObj->GrantRight(Right =>'SuperUser');
+
+
+is (__PACKAGE__, 'main', "We're operating in the main package");
+
+
+{
+no warnings qw/redefine/;
+sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ main::_fired_scrip($self->ScripObj);
+ main::ok(ref($MIME) eq 'MIME::Entity', "hey, look. it's a mime entity");
+}
+
+}
+
+# instrument SendEmail to pass us what it's about to send.
+# create a regular ticket
+
+my $parser = RT::EmailParser->new();
+
+
+# Let's test to make sure a multipart/report is processed correctly
+my $content = `cat /opt/rt3/lib/t/data/multipart-report` || die "couldn't find new content";
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /The original message was received/, "It's the bounce");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+
+undef @scrips_fired;
+
+
+
+
+$parser->ParseMIMEEntityFromScalar('From: root@localhost
+To: rt@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!');
+
+
+use Data::Dumper;
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($id, $tid, $msg ) = $ticket->Create(Requestor => ['root@localhost'], Queue => 'general', Subject => 'I18NTest', MIMEObj => $parser->Entity);
+ok ($id,$msg);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'I18NTest', "failed to create the new ticket from an unprivileged account");
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+# make sure it sends a notification to adminccs
+
+
+# we need to swap out SendMessage to test the new things we care about;
+&utf8_redef_sendmessage;
+
+# create an iso 8859-1 ticket
+@scrips_fired = ();
+
+my $content = `cat /opt/rt3/lib/t/data/new-ticket-from-iso-8859-1` || die "couldn't find new content";
+
+
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /H\x{e5}vard/, "It's signed by havard. yay");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+
+
+# make sure it sends a notification to adminccs
+
+# If we correspond, does it do the right thing to the outbound messages?
+
+$parser->ParseMIMEEntityFromScalar($content);
+my ($id, $msg) = $tick->Comment(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+$parser->ParseMIMEEntityFromScalar($content);
+($id, $msg) = $tick->Correspond(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+
+
+
+
+# we need to swap out SendMessage to test the new things we care about;
+&iso8859_redef_sendmessage;
+$RT::EmailOutputEncoding = 'iso-8859-1';
+# create an iso 8859-1 ticket
+@scrips_fired = ();
+
+my $content = `cat /opt/rt3/lib/t/data/new-ticket-from-iso-8859-1` || die "couldn't find new content";
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /H\x{e5}vard/, "It's signed by havard. yay");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+
+
+# make sure it sends a notification to adminccs
+
+
+# If we correspond, does it do the right thing to the outbound messages?
+
+$parser->ParseMIMEEntityFromScalar($content);
+my ($id, $msg) = $tick->Comment(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+$parser->ParseMIMEEntityFromScalar($content);
+($id, $msg) = $tick->Correspond(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+
+sub _fired_scrip {
+ my $scrip = shift;
+ push @scrips_fired, $scrip;
+}
+
+sub utf8_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval '
+ sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ my $scrip = $self->ScripObj->id;
+ ok(1, $self->ScripObj->ConditionObj->Name . " ".$self->ScripObj->ActionObj->Name);
+ main::_fired_scrip($self->ScripObj);
+ $MIME->make_singlepart;
+ main::ok( ref($MIME) eq \'MIME::Entity\',
+ "hey, look. it\'s a mime entity" );
+ main::ok( ref( $MIME->head ) eq \'MIME::Head\',
+ "its mime header is a mime header. yay" );
+ main::ok( $MIME->head->get(\'Content-Type\') =~ /utf-8/,
+ "Its content type is utf-8" );
+ my $message_as_string = $MIME->bodyhandle->as_string();
+ use Encode;
+ $message_as_string = Encode::decode_utf8($message_as_string);
+ main::ok(
+ $message_as_string =~ /H\x{e5}vard/,
+"The message\'s content contains havard\'s name. this will fail if it\'s not utf8 out");
+
+ }';
+}
+
+sub iso8859_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval '
+ sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ my $scrip = $self->ScripObj->id;
+ ok(1, $self->ScripObj->ConditionObj->Name . " ".$self->ScripObj->ActionObj->Name);
+ main::_fired_scrip($self->ScripObj);
+ $MIME->make_singlepart;
+ main::ok( ref($MIME) eq \'MIME::Entity\',
+ "hey, look. it\'s a mime entity" );
+ main::ok( ref( $MIME->head ) eq \'MIME::Head\',
+ "its mime header is a mime header. yay" );
+ main::ok( $MIME->head->get(\'Content-Type\') =~ /iso-8859-1/,
+ "Its content type is iso-8859-1 - " . $MIME->head->get("Content-Type") );
+ my $message_as_string = $MIME->bodyhandle->as_string();
+ use Encode;
+ $message_as_string = Encode::decode("iso-8859-1",$message_as_string);
+ main::ok(
+ $message_as_string =~ /H\x{e5}vard/, "The message\'s content contains havard\'s name. this will fail if it\'s not utf8 out");
+
+ }';
+}
+
+# {{{ test a multipart alternative containing a text-html part with an umlaut
+
+my $content = `cat /opt/rt3/lib/t/data/multipart-alternative-with-umlaut` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&umlauts_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /causes Error/, "We recorded the content right as text-plain");
+is ($tick->Transactions->First->Attachments->Count , 3 , "Has three attachments, presumably a text-plain, a text-html and a multipart alternative");
+
+sub umlauts_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+# }}}
+
+# {{{ test a text-html message with an umlaut
+
+my $content = `cat /opt/rt3/lib/t/data/text-html-with-umlaut` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_html_umlauts_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->Content =~ /causes Error/, "We recorded the content as containing 'causes error'");
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/html/, "We recorded the content as text/html");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-html and a multipart alternative");
+
+sub text_html_umlauts_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ use Data::Dumper;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ ok (is $MIME->parts, 2, "generated correspondence mime entityis composed of three parts");
+ is ($MIME->head->mime_type , "multipart/mixed", "The first part is a multipart mixed". $MIME->head->mime_type);
+ is ($MIME->parts(0)->head->mime_type , "text/plain", "The second part is a plain");
+ is ($MIME->parts(1)->head->mime_type , "text/html", "The third part is an html ");
+ }';
+}
+
+# }}}
+
+# {{{ test a text-html message with russian characters
+
+my $content = `cat /opt/rt3/lib/t/data/text-html-in-russian` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_html_russian_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/html/, "We recorded the content right as text-html");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-html and a multipart alternative");
+
+sub text_html_russian_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ use Data::Dumper;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ ok (is $MIME->parts, 2, "generated correspondence mime entityis composed of three parts");
+ is ($MIME->head->mime_type , "multipart/mixed", "The first part is a multipart mixed". $MIME->head->mime_type);
+ is ($MIME->parts(0)->head->mime_type , "text/plain", "The second part is a plain");
+ is ($MIME->parts(1)->head->mime_type , "text/html", "The third part is an html ");
+ my $content_1251;
+ $content_1251 = $MIME->parts(1)->bodyhandle->as_string();
+ ok ($content_1251 =~ qr{Ó÷eáíûé Öeíòp "ÊÀÄÐÛ ÄÅËÎÂÎÃÎ ÌÈÐÀ" ïpèãëaøaeò ía òpeíèíã:},
+"Content matches drugim in codepage 1251" );
+ }';
+}
+
+# }}}
+
+# {{{ test a message containing a russian subject and NO content type
+
+unshift (@RT::EmailInputEncodings, 'koi8-r');
+$RT::EmailOutputEncoding = 'koi8-r';
+my $content = `cat /opt/rt3/lib/t/data/russian-subject-no-content-type` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_plain_russian_redef_sendmessage;
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/plain/, "We recorded the content type right");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-plain");
+is ($tick->Subject, "\x{442}\x{435}\x{441}\x{442} \x{442}\x{435}\x{441}\x{442}", "Recorded the subject right");
+sub text_plain_russian_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ is ($MIME->head->mime_type , "text/plain", "The only part is text/plain ");
+ my $subject = $MIME->head->get("subject");
+ chomp($subject);
+ #is( $subject , /^=\?KOI8-R\?B\?W2V4YW1wbGUuY39tICM3XSDUxdPUINTF09Q=\?=/ , "The $subject is encoded correctly");
+ };
+ ';
+}
+
+shift @RT::EmailInputEncodings;
+$RT::EmailOutputEncoding = 'utf-8';
+# }}}
+
+
+# {{{ test a message containing a nested RFC 822 message
+
+my $content = `cat /opt/rt3/lib/t/data/nested-rfc-822` || die "couldn't find new content";
+ok ($content, "Loaded nested-rfc-822 to test");
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_plain_nested_redef_sendmessage;
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+is ($tick->Subject, "[Jonas Liljegren] Re: [Para] Niv\x{e5}er?");
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /multipart\/mixed/, "We recorded the content type right");
+is ($tick->Transactions->First->Attachments->Count , 5 , "Has one attachment, presumably a text-plain and a message RFC 822 and another plain");
+sub text_plain_nested_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ is ($MIME->head->mime_type , "multipart/mixed", "It is a mixed multipart");
+ my $subject = $MIME->head->get("subject");
+ $subject = MIME::Base64::decode_base64( $subject);
+ chomp($subject);
+ # TODO, why does this test fail
+ #ok($subject =~ qr{Niv\x{e5}er}, "The subject matches the word - $subject");
+ 1;
+ }';
+}
+
+# }}}
+
+
+# {{{ test a multipart alternative containing a uuencoded mesage generated by lotus notes
+
+my $content = `cat /opt/rt3/lib/t/data/notes-uuencoded` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&notes_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /from Lotus Notes/, "We recorded the content right");
+is ($tick->Transactions->First->Attachments->Count , 3 , "Has three attachments");
+
+sub notes_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+# }}}
+
+# {{{ test a multipart that crashes the file-based mime-parser works
+
+my $content = `cat /opt/rt3/lib/t/data/crashes-file-based-parser` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&crashes_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /FYI/, "We recorded the content right");
+is ($tick->Transactions->First->Attachments->Count , 5 , "Has three attachments");
+
+sub crashes_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+
+
+# }}}
+
+# Don't taint the environment
+$everyone->PrincipalObj->RevokeRight(Right =>'SuperUser');
+1;
diff --git a/rt/lib/t/04_send_email.pl.in b/rt/lib/t/04_send_email.pl.in
new file mode 100644
index 0000000..2bdf4c5
--- /dev/null
+++ b/rt/lib/t/04_send_email.pl.in
@@ -0,0 +1,481 @@
+#!@PERL@ -w
+
+use strict;
+use RT::EmailParser;
+use RT::Tickets;
+use RT::Action::SendEmail;
+
+my @_outgoing_messages;
+my @scrips_fired;
+
+#We're not testing acls here.
+my $everyone = RT::Group->new($RT::SystemUser);
+$everyone->LoadSystemInternalGroup('Everyone');
+$everyone->PrincipalObj->GrantRight(Right =>'SuperUser');
+
+
+is (__PACKAGE__, 'main', "We're operating in the main package");
+
+
+{
+no warnings qw/redefine/;
+sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ main::_fired_scrip($self->ScripObj);
+ main::ok(ref($MIME) eq 'MIME::Entity', "hey, look. it's a mime entity");
+}
+
+}
+
+# instrument SendEmail to pass us what it's about to send.
+# create a regular ticket
+
+my $parser = RT::EmailParser->new();
+
+
+# Let's test to make sure a multipart/report is processed correctly
+my $content = `cat @RT_LIB_PATH@/t/data/multipart-report` || die "couldn't find new content";
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /The original message was received/, "It's the bounce");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+
+undef @scrips_fired;
+
+
+
+
+$parser->ParseMIMEEntityFromScalar('From: root@localhost
+To: rt@example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!');
+
+
+use Data::Dumper;
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ($id, $tid, $msg ) = $ticket->Create(Requestor => ['root@localhost'], Queue => 'general', Subject => 'I18NTest', MIMEObj => $parser->Entity);
+ok ($id,$msg);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+ok ($tick->Subject eq 'I18NTest', "failed to create the new ticket from an unprivileged account");
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+# make sure it sends a notification to adminccs
+
+
+# we need to swap out SendMessage to test the new things we care about;
+&utf8_redef_sendmessage;
+
+# create an iso 8859-1 ticket
+@scrips_fired = ();
+
+my $content = `cat @RT_LIB_PATH@/t/data/new-ticket-from-iso-8859-1` || die "couldn't find new content";
+
+
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /H\x{e5}vard/, "It's signed by havard. yay");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+
+
+# make sure it sends a notification to adminccs
+
+# If we correspond, does it do the right thing to the outbound messages?
+
+$parser->ParseMIMEEntityFromScalar($content);
+my ($id, $msg) = $tick->Comment(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+$parser->ParseMIMEEntityFromScalar($content);
+($id, $msg) = $tick->Correspond(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+
+
+
+
+# we need to swap out SendMessage to test the new things we care about;
+&iso8859_redef_sendmessage;
+$RT::EmailOutputEncoding = 'iso-8859-1';
+# create an iso 8859-1 ticket
+@scrips_fired = ();
+
+my $content = `cat @RT_LIB_PATH@/t/data/new-ticket-from-iso-8859-1` || die "couldn't find new content";
+# be as much like the mail gateway as possible.
+use RT::Interface::Email;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /H\x{e5}vard/, "It's signed by havard. yay");
+
+
+# make sure it fires scrips.
+is ($#scrips_fired, 1, "Fired 2 scrips on ticket creation");
+# make sure it sends an autoreply
+
+
+# make sure it sends a notification to adminccs
+
+
+# If we correspond, does it do the right thing to the outbound messages?
+
+$parser->ParseMIMEEntityFromScalar($content);
+my ($id, $msg) = $tick->Comment(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+$parser->ParseMIMEEntityFromScalar($content);
+($id, $msg) = $tick->Correspond(MIMEObj => $parser->Entity);
+ok ($id, $msg);
+
+
+sub _fired_scrip {
+ my $scrip = shift;
+ push @scrips_fired, $scrip;
+}
+
+sub utf8_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval '
+ sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ my $scrip = $self->ScripObj->id;
+ ok(1, $self->ScripObj->ConditionObj->Name . " ".$self->ScripObj->ActionObj->Name);
+ main::_fired_scrip($self->ScripObj);
+ $MIME->make_singlepart;
+ main::ok( ref($MIME) eq \'MIME::Entity\',
+ "hey, look. it\'s a mime entity" );
+ main::ok( ref( $MIME->head ) eq \'MIME::Head\',
+ "its mime header is a mime header. yay" );
+ main::ok( $MIME->head->get(\'Content-Type\') =~ /utf-8/,
+ "Its content type is utf-8" );
+ my $message_as_string = $MIME->bodyhandle->as_string();
+ use Encode;
+ $message_as_string = Encode::decode_utf8($message_as_string);
+ main::ok(
+ $message_as_string =~ /H\x{e5}vard/,
+"The message\'s content contains havard\'s name. this will fail if it\'s not utf8 out");
+
+ }';
+}
+
+sub iso8859_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval '
+ sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+
+ my $scrip = $self->ScripObj->id;
+ ok(1, $self->ScripObj->ConditionObj->Name . " ".$self->ScripObj->ActionObj->Name);
+ main::_fired_scrip($self->ScripObj);
+ $MIME->make_singlepart;
+ main::ok( ref($MIME) eq \'MIME::Entity\',
+ "hey, look. it\'s a mime entity" );
+ main::ok( ref( $MIME->head ) eq \'MIME::Head\',
+ "its mime header is a mime header. yay" );
+ main::ok( $MIME->head->get(\'Content-Type\') =~ /iso-8859-1/,
+ "Its content type is iso-8859-1 - " . $MIME->head->get("Content-Type") );
+ my $message_as_string = $MIME->bodyhandle->as_string();
+ use Encode;
+ $message_as_string = Encode::decode("iso-8859-1",$message_as_string);
+ main::ok(
+ $message_as_string =~ /H\x{e5}vard/, "The message\'s content contains havard\'s name. this will fail if it\'s not utf8 out");
+
+ }';
+}
+
+# {{{ test a multipart alternative containing a text-html part with an umlaut
+
+my $content = `cat @RT_LIB_PATH@/t/data/multipart-alternative-with-umlaut` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&umlauts_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /causes Error/, "We recorded the content right as text-plain");
+is ($tick->Transactions->First->Attachments->Count , 3 , "Has three attachments, presumably a text-plain, a text-html and a multipart alternative");
+
+sub umlauts_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+# }}}
+
+# {{{ test a text-html message with an umlaut
+
+my $content = `cat @RT_LIB_PATH@/t/data/text-html-with-umlaut` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_html_umlauts_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->Content =~ /causes Error/, "We recorded the content as containing 'causes error'");
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/html/, "We recorded the content as text/html");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-html and a multipart alternative");
+
+sub text_html_umlauts_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ use Data::Dumper;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ ok (is $MIME->parts, 2, "generated correspondence mime entityis composed of three parts");
+ is ($MIME->head->mime_type , "multipart/mixed", "The first part is a multipart mixed". $MIME->head->mime_type);
+ is ($MIME->parts(0)->head->mime_type , "text/plain", "The second part is a plain");
+ is ($MIME->parts(1)->head->mime_type , "text/html", "The third part is an html ");
+ }';
+}
+
+# }}}
+
+# {{{ test a text-html message with russian characters
+
+my $content = `cat @RT_LIB_PATH@/t/data/text-html-in-russian` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_html_russian_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/html/, "We recorded the content right as text-html");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-html and a multipart alternative");
+
+sub text_html_russian_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ use Data::Dumper;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ ok (is $MIME->parts, 2, "generated correspondence mime entityis composed of three parts");
+ is ($MIME->head->mime_type , "multipart/mixed", "The first part is a multipart mixed". $MIME->head->mime_type);
+ is ($MIME->parts(0)->head->mime_type , "text/plain", "The second part is a plain");
+ is ($MIME->parts(1)->head->mime_type , "text/html", "The third part is an html ");
+ my $content_1251;
+ $content_1251 = $MIME->parts(1)->bodyhandle->as_string();
+ ok ($content_1251 =~ qr{Ó÷eáíûé Öeíòp "ÊÀÄÐÛ ÄÅËÎÂÎÃÎ ÌÈÐÀ" ïpèãëaøaeò ía òpeíèíã:},
+"Content matches drugim in codepage 1251" );
+ }';
+}
+
+# }}}
+
+# {{{ test a message containing a russian subject and NO content type
+
+unshift (@RT::EmailInputEncodings, 'koi8-r');
+$RT::EmailOutputEncoding = 'koi8-r';
+my $content = `cat @RT_LIB_PATH@/t/data/russian-subject-no-content-type` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_plain_russian_redef_sendmessage;
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /text\/plain/, "We recorded the content type right");
+ok ($tick->Transactions->First->Attachments->Count ==1 , "Has one attachment, presumably a text-plain");
+is ($tick->Subject, "\x{442}\x{435}\x{441}\x{442} \x{442}\x{435}\x{441}\x{442}", "Recorded the subject right");
+sub text_plain_russian_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ is ($MIME->head->mime_type , "text/plain", "The only part is text/plain ");
+ my $subject = $MIME->head->get("subject");
+ chomp($subject);
+ #is( $subject , /^=\?KOI8-R\?B\?W2V4YW1wbGUuY39tICM3XSDUxdPUINTF09Q=\?=/ , "The $subject is encoded correctly");
+ };
+ ';
+}
+
+shift @RT::EmailInputEncodings;
+$RT::EmailOutputEncoding = 'utf-8';
+# }}}
+
+
+# {{{ test a message containing a nested RFC 822 message
+
+my $content = `cat @RT_LIB_PATH@/t/data/nested-rfc-822` || die "couldn't find new content";
+ok ($content, "Loaded nested-rfc-822 to test");
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&text_plain_nested_redef_sendmessage;
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+is ($tick->Subject, "[Jonas Liljegren] Re: [Para] Niv\x{e5}er?");
+ok ($tick->Transactions->First->Attachments->First->ContentType =~ /multipart\/mixed/, "We recorded the content type right");
+is ($tick->Transactions->First->Attachments->Count , 5 , "Has one attachment, presumably a text-plain and a message RFC 822 and another plain");
+sub text_plain_nested_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage {
+ my $self = shift;
+ my $MIME = shift;
+ return (1) unless ($self->ScripObj->ScripActionObj->Name eq "Notify AdminCcs" );
+ is ($MIME->head->mime_type , "multipart/mixed", "It is a mixed multipart");
+ my $subject = $MIME->head->get("subject");
+ $subject = MIME::Base64::decode_base64( $subject);
+ chomp($subject);
+ # TODO, why does this test fail
+ #ok($subject =~ qr{Niv\x{e5}er}, "The subject matches the word - $subject");
+ 1;
+ }';
+}
+
+# }}}
+
+
+# {{{ test a multipart alternative containing a uuencoded mesage generated by lotus notes
+
+my $content = `cat @RT_LIB_PATH@/t/data/notes-uuencoded` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&notes_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /from Lotus Notes/, "We recorded the content right");
+is ($tick->Transactions->First->Attachments->Count , 3 , "Has three attachments");
+
+sub notes_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+# }}}
+
+# {{{ test a multipart that crashes the file-based mime-parser works
+
+my $content = `cat @RT_LIB_PATH@/t/data/crashes-file-based-parser` || die "couldn't find new content";
+
+$parser->ParseMIMEEntityFromScalar($content);
+
+
+# be as much like the mail gateway as possible.
+&crashes_redef_sendmessage;
+
+my %args = (message => $content, queue => 1, action => 'correspond');
+ RT::Interface::Email::Gateway(\%args);
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);
+
+ok ($tick->Transactions->First->Content =~ /FYI/, "We recorded the content right");
+is ($tick->Transactions->First->Attachments->Count , 5 , "Has three attachments");
+
+sub crashes_redef_sendmessage {
+ no warnings qw/redefine/;
+ eval 'sub RT::Action::SendEmail::SendMessage { }';
+}
+
+
+
+# }}}
+
+# Don't taint the environment
+$everyone->PrincipalObj->RevokeRight(Right =>'SuperUser');
+1;
diff --git a/rt/lib/t/data/8859-15-message-series/dir b/rt/lib/t/data/8859-15-message-series/dir
new file mode 100644
index 0000000..b9f8ec3
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/dir
@@ -0,0 +1,356 @@
+Return-Path: <rt-users-admin@lists.fsck.com>
+Delivered-To: j@pallas.eruditorum.org
+Received: from pallas.eruditorum.org (localhost [127.0.0.1])
+ by pallas.eruditorum.org (Postfix) with ESMTP
+ id 72E3A111B3; Mon, 26 May 2003 14:50:14 -0400 (EDT)
+Delivered-To: rt-users@pallas.eruditorum.org
+Received: from mail-in-02.arcor-online.net (mail-in-02.arcor-online.net [151.189.21.42])
+ by pallas.eruditorum.org (Postfix) with ESMTP id 15E761118D
+ for <rt-users@lists.fsck.com>; Mon, 26 May 2003 14:49:56 -0400 (EDT)
+Received: from otdial-212-144-012-186.arcor-ip.net (otdial-212-144-011-024.arcor-ip.net [212.144.11.24])
+ by mail-in-02.arcor-online.net (Postfix) with ESMTP
+ id 745EE15E87; Mon, 26 May 2003 20:53:15 +0200 (CEST)
+From: Dirk Pape <pape-rt@inf.fu-berlin.de>
+To: Jesse Vincent <jesse@bestpractical.com>,
+ rt-users <rt-users@lists.fsck.com>
+Subject: Re: [rt-users] [rt-announce] Development Snapshot 3.0.2++
+Message-ID: <2147483647.1053982235@otdial-212-144-011-024.arcor-ip.net>
+In-Reply-To: <2147483647.1053974498@[10.0.255.35]>
+References: <20030523202405.GF23719@fsck.com>
+ <2147483647.1053974498@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="==========2147500486=========="
+Sender: rt-users-admin@lists.fsck.com
+Errors-To: rt-users-admin@lists.fsck.com
+X-BeenThere: rt-users@lists.fsck.com
+X-Mailman-Version: 2.0.12
+Precedence: bulk
+List-Help: <mailto:rt-users-request@lists.fsck.com?subject=help>
+List-Post: <mailto:rt-users@lists.fsck.com>
+List-Subscribe: <http://lists.fsck.com/mailman/listinfo/rt-users>,
+ <mailto:rt-users-request@lists.fsck.com?subject=subscribe>
+List-Id: For users of RT: Request Tracker <rt-users.lists.fsck.com>
+List-Unsubscribe: <http://lists.fsck.com/mailman/listinfo/rt-users>,
+ <mailto:rt-users-request@lists.fsck.com?subject=unsubscribe>
+List-Archive: <http://lists.fsck.com/pipermail/rt-users/>
+Date: Mon, 26 May 2003 20:50:36 +0200
+X-Spam-Status: No, hits=-2.5 required=5.0
+ tests=AWL,IN_REP_TO,KNOWN_MAILING_LIST,QUOTED_EMAIL_TEXT,
+ REFERENCES,REPLY_WITH_QUOTES
+ autolearn=ham version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+
+--==========2147500486==========
+Content-Type: text/plain; charset=us-ascii; format=flowed
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+Hello,
+
+here is the digest I forgot to attach. And I also forgot to say, that these
+were the only messages after a restart of apache.
+
+The messages in the digest are the copies which I - for testing purpose -
+allways queue into a mailbox just befor it is queued via rt-mailgate into
+the rt-system.
+
+--Am Montag, 26. Mai 2003 18:41 Uhr +0200 schrieb Dirk Pape
+<pape-rt@inf.fu-berlin.de>:
+
+> I attach a digest with mails I send one after another to the rt-system
+> and they get queued into one queue, each as a new ticket.
+
+
+
+
+--==========2147500486==========
+Content-Type: multipart/digest; boundary="==========2147489407=========="
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27591 invoked by uid 9804); 26 May 2003 18:10:50 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:10:46 +0200
+Received: (Qmail 27575 invoked from network); 26 May 2003 18:10:46 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:46 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKYe-0000Yi-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:10:44 +0200
+Received: (qmail 27557 invoked by uid 9804); 26 May 2003 18:10:44 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:10:40 +0200
+Received: (Qmail 27540 invoked from network); 26 May 2003 18:10:40 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:40 +0200
+Date: Mon, 26 May 2003 18:11:00 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972660@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27578] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27754 invoked by uid 9804); 26 May 2003 18:11:24 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:20 +0200
+Received: (Qmail 27704 invoked from network); 26 May 2003 18:11:19 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:19 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKZA-0000Yy-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:11:16 +0200
+Received: (qmail 27690 invoked by uid 9804); 26 May 2003 18:11:16 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:13 +0200
+Received: (Qmail 27677 invoked from network); 26 May 2003 18:11:13 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:13 +0200
+Date: Mon, 26 May 2003 18:11:32 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972692@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27711] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27971 invoked by uid 9804); 26 May 2003 18:12:02 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:52 +0200
+Received: (Qmail 27908 invoked from network); 26 May 2003 18:11:52 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:52 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKZj-0000ZC-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:11:51 +0200
+Received: (qmail 27848 invoked by uid 9804); 26 May 2003 18:11:50 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:46 +0200
+Received: (Qmail 27809 invoked from network); 26 May 2003 18:11:45 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:45 +0200
+Date: Mon, 26 May 2003 18:12:05 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972725@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27911] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 28283 invoked by uid 9804); 26 May 2003 18:12:39 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:12:36 +0200
+Received: (Qmail 28256 invoked from network); 26 May 2003 18:12:35 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:35 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKaQ-0000ZQ-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:12:34 +0200
+Received: (qmail 28236 invoked by uid 9804); 26 May 2003 18:12:34 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:12:30 +0200
+Received: (Qmail 28224 invoked from network); 26 May 2003 18:12:30 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:30 +0200
+Date: Mon, 26 May 2003 18:12:50 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972770@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28259] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 28578 invoked by uid 9804); 26 May 2003 18:13:20 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:13:15 +0200
+Received: (Qmail 28534 invoked from network); 26 May 2003 18:13:14 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:14 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKb1-0000Ze-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:13:11 +0200
+Received: (qmail 28516 invoked by uid 9804); 26 May 2003 18:13:11 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:13:08 +0200
+Received: (Qmail 28479 invoked from network); 26 May 2003 18:13:07 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:07 +0200
+Date: Mon, 26 May 2003 18:13:27 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972807@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28540] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 29108 invoked by uid 9804); 26 May 2003 18:14:15 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:14:10 +0200
+Received: (Qmail 29066 invoked from network); 26 May 2003 18:14:10 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:10 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKbw-0000Zr-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:14:08 +0200
+Received: (qmail 29054 invoked by uid 9804); 26 May 2003 18:14:08 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:14:04 +0200
+Received: (Qmail 29036 invoked from network); 26 May 2003 18:14:04 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:04 +0200
+Date: Mon, 26 May 2003 18:14:24 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972864@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29069] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+--==========2147489407==========
+Content-Type: message/rfc822; name="test _________"
+
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 29551 invoked by uid 9804); 26 May 2003 18:15:16 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:15:12 +0200
+Received: (Qmail 29521 invoked from network); 26 May 2003 18:15:12 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:12 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKcx-0000a4-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:15:11 +0200
+Received: (qmail 29511 invoked by uid 9804); 26 May 2003 18:15:10 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:15:07 +0200
+Received: (Qmail 29465 invoked from network); 26 May 2003 18:15:06 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:06 +0200
+Date: Mon, 26 May 2003 18:15:26 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972926@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29524] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
+
+--==========2147489407==========--
+
+--==========2147500486==========--
+
+_______________________________________________
+rt-users mailing list
+rt-users@lists.fsck.com
+http://lists.fsck.com/mailman/listinfo/rt-users
+
+Have you read the FAQ? The RT FAQ Manager lives at http://fsck.com/rtfm
+
diff --git a/rt/lib/t/data/8859-15-message-series/msg1 b/rt/lib/t/data/8859-15-message-series/msg1
new file mode 100644
index 0000000..cc99c40
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg1
@@ -0,0 +1,36 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27591 invoked by uid 9804); 26 May 2003 18:10:50 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:10:46 +0200
+Received: (Qmail 27575 invoked from network); 26 May 2003 18:10:46 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:46 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKYe-0000Yi-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:10:44 +0200
+Received: (qmail 27557 invoked by uid 9804); 26 May 2003 18:10:44 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:10:40 +0200
+Received: (Qmail 27540 invoked from network); 26 May 2003 18:10:40 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:10:40 +0200
+Date: Mon, 26 May 2003 18:11:00 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972660@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27578] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
diff --git a/rt/lib/t/data/8859-15-message-series/msg2 b/rt/lib/t/data/8859-15-message-series/msg2
new file mode 100644
index 0000000..dc442cf
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg2
@@ -0,0 +1,36 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27754 invoked by uid 9804); 26 May 2003 18:11:24 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:20 +0200
+Received: (Qmail 27704 invoked from network); 26 May 2003 18:11:19 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:19 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKZA-0000Yy-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:11:16 +0200
+Received: (qmail 27690 invoked by uid 9804); 26 May 2003 18:11:16 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:13 +0200
+Received: (Qmail 27677 invoked from network); 26 May 2003 18:11:13 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:13 +0200
+Date: Mon, 26 May 2003 18:11:32 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972692@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27711] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
diff --git a/rt/lib/t/data/8859-15-message-series/msg3 b/rt/lib/t/data/8859-15-message-series/msg3
new file mode 100644
index 0000000..e23866d
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg3
@@ -0,0 +1,35 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 27971 invoked by uid 9804); 26 May 2003 18:12:02 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:52 +0200
+Received: (Qmail 27908 invoked from network); 26 May 2003 18:11:52 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:52 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKZj-0000ZC-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:11:51 +0200
+Received: (qmail 27848 invoked by uid 9804); 26 May 2003 18:11:50 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:11:46 +0200
+Received: (Qmail 27809 invoked from network); 26 May 2003 18:11:45 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:11:45 +0200
+Date: Mon, 26 May 2003 18:12:05 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972725@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [27911] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
diff --git a/rt/lib/t/data/8859-15-message-series/msg4 b/rt/lib/t/data/8859-15-message-series/msg4
new file mode 100644
index 0000000..831695c
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg4
@@ -0,0 +1,35 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 28283 invoked by uid 9804); 26 May 2003 18:12:39 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:12:36 +0200
+Received: (Qmail 28256 invoked from network); 26 May 2003 18:12:35 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:35 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKaQ-0000ZQ-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:12:34 +0200
+Received: (qmail 28236 invoked by uid 9804); 26 May 2003 18:12:34 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:12:30 +0200
+Received: (Qmail 28224 invoked from network); 26 May 2003 18:12:30 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:12:30 +0200
+Date: Mon, 26 May 2003 18:12:50 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972770@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28259] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
diff --git a/rt/lib/t/data/8859-15-message-series/msg5 b/rt/lib/t/data/8859-15-message-series/msg5
new file mode 100644
index 0000000..272c93c
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg5
@@ -0,0 +1,35 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 28578 invoked by uid 9804); 26 May 2003 18:13:20 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:13:15 +0200
+Received: (Qmail 28534 invoked from network); 26 May 2003 18:13:14 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:14 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKb1-0000Ze-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:13:11 +0200
+Received: (qmail 28516 invoked by uid 9804); 26 May 2003 18:13:11 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:13:08 +0200
+Received: (Qmail 28479 invoked from network); 26 May 2003 18:13:07 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:13:07 +0200
+Date: Mon, 26 May 2003 18:13:27 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972807@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [28540] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
diff --git a/rt/lib/t/data/8859-15-message-series/msg6 b/rt/lib/t/data/8859-15-message-series/msg6
new file mode 100644
index 0000000..3ae9d3b
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg6
@@ -0,0 +1,35 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 29108 invoked by uid 9804); 26 May 2003 18:14:15 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:14:10 +0200
+Received: (Qmail 29066 invoked from network); 26 May 2003 18:14:10 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:10 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKbw-0000Zr-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:14:08 +0200
+Received: (qmail 29054 invoked by uid 9804); 26 May 2003 18:14:08 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:14:04 +0200
+Received: (Qmail 29036 invoked from network); 26 May 2003 18:14:04 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:14:04 +0200
+Date: Mon, 26 May 2003 18:14:24 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972864@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29069] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
diff --git a/rt/lib/t/data/8859-15-message-series/msg7 b/rt/lib/t/data/8859-15-message-series/msg7
new file mode 100644
index 0000000..6149dd6
--- /dev/null
+++ b/rt/lib/t/data/8859-15-message-series/msg7
@@ -0,0 +1,36 @@
+Return-Path: <pape@inf.fu-berlin.de>
+Delivered-To: pape-rtdoublecheck@mi.fu-berlin.de
+Received: (qmail 29551 invoked by uid 9804); 26 May 2003 18:15:16 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:15:12 +0200
+Received: (Qmail 29521 invoked from network); 26 May 2003 18:15:12 +0200
+Received: From es.inf.fu-berlin.de (160.45.110.22)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:12 +0200
+Received: from leibniz ([160.45.40.10] helo=math.fu-berlin.de)
+ by es.inf.fu-berlin.de with smtp (Exim 3.35 #1 (Debian))
+ id 19KKcx-0000a4-00
+ for <staff@tec.mi.fu-berlin.de>; Mon, 26 May 2003 18:15:11 +0200
+Received: (qmail 29511 invoked by uid 9804); 26 May 2003 18:15:10 +0200
+Received: from localhost (HELO math.fu-berlin.de) (127.0.0.1)
+ by localhost with SMTP; 26 May 2003 18:15:07 +0200
+Received: (Qmail 29465 invoked from network); 26 May 2003 18:15:06 +0200
+Received: From eremix.inf.fu-berlin.de (HELO eremix) (160.45.113.36)
+ by leibniz.math.fu-berlin.de with SMTP; 26 May 2003 18:15:06 +0200
+Date: Mon, 26 May 2003 18:15:26 +0200
+From: Dirk Pape <pape@inf.fu-berlin.de>
+To: staff@tec.mi.fu-berlin.de
+Subject: =?ISO-8859-15?Q?test_=E4=F6=FC=DF=C4=D6=DC=DF=A4?=
+Message-ID: <2147483647.1053972926@[10.0.255.35]>
+X-Mailer: Mulberry/3.0.3 (Mac OS X)
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Envelope-Sender: pape@inf.fu-berlin.de
+X-Virus-Scanned: by AMaViS 0.3.12pre7-U23 [29524] (NAI-uvscan@math.fu-berlin.de)
+X-Remote-IP: 160.45.110.22
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-15; FORMAT=flowed
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+
+test nochmal in anderer Queue
+test =E4=F6=FC=DF=C4=D6=DC=DF=A4
+
diff --git a/rt/lib/t/data/crashes-file-based-parser b/rt/lib/t/data/crashes-file-based-parser
new file mode 100644
index 0000000..da1913e
--- /dev/null
+++ b/rt/lib/t/data/crashes-file-based-parser
@@ -0,0 +1,193 @@
+X-Real-To: <mitya@example.com>
+Received: from [194.87.5.31] (HELO sinbin.d-s.example.com)
+ by cgp.example.com (CommuniGate Pro SMTP 4.0.6/D4)
+ with ESMTP-TLS id 125035761 for mitya@example.com; Thu, 11 Dec 2003 15:17:46 +0300
+Received: (from daemon@localhost)
+ by sinbin.d-s.example.com (8.12.9p1/8.11.6) id hBBCHjN0031595
+ for mitya@example.com; Thu, 11 Dec 2003 15:17:45 +0300 (MSK)
+ (envelope-from noc@rt3.mx.example.com)
+Received: from d-s.example.com by sinbin.d-s.example.com with ESMTP id hBBCHjar031575;
+ (8.12.9p2/D) Thu, 11 Dec 2003 15:17:45 +0300 (MSK)
+X-Real-To: <mitya@example.com>
+Sender: <noc@rt3.mx.example.com> (Network Operation Center)
+To: mitya@example.com
+Date: Thu, 11 Dec 2003 15:17:45 +0300
+Message-ID: <redirect-137509289@d-s.example.com>
+X-Original-Return-Path: <vox19@b92.d-s.example.com>
+Received: from [194.87.0.16] (HELO mail.d-s.example.com)
+ by d-s.example.com (CommuniGate Pro SMTP 4.1.5/D1)
+ with ESMTP id 120757484 for noc@rt3.mx.example.com; Mon, 27 Oct 2003 09:40:53 +0300
+Received: from [194.87.0.22] (HELO moscvax.d-s.example.com)
+ by mail.d-s.example.com (CommuniGate Pro SMTP 4.1.5/D)
+ with ESMTP-TLS id 107945800 for noc@rt3.mx.example.com; Mon, 27 Oct 2003 09:40:53 +0300
+Received: from d-s.example.com (mx.d-s.example.com [194.87.0.32])
+ by moscvax.d-s.example.com (8.12.9/8.12.9) with ESMTP id h9R6erFm062621
+ for <security@d.example.com>; Mon, 27 Oct 2003 09:40:53 +0300 (MSK)
+ (envelope-from vox19@b92.d-s.example.com)
+Received: by d-s.example.com (CommuniGate Pro PIPE 4.1.5/D1)
+ with PIPE id 120757490; Mon, 27 Oct 2003 09:40:53 +0300
+Received: from [194.87.2.108] (HELO b92.d-s.example.com)
+ by d-s.example.com (CommuniGate Pro SMTP 4.1.5/D1)
+ with ESMTP-TLS id 120757480 for security@d.example.com; Mon, 27 Oct 2003 09:40:52 +0300
+Received: from b92.d-s.example.com (localhost [127.0.0.1])
+ by b92.d-s.example.com (8.12.8p1/8.12.3) with ESMTP id h9R6eqIe014669
+ for <security@d.example.com>; Mon, 27 Oct 2003 09:40:52 +0300 (MSK)
+ (envelope-from vox19@b92.d-s.example.com)
+Received: from localhost (localhost [[UNIX: localhost]])
+ by b92.d-s.example.com (8.12.8p1/8.12.3/Submit) id h9R6epst014668
+ for security@d.example.com; Mon, 27 Oct 2003 09:40:51 +0300 (MSK)
+From: "Stanislav" <drstas@d.example.com>
+Subject: Fwd: scanning my ports
+X-Original-Date: Mon, 27 Oct 2003 10:40:51 +0400
+User-Agent: KMail/1.5.4
+X-Original-To: security@d.example.com
+MIME-Version: 1.0
+Content-Type: Multipart/Mixed;
+ boundary="Boundary-00=_z3Ln/tUeUBipHgx"
+X-Original-Message-Id: <200310270940.51758.vox19@d.example.com>
+X-Spam-Checker-Version: SpamAssassin 2.60-jumbo.demos (1.212-2003-09-23-exp)
+X-Spam-Level:
+X-Spam-Status: No, hits=-6.8 required=5.0 tests=BAYES_00,FROM_ENDS_IN_NUMS,
+ HTML_MESSAGE,SUBJECT_RT autolearn=ham version=2.60-jumbo.demos
+X-Spam-Report: -6.8 points, 5.0 required;
+ * -3.0 SUBJECT_RT Tracking system
+ * 1.0 FROM_ENDS_IN_NUMS From: ends in numbers
+ * 0.1 HTML_MESSAGE BODY: HTML included in message
+ * -4.9 BAYES_00 BODY: Bayesian spam probability is 0 to 1%
+ * [score: 0.0000]
+
+
+--Boundary-00=_z3Ln/tUeUBipHgx
+Content-Type: text/plain;
+ charset="koi8-r"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+
+FYI
+
+
+---------- Forwarded Message ----------
+
+Subject: [DEMOS #12148] scanning my ports
+Date: Sunday 26 October 2003 20:19
+From: 1stwizard@isp.example.com
+To: no-reply@d-r.example.com
+
+This transaction appears to have no content
+
+-------------------------------------------------------
+
+
+
+--
+best wishes,
+
+Stanislav A. Mushkat
+http://www.di.example.com
+
+--Boundary-00=_z3Ln/tUeUBipHgx
+Content-Type: text/plain;
+ charset="iso-8859-1";
+ name=" "
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+Somebody at IP 127.0.0.1 scanned my ports.
+--Boundary-00=_z3Ln/tUeUBipHgx
+Content-Type: text/html;
+ charset="iso-8859-1";
+ name=" "
+Content-Transfer-Encoding: 7bit
+Content-Disposition: inline
+
+<HTML><HEAD>
+<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
+<META content="IncrediMail 1.0" name=GENERATOR>
+<!--IncrdiXMLRemarkStart>
+<IncrdiX-Info>
+<X-FID>BA285063-5BCE-11D4-AF8D-0050DAC67E11</X-FID>
+<X-FVER>2.0</X-FVER>
+<X-FIT>Letter</X-FIT>
+<X-FCOL>Elegant Paper</X-FCOL>
+<X-FCAT>Stationery</X-FCAT>
+<X-FDIS>Rice Fields</X-FDIS>
+<X-Extensions>SU1CTDEsNDEsgUmBSTAkkcGNgZmVTY0wNCxNhYUoiU0kOMEoTYGBjYEoJDSZnSyFhUksSU1CTDIsMCwsSU1CTDMsMCwsVHlwZVZlcnNpb24sMywxLjAs</X-Extensions>
+<X-BG>8E549F43-079D-11D8-B0F9-00B0D0B65B96</X-BG>
+<X-BGT>repeat</X-BGT>
+<X-BGC>#eff3f7</X-BGC>
+<X-BGPX>left</X-BGPX>
+<X-BGPY>0px</X-BGPY>
+<X-ASN>ANIM3D00-NONE-0000-0000-000000000000</X-ASN>
+<X-ASNF>0</X-ASNF>
+<X-ASH>ANIM3D00-NONE-0000-0000-000000000000</X-ASH>
+<X-ASHF>1</X-ASHF>
+<X-AN>6486DDE0-3EFD-11D4-BA3D-0050DAC68030</X-AN>
+<X-ANF>0</X-ANF>
+<X-AP>6486DDE0-3EFD-11D4-BA3D-0050DAC68030</X-AP>
+<X-APF>1</X-APF>
+<X-AD>C3C52140-4147-11D4-BA3D-0050DAC68030</X-AD>
+<X-ADF>0</X-ADF>
+<X-AUTO>X-ASN,X-ASH,X-AN,X-AP,X-AD</X-AUTO>
+<X-CNT>;</X-CNT>
+</IncrdiX-Info>
+<IncrdiXMLRemarkEnd-->
+</HEAD>
+<BODY style="BACKGROUND-POSITION: left 0px; FONT-SIZE: 12pt; MARGIN: 0px 10px 10px; COLOR: #00005b; BACKGROUND-REPEAT: repeat; FONT-FAMILY: Arial" text=#00005b bgColor=#eff3f7 background=cid:8E549F43-079D-11D8-B0F9-00B0D0B65B96 scroll=yes SIGCOLOR="0" X-ADF="0" X-AD="C3C52140-4147-11D4-BA3D-0050DAC68030" X-APF="1" X-AP="6486DDE0-3EFD-11D4-BA3D-0050DAC68030" X-ANF="0" X-AN="6486DDE0-3EFD-11D4-BA3D-0050DAC68030" X-ASHF="1" X-ASH="ANIM3D00-NONE-0000-0000-000000000000" X-ASNF="0" X-ASN="ANIM3D00-NONE-0000-0000-000000000000" X-FVER="2.0" X-FID="BA285063-5BCE-11D4-AF8D-0050DAC67E11" X-FIT="Letter" X-FCOL="Elegant Paper" X-FCAT="Elegant Paper" X-FDIS="Rice Fields" ORGYPOS="0">
+<TABLE id=INCREDIMAINTABLE cellSpacing=0 cellPadding=2 width="100%" border=0>
+<TBODY>
+<TR>
+<TD id=INCREDITEXTREGION style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 12pt; PADDING-BOTTOM: 0px; CURSOR: auto; PADDING-TOP: 0px" vAlign=top width="100%">
+<DIV>Somebody at IP 127.0.0.1 scanned my ports. </DIV>
+<DIV>&nbsp;</DIV>
+<DIV>&nbsp;</DIV></TD></TR>
+<TR>
+<TD id=INCREDIFOOTER width="100%">
+<TABLE cellSpacing=0 cellPadding=0 width="100%">
+<TBODY>
+<TR>
+<TD width="100%"></TD>
+<TD id=INCREDISOUND vAlign=bottom align=middle></TD>
+<TD id=INCREDIANIM vAlign=bottom align=middle></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></BODY></HTML>
+--Boundary-00=_z3Ln/tUeUBipHgx
+Content-Type: image/jpeg;
+ charset="iso-8859-1";
+ name="BackGrnd.jpg"
+Content-Transfer-Encoding: base64
+Content-Disposition: inline; filename="BackGrnd.jpg"
+
+/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFk
+b2JlAGTAAAAAAQMAEAMCAwYAAAHbAAAC1gAABZX/2wCEABALCwsMCxAMDBAX
+Dw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoXHh4jJSclIx4vLzMzLy9AQEBAQEBA
+QEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoaJjAjHh4eHiMw
+Ky4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIAGUAcwMBIgACEQED
+EQH/xACAAAEBAQEAAAAAAAAAAAAAAAAAAQIGAQEBAAAAAAAAAAAAAAAAAAAA
+ARABAAICAwEAAgMAAAAAAAAAAQARIQIxQRIiQDIQMFARAAICAgIBBAIDAQEA
+AAAAAAERACExQVFhcYGRobECEsHhMtHxEgEAAAAAAAAAAAAAAAAAAABQ/9oA
+DAMBAAIRAxEAAADtRZYE1ASghQFgUZoCkKSwLmhcllAEqkSkqFAlhUomoAS3
+IoJqFlDNpFEAQFE1AIVYAWIVKAJRNZpYCwVmmshKACA0CBAUCBYGwf/aAAgB
+AgABBQD8B/yP/9oACAEDAAEFAPz6/or8H//aAAgBAQABBQC2+ZeHjbD+saX6
+hwXeDW1Rg4xLLTa+m7ZiIEsI1MTiHP1dYpvFADiFM1/X6nq9byuwdPPz5oFo
+fWlEMQ9ULKrWq2ppG9Y2J6INQma9lVTRdlUKgHzXXSEECw1SYu5WsGoJPkis
+ZYpx31GvXZQ/JM3VwShzVTsp1EZbBI8LcaUSih86+s2Zl4Wp6+lAZnVsDkjd
+ku5m+lJTdXDG2SHM9M2wKX1YxsaZTTwmoVrYnqsMrM652yjs01K0mtbGAz6Y
+5dpfqNz06qpq5QNjiIjiZtbhtceNuf0jyeqGgu6rXMvI4omPWbPMYzEfMI+a
+xHnFvOP4/9oACAECAgY/AGP/2gAIAQMCBj8AY//aAAgBAQEGPwB72Yucb1Bf
+IhFEaeZ+xRXFQELN+HEUQdjU0Xn4g9gRCQcpw1yajGYsP/kFvUzvjUBWrIMF
+HI2OJQNEAjiEEFdTmfG/MTHq5RFOnpTV3kzCBx7x4YOD1AV5uYJvnqMA0hep
+jfwpYCwC4Bx3q55zeZRBCw9TkoIuHw78RdczSNH2mgqcLpRC+RASAkA3B13m
+cYd5mR84c/yOx4lWtRAZ6mGDhiP9WgXVyhWA+xDgMOWGMsTg/wBTz8SjjXrP
+8hHIlX1MZ6mDzgc/cIV/iyN1GBR0MQMKjnEzvvMz8mUkErKlfqU63iV+IKNH
+7mNZBLFQEpEDeDOV32IVn8WR4caoywqI2p695mbZzNUQIcKfk0bo+0NpCqn7
+CiQiNGXkdQen1DpjGeZ7WNw3pK+I93maCPc16+Zkf6XxMCsFwAkaiIB57vc/
+IAhZ/HqZBBbB0ZokAEOGxsYqBgPp8agQBu4VSMJdqx6SwDsGBrTmAR93uZGX
+6KePowEADAIjoX8gw459CICaW/MLGvodQfkDW71zBxRHtB3j3jC4PMIYoAgK
+NfPMCQNN7jCzvlzXPopzhQvNZY3CRya9ZrEFfRE0iCB5mscZuVYfKmAi94uE
+3Q8qfytQ7xD0svmFcmaxNPI8iMjh3pmF2HbzqeUi+YkiD/MrOl5LmbwPuWVf
+mXpv3hDH8qAjPpiZHXkRnSd6ZhB53mejzKV6US0K9TCCLyCeIhtETX5MsHBG
+JkD/ANiFkMCE2qGoCdZ8Q8AMGpYFqEhdhRIYH3CF3d1M/Mexma+4CwdQ2Ddc
+x0exAlmj04QUQd8QWLB/iB5GxmEg5TENVZqPYzFV8eHAy9T/AEc8a4n3Ov6g
+/VwvE6lpQ4VNysXzhS8esOO8w/rlF/rypjV3B5H1Knr8T//Z
+
+--Boundary-00=_z3Ln/tUeUBipHgx--
+
diff --git a/rt/lib/t/data/multipart-alternative-with-umlaut b/rt/lib/t/data/multipart-alternative-with-umlaut
new file mode 100644
index 0000000..1ad4fe3
--- /dev/null
+++ b/rt/lib/t/data/multipart-alternative-with-umlaut
@@ -0,0 +1,62 @@
+Return-Path: <gst@example.com>
+Delivered-To: j@pallas.eruditorum.org
+Received: from vis.example.com (vis.example.com [212.68.68.251])
+ by pallas.eruditorum.org (Postfix) with SMTP id 59236111C3
+ for <jesse@example.com>; Thu, 12 Jun 2003 02:14:44 -0400 (EDT)
+Received: (qmail 29541 invoked by uid 502); 12 Jun 2003 06:14:42 -0000
+Received: from sivd.example.com (HELO example.com) (192.168.42.1)
+ by 192.168.42.42 with SMTP; 12 Jun 2003 06:14:42 -0000
+Received: received from 172.20.72.174 by odie.example.com; Thu, 12 Jun 2003 08:14:27 +0200
+Received: by mailserver.example.com with Internet Mail Service (5.5.2653.19) id <LJSB7T54>; Thu, 12 Jun 2003 08:14:39 +0200
+Message-ID: <50362EC956CBD411A339009027F6257E013DD495@mailserver.example.com>
+Date: Thu, 12 Jun 2003 08:14:39 +0200
+From: "Stever, Gregor" <gst@example.com>
+MIME-Version: 1.0
+X-Mailer: Internet Mail Service (5.5.2653.19)
+To: "'jesse@example.com'" <jesse@example.com>
+Subject: RE: [rt-users] HTML-encoded mails with umlaute
+Date: Thu, 12 Jun 2003 08:14:39 +0200
+Content-Type: multipart/alternative;
+ boundary="----_=_NextPart_001_01C330A9.E7BDD590"
+X-Spam-Status: No, hits=0.0 required=5.0
+ tests=AWL,HTML_50_60,HTML_MESSAGE,INVALID_DATE
+ version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+
+------_=_NextPart_001_01C330A9.E7BDD590
+Content-Type: text/plain;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+Hello,
+
+ist this kind of Messages, that causes rt to crash.=20
+
+Mit freundlichen Gr=FC=DFen
+Gregor Stever ^^causes Error!!
+
+
+------_=_NextPart_001_01C330A9.E7BDD590
+Content-Type: text/html;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML><HEAD>
+<META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; charset=3Diso-8859-=
+1">
+
+
+<META content=3D"MSHTML 6.00.2800.1170" name=3DGENERATOR></HEAD>
+<BODY>
+<DIV><FONT face=3DArial><FONT size=3D2>Hello,<BR><BR>ist this kind of Messa=
+ges, that=20
+causes rt to crash.<BR><BR>Mit freundlichen Gr=FC=DFen<BR>Gregor=20
+Stever&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ^^causes Error<SPAN=20
+class=3D975501206-12062003>!!</SPAN></FONT></FONT></DIV></BODY></HTML>
+
+
+------_=_NextPart_001_01C330A9.E7BDD590--
+
+
diff --git a/rt/lib/t/data/multipart-report b/rt/lib/t/data/multipart-report
new file mode 100644
index 0000000..538e0c8
--- /dev/null
+++ b/rt/lib/t/data/multipart-report
@@ -0,0 +1,66 @@
+Return-Path: <mailnull@example.com>
+Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT)
+From: Mail Delivery Subsystem <MAILER-DAEMON@other.example.com>
+Message-Id: <200308221615.CGA36111@mailbox.other.example.com>
+To: support@example.com
+MIME-Version: 1.0
+Content-Type: multipart/report; report-type=delivery-status;
+ boundary="CGA36111.1061568918/mailbox.other.example.com"
+Subject: Returned mail: User unknown
+Auto-Submitted: auto-generated (failure)
+
+This is a MIME-encapsulated message
+
+--CGA36111.1061568918/mailbox.other.example.com
+
+The original message was received at Sat, 23 Aug 2003 00:15:18 +0800 (SGT)
+from mx12.mcis.other.example.com [10.1.1.232]
+
+ ----- The following addresses had permanent delivery errors -----
+<jesmund>
+
+
+--CGA36111.1061568918/mailbox.other.example.com
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; mailbox.other.example.com
+Arrival-Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT)
+
+Final-Recipient: RFC822; jesmund@mailbox.other.example.com
+Action: failed
+Status: 5.1.1
+Remote-MTA: DNS; mail.mcis.other.example.com
+Diagnostic-Code: SMTP; 550 5.1.1 <jesmund>... User unknown
+Last-Attempt-Date: Sat, 23 Aug 2003 00:15:18 +0800 (SGT)
+
+--CGA36111.1061568918/mailbox.other.example.com
+Content-Type: message/rfc822
+
+Return-Path: <support@example.com>
+Received: from mx12.other.example.com (mx12.mcis.other.example.com [10.1.1.232])
+ by mailbox.other.example.com (Mirapoint Messaging Server MOS 3.3.3-GR)
+ with ESMTP id CGA36101;
+ Sat, 23 Aug 2003 00:15:17 +0800 (SGT)
+Received: from STATION13 (rhala.dsl.pe.net [64.38.69.104])
+ by mx12.other.example.com (8.12.9/8.12.9) with ESMTP id h7MGFGac020135
+ for <jesmund@other.example.com>; Sat, 23 Aug 2003 00:15:17 +0800
+Message-Id: <200308221615.h7MGFGac020135@mx12.other.example.com>
+From: <support@example.com>
+To: <jesmund@other.example.com>
+Subject: Thank you!
+Date: Fri, 22 Aug 2003 9:15:19 --0700
+X-MailScanner: Found to be clean
+Importance: Normal
+X-Mailer: Microsoft Outlook Express 6.00.2600.0000
+X-MSMail-Priority: Normal
+X-Priority: 3 (Normal)
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="_NextPart_000_05684DA4"
+
+
+
+--_NextPart_000_05684DA4--
+
+--CGA36111.1061568918/mailbox.other.example.com--
+
diff --git a/rt/lib/t/data/nested-mime-sample b/rt/lib/t/data/nested-mime-sample
new file mode 100644
index 0000000..8b85d94
--- /dev/null
+++ b/rt/lib/t/data/nested-mime-sample
@@ -0,0 +1,396 @@
+Return-Path: <Xxxxxx_Yyyyyyy@some.net>
+Delivered-To: jesse@pallas.eruditorum.org
+Received: by pallas.eruditorum.org (Postfix)
+ id B5D3E1123A; Fri, 12 Jul 2002 11:35:27 -0400 (EDT)
+Delivered-To: rt-2.0-bugs@pallas.eruditorum.org
+Received: from postman.some.net (postman.some.net [193.0.0.199])
+ by pallas.eruditorum.org (Postfix) with SMTP id 2736011234
+ for <rt-2.0-bugs@fsck.com>; Fri, 12 Jul 2002 11:35:27 -0400 (EDT)
+Received: (qmail 11615 invoked by uid 0); 12 Jul 2002 15:35:26 -0000
+Received: from x22.some.net (HELO x22.some.net.some.net) (193.0.1.22)
+ by postman.some.net with SMTP; 12 Jul 2002 15:35:26 -0000
+Date: Fri, 12 Jul 2002 17:35:26 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+To: rt-0.0-bugs@fsck.com
+Subject: Example MIME within MIME within MIME message
+Message-ID: <Pine.LNX.4.44.0207121734250.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-192303556-1026488126=:25020"
+X-Spam-Status: No, hits=4.0 required=7.0
+ tests=DOUBLE_CAPSWORD,MIME_NULL_BLOCK,MIME_MISSING_BOUNDARY
+ version=2.31
+Content-Length: 11478
+
+ This message is in MIME format. The first part should be readable text,
+ while the remaining parts are likely unreadable without MIME-aware tools.
+ Send mail to mime@docserver.cac.washington.edu for more info.
+
+--12654081-192303556-1026488126=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+MIME is fun at times.
+
+
+--
+ Xxxxxx Yyyyyyy SOME
+ Systems/Network Engineer NCC
+ www.some.net - PGP000C8B1B Operations/Security
+
+--12654081-192303556-1026488126=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-2102091261-1026488126=:25020"
+Content-ID: <Pine.LNX.4.44.0207121734322.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+ This message is in MIME format. The first part should be readable text,
+ while the remaining parts are likely unreadable without MIME-aware tools.
+ Send mail to mime@docserver.cac.washington.edu for more info.
+
+--12654081-2102091261-1026488126=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121734320.25020@x22.some.net>
+Content-Description: first outer message (fwd)
+
+Date: Fri, 12 Jul 2002 17:32:37 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: first outer message
+Message-ID: <Pine.LNX.4.44.0207121732180.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-113777422-1026487957=:25020"
+
+
+--12654081-113777422-1026487957=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+first outer message
+
+--12654081-113777422-1026487957=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-387266385-1026487957=:25020"
+Content-ID: <Pine.LNX.4.44.0207121732222.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-387266385-1026487957=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121732220.25020@x22.some.net>
+Content-Description: middle message (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:45 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: middle message
+Message-ID: <Pine.LNX.4.44.0207121731190.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1711788944-1026487905=:25020"
+
+
+--12654081-1711788944-1026487905=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+This is the first middle message
+
+
+--12654081-1711788944-1026487905=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-1221085552-1026487905=:25020"
+Content-ID: <Pine.LNX.4.44.0207121731262.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-1221085552-1026487905=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731260.25020@x22.some.net>
+Content-Description: This is the inner-most message (fwd)
+
+Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: This is the inner-most message
+Message-ID: <Pine.LNX.4.44.0207121730070.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+inner-msg
+
+
+
+--12654081-1221085552-1026487905=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731261.25020@x22.some.net>
+Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: another inner message (need two before pine will do the mime-digest
+ thing)
+Message-ID: <Pine.LNX.4.44.0207121730480.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+again
+
+
+
+--12654081-1221085552-1026487905=:25020--
+--12654081-1711788944-1026487905=:25020--
+
+--12654081-387266385-1026487957=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121732221.25020@x22.some.net>
+Content-Description: middle message (fwd)
+
+Date: Fri, 12 Jul 2002 17:32:05 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: middle message
+Message-ID: <Pine.LNX.4.44.0207121731470.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1731270459-1026487925=:25020"
+
+
+--12654081-1731270459-1026487925=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+This is the second middle message
+
+
+--12654081-1731270459-1026487925=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-128832654-1026487925=:25020"
+Content-ID: <Pine.LNX.4.44.0207121731502.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-128832654-1026487925=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731500.25020@x22.some.net>
+Content-Description: This is the inner-most message (fwd)
+
+Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: This is the inner-most message
+Message-ID: <Pine.LNX.4.44.0207121730070.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+inner-msg
+
+
+
+--12654081-128832654-1026487925=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731501.25020@x22.some.net>
+Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: another inner message (need two before pine will do the mime-digest
+ thing)
+Message-ID: <Pine.LNX.4.44.0207121730480.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+again
+
+
+
+--12654081-128832654-1026487925=:25020--
+--12654081-1731270459-1026487925=:25020--
+
+--12654081-387266385-1026487957=:25020--
+--12654081-113777422-1026487957=:25020--
+
+--12654081-2102091261-1026488126=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121734321.25020@x22.some.net>
+Content-Description: 2nd outer message (fwd)
+
+Date: Fri, 12 Jul 2002 17:32:54 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: 2nd outer message
+Message-ID: <Pine.LNX.4.44.0207121732380.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1955637437-1026487974=:25020"
+
+
+--12654081-1955637437-1026487974=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+2nd outer message
+
+
+--12654081-1955637437-1026487974=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-362457126-1026487974=:25020"
+Content-ID: <Pine.LNX.4.44.0207121732412.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-362457126-1026487974=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121732410.25020@x22.some.net>
+Content-Description: middle message (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:45 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: middle message
+Message-ID: <Pine.LNX.4.44.0207121731190.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1711788944-1026487905=:25020"
+
+
+--12654081-1711788944-1026487905=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+This is the first middle message
+
+
+--12654081-1711788944-1026487905=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-1221085552-1026487905=:25020"
+Content-ID: <Pine.LNX.4.44.0207121731262.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-1221085552-1026487905=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731260.25020@x22.some.net>
+Content-Description: This is the inner-most message (fwd)
+
+Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: This is the inner-most message
+Message-ID: <Pine.LNX.4.44.0207121730070.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+inner-msg
+
+
+
+--12654081-1221085552-1026487905=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731261.25020@x22.some.net>
+Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: another inner message (need two before pine will do the mime-digest
+ thing)
+Message-ID: <Pine.LNX.4.44.0207121730480.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+again
+
+
+
+--12654081-1221085552-1026487905=:25020--
+--12654081-1711788944-1026487905=:25020--
+
+--12654081-362457126-1026487974=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121732411.25020@x22.some.net>
+Content-Description: middle message (fwd)
+
+Date: Fri, 12 Jul 2002 17:32:05 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: middle message
+Message-ID: <Pine.LNX.4.44.0207121731470.25020-120000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: MULTIPART/MIXED; BOUNDARY="12654081-1731270459-1026487925=:25020"
+
+
+--12654081-1731270459-1026487925=:25020
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+This is the second middle message
+
+
+--12654081-1731270459-1026487925=:25020
+Content-Type: MULTIPART/Digest; BOUNDARY="12654081-128832654-1026487925=:25020"
+Content-ID: <Pine.LNX.4.44.0207121731502.25020@x22.some.net>
+Content-Description: Digest of 2 messages
+
+--12654081-128832654-1026487925=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731500.25020@x22.some.net>
+Content-Description: This is the inner-most message (fwd)
+
+Date: Fri, 12 Jul 2002 17:30:31 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: This is the inner-most message
+Message-ID: <Pine.LNX.4.44.0207121730070.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+inner-msg
+
+
+
+--12654081-128832654-1026487925=:25020
+Content-Type: MESSAGE/RFC822; CHARSET=US-ASCII
+Content-ID: <Pine.LNX.4.44.0207121731501.25020@x22.some.net>
+Content-Description: another inner message (need two before pine will do the mime-digest thing) (fwd)
+
+Date: Fri, 12 Jul 2002 17:31:12 +0200 (CEST)
+From: Xxxxxx Yyyyyyy <Xxxxxx_Yyyyyyy@some.net>
+X-X-Sender: bc@x22.some.net
+To: Xxxxxx_Yyyyyyy@some.net
+Subject: another inner message (need two before pine will do the mime-digest
+ thing)
+Message-ID: <Pine.LNX.4.44.0207121730480.25020-100000@x22.some.net>
+MIME-Version: 1.0
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+
+again
+
+
+
+--12654081-128832654-1026487925=:25020--
+--12654081-1731270459-1026487925=:25020--
+
+--12654081-362457126-1026487974=:25020--
+--12654081-1955637437-1026487974=:25020--
+
+--12654081-2102091261-1026488126=:25020--
+--12654081-192303556-1026488126=:25020--
+
diff --git a/rt/lib/t/data/nested-rfc-822 b/rt/lib/t/data/nested-rfc-822
new file mode 100644
index 0000000..d4f118d
--- /dev/null
+++ b/rt/lib/t/data/nested-rfc-822
@@ -0,0 +1,253 @@
+Return-Path: <jonas@astral.example.com>
+Delivered-To: j@pallas.eruditorum.org
+Received: from example.com (example.com [213.88.137.35])
+ by pallas.eruditorum.org (Postfix) with ESMTP id 869591115E
+ for <jesse@bestpractical.com>; Sun, 29 Jun 2003 18:04:04 -0400 (EDT)
+Received: from jonas by example.com with local (Exim 4.20)
+ id 19WkLK-0004Vr-0I
+ for jesse@bestpractical.com; Mon, 30 Jun 2003 00:08:18 +0200
+Resent-To: jesse@bestpractical.com
+Resent-From: Jonas Liljegren <jonas@example.com>
+Resent-Date: Mon, 30 Jun 2003 00:08:17 +0200
+Received: from mail by example.com with spam-scanned (Exim 4.20)
+ id 19Wayz-00068j-KO
+ for jonas@astral.example.com; Sun, 29 Jun 2003 14:08:42 +0200
+Received: from jonas by example.com with local (Exim 4.20)
+ id 19Wayz-00068g-FY
+ for red@example.com; Sun, 29 Jun 2003 14:08:37 +0200
+To: Redaktionen <red@example.com>
+Subject: [Jonas Liljegren] Re: [Para] =?iso-8859-1?q?Niv=E5er=3F?=
+From: Jonas Liljegren <jonas@example.com>
+Date: Sun, 29 Jun 2003 14:08:37 +0200
+Message-ID: <87d6gxt7ay.fsf@example.com>
+User-Agent: Gnus/5.1002 (Gnus v5.10.2) Emacs/21.2 (gnu/linux)
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-=-="
+Sender: Jonas Liljegren <jonas@astral.example.com>
+Resent-Message-Id: <E19WkLK-0004Vr-0I@example.com>
+Resent-Sender: Jonas Liljegren <jonas@astral.example.com>
+Resent-Date: Mon, 30 Jun 2003 00:08:18 +0200
+X-Spam-Status: No, hits=-5.7 required=5.0
+ tests=AWL,BAYES_10,EMAIL_ATTRIBUTION,MAILTO_WITH_SUBJ,
+ QUOTED_EMAIL_TEXT,USER_AGENT_GNUS_UA
+ version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+
+--=-=-=
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+Material f=F6r att uppdatera texten om niv=E5er.
+
+Denna text b=F6r dessutom ligga som ett uppslagsord och inte d=E4r den ligg=
+er nu.
+
+
+--=-=-=
+Content-Type: message/rfc822
+Content-Disposition: inline
+
+Return-path: <list-bounces@example.com>
+Received: from mail by example.com with spam-scanned (Exim 4.20)
+ id 19WFzq-0005i1-WE
+ for jonas@example.com; Sat, 28 Jun 2003 15:44:13 +0200
+Received: from localhost
+ ([127.0.0.1] helo=example.com ident=list)
+ by example.com with esmtp (Exim 4.20)
+ id 19WFzp-0005hf-Tz; Sat, 28 Jun 2003 15:44:05 +0200
+Received: from mail by example.com with spam-scanned (Exim 4.20)
+ id 19WFzh-0005hR-Bu
+ for list@example.com; Sat, 28 Jun 2003 15:44:03 +0200
+Received: from jonas by example.com with local (Exim 4.20)
+ id 19WFzh-0005hO-AO
+ for list@example.com; Sat, 28 Jun 2003 15:43:57 +0200
+To: list@example.com
+Subject: Re: [Para] =?iso-8859-1?q?Niv=E5er=3F?=
+References: <002701c33d62$170fb2e0$a33740d5@TELIA.COM>
+ <002301c33d66$bf6483e0$d97864d5@remotel2tu76c8>
+ <64753.217.210.4.156.1056801224.squirrel@example.com>
+From: Jonas Liljegren <jonas@example.com>
+Date: Sat, 28 Jun 2003 15:43:57 +0200
+In-Reply-To: <64753.217.210.4.156.1056801224.squirrel@example.com> (Jakob
+ Carlsson's message of "Sat, 28 Jun 2003 13:53:44 +0200 (CEST)")
+Message-ID: <877k76uxk2.fsf@example.com>
+User-Agent: Gnus/5.1002 (Gnus v5.10.2) Emacs/21.2 (gnu/linux)
+X-BeenThere: list@example.com
+X-Mailman-Version: 2.1.2
+Precedence: list
+List-Id: &#214;ppen lista f&#246;r alla medlemmar <list.example.com>
+List-Unsubscribe: <http://example.com/cgi-bin/mailman/listinfo/list>,
+ <mailto:list-request@example.com?subject=unsubscribe>
+List-Archive: <http://example.com/pipermail/list>
+List-Post: <mailto:list@example.com>
+List-Help: <mailto:list-request@example.com?subject=help>
+List-Subscribe: <http://example.com/cgi-bin/mailman/listinfo/list>,
+ <mailto:list-request@example.com?subject=subscribe>
+Sender: list-bounces@example.com
+Errors-To: list-bounces@example.com
+X-Spam-Status: No, hits=-7.0 required=5.0
+ tests=BAYES_00,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT,
+ REFERENCES,REPLY_WITH_QUOTES,USER_AGENT_GNUS_UA
+ version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+"Jakob Carlsson" <esrange@example.com> writes:
+
+>> Om du g=E5r in p=E5 Hemsidan och sen p=E5 Torget.
+>> D=E4r ser du att det st=E5r ditt anv=E4ndarnamn och
+>> bredvid det Niv=E5 5.
+>> Klicka p=E5 niv=E5 5 s=E5 kommer du in p=E5 en sida som
+>> f=F6rklarar allt om niv=E5systemet.
+>
+> Bra svar. Men jag k=E4nner f=F6r att ge en kort f=F6rklaring av niv=E5-sy=
+stemet.
+
+Jag skulle kunna l=E4gga en massa tid p=E5 at skriva samma sak om och om
+igen. Fliker in h=E4r f=F6r att s=E4ga detta =E4nnu en g=E5ng...:
+
+ * Det =E4r jag som hittat p=E5 det h=E4r med niv=E5system
+
+ * Det =E4r en stor skillnad p=E5 hur det =E4r t=E4nkt att vara och hur det=
+ =E4r
+ nu. Jag har stora planer och en massa id=E9er jag vill genomf=F6ra.
+
+ * Niv=E5systemet =E4r en =E5terkoppling f=F6r vad man gjort f=F6r webbplat=
+sen.
+ Som ett tack g=F6r hj=E4lpen.
+
+ * Systemet finns som en inspiration f=F6r de som d=E5 k=E4nner f=F6r att g=
+=F6ra
+ mer. Men jag vill inte att det ska ge en negativ influens f=F6r de
+ som inte gillar niv=E5er. Var och en ska kunna v=E4lja att ignorera
+ niv=E5n. Speciellt b=F6r de f=F6rst=E5 att det inte har att g=F6ra med
+ graden av andlig utveckling, esoteriska kunskaper eller n=E5got
+ s=E5dant.
+
+ * Inspirationen till niv=E5erna kommer fr=E5n spel, hemliga ordenssystem,
+ kosmska hiearkier, skr=E5v=E4sen, akademier, politisk administration,
+ osv. Det =E4r ett element av rollspel. En lek.
+
+ * Olika niv=E5er motsvarar olika roller p=E5 webbplatsen. Webbplatsen
+ webbmaster och ansvbariga har en viss niv=E5, bes=F6kare och g=E4ster har
+ en annan niv=E5.
+
+ * Alla datorsystem har administrat=F6rssystem f=F6r dem som sk=F6ter
+ systemet. Jag har valt att arrangera dessa funktioner i en skala.
+ Niv=E5n anger hur mycket av systemet du har r=E4tt att administrera.
+
+ * Att ha ett niv=E5system f=F6r access g=F6r att man kan g=F6ra som p=E5 f=
+ilm;
+ att l=E5ta de med h=F6gre access komma =E5t mer information. De med
+ riktigt h=F6g niv=E5 kan n=E5 topphemlig information. P=E5 denna webbpl=
+ats
+ kan varje anv=E4ndae v=E4lja att h=E5lla vissa personliga uppgifter. Har
+ du h=F6g niv=E5 har du rollen som anv=E4ndaradministrat=F6r och har
+ tillg=E5ng till dessa uppgifter. Just nu =E4r vi tre personer med
+ denna niv=E5n.
+
+ * Niv=E5systemet =E4r ett m=E5tt p=E5 p=E5litlighet. Vi ger dig h=F6gre n=
+iv=E5 n=E4r
+ vi litar p=E5 att du inte kommer att f=F6rst=F6ra f=F6r oss. F=F6r ju h=
+=F6gre
+ niv=E5, desto l=E4ttare kan du sabbotera inneh=E5llet.
+
+ * P=E5 en h=F6gre niv=E5 beh=F6vs det inte bara att vi litar p=E5 att du v=
+ill
+ v=E4l. Du m=E5ste =E4ven ha ett gott omd=F6me, teknisk f=F6rst=E5else,
+ intresse och logiskt t=E4nkande. Utan detta =E4r det l=E4tt h=E4nt att =
+du
+ f=F6rst=F6r saker av misstag.
+
+ * Vi vill uppmuntra medlemmarna att g=F6ra det som =E4r bra f=F6r
+ webbplatsen. Tilldelandet av h=F6gre niv=E5 ska uppmuntra till att
+ g=F6ra det som =E4r bra.
+
+ * F=F6r att minska missbruk av e-postadresser visar vi e-postadresser
+ bara f=F6r de med lite h=F6gre niv=E5. P=E5 s=E5 vis vill vi undvika att
+ n=E5gon g=E5r med som medlem bara f=F6r att samla e-postadresser f=F6r a=
+tt
+ sedan g=F6ra reklamutskick.
+
+ * Idag n=E5r du olika niv=E5er p=E5 detta vis:
+
+ 0. Kom in p=E5 webbplatsen som g=E4st
+
+ 1. V=E4lj anv=E4ndarnamn och ange e-postadress
+
+ 2. Logga in med det l=F6senord som skickats till dig
+
+ 3. Skrivit en presentation
+
+ 5. Presentationen har godk=E4nts
+
+ 6. Du har svarat p=E5 ett f=E5tal fr=E5gor om dina intressen
+
+ 7. Du har svarat p=E5 en massa fr=E5gor om intressen och beskrivit dem
+ detaljerat
+
+ 10. N=E5gon v=E4ktare tycker du f=F6rtj=E4nar h=F6gre niv=E5 f=F6r att du=
+ =E4r s=E5
+ trevlig och engagerad i webbplatsen.
+
+ 11. Du har gjort ett antal kopplingar mellan =E4mnen och =F6verv=E4gande
+ delan av dem har godk=E4nts av en v=E4ktare, och du accepterar att
+ b=F6rja som l=E4rling i v=E4ktarakademin (jobbet som
+ systemadministrat=F6r)
+
+ 12-39. D=E5 och d=E5 tittar jag p=E5 vad du gjort i form av skrivande av
+ texter och arbetande med uppslagsverkets =E4mnen, och justerar din
+ niv=E5 i f=F6rh=E5llande till m=E4ngd och kvalit=E9 p=E5 arbetet
+
+ 40. Du har full=E4ndat ett helt =E4mnesomr=E5de. En m=E4ngd sammanl=E4nk=
+ade
+ =E4mnen med bra textinneh=E5ll.
+
+ 41. F=F6rtroende att arbeta med adminstration av medlemsregistret.
+
+ 42. Delaktig i utvecklandet av webbplatsens prgrammering.
+
+
+ * Allts=E5. Automatik tar dig till niv=E5 7.
+
+ * Men som sagt. Jag har en massa andra planer d=E4r mycket mer kopplas
+ till niv=E5er och d=E4r det finns systemautomatik f=F6r hela v=E4gen till
+ niv=E5 40.
+
+ * 41 och 42 ligger utanf=F6r niv=E5systemet i =F6vrigt. Den som har de
+ niv=E5erna har inte n=F6dv=E4ndigtvis tagit sig till niv=E5 40 innan. De
+ motsvaras av anv=E4ndaradministrat=F6r och systemadministrat=F6r och
+ niv=E5n speglar maktbefogenheterna snarare =E4n vad man i =F6vrigt gjort
+ f=F6r webbplatsen.
+
+ * Alla texter. Allt inneh=E5ll =E4r =F6ppet f=F6r alla. =C4ven f=F6r bes=
+=F6kare som
+ inte loggar in. Du kan till och med g=E5 in p=E5
+ administrationssidorna utan att logga in. Vi g=F6mmer inte inneh=E5ll.
+ Det vi g=F6r =E4r att hindra dig fr=E5n att =E4ndra inneh=E5llet. Vi d=
+=F6ljer
+ dock en del information om andra medlemmar i syfte att f=E5 s=E5 m=E5nga
+ som m=F6jligt att sj=E4lv skriva in sig och skriva en presentation.
+
+--=20
+/ Jonas - http://jonas.example.com/myself/en/index.html
+
+_______________________________________________
+List mailing list
+List@example.com
+http://example.com/cgi-bin/mailman/listinfo/list
+
+
+--=-=-=
+
+
+
+--
+/ Jonas - http://jonas.example.com/myself/en/index.html
+
+--=-=-=--
+
diff --git a/rt/lib/t/data/new-ticket-from-iso-8859-1 b/rt/lib/t/data/new-ticket-from-iso-8859-1
new file mode 100644
index 0000000..299392d
--- /dev/null
+++ b/rt/lib/t/data/new-ticket-from-iso-8859-1
@@ -0,0 +1,31 @@
+Return-Path: <hw@nordkapp.net>
+Delivered-To: j@pallas.eruditorum.org
+Received: from sm1.nordkapp.net (sm1.nordkapp.net [62.70.54.150])
+ by pallas.eruditorum.org (Postfix) with ESMTP id 48F4E11112
+ for <jesse@bestpractical.com>; Mon, 2 Jun 2003 14:58:37 -0400 (EDT)
+Received: (qmail 3612 invoked by uid 1009); 2 Jun 2003 18:58:36 -0000
+Received: from unknown (HELO office.nordkapp.net) (213.161.186.83)
+ by 0 with SMTP; 2 Jun 2003 18:58:36 -0000
+Message-Id: <5.2.1.1.0.20030602205708.0314c5f8@mail.nordkapp.net>
+X-Sender: hw@nordkapp.net@mail.nordkapp.net
+X-Mailer: QUALCOMM Windows Eudora Version 5.2.1
+Date: Mon, 02 Jun 2003 20:58:30 +0200
+To: Jesse Vincent <jesse@bestpractical.com>
+From: Wilhelmsen Håvard <hw@nordkapp.net>
+Subject: Re: rt-3.0.3pre1
+In-Reply-To: <20030602185607.GN10811@fsck.com>
+References: <5.2.1.1.0.20030602204834.031406d8@mail.nordkapp.net>
+ <5.2.1.1.0.20030530194214.0371c988@mail.nordkapp.net>
+ <5.2.1.1.0.20030530194214.0371c988@mail.nordkapp.net>
+ <5.2.1.1.0.20030602204834.031406d8@mail.nordkapp.net>
+Mime-Version: 1.0
+Content-Type: text/plain; charset="iso-8859-1"; format=flowed
+Content-Transfer-Encoding: 8bit
+X-Spam-Status: No, hits=-1.9 required=5.0
+ tests=AWL,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT,
+ REFERENCES,REPLY_WITH_QUOTES
+ autolearn=ham version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+
+Håvard
diff --git a/rt/lib/t/data/new-ticket-from-iso-8859-1-full b/rt/lib/t/data/new-ticket-from-iso-8859-1-full
new file mode 100644
index 0000000..493ca15
--- /dev/null
+++ b/rt/lib/t/data/new-ticket-from-iso-8859-1-full
@@ -0,0 +1,38 @@
+X-Mailer: QUALCOMM Windows Eudora Version 5.2.1
+To: Jesse Vincent <jesse@bestpractical.com>
+From: Wilhelmsen Håvard <hw@nordkapp.net>
+Subject: Re: rt-3.0.3pre1
+X-Spam-Status: No, hits=-1.9 required=5.0
+ tests=AWL,EMAIL_ATTRIBUTION,IN_REP_TO,QUOTED_EMAIL_TEXT,
+ REFERENCES,REPLY_WITH_QUOTES
+ autolearn=ham version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+
+At 14:56 02.06.2003 -0400, you wrote:
+>> This patch didn't help us out.
+>> We still got problems with auto responding e-mails sent from the system
+>> when a new ticket is created.
+>> The same problem appears when one of the staff replays to an new ticket.
+>> All Norwegian letters is converted to strange letters like ø
+>>
+>> We would love if this bug could be fixed. On our mail server we are
+>running
+>> perl 5.6.1 since we are using debian stabel packet lists.
+>
+>I'd love it too. I just can't find it. Can you send me
+>(jesse@bestpractical.com) a couple of email messages containing
+>characters that break your RT?
+
+Hello again,
+
+Thanks for your fast replay!
+
+I don't know how this looks at your end but it is letters like: ø æ å
+If your want to make this in html it will be &oslash; &aring; and &aerlig;
+
+
+--
+HÃ¥vard
+
+
diff --git a/rt/lib/t/data/notes-uuencoded b/rt/lib/t/data/notes-uuencoded
new file mode 100644
index 0000000..f27fdf8
--- /dev/null
+++ b/rt/lib/t/data/notes-uuencoded
@@ -0,0 +1,2368 @@
+Return-Path: <mhenrion@example.com>
+Delivered-To: j@pallas.eruditorum.org
+Received: from serveurlotus.example.com (unknown [213.56.193.67])
+ by pallas.eruditorum.org (Postfix) with SMTP id C21DB113AA
+ for <jesse@vendor.example.com>; Thu, 27 Nov 2003 10:55:58 -0500 (EST)
+Received: by serveurlotus.example.com(Lotus SMTP MTA v4.6.1 (569.2 2-6-1998)) id C1256DEB.00578401 ; Thu, 27 Nov 2003 16:55:54 +0100
+X-Lotus-FromDomain: DOMAINEQZ
+From: "Maxime HENRION" <mhenrion@example.com>
+To: jesse@vendor.example.com
+Cc: support@example.com
+Message-ID: <C1256DEB.005717B5.00@serveurlotus.example.com>
+Date: Thu, 27 Nov 2003 16:55:50 +0100
+Subject: Test e-mail which exhibits problems with RT
+X-Spam-Status: No, hits=-2.6 required=7.0
+ tests=BAYES_20
+ version=2.55
+X-Spam-Level:
+X-Spam-Checker-Version: SpamAssassin 2.55 (1.174.2.19-2003-05-19-exp)
+Content-Length: 144905
+
+I send you this mail from Lotus Notes to make sure it'll exhibit the
+reported symptoms (lost attachment and body). I Cc: it to our RT address
+to verify it does cause the reported problems. Could you please mail me
+any replies to my personal e-mail, mux@example.org ?
+
+Thanks in advance,
+Maxime
+
+(See attached file: Naz_Head.jpg)
+
+(UUEncoded file named: Naz_Head.jpg follows)
+(Its format is: JPEG File Interchange )
+
+begin 644 Naz_Head.jpg
+M_]C_X``02D9)1@`!`@(```````#__@`>04-$(%-Y<W1E;7,@1&EG:71A;"!)
+M;6%G:6YG`/_``!$(!(D#$P,!(@`"$0$#$0'_VP"$``0"`P,#`@0#`P,$!`0$
+M!@H&!@4%!@P("0<*#@P/#PX,#@T0$A<3$!$5$0T.%!L4%1<8&1H9#Q,<'AP9
+M'A<9&1@!!@8&"0<)$0D)$248%1@E)24E)24E)24E)24E)24E)24E)24E)24E
+M)24E)24E)24E)24E)24E)24E)24E)24E)?_$`*(```$%`0$!`0``````````
+M``(``0,$!08'"`D0``$#`P,"!0($!`0$!0(""P$``A$#!"$2,4$%4083(F%Q
+M@9$',J&Q%"-"P5+1X?`5,V+Q"!8D-%,7<H(U0QACDI.B)29&5%8!`0$!`0$!
+M`0`````````````!`@,$!081`0$``@(#``$%`0$!`0$````!`A$A,0,205$$
+M$R(R87$%%5*!_]H`#`,!``(1`Q$`/P#Y`NZSM9#1`&(!5%[G\U"!WW5B](U:
+M0(GDE5'DR3$]R3,K53ZB?5?D$I,JNC=`\EN#.=PHMVZH^%$6?,)R3(`R90/K
+M:AB0!LHFG"&6Q`,?*(E\^"`"3.=TC6=J`)([!0M#=1U'X3.TZ9D[X**G=6=(
+MR82\]X,ZC`&"H0X"9<3W2:(&GOWF4+VLMN7D2T[[AR*E5<YPU?>56:T,@P<X
+M3^D#'*J+3:SC(<8[92\XB/5]U7VV$E/Z=(!.459_B7`0'&2F%<EX)D]R%7!)
+M:$B1.<S^B(G-PYFQQ/*7G$P9/M!5>9,:ML)Y$]AW03FLXR78A.VY=&=IV58&
+M2!^X1-BFWO)S""S_`!#M,.,'VV2;<.(C48"JR'#&!V*>!LUV_OLBK)KO@C5)
+M_9)M=V0(GNJSAL9"7!!E066UR&X)^BD%P06DOR%2&00WG=$WYSQ[JHL&Y&>9
+MQV3ON7.;ZG:@.).%59,Y.#[)H&F7.`/QNBK;:Y#?S$XP"4(N'[:S[Y46H-8<
+M2#C.4``SZA!V")I9?5=AVO[<IA7=J)C$[DJN""8!VY"3C$;@(6+?\4_83O@)
+MZ=U4;@$YQNJ0<>')L[3G?=15_P#BG`0"[/=(W;N3NJ@/J]>8PG=M(<!P%46Q
+M<5)C5!&Z%UR0^=6_,JJZ220[',XE!J(VD@HJZZZ<3J+H)X*=]T]K1ZLGW5*G
+M+G3N$[-()F/_`,1E0TN-N7@RUY)[R<)V7=1V=1^ZH@.,F<CA$V/+F8CLJBW6
+MO'^8/4[[HA=U,^L@K/?#GB-N4Y)Y)CW15RI>5'#_`)CL>Z`7CY]1=`[$Y50&
+M1,Y]BF&^G!]I07_XRII!#JD3]E(RO<NI.KM-30T@.=P"LV`T[C]D0<?+(DD$
+MR0D5<;?59(+S)P9)2_CJN_F.)VR51=+7@S'QA($N$#9$:-.^JR/63]5(R]K.
+MP*CM1]UF'7L,=H1M=+IY0:#[ZL'R'N$#9/\`\0N`3-1Q!.8*HD..1N@]0)@@
+MQ[HK39U"J2!YA`/*3>HU9_/O]5G,+F"9@>Z?5D3Q[HC2'4*PF'9=]T_\?6!'
+M\QQ^2LZG,[R-U(3Z):<<"9A*+K.I7(<2*KA]4XZI<:R[S78YE9H))$NB?=+4
+M(C.^4&O2ZM<:Y-=^F<PXJ9W6[ME1_E5ZA!V]1P%A%T-/'NI*+M(U3J/LLU8V
+M[?KM\#_[NK,?XBI7>(+\`#^+JQ[/*P/,<'SI`]E/1D[@'.`D*V[GKMZUP>;N
+MOW`#RJEQXCZB^KJ_BJ_P*CEG]1JD5PW5D?HJC2`V8,]P>$A.&PSKO41Z'7E;
+M/_649\0]18YO_K:I@Y]9@K%:\DDM/P.4#G._J)39IT%3Q#U(48;>U9]GE*WZ
+M_P!3:=;KVMCC65@L,M],_=3C\I9@^_*IIT-GX@ZL]P<;ZK#3)EY5YWB;J9`_
+M]75`]G9*YJB-%&!@G<=T?F!K`T3'LL4=(/$_5-`TWE4#_P"Y,?%750__`-[5
+MS[KG?,U-`)..W*$N$F=QW4TKH?\`S5U?7)O:@^J3O%/5&`3>5<_]2YQS_3,D
+M90OJ%L9S[*CI'>*^JA\_QE4>VN?[)4?%76`''^/KCC\Q7-.?WG[PHR\9[\!"
+M.I/C+K8R.I7(CL\J1OC?K[!J'5KH>WF$+DC5TM&#J_="YY=].$Z'8?\`G_Q(
+MTS_QJ\$\"L[_`#1?_4;Q8T"/$-^('_SN_P`UQ.O/`(3O?Z<\JZAR[=WXG>+V
+MMQXBZB)Y-R__`#3?_4[QHV2/$W4\<?Q3_P#-<,YTD-[(7N!.70!NAR[S_P"J
+M'C(;>)NJ0.]W4_S3?_5'QII.KQ/U0@;?^I?M]UPDN()DCLE2J/%6>?8Y5X-U
+MWP_$[QDTZO\`S+U0#L;A_P#FA/XI>-1__D_5!`__`-A_^:XBK4+A#=]LJ'62
+MT@@2H;KO:?XK^.`#'BCJ7_\`'=_FI&?B]XY;G_S1U$]OY[O\UYWYA+")*;6-
+MNRJ/26_C)X]8UI_\T]1@XCSBI/\`ZT_B$W;Q5?D]O-*\R#P`#O&R$UC,SDH/
+M4*?XX?B,R)\6]1$?_M4=3\<OQ&F6^+>HB?\`]IM^B\LU.W(D$*.M7\L$-DGW
+M31MZF_\`';\2*6H'Q3U!Q(P?.V_15S^./XE:-;O%O42WDFIM^B\K#_3).0>4
+M1>-,R,YB86M0Y>HL_'+\2"X$>*NH?_Q/]%+1_'7\1FNSXHO2>_F+RG5#,F/8
+M=U(S+@Z23\)J'+VNT_\`$'^)M.W:QOBJ[@=WJ3_]8;\3_P#_`*NZ_P#WUX]0
+M/\IOJ`13_P!8_P!_1.#E)>;X.DJE4`G4&CW]U>O/2YTY/95"VD\@O(8(_-NE
+M-(*I$\#ZJ.((DP#MRC?I!,.#@3P@<YLQ@D*)T!Y=!!'V3,!(!DR-NZ/!DDS_
+M`)H=3FOU#\Q$3*`2[U;F3^J9FG5DG!VA%J&"\3VA,PC.H3]4#")SCZ[IPZ6Y
+M.?;*9Q$P1">`3L`F@0!<W48QB)RDW\W8=DM)B0,C=(G'Y3OG*H)A]7YC]TGO
+M`;J&VTI8`@$@QRF`F)A`6HZ<&,(`XM$-),HM.TR`DTC)_9`@]H'?NB(U9)D*
+M)Q`R/L"G:]K3D$_*"3`)DS'(_P`X2>0`")).=10F`,SG9*`Y^DS'9#0VZM.'
+M@0=NZ51S@XYR=PG9IF'?1!4<)QF4!`X&<E.9%3F2.Z$Z-(:!$Y30-4SL@D`]
+M<ETSPD2`X%Q$#W3.>TTQ@",3R5'[%V$1,Y\B8S&Q0:S^;4")R9@(2^`<P>T[
+M)`@^IH&GL%%2-?@B8^J;60">3]T+=C.J1O@_ND<_U8'(RJ<E))W`GE,?=Q,<
+M%+TDD@R?E-$C#A[A0$`79&/JF!<U^X^B'!YRDUOJ(*HF#AY@Q&$6KDNB%"0"
+M9!2<8.3)Y4$CW2\MF#^Z8.(;!&KZIG-P-L#A"V6F=8!/""0%I.0/<(75#/:=
+M@$.F:>8`/)3#3OJ'T*(D:[&8B=Y4C7$"&M,J)L1Q'>$3@1L8'L@8F3@1]4+G
+M&(VX2(#3^:>\%`?54P9'SNJHZ;B.^4]0D@X!(0@..QCZIR`V"7-.)0)CR6P1
+MDHFZ@-_B<H`WU1IGV3L#L0!`X)*:41=.<GX1,?\`TYA``=<C`&0EI/,90&PN
+M+IQMR437D#8PHVM)B'>VZ.>9&?T32';4C&4FN.24S!G,B.4@"7P-D4;CZ02G
+MUQM@'E,`7,D-F-TP:03#B)XW03TW0`3/=.ZHT[20HJ0(=JP<83U=1'Y??=$I
+M.>20)B/JF<\D^W<!#)DQB>$)+@3@D=@@.1J(C\RGI.TTAL`%!;@U'@&8)4U<
+M#40..%*LA!WKD@?,JQ3<*;=6J?959U`<%*HXBG,R=MU`JM05+G42#[QLF>8:
+M0V"1O)W4;):`V3G>$4@$&()W0/YA+H&$S"=Q$'E,X34D!.&MF<>_NBIZ.G5.
+M1C=6*32XZSMP56:Z#I#1/=6ZKAY8:!$;PI:1(7B!)SV0O<T[&(0BF-&<#W05
+M7:G%L8&%!,UPC`$_*?7)))4+&NF9P-@0G/R,]D!$M)DGX"3G%P`W^NZ!A;OB
+M1[R@JU"1F(;VP4!ET'TD3[("8?&,^R`O!&,&=@A($`P2.TJFAQZAK/V0U()]
+M),\A(QITP/C>$+R(+B![(IY&DQN$!U$=OE*99G;V0ZVDSVXE7:0I$@DY12W5
+MF0.P47ID%(N@03]R@.1DL'ZH<X)#L\RF$;:O]$Y8WR]0J-+I_+&?^R&A.=`G
+M'T0!\2[)*%P,1D`<(7N`@$_JB40>#_DD#^G"C!R=_NE()$'?!0$X%Q$?NF(&
+MY!&.Z;;4`24#RYH!!)]T#U7A@,\JNZ-6?ND\EYDN$]T]-[\MUX[0M!/;+?E,
+MUDQ/WW2=4TGL3V*!SAQQS*(D:WD$P?T4M+8@29W]E!J],CE'J&H`.XR$&G0<
+M!1:(&R/6WL%#1TFDTEW'9%#/\?Z)N+I/<F"1,=]7^JI5G`N(';`[*[?$.<7[
+MS]BJ=8@'&$J('2&YF3QV0`-+`XD3VY1<D$@'=`T>L@B%`B9(SQF$/&TIHT[B
+M2F;!.3'L4#N:&P[?Y*:1!WD=T\S(.3\RA,:2Z.?T3:GD%I&Y&/A*8&9CLA(S
+M$B/S8PG<V88QIQGOE$T-CV#9SB>Q&R<Z09CW(40V@#9$=IP/>%0;B#F#CW14
+MXU>QX4?]1X*3,$X(]U!,_2'0XS"%S@'20(CLAJF'#E)I!!:2J$XMS(]L(26P
+M)S[(W._EENH@3J4+FR2/902.TZCP(V[HH!!C(*A;#@)V[<J6F<R3J&,3NA3@
+MXW*8",@'(15&AQD8C.\H>8@M^"J#<1&QE-SL0.Y2(SOLE'IF<($XB<F/="8+
+M(<<3$I/TD@`SVE!I].))"@-S@1(=@I"=)SNA((80#*(/$`M:!`SE%%,`#7&$
+MCN"YT_51DNB`Z0#CA.0X_5(AQ&`23[E/L1W/"C@.).T9*3S+@1/9/\.AM!.2
+M0">$3P0[)"%VD9!D#<I./I[IL2'5$`R$!&&SQA"UQTF73/'"?.($%#8Y&3//
+MV35`XOU&)2):`?3!'U0O,M$B`>^Q02!D;F2>P0&(,[I,:XB-4B,Q"1#@=49"
+M`J9!R^3&R=Q;J$(0(GD(*A<(='U2"1V&;#_-`R(D;%,\DL$B>Z$;X!55)G48
+M1-'IS&VZ'48&#GE,<C<0@(`:\`QVDI`;[<]TP=B00G:7"<;\A`;M1:2,8R<I
+M-&0)@H0XZ(<-(B>4[2[5`(GL902`.:#ID(2,Y$D]D33Z().$(F,'ZH#:"6GU
+MB9_*F$SO]$F-)V,%(.Y`D(:'4P0`?LD`[2)D`]]B@#LX:1*<O<&ZH4$C9&YP
+M-LIW.#6R'&>Y0D02-.>ZE<\NHM`:)'/*HBDQ.=MYW2C,D[(2XN;))"32XM(!
+M@'906+-LEU4DXF(2TD$$.)3AX91:WDY0DM#?RQQNI5A]!+@3/PA<=\@HGNCT
+M_?/"AJG$#8^V$"UD8!4E,P9;G':5$"`?5()X4[6.@.VCLJ@2UT`B?E.3#`9$
+M\93:H/I/SA.P-J$``R#NH+%KAVLOD=H4I<9@SW[*(0T>EV/8)5'$N&9![K-Y
+M5+J<68(R-TTB=0P4!<-`@(`YVH24$],SF?A"]P#MQG&4&J'">$+W>H09SA%%
+MEI(D"=RA+9;D_;!3.+B\DCG9,]X(,.T@;?'R@6Y,]L94L4_X4N=Z7R(`V4.M
+MPR2W*=S@:@&P(VE5."?KTR1!)F4#LB"-LRBG<B2[L@>YH`$85"!G@'ZIG0<D
+M093'&<#@"4%0AI@D`<`=T#D1F0/N4P!!@#?>>$S8D3*L=2_@!58+)U4MTC6:
+MD3JYVX00'T[YS\I4W`\H9#G"8^`D\M#AZ9^40Y<7'@?.4#@)!R24Q,]FI$ZG
+M$_W5#XW$S'=,UHF(W0DB#DY3R0V29(W0"78)*KU':R`9]D]=^O\`*%&60\1P
+M@,#5`VGNG`:T.,9!30`!#N,I0!N(`5*.B*/G!U5ITC)&<_91N+<D-)[0FJ/D
+M:08`[H]#O+%0QI)@9S]MU`#1+A,Q^RE9'FD$'M'91@MU&-^ZD9N-.,;JHNTM
+M(I@02B]/8H:=1K6``''LB\T=C]D.%FX.EKA@B,$JE<`AT3SPKEXZ"1&?M]53
+M>!J(G`*54-6=H"B/I?MDA2U3DG&.%&]Y=!+H(/W4`N_/@&$B6R'#=)[AJ(P(
+M0-$/@"9303HF0TR/T2:06!I)E*3!$8E-K=K!``;QE`0#9`P8]L(&_(CV*<&0
+M<C.X"9Y]7QB0B">.9.4[OR['L"A<1$`A&_\`(.!.854F-(:?4?A$6AKX[;Y3
+M4G.#,[=RE.!(CNB'=&X.R8F``,)2X',0)"B)).T245(..>=L)B<D`#M`3:M(
+M!=,@)Z1!?F<'_50)K1)!Q(V[J0#3B#F,(6.;KV(Y,%)S@0UHQP54['4<?B,H
+M69)!/M/=)I(AS@8VRAD:P!CNH)<O/ID$<)F$APR?A-JAL2?NE,9P(RJ&=.H\
+M&=I1-$`N<2)4=1\F03_F4=,X$$E`SP-1$D0A$C&(/.RE(:7.<-_?A1D&8&_O
+MRHIWDZ2-1&GE)C@TN!.3^B3=&D-(`_NA,-<X!V"(E$(;S(SV1/:]CAK:1(D>
+MXX0'<B$;WZC@N(C??"*6'")R3_LIVN`S(,(&N$#VY2$G(;DX0IR3[2.R<.DF
+M!!_=(D!@')R?=-/IDD2>R!SAP),>R1Y(SV]D+V_S2V))YE,T:F@`X"J)&2UI
+M'._*1Q$&/<!,(C_JVV28(:<0.5%Z%J)!)W`R5&).!&F4;@9,Y![<*,#!,[[!
+M!)IFG)?Z@8CV0Y`T@C2A@3NG;Z3\<DJB2GOO\Y0S(`(!)[*2E2<^D][`3HR2
+MA>7:9,`;0H&WR`(.(1;;D",8*:)Q)D;F4B#.'8!GL@?+7$'OPDV"_.3.Z6"1
+MM]1E.ULSD2,90$TES#\@83M&3&W=,USA$R0=RG`T@.!WVA%Z$UQ+CB!Q[I.(
+M.`([^R<.EFW/*`DEYDX(@RB:%.8,&/V1MD8R<*,B'`[QPIV#^43D#]D`Y#H.
+MK`Y"*J#$F028DY4?YJN,`=N457!<=I&<H:,]V)X`WA'3#L'^F=OA0@2=AE3!
+MIY=$F-DIH3CJ?K(V/")H$>K/919@P1DJ5^EEO`(G(W]EEI%5(+ISE!4=D<$'
+MO"6OU1Q*:)=G!Y5036G7)VVE76.<+5S1I')=L2J;"00`.5,S\D@C2$^!JAQL
+M('LCH,<&E\@$YCZ(6-UNB7$!2@[-&`-]U*';.2#`CLE3'I$@'O/"8P!G'=%2
+M=J>9^<J*>L#$#$8P@:3DD@J5X<##?Z9)([J(?F_+)B<H'#@203@9P@GU';/9
+M,V&R0,)V9SC&4(=X<'#,R.%&3#0XC$P$3\N(`V^B&GM@XVGO^BL!C!`&GLG)
+M:W!@Q&>9E`#IG<`\2FEN=L?JB%4($Y"!Q#8&1[]TG26`Z<`;=DG0&C[HH0,R
+M<?)Y35&D.(Q!V]T\C5($@#(*C>('(.\*H?`@'O.-T+RUOIW,Y@J:XHU:5.G4
+MJTRT5&ZV3R-I^X4+H+B<Y']T`O(;Z1N.1A*GIDNP2>XE.3J('`Y"6&M^NZ:-
+M!)RYV0"G!``&DB1DH8&7'`,C"8N],ZAP)0._2XB`94%5\NTCGE27)?2+6N8X
+M:A(GLJ\`F)S^ZH8P!`R-Y3G\V)D#^Z0(&,F#O[)IY)A$&T@&08QRF=!:,$3&
+MR<^H0TD$_JHWY]!W^J!W-);!&#NG.EI!U9!VRD0)`G/"%P;$@2(0&!%0.F09
+MG*.F`#^:3O""FT$`D[X^5)1G4!@GL54K1HU8I-!F81><.Q48H]W9&,92\D?X
+MBJ<+=T&!T-,R,:>%1J!DDQ,=RKEW`<?OO$*C5/IDG._RIEVJ-_J&IW(A0OES
+MRXF(X4E0Z7&"@<1,[\DJ`'`E_ORFP=C]?9$1#@9R.Z%_]1`D%$,?R:@WV^4P
+M'IP<<RG@@>Y,X1/]1)!`&T(H6Q'..QW0^^Q]D1/],`X2!<<GY0(F""9]I*=W
+MJW,H`)P08'MF%)HSF2$03&`M(DP,D),+@(!GA"X:6D#/PF&XP280V1:><_\`
+M9,2Z?RC!X1%IRX?=,-R<G&44B#JU`$=\[)Z8]1,!/MC2<^^$FGUS*H36#5J@
+M>PF$\>HC()V2<'!V/ZNZ%WYL#G[J=D$W#=S')'9"-B=C*+,3JG[X2:#C(SW5
+M#M(U>TIB8P"!GE-F<F2.0A(P29RH@HVC?=&PZ03`)G[(2&R`"B$!XAV$#G)S
+MB.4+XU;P-]^4[MB0[9`XXU`^Q"*%QB"79C&4AAV^1^R0,#3L92<3&=T"!VEQ
+MR8PF$AH+73*0D`XG.4M0:X#2-X0/.CC"(.]6/R]X3"`<[I-;#8G<3]43_"=,
+MZAE/,M(C?NDQTMT_E'9(X;D@$HH1(/.T;)VQF<&<E-!UY)`^-U(R0)B1R)R4
+M-&:/3O,>V4[`6QB3[!%I=H+P"&SM*6HZ1'',($\PR(.3@A0P9)VCA&XNG)R<
+MPF,$02,?HB$&XD_7NF@',9[)2=CE%`'LBI#5J&6ZX`&PV48>-7JV`_5/HV@?
+M/ND02Z7?TG[H&@#W._PB@Z@):1'=.)>_)@\^Z%Q/YG8(Y0%@$@N`G$#NDTRU
+MT<\IAB3.3G>4[7"#N0).$47]43.)_1$PB(C$J,8#8@2C9,SM_9-(-@]$`X.4
+M),C`(#433@M]S*&)=&<]D!TA)S^O)4U9H;;AHDSV4%(@`;F.%+6=Z`>$#,$.
+MW]H*8DDDG;M"'S#C2,#L93R3D"2@(0'#5$B813J<6R/="P'RR22!P$S8:^)U
+M3N2I5D2'3,1B=E$^"[_5&YP:6@A19F7#"FE$`8`'.91%IJX$B!RGI_FG)C,!
+M$[\LP?8@JHC@M#OS23W1EQ\L#&IW9"XR?=3T*;=1>/\`,(:%0:6CDR)_LG<0
+MVI^:.=U-TW^&_C!_$BJ]G^&F!)/`SQ*&]=JJ%PHLI@>G0&Q`[+*HR<S!Q$1V
+M2I8,S!WRD_@^W9,Z)W.!R$$SJKV,<`2)PHF:@09&QY1-_*)SPA(&Y,$X0`:A
+MG&V^439))!B-ONF>-1R?9*EI%3+CGA`FEQ<XR/\`[=Y]U')!(P8'U1@M!_*/
+M24-2=0(CZ*APXQ$^\\(9D%HYQ*1<X52[4<\SND\Z3D$1[H'JDBCD`9W49>-,
+MF)V^4]1PT;DD\("XZC'`50@_U#A!)@S.=H.R-[A$0@#@3+>4@=[M3`"3+1&3
+MPA.6:8R,9.Z6`-1.)0D#\VK3\;H?].02[!$D)G3IG4)/[IO=HE(@@29^B:*1
+M/H+7`P=@HW5"`/T`3$Z0=1.=@%%4(+OR[[JAWU'P-1,1@=DP=#L"1L"F`]$$
+M92S)'M`0$[!!!(GE".2?L4A&LD"('=,X1!D%"<B#@(XCW2U9.-\D#,I!HX;`
+M3%IF,=Q[JAG'U`P=7NBU!QC?]4="B^L:@:6-T@N.IP'[\J(D8;R#DSNHFA`,
+MU!L'&P[%2TY:X\Z3E0@@.#IF.94K&D.#OJ51>HU7-I-;)V1^>[N5'2CRQZ47
+MI_PE39__`%9N'D/=#RP$&8Y"H5B7`Q!`5V]U$G<-E4:N9_:%:(B!I^,B5&X0
+M,G<X")Q,EHP.T*-Y.N3E3M">TF`/U2]$&"9A._5IR1"C.-B?A!:Z76MZ-;76
+MIZQ!@1.?<**Y(\USF&&N<2`,0%'J=J&`.9*62XD.^J!9($../E)PSD'YE,XD
+MNDRB+6Z))`)X'"!-B(+A(3THR2<`Y4;?2Z"W[HV^Q@^Z"S>OH5?+\FB:4,AV
+M9U'NH-!D$`CW]DAD`';W2DB1/"!'TGA(3J&4P^#M".1Y8](!)C5R@'5#N8([
+MRF$1W[A,^-A)A)L[`9/97L'3`,^J/9.08^>4(])@G$Y"<OQ''N5`4PTQ!^4$
+MD&=7"1<`-MT,Y)_=`33(R[9,'D$]BF<1H&($YRA82XG`[!!('29&%/\`RBQI
+M8\E_(55N'0I&D<2(SW0'4\P[Q'NA=ZFSR/U3%W])P=\H*C@#(&>Z*<D[G<]^
+M4GET9,'ND'-)!,@'E+C!D($W\@@DGNG]4G'/*;:8&.Y0.X02$EK<<)-/IF<C
+M"%I(<8)!/"(9B.40]$_TPG^1,80L=)V2<1,D2500D$@P!RC`_E[0%&'%QDHV
+MN.?[*`M;M&((_9`7&#Z?NC:^&;Y/":H6^6#MG:=D`L).YB?>$U,AI)+)]I0N
+M($[3[IV#4'`D-Q(0,-\B/E.2-0Y[H6D'?CB40(B/LB[."7&(`^2CDSG([(#&
+M'$Q"9CFAWM^J"0D"=+O_`,(2:0?S8'<(7?D(9/=.TC>4!'_F?&P3M<"P`M)^
+M2A:2?Z))^J<'.PCF4!O@Q`C&TIP&C+2<H:L0#&^$I`P=O9`9?-4FJ3`Q(3L(
+M$P#"C`WU$8[J0;XS/!0('21&_=&'M,G`GOR@I-+\0,]\(@2<@"0@8M:9,D@"
+M)".A3>\:PUQCF$``@G`)[C"ELW%E20.,%`U>)(,[<)FZ/,!R8X2;-2H07'/)
+M35&C9@R-R"LJ:Z?JJ2TQ.P*9CR6^H;8A`6%P&H93M(@B2/A4&7NSI,81:VBD
+MT'NA$@#!":,:AN@DID/J1$QMA7':12TM>2!C3W5>T8&F7#<9A2G2'&-H6:HF
+M$MRTQIY&(2<YIV,F<GNAV;VG8%,[4!L0$"+V!N9DA/3=@'5*9^EK1DAW/`0T
+M8TDR@DU`8!W0X)TR@^1CY10!ZCD%`TF"3$;(>Y(&??=/Z=XD#W3TV4RQQ=4T
+MXD")DJ@=,TX'U!&4G%L@D$GB.4G$EH``B(D(20!I`@2@<N;!&P[90MR<'ZG=
+M"Z)`F)Y2>R'8F3]95#U`"TZG#V0,PZ8$?*0_*3J.-P4.H3D0!PF@3F1(D%!`
+M#?2_=$]K1SO[H"V`3[\(AA^724AEN(":)$G.>4Q`!+I]D(9X$21N8^5(:C?X
+M=M(TV`M.K5SMRH@T22YQ([RHZKPY\3LJ<&J$D[8&<8E1P-8WA&]L1O\`"$M$
+MG]B@*#I/;N$AW.2D[)B?\DQ:6MW0)L3!E+2`X<#;*;=VK:$>F6R3\(A%I`R)
+M!0./H)X]RG.N8:Z!V"`G_J'Q*+L[20"6N`'*4-+IG;@SE+3ODD']$T@;$_=$
+M2!KB3!)PBI2'1D3P2@IO(;@[CDJ1FJ<F>RHO4R`P`MV]T6IO^']4%/5H$3]D
+M_J[G[(BS?.:1O/Q_=4*S@XR($*W7+M6,?14;C\Y&DGY5JHWU&%NJ(^%$7-#I
+MR0.$YVWPAR';@1P0L[#.=,D$IAM,I.P/5L4FB1#<HA\%LM.1WY2IN#!,"8Y`
+M,(>^2(14?+TDO#@Z,0-S[H!D$B3]CA/Z9P))XF4C`.WW3"`[9OP-T!:O5I.(
+M1#,9Y0-`@D'(XSE/I$3O&=T$D9R0,IJD<#`[(7B!`B$=(,\LN/YAPJ'I!U2J
+M*8DEV!">H?5$Q'=0OB=C\IV%LB0?NH%J&`,?7=$7-F2?JHW?G@`QS"?1J@9D
+MJB1L.$EWZ%,XAQP)]X0-P-!DQ[E(@3B4#D09!QP$[BTO_P`/LHR`'01(&Y[)
+M`-@@GX"@,1M(*>`!.('9,`(B-]H2>WU3)^$BCH,+@ZH&ZVL$NCA"!)W.=D`D
+M'00[W1:?^HY]T00T[$B?E+3F9$]D).ET.]/8IC_TNYG=4.W!CT@C8%%B9G[%
+M`X-<R6N.N=CLG#@&`-&D]QRBE`'YMW?JFP!,@_5`!)U%TPGR9$X*"0P693-@
+M?Z(!,Q)E2TFZJ;R)EL&%-!\0`#)[)$2-OL@,GU`PDP&),`GGLB)6Z2,3CE)C
+M!/K='O,J*($@X.^5(&.))B1SA-B2"!I$Y358!R(E#)`.R%[P70W[=D4G#TG&
+M90AI&3F4G>QWW":(W(*(-K!L-SW3U"<9P$-*9!=.$_J@DG!]]U03HTB1*%D!
+MV3RA;/<3\IVM($3R@-S9'SC*?3&<GV0L<>2=T\S)XVC=`8$"03GW1`B3.2>5
+M%Z_\7UE$9`$\\I%&X"(+H[(@V!+=O9!,M&04B3.`?NH#C5MN4[!G<?"$O=$#
+MA%ZM.K<JFSN:22<>T)P(;.#W]D`+RB),[[Y]BH!((!$<;RI`T#^J9$!"T@5`
+M73],HF/=,Y!^5*IV"&^P1.!@:1]45NUU6HUC07%YC9=5XA\,ML^@T+NWU.>/
+M^;G8(;<B\1ZHDE`]K33&"'3O*L7`)I``[;X4,$#)P@4X$[IZ;7.J0`F)$!HG
+MZE3V[?+]7/)02.>Z-,$1V3=I](*4R[5!^H2?CC(S,+*E$.YQ\IW`SB<[RDZH
+M=S$H"\:).)[H",QB8W[)I+1)Y[(=8/!TQPG-22TP2`J%F#W"9@)9'*1=+>\E
+M(&,\(!J-,B3CVV3M\H:PXEQ/Y<PA+FEI$X/ZI:R0#P/9`M3G-+-6V8"$F3I)
+M4C'28'I("C>\-&"#)[H`@DZM0'UW4FC^5)W/*`N;,@Q.QA25GD4FM+@0-E1$
+MT%(^HYA,YS8$G[%#4>W5G,>Z`J0+WP2T?*!P+1GGA"7Q//NB#M3I=$#N@%TD
+MQ!RG:USCI`)+N(3!X)[?&ZBJU@`()U`]U4*N2'Z9/V43_>$^H&3,<E,2#@DY
+M0.<F(,IR0&['*#4.47HF0!'MRJA-<2(X*1,E,7``CGLEJR<J!Y($<(0X@1Q\
+MI@8D).TM;W11&/H4],M#7`_1`(<S!QV2;!WF408(SC(0F1.(GE*!,ZM^$8-$
+M4OR&9R^2@%D-/$?*FI.=,PK_`(;O^GV;WU+KIS;MQ$4W>8YI8?V(^BJA[#<$
+MAH:'&8_LJBRS\H_-]"G^CONI*8.@:=D\.[JFCU]&K))!6=<?.RM7;W!SLM!&
+M?=4:CP!)Q&.RS5#4<<`"04#G21.4GO<&P,#V35`=>D#;.>R(:);)D92TYAQ(
+MA!K]&DXGE.^IZ]1.Z!R!,.)PF<9,R!\IB[N)^J)Q@B0W81D($^7.DD.)RF;^
+M:)@\04P))P!/$[):])!/?=%'`WGZ)W21Q'8*.G.HD;GNB:^#B`"$B#@D`0/E
+M)K3&'8Y0M?`!$%*F_.VRI$KJ!IL8\/:0\2,R1F,J-ATY_1,2.3GY3/?+G.TY
+M*BG`DSRI,MIZ=,3O[J/5&",I.>"W5'^B"322V<9V*'!=`R1^J8&<B&CV2+A#
+M06&>2/ZD0[V@B&#$X3P]N)`U;CN@U0Z2)^4J;QKEPD?/*`J8$8W3G.^.R&1C
+MD#9,7("JZ]6EV^V4I]6T=RH^W/'RGUB`!L<(I'7$YSRG+8XF,X*'40P`#,I%
+M[33].LCW0&?R`G?A.]L8&2>R%C@21$QV3Z@1IB/D[(&>),SO[I>Q$)B]H<1.
+MV)28<@%THAR8;M\J2A7?1:XL.7")W46IH:0D'M+<`Y0&T^H%PD'=&`"2"0!\
+MH&N!SF2,E.UX+>/HBBI-8714<=/[(]88``R)P8*B#MO5]$S'%I)X/?\`[()G
+M?E(#<_J@(AA`S[HO^:XBF)`R8[=U%JP1(A`[LNVVX3M:#]DVK.0!'*9KP'D[
+M(@H),`E.[:(@[Y3,(U9C&-)2>9S@1P4X4F3I):)C,)__`,4)VN&DQPA9$:9B
+M>$T'`R2'8^$;&SZ9^J'>=(,?ME%OVA`U,,#<C?ORIJ=,:H,=P%'`<^1D=R-E
+M*P@-G>4`09+@G>`3@$?(1@M!)(WY"3CB8&>4#.C@P1S")T!QS(3-=C2(SR43
+ML.:"=N`B!,1)<E'JD1!/T1:(/K*38+ITF!^B+#,<!M@\93LT3)=D;!$Q[F&6
+MN(/',J%I&F!N?E1IJ^%7@=<H/<]K!3>"3JA>B^);RA4Z'>U650PD"F`>?9>5
+M,:0[21!"MUJ]>G;MM:CB&-,D'!!*K-G*.H3Y<;25!+M1G96*CFU&@-!$;F=U
+M#6;MF)X470:;"]_J,0%9!ETM<"/=10&MW$A'Z2"1.,PI06HM9I+M^YPDYVI"
+M-(W)SPDV)&!CLHIH<3))_P`DY=-/1((!^J9^_?5]4[9PUS`9._=4`TP,#;A*
+M1K./H$;O*#X9VV/=`"0\@8/)0(@@0D-3H!2V.9(F/A!L3R$!8+#$2-\;H9(S
+MJR.`E`TQ.^Z1;Z?3QA`F/(!=@\Y3$MEWOLF([;'VW0D@$1N3,!4.T@C+FYX3
+MO=#(:Z1V0.$,G8<ISH`B2>WL$`Y_J'RFT@&>QV2<3'YLM3``3!^$#$$3$)BZ
+M&C$\)-$P(.4#W9[J@G8+CO/"K/WVS\J0R#O]"FT:<@C=$!$X''*3\")`"+2!
+M_6)[[(3ZAGO"!,@1Z8)[A(F#@#(^4Y$@-#I*1WF2/:4`N/K&!/LGT`.$I.)!
+M`;N>$Y;@B9A$[",_W3$>D0#*(&"?9,V3OP@30`22#^R0`DX3_P!)DI$``.(P
+M?;*='!I^<^Z><P)^I3:8.K_91.R9:,#.4":[,B1*GHRYP)!+?E!1`#@:@+AR
+M&F%+0:21)`GVV6D:%N'&BTAKG#O*/2__`.-WW3TZ)+!,CZE/Y/N?N5>$X5+W
+M0U\`&!LXB/T5&IVP<[;<J]>Z=;HG'?95:H<6N&J`-_4LU5>J6R=(`GA`UPU3
+MSP0C<,'T_=!I,@$1/"BE-,$:@2W8B>4PC2X:=_N$B#IEP&<PA:(+N/E1"=#6
+M:MP.$HT[Y]T0$&/NF;)!Q'P531_26@#9-@.U$Q[!.#)R,PA=F,GMOM_N4#\2
+M`#/"+2W48!RDUN9F<[SNG9^4`JA3+6M+6C3LAV<=Q"*H/5#<M]\(1EIWX4!&
+M)&F(.Z9X:#N93@&,!$]K#&)PAI&V0?T@IS);,SRAW=O`E'`#9VT^Z!-C2,`#
+MW0L#6N]61S[HR"!O^9"\^H``GZH$/RR8R=DOVC"+27F2Z"A].@"<E`XV`!W[
+MI$RR!$!/R0"/=,\0W,YY0`"9'Z)G#(AWM\IR,.)&>/9(#U`SL4#M#G"29(]\
+MH2(@29/8HV-BGJ.T[IG#TG=`-,0W&?:43@Z"1J,')3.PPC^KY39`]C@^Z!P6
+MP<;^Z;MW[IS,$AWU*0F2<!`V8G`A.W$D1E.(CY2<X&)@1&R!Q,D.(QE.);C8
+M'!GA"UL9))X1G\QB806ZUG:MZ+1O67M-]>H]S7VP:060<'WD*FP;C'R1*.LW
+M)#H!&9!0-(:XG3(CN@)T$0/J!RBTC6886G_#RHX!_-()W2)@S/\`F$!O>3F(
+MDS`$(#(/'RG:9.2?<)%OTR@1W_1$"X/:0)0T\D_NB+=9@`YW0+(G5B0G/YRX
+M#XW3D:J<$NG?;W2U$$S!&^/KR@31+-0(D',82$:-I,X"=K26''Z[IP(=!=C;
+M<_Y(IQ/L9C*)C9:!)SPE,8TS/;9/2:X#L9F5=(3@X``#'"-S8.7<*32UU*)R
+M.R'2';MF,RHIG4R,$1&^=D_I`&,C*DIM+O:<H7:FNVV32&(UAA=.>4=4`50U
+MC1,3/=,,.D`X_J*>OZB=R1G]%+RL5WODZ1W&,B$X<YIP9U[R=T[V&#J.V,('
+M-B()E72K%!Q:&N:8TYG?,JQ>75>_O*EY=NFI4RX@``G[*I1IR#@>YE2-_P"8
+M""94T@2[33,`R!A/2.LZS@#9-<5'U:HF2[8'X&%)IAH:(]/92KHC(VF>X128
+M$GU>R%V'`<\II.DP"H$]TOB?5VV2<9&J3'9-!+06DB3ND`YQ)B>#[JD+5!!^
+MIRDQVH@.<1[IIC9N/W2&",`DG<H:,20^03.^2F<0(#9^4X):`Z9B2<80@<DH
+ML$YP#1!SRD#G\T=D,``:O@2?9)\AL''8]T$]Y1J4"P/+':FBH-+@<'/W4#7D
+MR282<6C`,%VW*$D-)@&)'/*0.7G43P/9#K<1,C)W'*;?;[IL#DXW5#Z\:=1S
+MW3/)U#)CL=@F$N<&EPR([H2TAK@?J@(%T8=]$Y>7-`D1*3,?F<)2):_!@>RJ
+M&&,Q@;J#S`YP=ICV5BXKTS0TM;\E5?1,D'XE('J/$Q(@\!(U(;&H#ZIBT`3]
+M0AC$%TJAR\E\"#'.R;4!L,X3`AN(XR4(C)@[J`G.)&`W`^$['2\@C(&(*9I@
+M3!/RG8UFF9W1`EV9D8V2EQ=O*8MGMVW3Y$@1/[H'UX2:X3!/W3``M^".4VSA
+MJ]0]T!DB3`QLF>['.>R;TZ@0=^4PWD@F>`$#ZL1!@29[(FN(//P90$3,@0>"
+MEIT^D9CD(B4.'((CWW4U")]IG95F07'2I:8`]6Y$*PZ:U!X=1:9C'9'J_P"K
+M]%%;,8Z@USG"2I/*I_X@IJ)Q^%>\@2,3[\*G4]3L`EQ=/*O7H&HXP.9W56A7
+M=;W(JM]4;`JU52K&DP#W`0/!TC(R.>%(]TDDF0=T!,NF?NH!.^<@X3<;P2G&
+M"#''=.W2=6K&,(&+L'`SW2&_"8DN@0!'LF9@QD#V0.#B0V9X1,:PYTY/NASP
+M"DW?)_1!)`!@SGF4U,G3S]$1!+@&`D^PDH6983G"J'):3GYW2DS\(=68(&$1
+MP=OD%%*8$9R>$[SO'9,TPX#VYY0D^QR@7],$E/3U!TZL^R8`:9RDT9,X]T03
+MR"P;GC!W34R2X8^Z1([8Y2#@#Z.$4XG9VR0<3)SE)I!$$`9V'"0/](D"5`[7
+M'5+C!'LE4<7.`WX3&/,+@/NE4@B6SODH!;D3J/.R<[S((Y2&G5D3/,I#\\EV
+M_(0+48F1":>=H/=.XX/J@H9&0#]^4#D[N)D\IB&D>K<G$#"$D$#;4B=+J(]4
+M0<!`G:M\"4AG,#'*%ID;\(VCTZ?N@8.`YCLBWR(E,6@-[IP"0`#GL$#M/JP?
+MHB+BT1CV0AL-D;!*,R1\90'J!!).3ND"-67`-Y($H<EI$0$JA:'P!I;VF4$E
+M4L:]S*1+F#\KR()'PHBWTZL0A!!/:/='Z=1AVH#F$#`RZ=S[(R=3VAVW.5&"
+M-1D[(J=33K#`(<()*`B8,`9"=A@!TG[H9D?YI.R`$$K"?*F/9)KA,P$$^C3/
+M.#E$T`28P,*@Z>\3G[),P@88$"3^RD8)]_K*`F#\HTJ:@TEQ#),]LRE86]6X
+MJM928YQ=@87J/X=?A_YY9<WU.2=@6[)&<L_5Q'2^@WE[6:VC;N(//^PNQM/P
+MZJFS];8=`V^5[!TGPO:VM`-IT6-@1`"U&=+9Y<:`I<Y.G*Y99/G6^\$7MLXM
+M;2+O=/TWP'U"X`=4HD-'?NOHJGT&E4?E@*OV_0*#:8_EA3WA[9/F^\\`WS&%
+MS:9)VC"I6O@7JE1QFB6^T[+ZAJ]!HZ/RC[*J[H%!ACRQE/>+,LX\!L_PUJNM
+M]=1V>T#O\KG_`!7X3J=(`<8=G;_9]U]']4MF6EN8;@+R[Q9:U>J=<IT?+AC3
+M^8+/OOX8V[WMYYT_PU=W-EY[:1B"29QNL.^M7VU=S'M(B0%](])Z)2I=)#?+
+M`;IB(7E/XM]&IVMX:E,0#N`KN6-8YW;SZBW2#G!*,["#./LB<'#,R)Y4=36U
+MP(R#NHZA<[47$G/)2],2TP(2(Q+DS<M```[*J>F?3VSLDT""9,'L4+02=.T=
+MDX.D$9^2@:63J:=^)3/#0,'/'LF<(,`3'ND[6!$R`-]T41_(,XA,T`MXP/[I
+M9&(!`0C83"H<Y$'Z)$-!`)GVG`2>.8$'CLE3INU"/LIMJ8VG:W40,8F$VG!/
+M;:5.VVJ.W!RI_P"#TB"TR>W98]X[3]/E5(0[D8'"8@-(@Y[=LJ^RS#:8)$_!
+MW41LW;_V5F<,OTV<4F:-0SGLB>9=C,;'LC?;5&Y:PP.0=_=1.I.#B-,'?LM2
+MQQN&4-Z`T$S*CJN)>&C')37))=C!43B0)'9:C(G!L8@@\(2T`P<1W3-,Y<,>
+MQ2<Z`<[X506ML@$`0/H@IMR`?V0@SG(5[I5A5O*=>HQ]%C:-,N.MX;/L)W*@
+MID8D&$.!DC/:4[QB3./="#O\;2G_`%#@1.?OA$T&1/*!L3`XW`*<R&X_?"*$
+M@D$=_NEB),]DP(U29B$1AS2[,#W1#``D@\)VC&`@!DD*6UKU+>X;6HF',Y(!
+M_0_*&T9;G&VR+&C^W=-J).)!/9-+L$\]R@?2"Z&R`=I2:1ORFGN8^B=OYL">
+M4!TIF6_*E:,R-Q^JB;F"7P/A2TR"<2"#NK$:-!O\H8_4H]/M^I0VU1IH-+G-
+MGW"/73_QM^RNS:.[@SJ='SF51KENJ`(^JNWS0'&#,?,*C5B-QV2FD3C!P/NA
+M<0'3(1.D849(Y60GYV"8@!^EY"9GYM]CLGJM(JG5AWNBFEL@!(#.0CI/%-^L
+ML94'(=,(6[B-CW0(!NN#D)-_-$8&Z;!?,?5$&P/<Y]D06H:P`Z('=(G,QN4)
+M(VREZ1!C94.8)G^Z(!I.K5D[H6@'(3@%K0`,*!G$:O44B!$SONAJ`.=$A(M:
+MP01QO*H(8R4XTQM/U3AK74\[\A,2'1/`C`"&BALR"1&92(R8_P`D/]$$_HG:
+MV&]OE`4;;DI1G?U!"<`:1$_5,P'.,J`OZN\)$M+3DI?TEO$]T,$[H";$3.X3
+MC?<;(3\Q]4MAC[RJ=$6P2-0W^Z%PTG<=MD3@0W>.Y*`[$%Q^94-$./5&=TY]
+M6"YL<Y2HZ)/F:M,?TF,IFQH@$S\IM3@\DY'9&V`)P2HHQV/>43>8)RB#P7',
+M`I,,`_XB<&4V(@G*$>DY!)E!(UKC@[)SO&J!W(0M#B<S":3QF-I5!-:[:-^R
+M8[P8,)A)G@A(MG<[<HIW-!P0DT;`1!00`1,_*-HDF'8^Z&R>,Z0-]Y3M;!$P
+MA=(P(3`N/930D='E9F>R3!Z)(0N)P)Y1ET",#LJ'@AHC:.Z=F6\Y]T+"Z2.%
+M)1:[2=O9$%3:&-V$GDJS86M6XJMI,:7.<<>ZKTR3Z0)/<3*]#_"+PU4O+IMW
+M49+`9`(/^2,Y9>LVZW\*_`=O2M67-S3#ZKA.1M^B]8Z+T^G;L#13`^%'X7Z>
+M*5!C-,8[+H*-L)S"YY9;<9-\T-.V!`X/LK5&U!&6Y5FVH>D<*W3I:6KFZ2*=
+MO;!KXC!5YM$%L$;?JCI4I,E6649XW4VU%-U$`:0%!7MQH/9:IH]A@J&K2$?L
+MAIS'4^GBLTC.?9<S6\/4OXT/#?RG>%Z+7MY9$96==V6=7]E=LW%S->W%.V@!
+M>7?BY8&O:/>`1&Y"]BZE2#&$'8;KS+\5*M)G3*PQLI+I9'@]]3%.I$[<*M5'
+MI;DS[*U?5'/JN(V!RJ[W=FQV7:.J"7:I:<)Y,1@E.3#C.4.2=DL4XYVQ@X2@
+M%N9)]MD+I`PW"0=N8,G$(0Q(B-N\),#G#TR>PWE6;*SK5ZA#*9"V^G]'#-+J
+MH),<KGY/+CCV]?Z?]'GYNIPPJ5K7J$0U7*72:P$.<#';9=*VVI,8T-:,(W4Q
+M@:<+RY?J;>GU?'_Y>,_LYUG23H]69[*Q;],8QTP#P!V6Z*=,4<MR=D+:3=1V
+M6+YK7JP_1>/'J,T68G#1",6L[;K1TM),`)F@%T8F%CWKK^S)\9K[2&@C'OW0
+M&W@"1[[\K7T^DN@`*&JP.PWZ*S-,O#BQZMOI=(;+NTJI6L0]Q!$'B.5N5:32
+M)(4%1AG`77'-YL_T\O<<]<=-)+LQ*IUNG5&9'J75>6W4<`_51.H!P@M77'S7
+MZ\?D_0XWF.3-N]H/I46DY#1F5TEQ;,)TN;LJ-S:-)D-A=\?)*\/D_27'IC:3
+MO@\PCHO<UXTG'(G=6*UK!D;J$L+"?3D"#(727;RW"X]ANG:ZAT1!V#0H7ZHS
+MN.4;AR1`[$IMR&@B!WX59,R0(B$JC3H!@P=O=$X`#?[%,2"`V8[1B%$T$;&.
+M$+7$"!,?LI'L+'%A,GF#,H'QJW0#,&`#)1&"/3(^J=I9`,F1M"$`$R#E`[3!
+M@B2@$]RI-W9D]T,2^3A`GOU&3GW2:[)U`3\)/P9DIZ?Y/S$]C.R`Z8!$@?JI
+MZ,3"B:`UL$`2I*>F>%J(T:+Z8I`&E/\`^)%YE/\`^'_^=/1T>4/[(_0FDVCN
+MR&/CN=E1KM+GX`#5=O<./:?LJ=?6`!CVREYK2*N"&Y:-/=0`1O!5BYK&I3:S
+M2T>6W3@;_*KDSF!\+*[`\`"<]R$CDYA.7&#(&4P=),`(A^Y`S^Z1)!G0/A,=
+M]Q\)&#C5&$#P#Z2V9X",`!LM)'_2=T#7%K@1N<R41<XF9SQ*!C.F(,_"<-);
+M._(2#AF1/]DYB=A@<(A-(TSRD6D`&##ML)R=+<_IRDYSB!,EHVRJI.#1I.#(
+MRFCU2X9X[)$MU#&W"=S@1!;[94`2)&"B;@Q&1R2CMWTZ-4.=3#\'!)$'O]$S
+MSK)+B23V0`1F0#_FB:8;MGB4TM!RG=AOI=/?*H9QSDDG=,/4,XSR44EID8CN
+MFJ/UO+XR=XP$#[@;F#'T0N#0?E.UPTG)U<)G$8).5.@PTQ/Z)R<8.#PFF`3*
+M?TDS.$#/`QZOHA/IR8^Z,D1!^Z9SN`90"T[%I_5(CG4$<&20<\?"0'HG(A`!
+MCO/N$1+9].P3-!,G)^B,$;($((D&<;%-I([81,AN9G.R<]S'N@'$#)2:6@GD
+M)CM&"=Y2TS)]\%-!.:0V3LG;H(]3@R!RF</1._&H)R&@X/V"`>#,2B;!SB`/
+MNF8-6J8QF$YTGL([H%)B"A8)SC")FF9)D>R1&"-`^Z!FZ2!,(V#!C9`S)C`"
+MDIZ<D!`J>)&H`*:@T:B'\;94+&@G8#WV5FDT;8'U07NAV#[J]ITJ8U&HZ.Z^
+MCOPPZ"VRZ328&@$-$XB5X_\`@OTT777FU32U"G]>R^DO#EJVG;LP-EC*Z<L^
+M;IJ]+M]+0TC;W6@RB0Z>!W0VS0T",?"O-8'-Y*YTD*W;F8PK0IEPD;)K.E(R
+MKK&%K0UH$$9*EK2O18(^%<HTY;@<(64X*LTQZ/[J-:1!DQ*"I1!G4"K(:/CV
+M3$`^R&F?5I`">W"IW5'!ANZUJS`!)^%4N*;2,$PAIRW7*.JF6E>7?B%TFI>T
+MJE!LC5B3E>S=3H-=3,#9>=>-*#VO<ZEN#V4MXX23EXZS\-R\ZGW&_LJ/B?P$
+M+&U%6WJ%[HV*ZSJMWX@H5GOI4'>4WDA8W4^MUG4]%VUTQDA<9Y?)/CO/'OJO
+M-Z]I5H.TU&$9@X4-1@$P?ONO0NFVEEUB62))W6%XR\+W/3'&K2!=3<=X.%Z,
+M?-+=4]+O3EG#T0>5+:TV.J,&K$[!.:,#U'`XA36+:0K!I#H_ISRM7+AZ,/!K
+M*>SI^AVM(46QDE:1MVZ9)CYV6?TJHUE-K08+1F<J[4NB[!(@]E\O/?L_5^&8
+MS"2#K:1#0&^G$@;J!\<?HC+@3OOOVA`/S;Q!VE9D=./@BW@N.`AT@$S@QW1S
+MQJ0%PU$`[JZ2TU(@$SOV3XU;X0GY3L.8_NK(S:(&<$X0`28F?JCI^IV`#/NI
+M&-(,N(CNAPKU&X.3*K/;+B3^ZON#"X2H:M,3(:?E:QK&6*F*8`)X*%U,:O2,
+MJT6:A(S\H"P`8'ZK<KE<5%],&2,=U!6IM..ZT7MEN,2H33!DQ,+4R<<O&RJE
+ML)G2/B54NK=K3J#9G"V31+@8;!*KU:+7&'M,QN,+MCF\OD_3RL"M99]+8',E
+M4Z]%S,1)/*Z!U,ZC#3'=05*#"TRPN]EVGD>#R?I9\8);D$@X]DM.T_<K3JVK
+M"8S`56M;N!,"0NLRV\>?BN*N0-+3DGDJ,@&9;]U+5:1(((`P(49;%,N&\K3E
+MK0?2#@(FMD3^Q0AI,G^Z)K=HDQP@0`/TX2.()!`*6-X..4BW$Y^VR`<$R?OE
+M'#=>MS0?;9#_`%R<_=2>HC),#[HA4V@O`!&3B2K+:3J54L?^8;P0?V5=@+7X
+M&_<*9H((F9[+2::-$GRADHI/<IJ!FBTYVY1K)PCO8#03$DP1'"IO+7#3'&X*
+MNWAET-[[]U1J@9S@SRK5TKN.#&^V2FU`,DP>$50P)(!08#)].4`!P:9W2,'(
+M@?"1;!QM"1T[&8X4"F>('LD73N,\)$2<"$@UL[[=@AH[@W!&4\@`2AF,-$=D
+M0((/I*!1/'PGJ`#&_NAV`(^ONB@3,Q`RD*6F(S[%,(C!V1`@$1&4(.8T[?J@
+M)P&#P3PDZ"9@;X3$0X#(3;-R1WA`W<GOM"DHEC6G6P/!$"9$'OA"=)<3D3RD
+M6>G5M&=^(0(PYT`&#R2F`@S(ENV)3P,D&1$(20?2#))A`[LM.WP.4M+3`#ON
+MD/T30"[&)0.1,YVRFB&#YP$@")$_F2C3SE#1')P/CLG@;;)FQL#LG9&,F!C?
+M=`G&#!,@]D,>WU3Z(&K,3"8?E(!S*!P($Y^=TX`(._<Y0G4($F-\I`$`'7(]
+MR@=N''2GTX!.24IWGD=TL:B03E`^F'1V]TSH+AJP.4A&K#DQ'O\`=`FALP#C
+MNC/I(@QVA1P6SZH@I\H$<@R[Z]TV8+2222D8!B/HGAH;((<>QP`@;40R`1ZA
+MF0EN`(R,$IB"!NB:-I/&R!B"#N91-P"<D)1!C]$,'43DRBK8J6W_``KRO()N
+M/,U"J''#8_+'SR@#G"WT;MU3]5".YW^45/5,;3V4T@F`AVX([>ZN6=)U6JUC
+M#)<8WA4QJ#N-UT/@6W;5Z_:-J:2USQ/;<*I7M7X)>'/X/IE*K48-=4!Q/.P]
+ME[#TVDQE!HD;!<GX2ITZ-A2T@"`/V746=4'2&F5QSRY<<8U:#"2(,1NM&UI2
+M`9,'=4^FM$!QC9:UM3!`@X66XEITVC:?96*;?Y1)Q'"&G3B".%(3(`;V4K41
+M'+H!*FHR8$Y]T-.D7.!*LTF!A$Y[*:5&01$HFM&G5^ZE=3!$\)FLTM@YE%0U
+M&F/95+FG(GLK]8D^F8,*A>`G`A+1F7X]!,?*Y?JMHRO6ES!`.5T=_4+9!&.Z
+MQ+BH"7$B(&%C*FF/UNWL'VIM/+:`X:0LNX_":UZ_T\MH-(J.&".Z@N:[W^)M
+M)R`?[KU#\,NMT+6[;0K.&1&5Y/W+A=UWF&^GR?XZ\+=;_#SQ2VUNVO;2<[4U
+MYF")_P!%V'2[=OB7PT0&@E[(F)S"]6_\9%C8=:\(_P`51:UU>V]0<W>,KRK\
+M`:NJU?:U#,9S\#"WGG,M91N;T\?\7]/N.D=<K65=NG0XP8W$[K*MWO-0.&8]
+MEZG_`.(GI/\`_<MO6IL'\RG!('N5QW3^FM92:"UTC,%>F>7&8O3X?#Y//=AZ
+M?4K-HB08]E>I:B(_0\*:VM<#TG'SA7&VA#@"V9]EY,LY:^]XO'<<=519J),Y
+M/"-I>(EH6M;]-J.:!Y#B79P%J],\*W-=H?HT@;:@LSGIK/R8X3=KE1KW@Y2#
+M:T?E.>5WMGX.`<#6=,C(6W;>%K%E-H-%KL025VQ\-KP^7_T?'A>.7DP9<$C0
+MUP=QA6Z'2NHU73Y+W%QDSRO6*/ARRI^H4F`]EHVUE;,9'EB`NV/@GVO'Y/\`
+MUO\`\QY78^&.IU&RYND^ZL,\)W^DAQ:#[+U&E;T22)$=D?ETI(C$+7[.#SW_
+M`-3S?'EG_E&]TE^H'YW0N\)7;F>H@$GY7J-S3IM&1QM*K-8P.VW[J_LX)_\`
+M4\SR^X\*7S#Z:<@'$*"MX:OV"32,[X7K;J3'#\L(76M$M]7V3]C%9_ZOE^O%
+M;SI=W1,&@<;JE5H.#9#")]E[7==.MZH(-,&5F77A^S>Z/*;]E+X)\KMC_P"K
+M_P#J/'GM(RX`905*(\LN*]4OO"-BYN*<$]ES_6/"#X)MS_\`A_V%F^'+X[X_
+M^CXLN+PX"XI2,CN5"6MC@SA=+?\`AJ^I./\`+)![2L6YLJM#4'TG-+=Y!_R4
+M]<HZ_N89\RLNK3(<9`(G_94+V!S",Z09VW5RX$R"9C8*.F*8.W$K<KAGC*SZ
+MELTTY,0J-Q0(>0&Q[+<K,!(`[[J"K2!F``%N9O-Y/!*PJP+7Z2/RA,UWM"TK
+MRW#Q``(6?6IAE2(D3DE=L<MO%Y/'<48@R<X1;'<QR$5%K#J#Z@;,^J#PA<`#
+M`(`[K3D$:2,?:=U+2<`-R%&#)U'/8(@&F26^V#$%5!-<"[.2IJ)$ZR"H`&AH
+M..>5-0@$`^^%4:M`32:0#$(M+O\`"5!39%,>MR+1_P!;E4W"OC,@M.,22>ZI
+M5#L#B`K=WO+HDA4J@:79'ZK-:1O.O/W05`2X"!`'Z(X(,EP@CNHWY(P84.@N
+M@'WW3$F(Y3R0[OREJ:701`Y0"Z0\&1!Q*41#6QMV3.,NW.F4@<9&=D#_`&WV
+M*D86B3@8Q$84<B(V")D;DY0*,X_[(GY=)B-I":9($#Z;)YR1O"(?!`@2`>Z9
+MPTD2/LG!YS&WPD\!I<6@ELX*<*%HB=]L>Z?!+2XR(C'"9I/`YW31!COE`1]+
+M8WD\X2<"1Q`.R$EI@`00<E.8\LG(C]4"<T2#L/;)A*)',SOLF)$B),^R0(B?
+MR^R!#`F3V3.$.)S]4Y,C`3#!GWD\H''S@#;NG<6N@1D[(2<D@C=,XP`<24"A
+MP+FDG)@A24VDX+@,')/*!I!;O]43G"=P@3XDC;/="V2T2!ON4Y$@"4P,-Y^?
+M9`S7=C(2+A,$[8,I2#J[>Q31Z<9`]T$A@S,8&$FY(`CZ(0!(V$[3.?A%)C?<
+M(O18#?HG`+6->Z(?C[(=Q^B=Q,MD^PE$,[5P<#DIH)GDI5!#N/@)-'K$.F$!
+M.;I9)<"(G!W48G5O$G8HW3DNV03!11D-T$`@&$@[88QN"DT"&S@E*/1C;LH@
+M00\P=OA&=R!C.)0T\&(W3D'5![JZ#N/!,(V.B21GN$+0TF"G.V=D!TX)U#E;
+MGA&N+?KEO6C#'`[_``L*1O'T6ATUPIUF/R8,YPI87E]0>$KYMQTN@YCL.`./
+M@+LNBTW5-,X$;]UY5^"5S5O^ET0=F`"3/LO9NA6^BFS&?V7&SEQC7Z;2AH)X
+M[+5M@T&.V%2M6P,<]U;HNR,@?59;BU(:TYGZI4A+O;E1.S$!3VPVD*?6DU)H
+M&)4C&2-X2:T1Q,(I:,3[?"H<#2()F#RF(G`10#A,[TLD&(10.I>C5&V.RH7[
+MM+'9V"MW-7T?FC'?=9=\^=0=RH.8\27[:3RW(E4NG!UY(:/S<!0>--+:X(=R
+MH/#W6:-D]_G/`U#!*\N>?K77'';,\16'\'U`U?ZAF"LZTZQ4M[X.UF0Y:/C?
+MK-"ZJZZ+A`9!(^JXVR+ZO4`."9)7&WV:ZKM?%]Z>I^&JE*H20ZGM]%P'X:6M
+M>RZQ7-++02/U"[#J=84^D>6#)+8CZ*CX2LC1I5*C6$N>2DWKUC4NG+?B]KO.
+MJT61EC>WN5@]'Z#>73FZ:+HVD@KTR[\/_P`=U'^)JTR2T1!"UK#HK+>D`RFU
+MH[0N^'AM_L]W_P!#'PX3'"<N(Z1X.>S2^X/P`%NT/"UK3A[J;3\C_1=2VTEA
+M):.P3_P[BT""N^/AQG.GES_]'RY_63;]%MFL;I8W`X"M4[)M.G#6A:5M;9DE
+M7&6[-.6B1NNDQD>7+S99=USKK5_F3"E%M4$`K;-!NK`1?PX+IC;E:<K:QJMF
+MXM,#)0T;,S&X*WFVX.XD0B-NP"=@K-);6&VR.HN.(X4IL<2(@96KY0+@0%.R
+MCZ0`?HJCGZE@7G/"B_@M.=,KI'T1,!JBJV[7$@A6)ISC[>IO^BCKVSRXZ9A=
+M#Y!DB(X0U;<#>)A!S+J%5C9(.$#608(GE=)5M6.'TX5.I9-+L(;8E1C2[2!^
+MJ@N*`$P-^ZV+NPT9`YW55]!PG5'=-&V'5M&5'9:(&=EG]5Z!95J;@ZBP`C<!
+M=$ZFW7@?9!68'$`-SLKTLRL>:]:\#TGS4HXGC_87-=7\)7EHPN:USF^R]IK6
+M\#+1GV52O9TJK-+F@@[@J62]QVQ_4YX_7@%6UK47N\QKA'>5#7T"F8&W=>S=
+M?\,6EXPN--K701@;K@O$_@^ZM];Z#"?CLLWP[YCUX?K9>*XQ_J!=V&RJWE(.
+M;@;[+4NK*O;^FJTMA4[ILM(VCCNL3<KKGK/';*:`TGV3M<`UX+=3G#!).$SP
+M?,Q(0O'^&?HN[YUT%FG6(!$^Z,;0),(8(@9]RB.#Z096D$&QG)_93T`T/@F(
+M&R@!AAD%2TCD>J!SA-)PTZ6D4P"[CN44L_Q_J5%3<\,`#G$!/KJ=W*^IN%>;
+M%Q<X=O=4ZK6A\R3"MWC1F-]U2JD`ZHRLUI&2!(^JC<8GG^Z-T:/?Y0R`T&#*
+M:0!+3S!CE,=,X,]T[FGG,IN<?6"H!D0`""C$%\'O!S"9TMXWY*$9@@GZ(IW`
+M`;_*3?I'$)-TDAIU1.1,)V!@>700R<2J@VMP,GV[)%PWD`QLE(V$I.;F"443
+M1C3(E#4WXSPGW.!OB4+L`0B':1_LIY'F#\H'OE`W?:/=.!P<\H=E4TS$G*6)
+MTB3!W)0U`9B1&Z<!LS(C_"$#X$C8>R3_`$`01!V*$@%WM[2A($3!440+8DP>
+M_P`)-]7'LFW$@@$<).:X$#(,3"(<Q`,)C@R3LD1D28"9X$;P@0@-C5&K>$3=
+MI]D.B&CU;IZ8/.?9%.W2'1$F.4Y'9XD\H""`08![RA`)[0/=!+H`HZB]KB3^
+M4`RFQIB!\%#"<?E`+C/(*0.T3`+H]Y1:<8.$&[MOH"GWF(&(D(:2`L-/3$.D
+MYE"3G(VRG8)S*C=^;V"`B`8[\IM()X)2.`3,?W3%I:[5)&-R@,8;G890.:"[
+M`]TXUQC>3E-SEKI*`FQHQCB>R>(&#N4)V`[CE.X>GCY"!VCU2,]D[@#.\I@)
+M`EQ(`[IVF!/*(36_U"!",@:-6!/"C:7-.F)&$[LY&P/*`P&^V=RM"P8YSF@3
+M/8+.8'<NTQ^JT.CG_P!2UI."X82CZ5_`*Q90Z!0)`EPF3]%Z]8M](`_1>7_@
+MY58WHMLT<,'[!>G=/K#2#.ZY95QQC2HXW^ZN4F8&RIT'<\*U3<(X6'18IM$[
+M^RLT0`/T56F9((^JMTHP9V14S8#"XCZ*"I5/F1SW4I&(!4+J<&82K$U"H8,G
+M,IJK_>4-,:<G)2JQ'*""Y<2/U5&\`@DX/"N5G2=\CLJ%T0>0%#3A_'#'AP<)
+MB<X7"=>N"2&,)'<SLO5O$5@RO0=.>5YSXDZ.ZD7/:V2"N.?B]NFL<],FI;U/
+MX)I+IGNJ@K,MGR-QE3BI</\`Y.DG^RT_#_ANO>W#7U&G3,Y7*>'*_&O:`Z1;
+M7?5*S1I(9O\`*[OH_2&6UJT:1,=E>Z#T2C96[0&B5IOIZ6B&X7HP\4Q8N5K/
+M99TV@@`;3LH*U%C7:8"TVTGF1D(769).)*ZQBLKR#'ID\J7R/3^671@+4IV9
+MQNK%*P<2"6R5H8M&T=@Z4?\`#/#IC!X"Z&GT\\L'LI!T\NC"I(YW^#($[J6C
+M;3(^JZ.GTX!OY0C;TYK0(:$-.=_@L;;H76T#9=2;`%L`2H*G3`'9"&G/?PX:
+MV0W"C-/3,#]UT3NG']5!4Z:X@]BJ:8+J<_EYW1A@B#QPM&K8.!(TE!_!G)VQ
+ME$9KZ0&PW0NH-+<P!W5^I0]6)P@J48;M'LJC/K46Z?3RJOD>K:%JBF9(C=15
+MJ0DGE!F/HASH.QY*AK6D[#9:3:<.)_=1U</CCE$8%?I_J=$2JS[)[28;(!70
+MUJ37"0`JU1C6B9E7;.F!<4R`0YIVV*HUJ#@[6`8[!=%<V[*GJPJEQ;`43!QV
+M*2&W.UVDN`@]U5N*`J>E[9"V:E"23'W52K3@D!NRHX[Q'X9MKT.+&!K_`&Y7
+MGWB?PO<V)<X,);G9>TU6%C28A9E[:T;MQ:^#\J\7MO#RY8=/G6_MWTZQU@@[
+M0JO'8KU[QQX.I50^M;MAR\RZSTRXL:[A49!G'LM7'4W&YY)E6<W!B)([A/,N
+MV&G9/4`D3QOW2C.28696M'@@',X[*6W!U`#G*!L!HQ[94M`-<2X"(*J::--\
+M4P"V?<!%K'^`_8(:#:?E-U.@QME'II?X_P!U/:&J"Y]+BT&`<$0J56)G<<1*
+MMW3?ZI$\A57B=GP)R>RM`U6T&V;'MJEU5SB',TX`[RH!^;=$\^K!!08=.8(X
+M[J!-;_U2.R$M<)D0>Z=I@YPED`D.SW)0"]I(DB?JK+`*-E4(>TOK```;CNJY
+M+B28VY3.RV!QPH&#3&J/U2`C,'X28<29^Z?7F0<'NJ#IR':Q,QC.R%P).`#/
+M,H28.TH@2'9.-E#9Z>VYE)XW!;/:$MAV0ZR3/?94/Q[)#3W^H0Y<#`VWA,"#
+M+H(*`G279PEI,;[YQE,2!DRBD:9S\!`+MH/Z(28<"6Z@/U1-TCN/=-5<=69A
+M-*$'?LB)<,SN(3.(@P)0D@`X/RH"DY,<)'CW*8G,?=(.,Q&P0.)C3LG)(,-.
+M=Y".JRFV@VHVL'$[MY"BU:=\]E=!W%SO<C<I,&#,^T'E,'N:)82)WSNGU8D?
+MHH&,9$9E,Z!RG+R#IS'N83AP@;#W"!-D-Y^919T%QVVSRF!&Z(N;Y<;'5R@$
+M$`R/T1``N,3!V3M>TM(."$@0?A`+H!@S\H2'''[HR3IC!^0F(@F?M*H;3F2Z
+M`4OS2(!]TX(CB1PD`.,=RH%`$22DV(Y/LE@G/T3C3&((F,HAVB"1]$M#MHG2
+M<PFV(`'W3@D2/\7NBGDHBXEA:W8J-Q),$B>(1@P,X]Y0)N/G96^G/+:@)G&%
+M4P3),@^ZFH%K8,Q"#Z&_!#K!J=.I4M660-U[3T>L'4FS\[KYE_`GJM*C>>2Y
+MP&H[DKZ&Z-=--%I:<'LN.3EU77V]00.)X4VO.)PLBQN6N$3^JTK7U#<P?=9L
+M:B_;N)*NT2-,;*C1@*Y2(,2BI2Z,)4W3(*9S03C*8PTS$2BI'1&^.ZBK/$0A
+M?&C_`#4);//V2@*ASA0NIR=1W5P4H;LF%'!(4T,R\I![(E<]U?IU.H3S*ZNZ
+M8!L#*RZU#SGD?LJECDK+P_1_BB[2")76=*Z73HT!I8!"NVG3PUH<0M&WM]+`
+M%I-*3+8Z1B$S[<G@+5\K`'9*I18UA)5TK,%J`)`"E9:@P3"FJUJ5-N7"/E9_
+M4.M6U!I'F-GY6+G(U,;6C3HTPV3IPG#Z+($@#W7*7OBBBUITU6_=8=YXO9KT
+MBKGB2N67GDZ=,?%:]&JWM%HPYJB'4:6K\X`7F%UXO#&F:F!S*JT?&]`U-)JM
+M!^5SOGKI/"]=;U&F'?F&R1ZBT\[+SFS\34*K`?-!GW5ZVZY3?4TBJ%)Y:U^U
+M'=4^IM$Y4M._:\#(*Y&A>^9EKMU8%RYH_,M3R5+XXZS^)I.;^Z<5J+O3@2N7
+MI7;MR25)_%/U#2\_"W/-6?VG2>73?S]$#K)AF`/HL2EU"HSG;E6:/5SR2MSS
+M?EB^);K=/;I,#/95:UA(,"`K5/J5-[<D%6:=U1>W,`+I/)*YWQU@/LBV7053
+MN:'(;E=:ZE1>"1"K5NGTG">%N65BXZ<DZWAIE5JM$3!P%TUS8$$@`1*HU;`@
+MDQNJS8Q:E!@;@'OV5"XI>HY,!;=:W<#$3"JU[:0=059L8D!KLY"CN*;7D#A:
+M56VD$8^57\HM.1*(RZ]!H!`;"H5:$/)B%NW%$ETZ57N:`TR1*K+G[ND"TF-E
+MEUK5S27[+JW6[<N(A4KVW#@0UL2J.9K4V/I$/R2N4\6^%;?J+7/@:O;_`+KO
+M+BV:#D2JE:BSZ+4RLZ37U\\>).B5;"Z>W3(G!A8P8`2"(CB%[_XEZ%:7])S0
+MP!W=>7>,?"E6SJN?2G3RKZR\QVQ\F^*Y-K00!.VZFI?F&<IKBD*3S3$R.4=$
+MC!,++HOT0/*$X/RBTCO^J:B!Y0D@(]+?\014%T&C())[*I4(U06P1A7K@`O,
+MP>-U4>X$1@-X'*"#3/J@X0.QQ,J4O@$MVV(0.#0!ZM]S*B(R!$@E"8(C@*1[
+MB7N>3E,6^D.<TP=O=!&WW._NGJ=VDM`2[XF$CF)CX4-!!C`._='2@RV(Q,RF
+M=$3&W"9F&QGZ\*FC@$?U$GV2<8Q()*4AVY2T@$,@^Y0-)!S'NDT-G'.R*8<9
+M2&F`#@\J&@M`,F4QPX@[*2&[@?5,6^@F!.T*P1N.<9E$T`,F8C=#IAW^PC:&
+MQ`P.5%)@`IZI;OM.4!GC3D=D6`US8,[C*3P'8;P%0VG`C'LF+27]D36X#G3G
+MDIBX!T@@>Z`8&"4T0=_UW1%H.9)!0N@$_M*@:8WRGUY@?J4MX'*3&M[Q_=#1
+M$G3,QW1%Q&<Q\I@T;;$^Z3`Z8,GX0T3A@8X[ICW('Q*DTAV=1S[IJC0(TN(^
+MJIH(/$@)'8NW]TX``R0$HD&<B>ZAHFX.-N?=.TY'LD1O'";\L'<E4&(.Z;)D
+M$(1)!S]TH.H;GW"&CD`9V1..8'VWE,&EQWCY38D0=T!8+0`(*:/3L8Y1.:9R
+M1MRF`@YGW(4".!L430]L5`"T;C";?Y&Z1F2V20$31.C5(3.,-`2`=&^Z3PW2
+M,S[*KHPTC(A24JFDZH!`X,J(F#QA.-LF1[%#3;\+=2J=.ZC2K4GQ!V[+Z2_#
+MOQ'1O>ETWBL"Z,B>?NOE<&#.K]5U7@OQ;>='J-#3%/V)/]USSQ^Q+CM]8]+Z
+MB-7YC]UU/2:XJ-!#XYW7A7@?QG;=0H-+ZH;4Y;,2?NO1/#O7FD@&JT_!7&\,
+MZL[>E47M#<E6:-1O*Y.AUIAAKB!*T;?J+7'+@1\J;5T+7MYA!7JR-A[K/H7`
+M<?S?53ZQO*JI6^KW"GI4S@`_HH6.;N/NIJ=3L1CE6"4-#1.?A,UYIG5@SB"$
+M+W@'?=0U:K3/L@JW>7$CE*RMM3M1PC#=1WGV5RR8`?=6(L4[66`A05'LIDR1
+MA6ZUS3I4"=0PN"\:^(_X?4VD<[83+.2+CCMTU[U:WH-)-1N/=<OXB\;VU&D6
+M4WR1OE>:]>\0]3JEVESH/RL!]:[N'ES]><%>3/RVN^/CCM.K>.JKI#7&/E<C
+MU[Q;?.)<"\@^_P#JH6]+KOD^HS&\JQ0Z`^JPAU,GY"QWVZS4857Q5?UC'K`F
+M)@PJUQU#J3G"H*CBWVE;=;PHYM:(.G?'?[+8Z5T6EH%-S,C@C">L=/:3IA])
+M=7O:!;5)SC.ZSO$/2KRE4-6DXP.R[K_@[:%0.:W3_97*G2V5Z&DM!/=)-,^U
+MEW'GO1+R^H/%-Y,#NM^EU&XIM\S45?J=":VM(;'O""KTPAT:#'"Q8U[;;'AS
+MKS]3653@>ZZZROJ==DM(7GM&P?3:'#$+6Z76K4:@:7'"2V+K;M#5WAREIO?/
+ML%C65Q,:C'NM>@6%HV^JU*FA.JNU0)1M>X['9`Z"TY^B!WY/VRM,Z6F5W-Q)
+M@X4S+EXR'&5EE\#>5(VIZ<.A39ILTNIU6`2?U5ZWZN',+7+FJ;R2<IV/(Q*W
+M,[&,L)75"]HU!N/?*E#:-5F'#X7)&X<UI+7J2VZI4IF!4)"ZX^9ROA=#=6-/
+MR]0`^RS;FQS$?9%;=8U,`<<*V+RG5;!+9]UWQ\LKCEX[&#7LBT&`9E4;FV>!
+M,976NI4ZC<*C=V6#NNDRCE<7*.:0XR,J"Y@;-"W[BQ.LDM69=6NX`"TSID5Y
+MF(4+V@L@MB5JOM"&203"IU:0!C9&6'?48.V%0N;.6X"WZX(&P@\JG6AWHB6K
+M6T<M<T"'D`'[RLKJW3_XBD]I8#/LNON[7/RJ-S2TMTZ?N%>D[>&>-^@5:-R:
+ME*GCF%RX9I?!!@<+WCQ'TUMQ:U&.`)(PO)O%/1JEE<NFG@[0MV;YCKX\_E5*
+M#6FDTZ@/NBTM_P`8_5-0I12`!'V1^6>X^RY:CLJ.KUJ;*U*G5=396:&5`#AP
+MD'/V_14GP#`D_P":M7@;JF/]54J#B=\[JT`2UI`G_)';5*#!4%>FY^K8M,05
+M&R`PB.VR$.]4Q*E@,TF/>UM$N<7;`[J*."XHW!NJ0"UO!E"8(`.4`#Y^DIL"
+M)._;*.(.ES3(.W9,6MWDQ\H:"[V)'PD08TS(Y3@`S&!W"1`+<X*0"R2[)/NB
+MDAPW3M#=^R48VGX0`?9R=X#<@G/&Z=S00(&)A._3P#@(!#3IDN$?*>2)D[;)
+M@V#G>82!D<_912!@Y.`G=$C.`F($@$004[P)W]T`@2[*=QQ)^GRGB`29^(W3
+M:0002<?1`Q@QI^D).!F>/V3F0()(0N_,1G/'=`+B8B1',)LGW[)W?_<?IV2F
+M'#)309H=].R=I=M.#E.=.G=Q[(0V02#LBGU.+B<92$@?(W2S`D1"8S.#QL$!
+ML+IQWV3DQ$'9"P@-S)^41`P=39.,<(@9DR7#.8E2-UAD84;0-S@#V3M,#)D`
+MH#+O2&D9]E&XSB-S]D;3#?S2#&$X8"T\CN@`.],N'W3M_/+A',IRTR`8&R8,
+MW`+23PKHV>'9CG.R$.,SCX"-K8P3OCE!'IG[[J`B1(C8[2G$EI`'ND9D;93&
+M9S$H'GU&!`A,XNS`PC:"3$A`X<M."J!U9^46LZHC9!._^:(&<#/N5)`+7'5L
+M"3NC)`@X^J%\AP(B-L=D37<P,"/9-`B_@@83^81$8/*9NH@_?=-,CO[`HK3Z
+M9U2ZLG--*L\'V)7;>#/Q%NK2X:+EY<WN2??W7F[2Z03@GA2,G@F#NLW"9=KM
+M],=#\?6%^&!MP&NC8F%V?1>LFI3:X5<?*^0+2]JT'!U.J]KA/*[3PQ^)'4.G
+MT/*J.+QP7%<KX[&;C^'U3:]9VE^?E:_3NK-JL$/E?,O2?Q4\VMIJ@M;R05W_
+M`(8\=V):T^<,C8X7/5G998]OHWI(&=E,V[;OK`(W"\UH>.>G>2)KM/O*+_SI
+M;&`VN"1C&?[)N4>B5[T-F'?""A<^>,.,''RN/Z1U5_4BT,G2>2NOZ=3;2I#6
+MMR;9VNL>6'\TC]D]UU"G:T2XN&%5N:K:=-Q)^ZX[Q7U-Y8ZFUT`*97U:DVF\
+M4>,0POITWKC']5=?57>8=S*C?:/NJ^27%:%ET.'MAL^Z\F6[7IQD@;>QIW#-
+M6G)Y5BUZ'1G\@QG9;G3^G!M/0=BM.C9L:P`"/E6+_P`<]2Z12`/IB.5<M.FL
+M;_\`HP/HM@46@&0(_=)K6MSP4Z)&1<=+80?2"=YA5Z73-+I#1[0%T+FA^^_9
+M'3MVZ.,)VO3FZUI(+7MDA/0H:29&!PN@KV;8,#V*A;9@G9+"5AU+9K7!V,;J
+M,VS702``#PMNXLQH=ME56VWKF)E9K49-Q:CRS`_U06-LUY$B2MPV8>'$]E5M
+M[44JF<05&H@T.:T@#A:/3ZCO*#3NBI6S2Z2%*VV,^D?"B[2M,C8?=([R%+3I
+MP())"*E193:0R0)P.RK*HYA)QO\`LH7/(,*[59I$JI787.V1=&I5#\(C5]4!
+M0GTDD[A1O=I((,PIH6WU!I$#=0.<"R1(^%%K+G`26I`D:H)DY24T-]9S8TJ>
+MC=O8R0X[\*EL3G!4-2L6OC/PM)9MTEEU8C#C';*U[:_H5J8@B8R9W7$4WDLP
+M[Y4E"[J4LASH^5TQ\ECCEXY7;U&4ZK2&@+.N[`#(;NLNSZN6.ASX[Y6O;]1I
+M7#1Z@O3CY97GR\5C(N:)8XM((E9EQ;.+R<P5UC[9E:7`A4;RP@#&/E=Y7#+%
+MRMU0=I/99E9NA\1GA=1>VS@8`$+,OK?09Y"U&+-,>H/3J+=/UE4KVF'QZ<E:
+M-U3<7%NK2!^J@K,I@8=!"LX1SG4K,E\'DRN;\4])IW=B6%@<Z-NR[CJ%'4#I
+M[\+*N;4#&_<]U9=7;.GD%3P_58\M;L#W3?\``:W;]5Z?4Z30>\N(R>Z;_@]O
+MV"W_`!=?W*\#NZD/APSR=U4JOAT@8XY4U?\`.2,859PAW8>Y7.NVR+H.TB,%
+M,7##HC'>0FQ$B$S@`T=SQMA0V,U6;-!@C8H?,TD.8=MI0%H`F9A`(,AT^R"5
+M]8OJE[R2YYDDF24.L3JSGNA@:9!Q/^PE&(S!W0V(5#N`2"93O>#F<GD(/3R=
+MT.`"V-C\0AM*7@MD[[$RG;4&PD:0HP`>8]O9+$S.P022,DG!2#O1$X''NHY`
+M[YV2:(S.>Z`@0'0-MLIVN9D\;*,@S,XC9-`!DN@'A%'Z9U<#$HG.86CG/*BW
+M><E'JW`.3RB;.'-V=(/()V17#6MKEK*S*FQU-F-O<!009,0$HD';Y)1=I:=1
+MC7`U`'CELD<>R'6W0TM.1PHL@`0(&Y3`8,9A#:1SFAN\CO*4LC\V3R@CD8"1
+M&<X@("V$1CV*)IQ!A1Q)DGA(-,?JIHV,/;J.8!3ES"Z3IGVQ"`M(WV]D)C)G
+MX"NC:1I;F#PD).9;@[J(Z@8//9.<`'ME!(UP@B`>9*(P&@%TQNHH@9VYRDT2
+M,J&TNKT_F&/T14:C09G'90`&,?;NFC.VWNJ;62[UR#(D)FS&#C]E"&.B8._&
+M82(AOPH;3D`#?9"/SQ,R@9L3,':"A&"8P=O=4VL?TB=C[)B?4,S.)4+2YVD@
+MP!LE,;*&UAI$!TB/=`XC!^ZB'IP23W3.RT#GE71M)N![)X'$Y*A`(?F8GNG!
+M(GV]T5,WL.$F<-])A0DGO\P48,N:9G"(E#23,_24X+2!`CVC"BUG,DMQPFU8
+MR0"-LY1=IZ<9''S!2:2)$P.Q4<@/W,<Y3:SH&=_=#:>3K&))X3AQ)D;;C*AI
+MN(V.0B$ZMX]DT;3-J.;D3@YA7+?J%RT'2\YB<K.:2V"#F.Z*B20-.2EY-MBA
+MU;J#RUIN:AT["=EZ3^%5AU7J-VRK4<[R1./NN9_#SPA5OZK:U8$L]QNOH7\.
+M^@LLZ%.*8@#Z+%D[9S\GR.O\%]*9:V;!O`75@-#!(@0J73J8I4?8+.\3=6;;
+M,--A`G`6,LM,XQ7\5=6;1I%C=SV*X[1<WUQ.8[*6GYW4+HO<XQVA;=E092I;
+M+R9WV[>G&:!TKIS*(!?$K5H46X`&%%2:YYX4]'7OL>5C;K(MT6L`G2G>0'"%
+M$R3N8Y1!CC`F5-[;D.XZA&`0F;3^P1BF=)[J;R@Y@/[J=KPA\OG,*Q1;!C>$
+M@SB"/HC8P$AORKK25)I:3D`=_9(6[=)@#Z)B0-A/"-KQMA:E9TJ5:68CY4#[
+M?2"8RM%PE^P3:0Z,*5J*`I13_=4G4?YA@8'=;GDRT[2JXMP2#A9UPUM4MZ`@
+M$_JIJ5-IF0K#J):.WU0-9$08/92B(,=JP,!3-I_RSW4]&F3EQ^JE%.7#N4D&
+M959B'#!Q"J7-*#$;=EM5J30W;*HUZ4O@<;H,FNQT$D0J[R)C<[+3NZ3B-L+,
+MK-WWQRHL!4R#$9[)A4+1,92;)$&$&\JE-4<">Q]TWE!XF2/JD]P!@CZH758:
+M2W*=()K0UF#,IY;$"%!YH&"=NZ'S@';_`$2&DSAWW1T*]2B):2(XE1MJ@GF4
+M;(>V"5N,6-CIO6"QH8]Q$]EN4;FE7HX>,KAZC8!(,?W3V74*U$^EQ@<%=</)
+M<7'/QRNMNK9NIVD2LN_M"701]5-TKJ].M#:KOKW6MY5*NS4UPSLO5AY)D\F?
+MCL<1?VV@06K(J4'/J3)#5VW4["23I,'E8E]9.`)&'+MVXN<J4X>03C*KW%LW
+M20&_5:5S;Z'Z@JM5P+M)V"#--"F#!R4WD4NQ6LRC1+`2""47D4?=.%?)%<D/
+M,#.T`JN]LNQL=@58?!=),2>56?O.XXRH])J@@$.TRWLHV@@',GA&]QYR.4SO
+MRDR,<(R"1)`W^4B(V!2G>&IB3JVX[HIW:-1`.`<80.B9!(([IYDP2)[IB2)Q
+M]D#.=/(^Z)H@:L'ZH3&G#3O\)<B<E`39:,Y]QPG#1#G2,8B<E"UY`<8$GLB9
+MP"WXA`L-,&9&$],-Y=$<QND0X$AS((.QX38@J@2#N)W1-:-.<#ND#C;"=VG2
+M1[;J`,D\!(F6$&2!P4X+9&K,_1"XM#L9212@=I([)J@.K?YY2$1L?ND70TB,
+M)$,0W`)VQA,&[P41(G/ZI-B,(I@0`1PGDG)^$SC!$<]D\CB90.T'8'<[(23_
+M`$[%/J[DQNGQO*!G<9"3M)$_W2!@2'1W"0@\?K"!:6YQ$\)I@<_*)Q]&YC@'
+M9,=(=D?J@1$B!GNF:!IWA.(!F<)0W,2F@B!&9RA!&F#PC^<I/T%H`$)H,PP?
+M?E."9W28!`$P">R8B#G]$"=J(_U3M$X;B>$YD`MF0=QW3-@C+9(Y0%I;K)VC
+M8=T-0R[_`%3AH<[N!PF>-),B/8H'8).9,(!IF/;!)1$ZL>R$B,<]T#@0P'?@
+MI@9/Z).,-B4FG&V_*!P`1C/LD-0!]1CV3,.(`^,HFF),8^4#C5N/L$[9/IC,
+MY(2$@8Q^J*1(:1]$"(],0<<)J>F-DY@M$?ND(F0J&/I<5+3(,3D$IG!I!.QV
+MA.&P-ON4"(!$#E=A^'7AQW4;MKZC)8TA8/0.FOO[UC`PP2O>?PZ\/FTM6`L'
+MRLWIG+/UZ=#X.Z*RA2IT*;,-'*]+Z);MH4`-`"R?#5@U@!(B-PNB>6TZ>-EB
+MN<YY1=5Z@*%%P:1C<RN)ZC5J]1O8W"TO$UWG0WE0])HAPUN$GW7#.N^$VNV-
+M'RJ0B)A7K>F7#U)K2F2-ONKU"G!CLO/>7IQA6S`QHA3&F0"4@`'2<*1[FAN(
+M6:Z2$P"6XP5,T#&%`Q[2_)_56&'MNIMK22FUNG_52M'HQRH&$ZO2K5`#3_=6
+M4T`,,0!",LT@3^BD$.F,)G./*K*%T\H28B``GJG_`*E&7`[S`02XB)1-B9C"
+MB8X1,DX1TG09/*FQ/'I@#Y3^6,N)@CV3-)(D(Z?YC^J*!](O?`0U:`;D?16&
+M-&8'Z)ZE)KP`X2.45'08W1&".Y5@4QH,)4F'4('W4^D:)'*1*I5*0!DSE5*U
+M(:C&Q6C6:!.)A5JC6S);E18S*U"096?=6S1_3!6[7``^50K,#R3C"G2L-]')
+M)&`@\HP?9;#K=A&!@JLZD!(`0K(?3)!&WNJ]1C]/I(^@6S5H`B`,JM_#:21^
+MB:1EO;IG!*A#'ZCO'*TW6WJ,A)M!H$&$TNU&DPG?CW5BG3@;[J846L),<HF:
+M8$Q\*LVH'M.QS`55WI<9'U6D8C>)5:O2#G;;K4K"FUY:[4Q\%;/1.LN8[RWN
+MU`+*=;$B<J&M1<W:<96ION,927MZ'9W%"\H2""85#JMB8)CX7,]%ZL^V>&/<
+M1_OX7765[3O*(@CW*]/B\ORO+Y/%KIR74K0LEQ'ZK%K6O\S6Z3"[OK/3@6DM
+MV7-=4M2*1TSCL%ZIR\MX9#7M`B?U2\QO<?=,*)Y!E/Y/S]TTO#Y%K-)F71]5
+M6>0<`!6*YEQ;`&..56>1Q"E>H+@2/8)/!D2-TSMA)QV2(F'$X]D-&<,X[(2)
+M))&41`'9"X]T#$&9@&=BD9`B"2$PSMQPB<US1+AID2)Y1`$&1.Z1!X!!/&R=
+MND9<X^R8D2/[HIP)]61"(2.#'>4)@#_1%3=+80/+MR23WE-LTZ@DZ8&04TX@
+ME$.!Z9S!2W[I9#1.4I+M.?:.Z`?3S,<)`$DX=/`2AP<<X2/<[#;**>J^GJ;H
+MU;>K.Y00TGL=DY!C4[Z2D3)D'/NA#`@..GM&R0)`^>R=I+<S![)R22),H&@`
+M3E-B!E.23DY]TG0.?H@89=CZ!/.,9[I@Z,#*0)$^Z+H[-B8D]PD<MS'=-,@1
+M]@G;OB"B%@'\T=DQ[!%D[QGB4P_(80-&8.",(C@;`I-D@`&9X2&\0#V*!MAE
+M-N,\\HX)'!.^R$N&G;/[(:.&XX,?JF(,SLD)&0TY"0$MQQNH:.#B(&4BT<$'
+MW3@B-Y`V"0#]<M$&,C*!`#<\]D\M%0.C4`>4T>F"W=)L!ID?14/=.I.JEU*G
+MH:=FZIA1N]@B#26ET8:)WV0F-!Q]5`Y!<=1&!RF&Y@>^Z0(E.8!$85":V3D'
+M"<`:<#;=+4T99(/)*=H$=^Z!"1$#*<09F?HD1MRG='(DSW0T8>SI]D]-OJ@D
+M;[)@,;F?E&(&^_LBB.6P>58M[>I7<UDD@;*!NDD9,_J5VGX?]#JWMPTFF0T'
+ME&;Q'5_A/X:T-;6JLDD"%[?X8Z;%`'1"Q/`_164[:FP#``7H/3:#*-'3V6<J
+MXR;NTEK1--L]O95.LWS:--T.B%-?W+*-)V8A<AU2Z?=7&ACC!*Y99:=,<=GH
+MNJ7=QK<XD$KH>F6^&B%3Z):!K!A;MM3T`8^<KS97;TX8Z3T:(:,2`IF-QG9"
+MT@-P<!4NJ7].WIDS"Q:[1+U"Z;2!,C"R:O7*4D!T%9?5;]URYS:<Y[*G1Z/7
+MN8<7EL]ESN[>'3<C:/6FR2Q\QV5RRZR'D`N+21W6=:>'SI:,GW5D=%J-R&N!
+M"OK4]HWK6[-5FH'Z`JRR^+?S#3[K!L35M7!KP8^%J4GT[AHV"FMK&A0O&N/Y
+ME-4K!W]06-68^B8&J!V34;PZH.RSNSMJS?34>Z7%`V=63`4=&J'"094K3Z@1
+M&5N7;-@R('I&ZDH@ALB$PRTG]$3()&(5TSM-3P8S"GIM`,A1,(.5*#VQW32[
+M6*8!@2BTMG?.TJ&B1JVE66%IW"!1`P[[(RW!F4@0`)&/E-J&<H(J[/JH'4X^
+MJLN+2TF=N`JU1P#8,E18JW32-C*KADF"%;())2;2C?=.U4JC-(5=U*72X0M%
+M]+42!.%%5HC/9$9M6F&["0JM5H`)G*T+EI$P-N2J5<8/?E39I1JN]6X5.ZK%
+MA)/"N7`QJB#^ZS+MI<W)(!X4BZ05>IX(:[([JG5ZH8!UPH;\-9(:!/*R+IQ)
+M@'*9$TZ&WZDW5)=,JT.HTW-`7$UKEU/)<<=E2NNN/I,($^V84F5B7';T,WK#
+MC4/;*FIUJ+VP0"?E>3?^9;IE3,Z?=:?2O&#&U`*U32??*U,_\2^*NZOZ/]30
+M1_=6>@=0=0KBF2L;IO7;6ZIC2Z9&_P#L*=[VEVMI'>97693+IQN/RO1[6LV[
+MMPT1)&95'J=DT,((^BQO"G4SK:U[L]UV+A1N+4.!X7J\7DWQ7C\OCTXBK9-%
+M0Y_1-_!-[_HNCJ6)+S#"A_@3_@*].W#3X'N-RZ8G"KDP2"#&RL5\F&S([*"I
+MZJ8@F1B%-O2A._<#W2.H#V3NB3N1&2FJ.);OM$?"J<!P9@_KNF<"<[9A.6Q@
+MQ*8YQLHIO[[IG;03/U2(W&>R:">3\J!#,G*7S(A-M[)\=\<D(',1DR`8R433
+MG]X*`C)!G='@;<JAIGCZ%)P'"1'SC=$/RSNG8$D=\#(3">Z=P:!D^^$HD2=O
+MV0)F#R)3NB,QGNDX``9'U"1'I,<(IA^4$B>Z9\``C]D_],S!A*0/^H>R`(SB
+M<>R=@)G.Z3@``Z1GA,#/$(%IC'"49!V^4XR)/"3))$9[94"(R2(3/V_=&0#.
+M-]\JUTQ_3J=.Y%^RJ\NI$4?+,0^1!/M$JBG!C<=HW3"=6#D(FDZI`GZIRZ6P
+M2/D;H$"TN`<(2<,P)^$((@#;Y*=TD8?C_P"Y`\C3_ACLG:X@[S*;$$S@=RFU
+M9E`1)X.=D)!C"67&>P3`8,9*!`^J28'<<)R0-G<]LIB!/")C27`-&78B$"&/
+M43@IP#!,'2>3RF<V)U-@_97.FCS[6K;%S6@`U6$[D@9'U'[(*U3`$['OA"<X
+MQE.-,R,SRD[3J@;(`,C_`"3MB/5^Z3@#!VA,&R#E#9`Z3@;B-DWJGG"+9I2_
+MJS]4`QB3RB9&9F/V1%HD!IG*<-CT.G4TY:$"IZ=>DR6]P8,IB0#I&8[%.6Z9
+M;I(/,H?S/COR25%$QQ+H(1F3&G[E-0\LEQ<7#'')4]M3+W-`R51?\.]/?>7;
+M`&D@D?NO>_PS\-MHVE*:?J@25QOX.^'75`*[Z4S!R/A>[>%NG-H4VC3&%,KI
+MPROM=-+HMCY%)N`"KUU<BF,$2G>YE.F-6(7.^(;\'4QA@^Q7*UJ17\0=1=5<
+MZFUQ2Z#8N)#WY)]]E3Z=;FXN`]V5U%E1;3HC'U7FRRV]&&.EJW8&L;Z=E:%0
+M!NX5%U9K6Q(V[IA4<YN(*Y6N\BS>WK*=(Y7-7?G]0N-+7'3^RT;MCZSX.%H]
+M.LZ5"V#C^8Y6=;:WIF].Z2*;AJ))706%FP``-5>B[75@;<+5MW,ITY<8"WC'
+M/+)<M+1@:!I&%:9:4Y@M6%?^(;6U<9J"1[K/_P#.M`5L5!VR5O<CENNHN^D4
+MJK(T`#NLVXZ54MI<P$CV5OH/B"VNP/6"3[KH&TZ5Q0($%/69-XYV.1I14ECQ
+MD8(*I]0LG4W:V;>RZ+J?3#2JZV<=E5%)E1A:=QW7++'7%=\<M\QA6M5[:D1A
+M:5JX&">56NK1U*H2V?E-;EP,+E.'2S;7ID%L0$MLZH"BH$>7JG=-4>5U[<UJ
+MD\'=RFU`"`LRG4@R2K+*X(W51=IU(;&I3MJ@;'99@K09+AE%4N1$"")R5EIH
+MBI()F$+GQN539<0!D3\IZERV03G"HLNKL&^ZA<\..3E47U'.,CNIK9WJTJ-K
+M=-@+D]:0?9-3<!F$3G`CU&%649<`8]MU#7>W3A'4(+L9&RC>!&<H:4KD@M)A
+M9U8')C&T+3JL:X$!5GT@UL&,*:V,VJWDK.OFAQ(;_HM:Y87/V5.M1(.RD@Q*
+MED'DR<'.5G7UK;TY[KHZU%P8=(63=60?5.K.54<U>6C"XN#C]`L3K-E+"0S8
+MKN+BP@2!C9974NGZV$'93U-V/,>LNJT7END$#W61<U*A)>:;@!R.3\+TOJ72
+MNF^0T>5_,`AQX/NN3\06MM3)T,<Z<XG"W+)PW[[Z9W0.O5K9[6ZX`&>?[+OO
+M#GB)MT/++B';1_L+R/JE-S'DM'I.T!2^'>LU;&Z`.8Q!6KX_N+%LO%?072KK
+M14:X;E=]X6OQ5IM#SF(7C'@[KM.\HL!/JB8/_9=]X=O13<TAVZ8WZ\_DQ^5Z
+M0&L(G4,I:&?XPJ5I=TS;,/<*3^)IKU?NO+^T_/.Y=+W%I$959T`[J>M(,0(V
+MF=U7=(?&5Z;VTC<09`XY2JX`;!VS*?&L`NB3DH.=\;)0F@D3G?=,\.!S@^_*
+M:21OE(N)(!=/RH&R7<;)`ZN3D<!.R"9G,[(<;YP@6)D%WLD#@$.^Z4`F9,>P
+MA(#$?N@/`<07`\2G<X08V*%O],IW#TB!'=%(D3(Q.Z>1G.Q0_P!'O.<HL:=4
+M(AB)_JSV2,%H2<?Y@`A.0)'$A(I@.Y3F.#.,IB0#B/V2`)J$;#]40Q;G)^XW
+M2[B=TAI+^XXRFIP,@(&(EV_Y<)&0)`P$9PS!R3&$!)P3(A%+$23]$[9QF`F#
+M)P!A(@8$P@)O?^Z1:"TQ`)Q\IRUHI?F:3.R%X&D\QE`Q&./NFGD1"<`D8!A,
+M,G,Q[H'/R.Q3'!DQCE,X;QC,)VM$-<1,^_9`61C`QW2:(!.#P@(B!C[IP1.T
+MSB$!.D-$;I-QP2F@:=X]DXC7^9#9$`),:"=($SPF@2<(@&Z@9QR4$A?-'0Z(
+M&=1&4?3'`7U&HX2UC@YPC@&5"XR,$P[V3#?'&$!W`FK);OD#="-QC<X1!V`3
+MVB24S@=/^J)L,08YC!"4278,@(B#(CD(2,X/**8`8P8W3D8[X2`W))@F,IRX
+MG$J!?TB!SV2$@@B3';E/IDF)CB4;6PW/.RH0))DG+M\IH:1DF`4AJ)B2`?=.
+MT$@-YE`@PDMW(_5=E^'/AYW4;YA<UT!P,?9<_P!$Z=6O+EK&MC(SE>^_A%X:
+M%K:L?49ZW9_92UC.ZXCJ_`'0:=G0IL;3#0!L%VU.FV@WM"@Z?0IV](#:`J?6
+M^I-8TZ'?<KEEDSC#=;ZB&^EIR0>5SH=4N:Y,[G=!7J5;FK(G=:G2K4,:"3\K
+MSYY;>C#!;Z72\ILN&%>KW(IT\QLJP(`+01E*C1-5^J?NN5=I(9M1]5\SOE7:
+M;M-,9(,*2VLQL&G.ZLUK2&&!!C*QITE9U2JXU>ZN![BP1C$8*J_P[F5@8WV*
+MMTQI;D9(315RQ9Z0X1C<]UC^-.M&RM##@#NMFU>T4R-B!"\]_%-Y<2!\_.ZZ
+MXS;SY\./\5>,',8Y[ZPF<"?E<A7\>5_-.ET@']%E>/7/IW08`=)Q^Z[C\"_P
+MUZ'XDZ"[JW7*]8L-7RV4Z#@#L#)D>Z[SQX_6-^LW6A^'/CJJ^JP&J02=I^%]
+M#?AYUD7UJPN?F!SLOE?QIX9'@_\`$3^`Z=4?4MG$/ID_FTDX!@;KW7\%;ISP
+MUAF,?V7++#URX:W+'KMS2;5I;;KG[^D;:L2``"9E=-2@T&&(D+*ZU1:^FXM&
+M96?)-QT\=U6-69Y]/4(E4*E`M>8^JNL?Y;P#SA2.HM>=0V.5P[>GI3HU#^7?
+MV4E:"W/'*CKTS3<2!`05*X#(..Y5G"6`,->/A.ZH&[''RJU>LT2951]R"V`X
+M+>V%]]UIP24A>-`!!SV60^L7$29E.R9P%A6Q_&`DP2)2_BIP=OE9;6NB#REJ
+M+9.P_=%;3:NJ>ZFMG'5JGZ+$IW`!''.ZNVESD'ZJ+MO4R-`DH75`2J(NP6[I
+MZ58ND?JKM(MM<&NB=T-;).1'LHR^`)2\S8?9:A2:UN3LH:C2YY4KW`@D'/RH
+MWNS@_P"BJ*U2B`<M'OE0OHM/?93O?ZB21'LHO,SW4%2K1&F(E5&VC"XD[RM-
+MSVZ5`ZF0XN_I(51FW5NT>ENRR.IV9+3S/"Z&HTZHX56XI!P((B$B;</U&Q=4
+MEI;@[K'ZGT2CY)<\Z3W.5WMY:NF0W?*P.N=.JUIW'NKZRL[>:=<Z9T]C=)R9
+MSNN1ZI94Z3M=,F"3$<+U>\\.VOJJ5@7.))DSNN;\3=`M?))INTD;2%<<KCVW
+M;*PO`?539W+070,"%[%X=Z@*K&%KP9@@KPBO:NM;LN#@(,2O1OPYZBY[`VH[
+M((&-E<YJ^T3*;CVNQOHM*8UC92_QW_6%SMK=4A;L!)F.ZD_BJ7<_=.7+A\97
+M4$G<?[[*!PEA=!`&)!V5BX=AV?;Z*H\DN&,;A?1KA0/$R1A#48=$R43_`%'X
+M0SQL2B!`]<">Z3@!!$DSRG+CI`!PF=)`QL-^ZBFEQ)=RG@`Q)DH0X3(G*<.(
+M.)5#P8&?LG:3$1DI,)&"XP0FGW_5`8#-&"9!VXA(^IQP`$.-0DY'9'J!,.)#
+M9RE@$_\`+`G=(B!EVV(2`]6#A$0)W!$2B`R&X.4Y+HB?NG<0.,).@"/K\(&U
+M&>2>4.2#D=OE/(`:3N<)SD`@B>W"*0@.D)F@EV-Y1`C3&`)V"3G1,``H$0`Z
+M3A!$CL$3C!R`3\I`Y&=NZ!A.@-G!X3&200C=+8$C*;4)WCL.Z!MP)CY3QB9V
+M_1,2)WPD\C&-T#.DY.\II@DD`GW[IM7J[)SET[(IR9C`$#;W2`)@3!2&!@"4
+MQ+-,YDH$<>G$1E+`<(XV3-P?=.</VG"B"DZ3.9*8SJVVW3ZAH]TA&G!@JAFE
+MT&,<)Q.DG)">)!]\X2C$NR?9`P)TD'(E.R3)T_=,V"#@R>.R=A8706F3L`@0
+M)D1,S&R-XGX[(0<2/L43W"((R$`@F0`)3$SD#`12.1$A,<F.Y4"R<$!-D';"
+M-D8!X3^D@X,0J$QIU?[A/480-Y,;2K-&"S2?H.R,4QDD3_=39I4$`Y'PIK:B
+MY]1L`G40("E;2R`T&3Q*ZOP'T7^*O:9T@@D?1$MU'5?A3X9;7:RO5H[$1C?W
+M7MW0K>E8VP,M$#E<CT1]ITFR8`X-$#<_[[*'J7BHUII6[RYNTB<KGGGIRQQN
+M5V[+JO7&!NBFX2L1]:I=.EQ.5D6#JE=P<Z3,86YTZD!$S"\^63T8X:7>FT&L
+M:'$;=UH,=C?8J&BV:8#0K5K0+M^%RM=I#TF%S_>%HVC(`'9-;T&Z1)QV5FDT
+M8(*RUI9H1N["DR[!4(@;'=.7EA$<HNDAH-<2-N94;[4G\J)E729[*:C6!,G?
+M=3VTNE-UH\;3)X6!XCZ#4O!G)RNS\^D<0GI&@1F/E;QRC&4V^=OQ$\`=0K.-
+M6@PN(R&_=9/@A_C7PH*E.UI.\EQDTWM)`]P/HOI^M9V%=OJ:T\255O/#O2ZS
+M8+&2?8+K++-5C6G@W3[3J'5^HNZAU4E]=[L3.,^_"]E_"+IKZ6EY$`QQ\(AX
+M5L:;P6TVD#;"Z#HC&]/;I&D!8RR28<[=I3.FD![1\JKU!H=2,1E9(ZL(`+\#
+MW05NJZFX.3ME8RSCMCARSNHO<RYP8@JQ;U=5,&=MU2NW.JOU<3/=#;57-,$1
+M.%YYQ=O19PN]0JT_)P<K!NKD1E6.J5#!))'PLBJ`YQ]1,+6]UF22%6N'.P#A
+M`P%SMC!PG93D@QGA3T*328.YY6ML]AMK<N?);GY6I3MP&R6P0$=C0B.<*Z:?
+MIR``H2,NI3TDX]U'7I.(P%JFB!E"^BW1($*1JL"Y!IGU3E#;7<'\V%/UL!K?
+MCE<_6N7,J:0=UK3#HZ-T7P9PKUK7]_=<Q85G",K8LJI=$HK<\_\`E",Y0&M(
+M_,J>LP/TA#YT'3Q\K;*\:H([?L@J5=(D@J$.!V=[[IJSI&#*!5JL`F!["<*(
+MOD\9"'U&9W0M!C.,;**D80.<3LG>[U1Q*B<^-MR4SB=."`FTT*070<*-S!).
+MQ0PZ<G?A-#B"<_Y*RI8&HQIQ`,\E5;BU8]L1^4*VZ.^5%(D`X6MLZ<[UCII<
+MQVEL\K@O&'1:F@N8#MG/RO6ZU,/;!&%C=8LJ3@X.#=E>*SO3YSZK3K6US%0'
+MT&22/=;G@WJ#J5W3;,-D3&W"['QMT2TJT7O$!PG^ZXFPMA1OAI$M!WGW6[JQ
+MJ7;UBROV.M*;IF1O*E_C6+G+"NT6=($G#0%+_$-[E8W7-\XU]#JN#`YY*J52
+M`?1'NK5?#N!.ZK5A$C$;1"^C]<43R9/?LE$24[],3J@SV0N_)^B@8G.(2;#B
+M9@XPDT$X`^R$MAV1D(%$)",SLGW$%+2=])`]T"D:0($^R<QP"D6ZG?ETE*#'
+M&-U=!XC)/*<@:>Q]TH@"?V3M&=X^$0TDQ@8"=S1&=B)3R`-`!GNF('Y3OO**
+M;WTQ\)&#G!^4OZ)PD-\8'NB:,!)C8)$!I(TIV``;_?*8S.P^RBIKAU%PIMHT
+MG4H:`X%^K49WVPH7;[9/9.\&-XG@'=,9+AD&.Y5^AMC),I;X**1)*%D:23,H
+M'='/Q,IH&DR-]DPY=)GY3R=$G"!F@-)G;^Z0@D\A.&NXS*?4?+#8&.VZ`,!Q
+MB/A(D!I:0`/;=)P,9,2-I3<>R*(1$;'O"8M$1!'RA^3"(S&Y"@&)^B7>(!*(
+MQI&3JY28`7?YJH3)B&F`[!2#6"?4F]IPG<UP&D_*!"`"!E$6F-HGW3-;!W'N
+MGTSL,'E`@)S#1PD&@$3_`-TQ&0!E$X''Y8WE(':`W/V/=/I))@),$@Y$^YE)
+MK2!,!`SFAS9!SM\IPSN)2_,2(A$<.&,`1E1"#<D@1!3-&IOYAOPIJ;):2""1
+M_2HWB'X@HK0LZ8-,8))4[J(+<&",Y3=*!-,8!!5]E.7CL."L4Z1]-L/XBO38
+M#^8P3E>B^'*-'I%@*E0LUZ>#"Y3I56E9GS-/JX&Z;JO4;FYJ>6"X#L"8"7+7
+M3%Q]JWNI]<NKR[\JE4=H!C3.X71^&K-Y8'/G,87/>#^DNJ%M2H)F,%>@=&M6
+ML#1'/'*\^==<9\B_TRVT@'*W+2B2T0(5>RHDN&_T6U:41H!7&NV,%;4,-&RM
+MTJ3ALBH49:%9;3TK-=)!4F0-M]Q*GMV0#(F-E'1'J^BMT6SB$;]0BF=\'V*)
+MU*?@(W,=P)"E#'Z?=#U5C1&D"#(,PCIVIU=L*=K3,'=3T6:LB%E?52?;.!].
+M%&:-0#?Z+6%$N(X1FVD@IK:,;RZ\2'$0$%6K<M:`#*W76S2(&?91FS!!!:KI
+M&)Y]PT9+DS;JL<$;+2KVS0(A5:U.FSME32\*O\35=@G"=MP\'*&KH#C#=^R@
+MJ%[L?LII=M!EZQH.5'6Z@S3+?NLXL,[GZI-I^KY33-J6K7?6W,IF-$>RD`:U
+MD",**H^?3W34B;V=PEWIW"NV%`ZQC*AZ?2+W_P":W[&T@`D96:W.#VM$0`!M
+MN5:-`%L3(CNK=&UTL!")[`QIDJQ&<^F&S`V]U4NZH:R-NZL]2JM#?W6!U&X]
+M4S]%-M:VI=;K:FN`&^ZYBZ=-:/HMOJ+]605AW&*DD+6*7A=Z>X-<"?JMBV?`
+M&GNN>LZGKAI"V[)P<-BMZ8K2IO)]1V'"($&(Y5=@=C.ZFI%F))'$*[1,T&1B
+M$;((AT(?,:!L-E&*X)SLLVM2;2^ANWU"@J/DF-/R4-2K(,<>Z@:USB3G"SMJ
+M8K6(GORFIQJ@C?*9@EN94K&9!Y5BZ$6C$#(0&!,B%9IT^"AJ4A&)`5TS5&L1
+M.^_*A=`,GA37+!JGMW4%3+(!6F=&UC29$*EU1NJD((D^RM0`-Y3,8'-AQ)2,
+MY.!\3].N:E-^EARO/+VWN+/J$U&:0X[+WGJ-JQ[(`PO._'/2AJ+PW:2"%TQD
+M8]K.&+:5&FV9SCNI-;>Q_P#WE5MZ3FT6MTNP$?EN_P`+E=)IX77R_`C.ZKO`
+M$G^ZL5LDD'_55ZL`[8/=>ZUP0C,YPF>#`C9$`-)W33$S*&C#`$2F&X<,)3+I
+M!33G=0.7!SB?V3`8W,>Z33F"/[I\3DC*H+29#C!]A,I&.3E-`G3LG/I&2/F4
+MV'#0,A,<SN>Z0<(,PG)#0-L[A`A(,GG9(G&YRG).F!L$C!W,1W1##`P?HF!@
+MD(@(GLFQO)12'Y)'ZIB9;JGZ2G&TSD8^4S]Y;(4")X(@H7&#/ZS*-P)$Z8)4
+M>G)(/UX5-$UQ&0?E(?/*0!!,)X@3!0/Q";U;$[)V_E2'YIF90."9"3S@<I]G
+M1N?=`9+23)0*#I,#/[(2""#_`&1;`B29Y*4%W'L@'>$P[&81Z8(S*0;V,^Z*
+M`?XA&$33Z?=+CG[;)-V]E`^H:?RC='3+7.EY(&TJ,`!TI^<!#0W#GCW1>=-J
+M*,-`F=49*$X$<)G-!@,G43D*H0,8@?5.3D0!A#ICM@_=.TD?1021,>G?&4Q`
+M:>X3R8`:9`R,X0PX.#?U[($R7&(E$6[%KI)W'9#!$Y1,^,'L@-KO1V*`3YFH
+M\H@)`TPDS$3QN$&MT4C?A;-)M-U,Z78Y'=9/0:>JH&MS\K6KL%%FB#/*Q>S_
+M`!7J57`G\P'>5I^&[9UW>`@.C&^ZS&`UZN@-R>Z[OP/TLTRUY'N"N>66HW(Z
+MCH%B*=!GH"Z;IU!K6B&K/Z;1R#$+>Z;1B#W7GW]=,8T.FT1`)"U*%/(@0.RJ
+MV+-+1[K3MV"97.NTB:VIG3MA6&4=42E2;#8VE6Z%/GA&I$=&@)_+A6*=&?\`
+M)6:3,[05-3I@[?:$:5V40.(4C*$M]O96J5)IYW1BF&Q&Z:6549:B1.%+3HMD
+MX.%:;1Q*D9;SM,J6&T#*>WLC\LAL!6Z=N1`1&B-6VRK%40P@$\J-P(_=:3J`
+M!.,*)]($0%$VR;BGJ!G=4*]OK.0<+=KT@W,`JI7:UL]NRB,2K;1@&/A15:+`
+M!`,K0N2)G[`*G5(CU*;%5U(;DPH*M5K<3A'?W#6-C^ZS!4=7>0V3QA#6UD5"
+M]_IX5NTMGU*@)'T3=+L7ZI=RM_I]F`084M;F)=(L,R1]5OV%`#C906-NYKI#
+ML'A:5`0`8^4C5B3RVAA[PJ'4,-*OU9T[0LCJU0L80,CNE28L/JM1NDDOCV7/
+M7E4%QRM'K-7)B9*YZL^:D:E(TDNCJ9`S*R+X#(,"%L4V:QE9?5Z6G8+>+GE5
+M6Q)\[?"W^G@B).%A]-9#YX[+:Z</-K!K<*7)J8\-$/.`!,)5''5V]EI6]BUM
+M`.]LJ%]JUS\K7.F9K:L:@T1V4+GSN0!W5FK;ALZ9^JJNIN`/IF5S;#4J`"!$
+M=Y4M"LT$`'?NH7V]1QP"HJE&JW+6II6JVHTB-0^BL4-,@@Y7-BI7823("GI=
+M4=3YE7E+'347-&T)5C.Y6-9=3IO(!=G=777`&1F?=:QK-B*[;),95-_H!P%9
+MK52XA15QC!E;W$5"[>3[HF;D@_W4-W@]Q\IJ)<>?T4+$U;+28_58W7K(UZ#B
+M6SV6N#)@;?LH[MWE#N"NF-TX91Y[4L'-J%N@X*;^!?\`X"NKK,INJEQ:)*'R
+MJ7^$+ON./+Y#N3+M,$_)4%PYQ`&HD#8RK-<:B`V/5Q,0JE<-V:\P,;1*]*(Q
+MS!]T,^DA%B#F$V6ND.RTJ(`D`$PEQ[]T[AOD?"3V@#!,';"!-/JD&(]T0,NP
+MXXV*9\BL[4S3F=/9-`/,R>$$C=()F2?F$PR<X3.;#LS([[A(DY$X[JA-C3D2
+MB&G&)([\H1R"92:`>3[!`3B-6!$Y2:-3PWOW2B=,'/(3$Y@!02,)#2,04&-6
+MV$G1\^R:0!M,@*A'2TDDQ]$P(P=,Q]D0PWN@&#)P(WA%27%4UG%[PV2(,"`H
+MQI@[>Z08#OEQY2`(<"Z80+TC&)3#:0!"=H.QC*<M],_E0,-H@2G9I+@1GV2#
+M8),[)-@51)QL2@=S<P8Y2]ASV3/,&`9PFG&\F)D(AL\F4FC)WRG<"?;^Z0&8
+M!Q[J*0@F8P>2F<`<@X]RG(.G\WT";8Y$`_JJ$V)SB/=,W_ISRGTD-G'/*0C2
+M21!X'=`Y$_/LDQOJ`G/ORF!!`Q]47],0-D!%HV).>R$M@0#ORI/3_0'("/27
+M&2=D#.$1P.Z>F/5D[);#;ZA"S88WYA`8$F92@2#KR>Z>89@2A`R<">R((MD`
+MZA]$3.T'ZE-&T[=@G+73IB>%`;/2\0`83MU5*FV_8(*9),;*WT^BUQD3\(-C
+MP\?*8"X>P@*]>.#OZM@=E2IQ3<UL&#M[*Y:4C5J-;!(&\97+*\K/RN>%+(U;
+MQKVM)@\C*]1Z%;>70:V`)7(^#[-K7M<6@>\?*[_IE.=.!G]%Y\[MTD:-E1V&
+MRV[&F!`P)6?8T@&:C)_NM6RI%Q`C/=<[7;&-&U9Z1Z<+3M:8E5[2EB./=7[6
+ME#?=9=(L4:8A6Z+(XPHJ#/3D25=MV>K))114VQCG?"G93<8Y*EMZ4%6Z5(1@
+M1"U$VAHTR"9`^ZG;1U#:)1LI@'T[JU09W(CE-%J"C;Q(`R%89;D"8W4LM#AG
+M=$XC3(X2QG=1>6!QL@J,Y4T^O'U0N!T$`[J"/2(@M4-9K8]U8,=Y"AK"9VGA
+M04KPAHP2LN]+0"=EIW8AIET3W6)U.X:`YH(6+6I%.ZJ9QRLZ\KX(`SV1UJI<
+MXL;RHA:U'ND@=UF+I2<U]R\``K7Z1TF(&G=3V%BSS`0`#.P72]&L2Q@Y(.ZO
+M:ZTIV?3M+1+-EH4;4-`&GZK6I6T>F`$YMI;/*UI95*@UC3^ZL$`-D;%)M&'3
+MI@)G@AN\J-\`KO.GNL;JKO3(XY6K6.!WX67U$@L,]E$TX_KKX><PL&?,K&"M
+MOQ/N[]I6!:`FN-HE).&,FO;L)I2?HLSK8:&;[+<M@#1(*Q^N-),0(A:CGW6=
+MTN/-#"=UO]/HAE36TB%SEJ_RZX.-\9716Y-6E((QF0L6.DK?I7-)M/\`-^J=
+M]U;L'])*YRNZL/3/J'NM#I%C7J-!J$GZJ7*QOTFMIZ]\UQ<0V(Y47\4V-7EG
+MXA:]GT=I:"6@K1H](H`QH`^G^BZ3#*N.7DQCE*ES5+891)G?"@UW;G8HNCL0
+MNV-A:4A+F-'T4+J5H)/H('"Z_MW\N?[T_#BKA[J=,^=2(/PL2[O*9K:0`%W_
+M`%*QI7-,@`01D+E.K>'',<:E*,"<J7"SIO#R2]LO348T5&.Q\JWTGJTU?+J.
+M_58G4*]>WFDX.^(5>T=J.O7Z@N=GX=-_EWS:K7LF9!Y4;JD3)E8/1NH#%)YY
+MV6M4<'&6D0=H3_#1KIH(G49]D#):V=7NB![C/LG#=+=$9*U"](VN!=`&?<IZ
+MS`6.U9[05$?^87<A/5J_RR=EO'MQRBF^BPN,0F\AG8)JCZ9>200?E-KI^_W7
+M5P?'U8`%QU2,S_95JA&GXXY&5/<..HD<?15ZCGEQ$-''U7L<T;P-@`T("(.,
+M%$\C)D8.0FQZ2@1EL@[C$H,<E$8F0A=!$D\=T#@M=JG4#P1_=)HDSC=,-.DD
+MB'#MLG!`!`_50%'J@A%#2_.!'W0L(B)`.\RD"6G`G*H0:/I/V3AO)'V2:Z-C
+M[83`M$X[YE$.UHTQG.V41$#O[E,-(R.4[C/]6/9%-@N_=+2T$['^Z:1J.G9*
+M>W"`B`6D3/\`=-I$3N-MR4X@M$P=/!35B`XM!+AR=N$-A(AN$VF1@?=%4+=)
+M$1]4FX!P2@30=MCV2<W>?E(F#&\8RFG!SG9%(F!`^"D!Q/T2!`/,I2-<@$0B
+M'<-1!!@3"%['DP!MB`B);]1RB)D2XR9Y0`<-DREI$0=D\MUD@[;2B9),B".0
+MH!P03,<IBT:3!F43C,Y^R$F`?5^7"JA:-P1O[[(],4QZ@9[(0?41!*1DG),2
+M40\`'&!RD=42#[B4[)+,$9,I-,GU'`V^44Y._J1:AL"A,C(/.Z6I-(8B792-
+M-]/)!`(D2,(\AN")&4,N,R0.X"!VZM\PGI8<)_[I3`&W<RFV``.1.2@<F9`R
+M.Z,NG<GZ(6-)J`3MRE`:)!$<*"2W8Y[A^JVNG6;VMD_U+/Z6QSZLC!G,+K7V
+MWEVS06Y/`4J6LVWH5:K@P3''8+4Z?;%K@UNHYS!Q^R'IM-H9#X).<\+7Z!0#
+MZ_J$CD0N.5KIBZ?P?1:UNIQ@]IGNNSZ=1)<#`6+T.V93I"0T<X73=/IYWCX7
+MF[NVXT;*D-#<$!:_3Z.F"(@*GT^CZ=EKVC`,K-=8O6E/V5VA2`SRHK-HR`K]
+M!HD#*-Q)0IC8A7:%.(TA0V[,XV5ZWF`8E%2V[,#"M4VDC`P@I`00,0IZ$C*T
+MR36$>EW_`'4],#5'9+^K?/.$3()$;'E0-4;.3VW2`,`$R1V1.`'NG8W2Z=_?
+MNH!T`YXV2%/<SE2N`D#NI&,!&?N@JFD0"0)5>[+:8DK0KD-,3L.5S_7;P,81
+M.2LVZ63;/ZU>L@C4N>J^96JG<B5<<RI<UI()^JUNF=-$:G-$^ZY[;Z9%GTUQ
+M!<]ICC"MNMVTF_E@]H6\;=K&Q_9974=+'&9V5TDNT?3F#7LNHZ32:&C&`N;Z
+M29JY75=.+6@"-L*XM5H4:(<,`&$-2EI)@84]"JI:;0\C&ZZ\5GIFNI2/<*K=
+M-TCU2NA;:MVC!5.\M`9V'NI<5F3F[QL#&RQNI$$.]ALNAOZ8;J@RN?ZH``3V
+MS\KG>'1QGB9S1JS]UB]-@W!+5K>+B"TG;V6-T36^Z[:3]E/C&3IZ+8MS!SLL
+M?K+<Z=^5NTJ?\@>_=95_3)?EOR4E<Y&#<T?+$B)&0M+HEZ&#0\S*:O;:J<PJ
+M#0659_1:LVL_#H@UCJQ>#/;*Z+H=:EITN@G;"XVRN@)#S@>ZU;*Z-.'-/U6=
+M:JW>M.\M7-;3D\*AU_KUITZF2^HT1W(6-=>(&6]@XN<!]5XO^)GB^O6JU&-J
+MNP-@3C]5Z<.MO'GWIV_BS\2Z%,O;3K8'O_JN0J?BJ[SQ%<P8V/\`JO#?$/7K
+MVXNG`5*AD_E!.5GW1ZE9U6/NJ%:FTY&MI`(7;]JWA)I]3>&OQ#IW,!]5IG$$
+M[+N>F7]#J%MJ:09XE?'OA?KE=I:"^#N).R]P_"#Q,ZJ!1K.]0.<_*XV7"\EU
+M\=OXIZ2RJ'56B"1A>=]6\WI]SZB=(.0O6[A[:M"<&>%P_C?I+:]%[@T`1O\`
+M93/'<X=?'Y/E8]K=,JL948\2.Q72])O?.HQJ]2\ZZ9-G?FWJ$@'8<+H^CW6B
+M]:T.])[_``N%M^O3K5U'74G;F!"EID'(.>P4-L-5,3!'96*3(GNMPM0W!#7P
+M<SNH:M5NF0,*>X:&DR>%0N7RSM"Z1QR+1J]0>(*7E'_&%6\QO^RG\QO;]5UT
+MX;?(=S`,`AP[]U7J"'=YR)4]P6DQ(D<GE5ZI;`(,[X[+UUA'/&X&X49``!&Q
+M1G3)GE"8@;'X40TDP#E,,.G8A&7$L#3$`R$((&44H$D&)PD\`G?(38U2)SLC
+M/Y8QG]40P(R!]^Z<:0Z7'&^R83SMPD021P@.D01$[\I0)V&W?=,W;$@_*;82
+M52G#6C(^436MW<^,;^Z$9.R7^X0)V7D[Y3N;!.KC:$@V$S]I/^PD#Z6D1D$(
+M2UHF<E(CF9GA(-)('/RH&`U'YQW1`$?/=`"0>Y[(].F6ELGB#_DJ:"0!L#'M
+MRD(_*1F-TGD%VXPD"-,R"XJ*0'H@[CE*'3,_ZI/`(Y^J=FDND?9`@&:#).K@
+M<)23C4E5!D'(D2A8`AH36G8'/L81`[`B.Q/"%H(_F3@8[IH9)R8^TH$3`B"G
+MR6:9'>4_Y6G;`Y*%@D9)]D0S6#`GZH]()R=DP!`&84M33H])@#@JJC$`=@,`
+M),P[5!(V*1,Y,`%,W<F=^$#P[D(@TZ2-O[I1$"?HF.#Z3"`FMDY(G"9S"XR"
+M??"<#`DP1[ILCU<?V1"`]A[>Z<`.=$B?V2`#L1_V3DD`8CA3H,`<F28V[!.W
+M43E*"=L2IK5H-5K-LS*#J/`71ZMY5%31+>_?V6_XP8VW:VFT-U;=H5[\.6&C
+MTXAK,D3\+/\`&.IUZ6NF)V"XYYS?JUAX[;[52Z)0\RL=8+I@8*[+HEGH:V`-
+MYPLCPO:%T$@D[X7:],M0W3I&%P\EVZ3AI]%MA`U<KI;"B&M@9[+,Z50AHU#E
+M;UE3U$2V,+FU(O6#7:1@`K6LJ4C\H5&SIF=_JM>UI'3[^RC<6;9@:9$*];4S
+MA0T&`$*_;T8Y*-;36],`CLK5!D-'LHZ;6@*>UAPGCL5=":F`0`3/]E8IC$<0
+MHF`[*:EL!,!6"4-]0,HP!'RHV[_W4U(2)[J$`W>8(A3,9)S`A%2IAQRIPT!N
+M4T(6TQ,$\)5H8P03E2NTB?=4>HW+64YD*45>IW(IL))7+WCC<5=,D\JQU>\=
+M4):UQ*/I5`$!SQ)]UQRNZZ2:2],LVL`)9GW6DXTZ+,B!W0.<VF))B,K!Z]U-
+MP!:TF"/LIO2^NQ^)?$-O947_`,QH`]UPM;QK1N+WR65&DSM/^JI>+A<7K7_S
+M#GW*\TNJ%?I/7J=1Y(:X[Y@J7VK>&..^7T;X:K"XI"I,SW74VE2(_P!RO//P
+MVOA6L6YS_P!UW-O5D`XE7&\)9RV*50"`#A7;6L0T$D2L:C4(]2M4:OW"Z3)F
+MQLMN1IW56]N?285&M<:6D3^JI75X,@._T6KFDQ!U&JV""`9"Y[JC_28C*T;J
+MX$.AP^%E=1?JIGV7.UN<.,\6.!!D[K&Z!J%R6G'Z+>\3T=3'/VC]5B=#9%WM
+M,G"EZ+'7VXF@`<E5+F@#QMA:EC2/E@XRD:0-0J1AB/H$B%E7=!P>2W8KJJ]$
+M1$+.O+8%LP`MQ&`ZB1D'/[([>X=3<`XF#[J]5MP&$9SLJ%S1+7'>.86M;5<N
+MZ(O+:`001C=>4?BAX9NF:JUNS#1D0<[>R]/Z9=.;4#7-AOLMFKTVTZE;::C&
+MND=EK#+5<<\?KY)\,7%ET_QI:W'5Z`?;TJFI[*@X'RO6/_$'XT\$7_X?OL>G
+MT[*YO:VGR'TF-FB`X$Y&TA:?XE?A9:W=%]6VH-94`P6#_1>&>(?!W5.FU1Y[
+M/29&QD1$S]UZ\?[>^WGRQF4U^&9T6L0]I<W\OZKTW\)^HO=UIM,-($P8^JX3
+MIUAY3,,)=M'NO5/P=\-5*50755K@7&<K/FU88O9[2HYUJV=U0ZN/,HN:3L%<
+M8X4J$3LLN]N&ZCI=@<+E>(N$W>'`>+Z1H5!5:(`.3"BZ7?,?7I"#+MCP5T7B
+M.C2K6K]0W"Y#H]L*74H!($X,KAY/R]F&OKU+I%8.MV@X,+0:26R<K'Z!'ELW
+MD>ZW'M`9(/RKBEJG>G+O5CGW61>U0`8=\+2OGQ.?E<]UNNUC'&?U7?&..5X5
+M*MZX5"&D0A_CG_X@N?KW;_-=IV0?Q=3_`&%W<O6O`[D#S"7&>%5JN].D1!5N
+MX+7OEK(G8`[JM<L+':7"(X7>N2%S2,@$2)R@&>%(X3$ND(1`/?V32ASI(@I@
+M.-T<NR!]<I.+2[`COE`!,[82>T"(TDD?;V1L$B"\#G)_1!IYS]U`B#JV^R0D
+M081-`#ADHB!L'?W5@8DNW'Z0$VDGZ(X])WA)S=D#`^F,S^Z8"<1E.W:"=L)$
+M$"90(3$29W14C#FRT'V.Q33@YB.1RF(@9A`3RW=I4=:=3B'&"BS`@@2F<YP&
+MG60#N$$;OM*<3L2?H4G:NZ0S$_LBF#=YF$\3.EWNDWDDGX3Q!,$*:#$G;4G:
+M3.)38)C=(G$*AR3P283M)/=,"WE.2)B`1WV4#ZCR3&TIL[`PGYV$!,_?@]I0
+M(EP&28"%KB8!/W18#>-LYW09)[J@I))@Y"D-1SZ(IP(;[*-C9,$@>Z)H])R$
+M#$&3V[A(`B#"D;ALD84CRP6X:6#6#.J=QV326H7$XS/PF(=));]`C:9(,#/=
+M'#8@E#8!J()&0/9"3#I<V>Z.!IF"`>$PQ^5L'N@.D3.!N-TU0D-@3*3=X@9V
+ME$]D@X'R%`!(#F@".,KHO`/0J_5NIL;HEDY*PK.AYM9M,'\R]J_">SM[#IS"
+MYK=3LRN7FS]8Z>/';J.B]!I6/3?+#`-(R>ZX+QC18[K@I,=MV*]*ZSU.C0Z:
+M\AW&%Y:*K[[K3JO+BO)CSD[R:EM;_ANWAK1I=\KM.C420"6K!Z#1`<UIQ`C;
+M===TFC#`,I]<JO\`3Z1:X`C=;-K3../=5+*EM#25K6K02TD05*W%NPIY$A:=
+MMC(W5*WVQPM&U:3[?*C2Y;#8PKU!P)WA5[=@(^%88T#8*JLTCZ@)QRK-NX0?
+M3"@I`R.59IB1/*"1F'3F%,P^J)^JA;JGCY4]$9R,%3:I&@D[%3T],?""@UQC
+M;Z*5[6CA!(VII:9":I7QARJU*P!W52O<-;JSO[J6FEB]N]$S@+G.L]1)!:#O
+MV1=9OP`6@Y*Q&%U:M!,K%R=,<4ULQ]:KJ,Z>RW["GHI1!/NJ_1[<""0"%M4Z
+M;`T8CX7.1M0N*3GB<_"PNOT&LIE[H$!=:]K0"3PN3\8%S@\,)5LU-D_#A^J>
+MNJ?9<=XXZ36NJ(-,B09&%W#Z+G5O4/JAO.GBK3RS"LO&EN.KM%^$[:M&T8RH
+M22`O3K0N\L?YKA_"EJ;>H.R[6U>&@">-RL8S2Y]M"DX3$J1U9K6Y.0L\W`8"
+M2<!9/6^N4K5A+G#'NM[9TV+R^`)`<LVO>R<N"\W\2_B):VE8M=6`(]UF67XA
+MT;FM#:HRL7+ZZ8X5ZHZXU[F5%7<U\@1\K`\/=8IWK`=>>RZ&VIE\'/\`FI,C
+M+'3'ZW2!ID1PN>Z93#;T;[KM>K6Q-N[&ZYFE;Q?2-YRM6\,1NV3]-,@[*>FT
+MG)RGZ=;%U,$;J\R@`?WE,>6:SZM,021"KOH!_P#3]5JW%`$8(*A%&&G"VS6%
+M<VL&#PJ=U;`4W-Q+AN5T%Q0;JG]U4JT&G<#/*VPY*ZHFD9`(*N=$Z@^F0UQV
+MV"N]4M@>!LL>O0<QVMLA6KW.74FM3KVY!`]EQWC'PQ;7K7.\D$GL-EK=*NM1
+MTDG_`#6D]OF-@D&<JXYZXKEEAJO'*/@ZA9]2#ZM,%L\KO.A.M+2@&L`"TNL]
+M-;5S`^BQ;JS-*F&M`$<K6^>$]99RT;[J%)S(:1]UF0ZM4]!D$K+N65&F"=D5
+ME>BW?+B2`N>6=^MX82+?B"U>RS+78D+DK*FZGU(:OZ3L%U=[U)MVP,F)[K.?
+M9M%<505G/5G#4NNW0]`+6M!)(A;AJ,-'4:D'@+G>GO#6#U+294+F$$C(X*WB
+M9(>HU@1#>5QWC"[\J@:<P7X"Z7JA&@DN/U7"^)W.N.JT;=N9*]&$^O/E?BST
+MOICZEA2J%IEPG]58_P"$N_P%=?T3I8'2J`.^E6O^&-3ES]WQS5W]APJIW$`R
+M5:O=+JA<&``F855X,D@8]BO6RC>3J,[\@I2P$Z09XGLDX3).23W0/W'<;H$(
+MP3E,8G?!1`9Q"'?!_P"R!O8@GW2';!1%C@W+26S`(3.:0^1('$JAA^6(B$6\
+MG?;"%HR`"B(@Y"@+4<'@)#\LF4#<MW1-((&-^Y5#YVXY3/'<$)H)"9X).24!
+M.<!L9^B0.?9!F$T3$H)`6')&3NA?#N8'NF)SN,)MW3!'>$4HVQCW"<'./W3`
+M83!L'`4!.;!("8>[3LEI`,28`S*<`:?9`P@`04@)G"1$B-B$6F&_"H;TPGF!
+MQ]TS0=<3&.4S=Y/'NH@CEHS@)`#5ONG`(W`^)3&)[>R*?T@'T@CY03Q)E&V(
+M(WG;A,V`_@9V"(:28R8E'ID0-TY)#2UKX!W2!@QW0)H.DR["+4(@Y*#$P#NG
+M`;OPJ&D`@?NB<2`1&>$((!QD)<G5'V0&R'-C?Z)G-C))]A":D&EI#0G@C&([
+MR@>F)=)!PC,MQ*"F,F!"EIM+GC2TDSG*@N]%8'7;7`1G.%ZAX4N7&M3H:H!@
+M&%S'@SI%-U'4X&>Z[?PCT84KP5BTX&Y7F\MEX=\,O6+_`(W:VAT@PXDQB%R7
+MA.@'W7FR22>5L_B9=Z6-H!RK^!+?8N$Y[+CCQ+8U;P[+H=``-DB>5U73*>1C
+M'98O1Z0$8PNCZ53AX)V4VQ(T[)D1Q[+1H`$P!E5[1H!SSP5>H-;M*S6XMVS9
+M`,%:5N,#"J6K1I_-^JT+1L-U;A%6J$Z5:8`(]U7H@=HE6!M`$JJGIQ,*=CL1
+M.%!1VR-E8HB8,9*C6DM$?4*S1:V1.%#0:6^TJ1U4-W5V:25:[J;]-.GC<F=E
+M%=5XY_55[BY`$ZMUGW-QO!E9M)%FYNL8&8PLZYN"9R@=4)D:OHHG-+C]%BMR
+M,[J=1SGXF$NFD`^KNK56VUSB%&VV<P86+MVFKPV>G5FM``6BVKK:`#"P[*F\
+M/`+EJVI(@?ND6Q/4<\LCA974+!UP3JV6TQNJ!PIQ;-TSI]UO6V.G%U.BM827
+M#G=05K#2=L=UV%[0:&N`;E9-S1;L`5-2+.6/96K6OD"(6FU[64LIVTAO^JAN
+MA#2&\+#2IU6ZTT708,+@?%+[BZ<YH<0%V=_2?5:1)@K'N[`YU-3MJ33PSQST
+M2]JW#G4VN<)69;=+OK=C7.:6EO*]TN>F6[YU,&5D]1Z-0J/T:0>(6KG9#'*N
+M:_#?JU>C=,HUB8G&/]%[CX=N&W%HTC)/N5Y=;^'Q1K-J,:`9X7HW@FF:=%K2
+MXDQNN/,K6=F4;74&`T"#&0N9-$-ZA`&Y767PBD8,K`#`;[5L96ZY1M]-MR+<
+M1V4WDN`("L=,9_(:8X4U1OK.%TG3G6<ZE`@A0U:9#96C5I3PHJM*`<$K49K*
+MK-!/^2J7%/>/LM>K1&8'TE4[FF0#`!51AWM)L:EDWM$`D@[KH[JB(]0"RKRB
+M-41^BC6G.D/IU"YI+2M3I=X'0UYSLH[VD`2"/LJ<>55#FR"J6;=`ZFVI3G"S
+M.J6@+.ZGZ=<RT!SOD*2X<UV(P597"RQR?4K8"8"P[VB0X@$Y79]1H,<3PL*]
+MMOYI`B%N\PG#&HL<*@)V[E:#'M%/)!)&Y45S2%/U1D*M4K@-U2,=UPUJNO<;
+M%BX&)S/NM`NTLV6'T5_FD$#$[K8K5&LI29F%UPY8RX9G7*Y92<22,+B>AU#>
+M^,=.N6M.P71>+K@MMGD/CTG=9OX2VC+KK!K%N=>Y7HG3SW\O7>EVX'3Z0#?Z
+M>58\@?X5IV=LP6K`!B%)_#M[+K'E?`-Q)JDSG=5WP<RW'ZJS>.EY@0"<<JK5
+M?G!)^5VKLB<)&,DH,[`C*E<1.VWO*!Q;I(@ZIP>(4-!R3,),'I(D=Y3EPC`G
+M*$F3/]D#N)VC?A.2YP&HR>QW0!_^RG)Q&^-^Z:#9!B-NX1GU.)(P3F,)@8G;
+M/UA-)D-)$#8*AY$!K6DG^R4XVV288[#Y3N)!(TX]U`S2=XA-F8C'=&S:8QLA
+M<[.V1A`T@-T@9F9X2).F83NRT2`8"%Q((B$T'`)]1!SRF<T@Z<&1/=2LK.;E
+MI=MI^G9`]V9C`[*J$@Q^6?A-)&8*)N4S@-YE`HR"3]-D],P8C8II'=(QI'Z$
+M*!R2-FE,3&THI`&\D'9`"),H$>#.R<9]Y2!W`&4[2([_``J"#HD&"(V*8$;C
+MZX3$B,3GW3LB,$SQA1"8`23JX3ZMH/RG%0:1Z8@S,)C^>50\C5P)2)$$P/9,
+M=$A$S000XG:0BADX[)-/9.V"1.>"B,:1IC"`&"&_*?&Y@?"=X$0(CDII!,DX
+M&\)H)OOE$.^`WMLD0-YS&1[)@?5B/[J(=ADB`M#H],5+EK($SLLX1W``XE:O
+MAMS/^(4]1`$J9<19-UZ1X.L7-HTSGX'*[BT'D6VJ,`25B^#:;/X.F&Z5T=YI
+M9T^I$8;_`&7S[;M[,L9T\W\;W)N.M>6TB-41]5T?A"EY=LR2)A<E??S?$3R9
+MPZ%W/0*(9;TY'J,!:O&+'DXNG5]%&V%T?3&-P,@\K#Z'2'E`RNCL6P!@CX48
+MC1M)#8._RK]!N0"JELW`D-^5>MFR!$K-:BW:#(,+0MP!OM/=4K89VV5RD1GD
+ME%7*)C`&RFI27*O;\*S1&>))V2M1:I``S*LT0,$E04FY$J9KH&!NHJ8OTM]X
+M5*]N!,:LA/=U@![K)O*^IQB=E+5B2M<:G$`D*(ND$3E5M0D[H@2,0IMK20X^
+MB-CA`(.5&R'$#E6[>A)]E%!2]3NZLTK<$"1NI:%L"9A7;>B(`(5TOLJTK6(+
+M6Y5RWMR`)^RMV]NT1'"L,I,V5F)[(*5,1B,=U.UOHB%,RB`,DPK%.@(!W6IB
+MSMD75OJ!.!&2LJ]MW-<3NNJJ46EN6CX5.YMFO#F@#/)4RP=,<G*Z71@;*-])
+MSC,86[5Z<W48F"D;"&@=NZY>KI[1S_\`!S,A4>IV8T'V755+8`0,E9G5*/H(
+MVQ*FE<)U!OE.?MA95-CJESM,E=9>=,\^L0)AVZL]/\.`.U8.%B[IN1B6=H*F
+MEI$+J>B6XI4P1`14NF"B[;;E:%*D`P8XX*2<I:J]3>!1,[A8]@T5;V?=:/5W
+MM#2TF.%#X>M]5?61REYJ=1TMC1B@&B9C=$]FY.ZFM9%,<!'5`[_5=XXJ%1I4
+M>B2<A6:S=1C:%`6_>%4JI6G@?*HUQ.P^5I5VAQA4Z[(;I)"J1GW=.1,02LVY
+MHF3^F%MU&C3@@_*IW3&AIG[J::CF^H4C!D[%8US#:G(@[A=-?T0YC@!(6+>V
+MA#C@&5-MQ3MW.#Y!P<K0;5)9E9_ENIO@"0,Y4[7X@'C95SRA7A]&#*R[]OIG
+MGV5ZX(#9CA4*YD<@+<<K&7?,+V1)G^RY[J-9].OY;1.5TMZV-7[KF^K4GNN`
+M1,$\8A9RGTQO+H_#%*+=I(@]U9ZM4`)@^RJ]!?Y=D&@Y`0]2\VK3JN8,,;)R
+MKXYPF=<GXVO/Y6AID@[RND_!GIU6F&W#@<G405POB=YKW[:8[QDG)7J7X14Z
+MK;*DUV,`8^B[R<R.&?$>CV];31:)B`C\_P!U7&!&HI9_Q%>OT>/V?!5TT3J(
+M#>T&56J@1+3DXRK-P/YG`/;=05&>K2X<*O2K$Z9`!!^4GMTZ02<Y([(W-&WN
+MA,2>W!)4`;B,;IB,Y*>!ID83Z,$E`+A.!*9A)&Y`]D<YF8^J8@S,G[*A-;NX
+ML($Q,)#\T'[!(`<S,I1+H`_T0(&=X!"3L#<X/T*)H&C;,[IC+G:B20@9KO3C
+M.$B&G.T\=DA`W"(@;9A`.YSA,[#2/H%(X2?D90M:)(C$_9`+#C]4=Q1J4M(J
+M-`-1H>,S@[)F$MJ-J`!VGAPWA27ES7N7,\^IJ%,0T1L-X_5!`P-`,C!_5.(X
+M&R>!`<4Y:-.HC?*`($[3E-$$1RB($8R)[*ST9]K1ZE;U[VV-S;,>#4HM=H+V
+MSD3PH*PS.,A,YQU2[C`^%/<FD^ZJOH4C3IN<2QDSH$X$\X43J<F.(^R``T$8
+M(F=DP',<J0T26X!_R3FF2XC(X$E4"TPXB`0<2DW$1D1OS",L(=Q[%,6$^HN`
+M^2H&$1./ND^`8&>^488=,EPC@S"8@#5(:<1GA`+FM!'O[I-TD=C\I.:#_4`#
+MV1A@+8)&_=4"($<HZ+A3J:NV1'?A,QD"-6Z=S&B(<!B4`AH.2[)RFD2?43]$
+M>EHD:D_E,`:2_A-@&'W/=%&29.W?=/Y;/\8']T!#)F3"!L<D[[J:W>:-9KQ\
+MSV0!LMEDCZH(PX3LH=/6OPTZ[2T4Z%=X#L`2?9=IUR_I4^CU"QP&L0#*^?>G
+MWE:U>'TW?E[+;/BB[KT!2=4,;+RY^&WIZL/++_9TW2GBKU\O)D:H_5>B]*IB
+M&,#3``@RO-/`G\VY%0Y)S\Y"]0Z,"6,Q&Q7/.:X8SR]KMU'0V::;<[?JNBL6
+MRT`F,<K$Z.STMG.%OV`P#N3NL$7;8`&(5^V&`Z?H%4H,]0,>Q]U?MP<3\J-1
+M98(`D0K-%IB95<$@YV'Z*:@\GX^4VU%VB,`JU1C"K6WJ`5IK0`,J5J+`VU9"
+M:I4`:/5!0EVD8^RS[^X+=7J@!2J'J-Q!/JQLLXU2Z<SF=T%U<&H^)4=!QUP=
+ME!8:TDP"9^59H4G:<R5'0;)!!]U=I#/;W[*:78K2E+CQ')6A;TAJ$`3ONHK>
+MGD3E7Z%)P:.<YA61+4E&D&R>RL460)A-2!=@8PK%(-@3\0M*DIB`TR,J2F`7
+M)`!V&P2G9^;M"HL4Q(]E-3$-Q^JBIG/]E/3$C/PMQ*"KANP,JNX'=6ZC-\J(
+MLA^)"595>G3#B<"$]6D"V8E3,`#\B/=/6(`(^JFFMLRM1R8$86;U"WD'`6S6
+M[XB%1O=);@;KG9'25S;J.BX@C8K4L-#F`1E5NHTAKU-$'=*SJ^7$F5QZKI9N
+M+US2U<1"JW3A3IND20,*=]VSRSZA*PNMW^#DB>)2UF1G]6K&I<Z`5K^'*9#!
+M(^JP++57N)W!.ZZKI+0RFULS&%,>TRZ:U'#!S")X!&-^R"BZ![E2O`^(79R0
+M5&&9(QW4%1F8C'=6G-QOME05)F`JE5JK`!@&53>S)5^O.D\*K4VF=E44JS-!
+M)5"\!+<8E:=U!DA4;B#@G)4VU&749)(GC*S[VE`+8,E;%8-!D$*A=AIU1OOE
+M-*YZ[IN;4)B.,*`@;[$\K3O!/ITX69=L]4@^\*QJS:.JXND$<*M5T@1"FDD^
+M^RCN"-)!&58Y933*ZBZ).^E85R]KJL-'J&5O=3PUTD?*PJ[8?M))PY,JXKMC
+M6+:6(B),*OU*\++=[FGU.!!,*%U;2T,&_99_7*[OX9P$1&4PIE&+;UA5Z\V?
+M5Z^WNO:_P_I>784_3!(F>Z\2\)M?<]>:3_34X^B]Y\*L?3Z6QH&-*].$_D\W
+MFO#3?<L#R'-,C!A-_%4_\)5.L[^:['*'5[+W/%M\35H#B2#CE5JH!XF%8K1)
+M@P%`^`X:_4#N`N;VHG$Z"T_E&0/=`?B92?ZC[#8)H..(,[H&V!E)H`/_`'3O
+M:00!VF4P&0$");DP!/Z)B"6@QCO*)^<F7$<RA<XP`0"`H&W.HDRG!`9G&(W3
+MD@DN``D[#9(P)VGYA4)H]4B8Y$H@.QVW3-,[`RB$'!*!M(T@YG:2FG$2?E$T
+M8[I%IT$D2$#;@0XR1E(8&'9.$FP.9]TG;",>T)0YTD[X]D`&X+D0@-$B?HA.
+M1(R@=C?<P<I">2-LE-Q,0@:=.YQ\(">WW3`Z<R92CTS'SA(9.1L@+4Z!)3!Q
+MU'8)W`$`C*1#7-)_+`^Z@$O=J,.,)M9<6R2F,S(28UKJ@:7:9_J*JB#G`G(P
+MF!=@G<I#!P9,[IB1VE`@2#P.,)VN,&2,8RE(G_-,T2=(]1C;=0*?3G;X1AQ.
+M&D1*!PSP([E*??Z*B1I,C&R<ENG!!GW0-!:S43B8W3P)B1!4!`D`GO\`5,\G
+M5G)XDI2.\`>Z1D@095(4F/68E(NA^_QE+20(SG8]D[A#@`\N`R3L@=CM.'-D
+M1M*$.,'3NF:`1C]24I]1C"B#IG`](GY4M!H\P0/U45,8!G92VT>:-P)RBQZ)
+M^'E.&LU8C$_9>H]$;AITXP%YK^'%+66DN:.TGE>G]$IP]LU&G"\7EFZZ1U71
+M_P#EMF8XA;MF8`GY6)TMHAI6W:-,`X^JYUN-*UG`S&ZOT(`ET_'94K1I(GZ*
+MVP:>2HTG)D8)D[*2T$B!^ZAI9V<KUHP'.!\HU%RS8("L#;V_=16\-`SPC+PT
+M079[J:VJ*\J:)D\+&ZA<:I;B>5<ZE6'JTG*Q[DDN)/*R!8XET'\OPK=O3U.S
+M\JI2@>TJ]9MDC8B4T;7+1AU?FPKE$MF9D[95>@P`[1"O6H$[X*?\6+-J#J#B
+MM*W`(`GW5&DX-P#*O4*@:!ZLA6"Q2;Z"1]DS"2^",;RDVLTX$2I&:"Z9&RTL
+M'2,84[#J(,`2HZ6DN$?=3!XF`=D@D8R/ZL`*1CHV.V84#*ITZ<;J1KLC:2M"
+M<.PF$%V^/=1.?I^4[*D"<*[-)7QI)D;J*KZA&)3O?)F,)M33)4%2L#JSG*HW
+M^)P%H5R-EGWC06F2L9-XL;J-32#E8ES=Z'D-,0M?K`Q`.>"N=NZ9+BX$X7#)
+MZ<$M:^/DDZ\]ED7]TZK5#9QLFO2]F!RJUI2<ZM,<_=0L=%T"D"QN`)72=/;I
+M8,+$Z*P-IMANVZW[0[?"UBXY+=`$NF,*R6:LGLH+8^KG*M2"(E=,7.H'M(DA
+M0/$YB%9K8!C*K$R2(SRM(AJ06D'M]E3K`D?*N/@XE05&C/8<H,^X(F#B%3N6
+MMTDM_17;QH@F=\`E4:\G!/'=*U%.LT#X&%2N\B>>`KU;_P"["S[P2^`?JC3.
+MKB7$[$9^JH7%,:'<DK2N6@`DJC<B!$ITK+>0UY]E!5?+C[*6[:0XF8$K/JO`
+M>?4<JLW$-Z&$96)U!HF)^/=:]T\%N3@+&ZD[USJQ]T^//E-51KO`>0[A8OB>
+MX:*+@2&X[9`6G6,N=J<X97,^*;@.+QJ)SF5OQSEFM/\`"JFRKUD.=!@_W"]X
+MLGLH=+9IWT_V7A?X1@#J`<7""<#[+V,UGML(.T?9>GPS^3Q>>HJUZSS734C*
+M'^-I_P#R+!N6N-=YU')[H-#O\7ZKW/*^7:WY@V`3['=5J[8)])'"M7`!)*KU
+M-);.?A<'N5WD;<E#I&Y'V1G)W.$Q(TX)^J@"0';?0I$X+I(G$)P`1F04SVN`
+MQA`&HDXF$^II$"?JB(`=.1^J8P3OCW0(E@,M)]TL:I((!2(!Q'WQ*38`,EWT
+M*0.TMP&X]T[B`,Q.R=L;[RF@3B539PZ/C9(N]\;0FV.GNDYHWV0+$2#OA*I&
+MF2A`$P#$(J[FOJ:F-#1VW40AI#07&(]DB0YL`3R7<I?U>K./A,YIX./A#H+0
+M"(V^4B2T07$CLG@1G<;82<UIYD(I'8P=T(@1+D\#/JE,6R>ZH(")@X3.+=)W
+M)3_T[(7-,?FQLH%)$PF$\$%/I($2DSG2=MT#`NDG5D^Z6VQ`QLD0>=NR$P!W
+M^J51$_U2,\2GI5#3,M,&(D'9`UH.Z<@!`MR8.V8)2/M@]DT`'O\`!2`$D;_*
+M!QN<A.`(()CX30=P0B:#J.HH'+=X.R;8C$_">,Q&0DYL8@;;('#LG`@IHSF`
+M/A.1C:#O@H3ODX0$T8P`4B)$:1/>4FMS,.A)S9=@[(@F2/\`)3V7_/;C"@`X
+M*L].$5`,CX18]'\`:139(X7IW0A(;"\R\`L,,#B97J?06AM-N%X?)/Y.N^'5
+M=+@-:3QNMRR`+1DQ&%B=*@M&(6Y:B",;K%:C3MP/+YV5BGAH[2H+0"/[J<8?
+M"RTEI.@C'*T+,`JE2:(SLKE"&MW1J+3<`DE5KNL`(!QV2K5@&F52N*FL0TY4
+M$-R^2294%>JPT@UM.'3.N=PFNW$-,G94PYSZ@RB=K5`-DP9/*O6T-V!PJ-!N
+MD@;*4U-.2Y1J1KTZK0T$NA2_Q8#0)(GNN;K]2T#33,RL^ZO+UP])(:<[)MOU
+MUV[=G463ET0IJ?4Z9P'97FU"^NGW'EZX.RZ+IM"Y>`XN*;OX6W&?77TK]D@Z
+ML0K=&^8X8=E<U_!5A3U:R.52_B;BC7(!)"F5N/:XW'+IZ!97$M!+E<I%AR25
+MP=GUBJP`..GY6]TGJK7#-3]5<<I6K@Z$C`@!."X9;((4%O=TWM$'=66%I_JE
+M=8Y]&U.)R3E&S:$P`W)1@8C.%-&PAP)_,F<^.4SF&)&%&1!&5`U0^J95.]!+
+M294URYPG*KU'@CU'/99JQC]3I%TD;K%KTR)&)*W[_208"Q;W!)W]ESR=<:RK
+MVAJ'PH[:AIJ`E77Y:3*%C0'3N5AO:[T^`0MRR/I$\+$LL'?"V+(@D$'A:CG6
+MI:C$85AS@UN`%6M.8(]RIJH],S"W&$55QR=E`70"#N5)4V)!5>JTB3,%79H%
+M5\'A1/<'#(^O">J8YG]E!DDPJF@5@'-@B0J=S3GLKSF0"3^BJ5ORF/W6NS;+
+MNF.DPJ%<<1E:=WALK-KCU?KA--13N&!X^%F7Q@XV'ZK6K2LV\``D@(LK)O2W
+M<A9%T(<2!CNMB_:",+)OB6@I9LM9]S5()@0LV_?J)$3W5^ZRPZL`<+(O'#6[
+M,]HX68XY,SJ3RQCO+S\C9<;XCJ@U<Q._^\+INN5Z8:0V,\E<5URO-0EIF#E>
+MCQSEQSZ=W^"[7/ZJTN_5>N=>J"E99($!>-?@<[5>NK:X`"]!\47HJ4"P/]EZ
+MO!/Y5X/U%Y57]0I%Y))W3?Q]+N5GTM)I@D$HH9V*[^S$E?/=RQH&.>.RK/TD
+MF6\<*U6>X$_E^55KETB2`2N3U(@!JDX"&-1_9)Y)=O\`5($ENXQPB&<T9C/U
+M0F=/?W1N)`$D9V0:N1PJ&`,;_JGQ(/"=CRUI(`AV"#E,QS2!'?9#9CET9/LB
+MTZ1L1\*2N^B7@TJ98`T3J=.>3L.4S3+0[3M]D`P9V/T2$P2EK)))W]BF:X:C
+MG`0/EISRFV/*69$[#ND2,`2BF&<@Q*0!W,E$8G/Z)AC9N_,H&VD$2-X3-ATY
+M._=$8,<?)3:1N9@\DX4"&WMW"9TS.93M@DC8(G$`?"HB^=BCHN+'!['9;D'L
+MG<1/I!CLFY)@XX0/4<ZI5<][M3GYE#'L,(RYI,B"0$VIH,Y01P7/P9/LD#P?
+ME.^!$RWL4(TSOO[IH.<QD3RF'<D$I2#B-T[2'`P<CA129G)A"=AV1-(C3CV*
+M3O*X)_U5`-WA/!!W"3@`2)3P)WXR@<`#U#CND#!S]D0PZ&_E&Z0WWV[J(;?M
+M\IR(>,[^Z4,F&X!Q\)W@#TDS'(0,R(W@^Z3BX^WR4WHT[_3**6EH&G(Y!.55
+M$2#$-(')W09`AN?=)L3))QC9.<&<91#AW$X"N]*@U@=PJ;6D@$`1W6GTND&D
+M.QE9H]`_#_-8`#CE>J=`TEC,%>5?A\0;IC2['*]7Z""UC>5X\_[.KING;@<+
+M?M22T2L/I>X&WRMVT:-(GCA<ZW%VW.!A6*8+G0H:(!SL%;MF@0866XLT&0P<
+MJ1QTB04-(#Y$(+AP!@%*H:SS!(*KO=IR8E%4<#Z95>X(#42JUV^7$3A16U,%
+MP=ND]NNI`,@*8Z:5/=-$A[BJ*3,X7->)/$C+)CB7[<)>*^JTZ-LXO>`!LO)/
+M$G6!>W[J;JI%.=X6+;O4>KQ>/?->A>'_`!11N+R7/G,1_L+L_P"-H5K::?(7
+M@5O6I4&MJ4ZI#VKL?#_BNG2L]-:H!A;PMG%<O-XK>8V/$76V],OA5J&&3NM[
+MH/XA].9;-#Z[!`Y*\:_$7Q"WJ5?RJ#O2WGNN0;<U!@//W5F][CI/TTRQGMV^
+MIJ'XAV%W6;;4:S7N=L`5UO0;47E`5WMW7R1^'O5FV/B*E5K/].Q/9?6'X<]<
+ML[GIM)K:C2(WE+;E=9.'E\7[7]6A?='!;+1E46V]S:O],D+L:9I5&#U`SV*!
+M]A3JR"``LWQ_@P\UCGK+JSZ9#7DA;O3>JLJ0-2IW'A[S:T4P/E4K_I%[TT"H
+M#+0?=3^6/;M,\<G7T+AKA((A6:;Q&XA<7T[J;F0UYCY*W++J#:@`:X'X.RZ3
+M*5FXZ;3].GB56JF780,K2,$''=.XDB1"5-:15!J:>ZJW+0`3*GJ.TD@G"AKP
+MYF%C8RKQYU0`LR\;(,B/=:MY3AI)P%DW67;[_HL5O%2TG5$?5/I/,(B3&-^Z
+M"8=!^ZQIO:S0QMN%J]/.`0<E8]%P)Q@E:O3#!]E6:VK-WLIZ@!$E5:#HCD'L
+MIG.B"MQD#Q!.57K&3A2UG3,*"I).TJJK5"2["'28D[*7TAV<E`YPV_NM1FHG
+M.)&^_NJU?(CA3U7>G.55NG#20`M(IW+001NLZLT`GM[J[7<1,*C=.,F/T15.
+MY=!,RLZ_@L)!A7;DDM67=O=M,@H,^NTQ(*R;W+HB/A:5Y4`$;$+*NZX)))D*
+MEJA?._EF<!8M^ZF&;Y&X"UKYS7,W'U7-=4J$^81$#<K.OPY=L#Q!7,.8'3V(
+M)"X^_+JI?.0<96OU^L#4>`[/"PJCCHYEQR5Z_'-.'DKT7\'!Y5HXC[_5=3UI
+MI>#J)$9PLK\+K`T^@,K3EXG]5<ZS<^LM8X%TP0%Z/#.-O!YN<DEO3;Y+<\*3
+MRQW5>E5'EC40"B\UG<+>DV\%N7;-('IDY"IU6'))&#RKE8>C42T@=U4KQ,<3
+M*YO6@)B1B"F<P`'41V1.T@X(S,SPA(!F2)'=$H`)@#8<IG[_`"C'Y2.^R&`2
+M8B/<HH'!LB`?A$S0:<:I)X[)$>KO"36@/@=OE4.\$'26P0<@J>G7;3MRQE*G
+M+CE[A)^D[*`-`)./F=D0D-@$`(&`],DC!2(;J<B:T?$)B,[1]4#5(:[(V3$#
+M223'PDX;(@R8:(S(W4`B08#1(_1,_#>_`1%HG83O*8-$@$?7V0T89:?G,)X(
+M,C'9,1C';=2.CR\B%0,Z6D#&K=)Y=`U2G#89,?FV/=(M$80,9)B4(:?>$^B1
+M@0$;8TF9S[H!#009"8-_I.(1N:(=F$+FD@#4<\H&<P1!S'NHB(=)^REJ.&F`
+M[[('#83'*B@<(.^R1!=DF.,(R"'&9@]BG8T%QU:BT;P@%H+=."/A-^5^[H[J
+M2&D$"?JA=$S'TA`TR?S"#WX1%S2(@`]^Z%D1S*8;Q.P0&#Z3D"<)-TDDDXW[
+MI/:)P<;Y1.:0R2[=$"V1L[8X2))F#+DW!.K=.T`.W/T[H'@D3(2`)D<$)JAU
+M$Z73/!2:-,912CU8'^^Z/2(@B#V*`CTET@'@#E$,G5JB"J@K=LU/\UT%C1_D
+MM,0.%B]/:75=/?E=3:TW"T)(PW("YY5J1T'@`?\`JVM$"<97KG1)\IN,PO(O
+M`LBZG'SVW7KO0/\`EM)7ES_LVZ?I@@#/ZK;M1L9C&Y6/TULM!F%LV[8;.KA<
+MJW%ZCF<J_0;GD^TK/H`F#(RM.S;@9E9;2%Q#-B%4J23)5FJ[$;0H'9$'"NC:
+M%Q!R!LJEVZ9$[JQ<``0%4=)))2I"I-@:MH69XEZBRUMW0[*L]1O!0HDD@0O.
+M_%M]<]1N#:VY)DY/99SRF,>CQ>/VKCO'WB&XN*[F4G'1/=<5_P"H=4)=J))^
+MB]1;X)-2B:M7+CG*Q;OPH^WNH#-0!Y*S/),7T,)CU'-65.N6B7[9C=/=U"PP
+MYQ`[%=)5Z=</=HT&6M$``<*.MX4O:C"]M$GY*D\D^MZD<9=/<XZI,'&^ZJ5-
+M1$SD;KL7>$KL/=J88!'U4'4/#%U;VVMU+*Z8^6.>>,_+D[>L^G4EKB,[KN/`
+M7C_J'0WL8Y[WT@-B<A<;5I:7DZ1`*C:)$?7==;)DX98_*^EO#7XT6!#65ZP8
+MZ/ZCA=WX?_$WI=X6M;=TSJ'^(?YKXQ:7LR.V#W5SIW4;FVJ"K3K5&Z#OJ(4]
+M;.JX7]/C>GZ`>&NKVUZR6O!'L5J]0H"[MM+&APXY7R9^%GXM_P`$&6_4*NGC
+M43@[^Z^A/!7C_I'4K&F]M]2!C(UC_-7'/?%>?/QY8)NI]*+=1##J"S)KV57)
+MP/==1<=2L[QYJ4GM+3[[K!ZZ^@X$-<TGY6+X_L,?+9PL]/ZH*@$.RM6C<AXC
+M5MA<&XUK5WG-=Z>ZV.C]4%0:=0D?JL[LXKO-7F.ANJG:%&:GIR1\JLVL'4HE
+M`:@.)]E*:*\+3.HX65=,TM*T*YEL<*A=G$AWT6:D4:DM)(Q[*"H\`8E'<.WQ
+MD*M4J0V94;3T7CS/E;72WN(#L^ZY^W?_`#-UN])&IN^R%;5&IM`^ZD+B<'Z*
+M*W`@2,J5Q:<R%4@'1F/^R@N'%H($J4D..\*"JZ'9=ONM0JMYD/DF4!?ZIVG=
+M$\"<"`@=I@P<JQFHZCH:"3OPJM=V5-7C^DX/=5:T8$_Y%:16K'(RJ-V\3\*W
+M7=B"0,K.OW`M(!$HBE=U0V<Q&ZRKZN`XD#=6[Z9)+C)Y61?O+FNC_LK%4.H5
+M]6RS;EY,Y)"N5PTSWE9]\XMIEJM8M9G4[EM.1J]BN3\17H-+TC2)S!6WU60!
+MDX).2N.Z[>.8[:`PG(S*N$Y2ZDX875GDO<"9Y`5*@WS;UM!LET]X*>^KN=4E
+MHEP)VPM#\.;=USXIIO$PS,_0KU7C';S7F[>R^&Z!LO#5*B['IR5A=4JL-P7&
+M)GX6_P!8J.IV082`(Y7%=0JDDN(F-EZO%CK&1\_+^657V=2H!L2?NG_XE0_Q
+M?JN6>^7DR<^Z;6>Y^ZZZ:]8X2X9',G;<JK6:7$RT;8"T[H>2#Z!K/YIV'^:J
+M5:[)E[6O`P1IC[+S/4H5`X"=B@:(SOPK3G-8QX:^6NSIC*KD@B>WZ(!J%SW%
+M[@.V/]^R",_F@SO*DC;4=QE"6AQ!(^@4`$&`"?@=D_IW=O\`*<S$D;93;'V/
+MU5!@!T^G_1(M!RUV)(]T-/)B?U1,(0L,"=X`GW3M$&3M[I/<"^-(`X`3O:0=
+M+I!!RWE#02-+CS'9,"X8.W9$TM!!+2?JGJ5-1+G'/=%`!IW$RA?!='T4D>DD
+M"4QT<S@930$3L"9&R,MD#@./.R8:"8[[IHT8RF@XGD;X2SJ`)."B8!&3PB+1
+MJS.1B4T@0SW/V3B0=XX12)TI@[!$H(W[\DE)[B223]E(`#."/=`\N`TF.\HH
+M,:M\$H07$0<A.';<G9)D8!X13.U9V^J<.#1C20?NBT@R)`"8P0`<'NH@2XAT
+M-/\`9(O!.70GX!D&>83>K,"1\H&:]P)(P"$W!).4^\"82()$3@I5.3@9D'A%
+M4)D&2!P/[H&M`=C[IVN,^HC!W1#28QGX^48)W:0.P3:0,@X3L#9&H2#P#'ZJ
+M@8(=)@E.Q[PSRPYL.()!'(_[I&(Q$#&R1$G3C3V"@)@)D'2"9WY^J1!Q(]Y'
+MPF`Y..Z<CU`MB"9@H-'H30ZX)+=PNF+'MHC2L;PE0\VKS/M]5T=>GI:!N3L.
+MRXY]MQ>\#S_&R-H7K_0!#6#;"\B\$M#;_3.)V^B]=\/D:6CLN&?:NKZ61H$#
+M$+6MB2(_99'33D"`?JMBS:/3&WNN5;C2M&@C;]%HT@UK?=4[6=H5@XGWX4;.
+M\->\F<=D-=H&>>45(-&\R%#?/`9RFH6JE5TO+0=E!5.AA<?U4X#34))^JQ/$
+M]\*%`M!S\J6Z;PQW7.^-NJ$%U.CDS$?9#X/Z4W4;FHSU.S^Z;H/2JO4KXUZC
+M26S*Z\VC*%`4Z8V[+S[W=U[Y)C/6*%VVF&>6P?943TEMR\D#=;=G85:]3;=;
+M-KTSRHD9"7^79N8].:Z3X48^MYCV8/9=E;^#;+^!DDZHSA6*#&TFB!GNIA=7
+M'_*\PZ2NF,QCCE<KU7']0\.T&U7`-!,[KA_Q&Z>*'3W,IXC$KV2YL:CJ+WR`
+M<8*Y'Q7T(WC7`M)XPLWCITPRW>7R]U2VT5G`;?XEDUFAH&73)7K7BKP15I7!
+M=3:8G:%Q_4/#-Y2.H4G.S!PNF'FDXKV98XYSAR]-Q>T3.,J1N@X!,QNM"YZ3
+M5MVZGTG"/LLRK2J-,220N\RF73SY>*PJC@&2''.)'T6MT+Q'U+IM1AM;VJS$
+MEH<8^RQ'3N1'M$)WD:,8'9:NJY^M>P^&?QDO;6W#+ISW$#=I/WW6YTK\7&W_
+M`%1E#4X,.[G'_5>!!Y`#I$D3(&ZEMKFI2>"UV1V*SZZG%<_VL+\?973>HV_4
+M>EC0YI,<+.I7KK._`+S&VZ\6\`?B(ZRMA2KU':F",G??W6[?^-FWEXTL<2'<
+MA9\EEF[VY>/PY89:^/=^G7C*EN#KD0KC*HD1LN'\$]4-Q9M:7#@[_*ZNA4;I
+MR5REVWE-+I=S,A5;P#08&/V1!YU3M*KWE26D?LCE5"[AI,;*@^H3G:.5+?U#
+MF8*HEQ\S<%9K<7+,GS02<%=)TD^F`87/=+IEU0?JNGZ:`U@GG@++5:=-WIP4
+M-1QA,PB,[H*AR?=:VD(N[[%05W<3*3G9CA05'K4*%S\\QW*C>^!_JB,<S)5>
+MX<!@G"U&*BKO.HSM[JI7J;F?U4M5Y,R#]51N'N&6XG?*J`JN`)^51NS&21]%
+M-7>8D[G=9O4:@`,.CE61%;J#Y;`B?98M[6`:X=^2KMW7#3_=8O4:I>Z!WE;2
+MHJ[A$G8K(ZE5AKAJGOE7;IX;1(<[/>5SW6;EK6%PF7;DJ,=UG=;N@V3/I'9<
+M5UJX#I@2"<-E;?7[II9`P`5QO5;B:CSK)/)7;";Y9SOQ1N:LO'IDCV7:?A/;
+MG^.%PUD!HWWX*X>QMW7%ZRF&DS/&Z]L\*=(H]-Z*P-/K(EV9[_YKOK=F+R^3
+M+UQVGZ_?L<!3<<M^O9<CUV\ITNGN/G>H]QM_N5=\05A1N708U&%POBJZUO(!
+M)R9A>SC%Y<,?:B_BJE3U^:&SPEY]3_YPJ%NXFBTM+H^`CEW=WV"Y>U_+U^D=
+MGXR\-]/;3\QE,LJ'AIW7$]0Z8UCR((/*],ZXUUQU`TW?E'U4;NC6M>@T.'U7
+M.8UPGD]>WC]W1=1`U-)CF5"_2*8,[S(A>H=7\&"Z9_)<1&=ES]WX!OZ;=37A
+MXY$;*;=9GC?KC3$("<SO*Z]OA"I3=_.!!(V*AN_"=<#71W/!57VCEB0!F<I2
+M#CGNKG4NG7-G4BM3<!W"J,;)+9`GNG#1SIU[X/=+T$-TM`(W=&Z?T>G&P[I,
+MR<'[!4,79QQC9.(F7O,DIZE-["0]KFD\0F@3.<\(&!&0#)E,2.,RB:&YU`F=
+MDTCL80(#G>$PTG`$0B@2E`T[01RH'I`!DDB>ZF)HFU`'IJ-,&,Z@?V4&S!P1
+MN40B<';.RH<@#'VSDHGZ0"21)0D@SEL>R9VEP`D;=T"D$$`1/NA><!H?,)P0
+M<')X3`$/,('80T;QE-<5"][JCX)=V$)8\N5&X@C`R/=`+LQ&D0B;#3L(GE"X
+M#&1LF`':<**E!/&W*#5!+M2?<`2?9,6P"`<3L@0$\X3%N)B.(2]H'W2@MP8S
+MVR@0`#`G@AA`&"A'[>Z378.<[9*!H)VPG$#!PD!P?NEIF3.RH36@8)!E2!H<
+M,`F/=`W><%2-<6[8/SNH!:!D@$CY3:!S(/.$5327$M$#M,H#$D"3]4!M!([_
+M`*I])#])!!Y!3"-')?QV2+3.<(.H\&4PTS`&)(717=-C6^IO')6+X+HES0X+
+M<ZDUV@/.RX9]M1;\%TP+T$N$+UCH(_E-D$87E?@5A-ZQN"<KUKH;1Y;1,D+A
+MEVTZ'IHV&J%N6#1@%8O3P0=]^ZVK!IQ$@=ESK<:UH,3(PIVRX`<*O0/V^5;H
+M3IE1H4%C252N*PIU0_2UPWTNY5RZ=%,R8^%B7U:*AS"59-@OKEM.B7%WJ/9<
+MK4I5NJ=3\O<3PKW5*[GU0P$$=UM>$[*G2?YM025RSYX>O#'TFVQTKHM'I_2Y
+M;AP"IN:*M<ZFR"KW6+TN8VDQ^.<H^F6C0SS7%3+FZACQ-T_3K713D`2KF'&.
+MW"JU;JG3)9J$!5+OJ]K;-U/JM[0L[TWVTG@-9`!D=D5&EJ."!]5P'B7\0K"R
+M:[36:2/J?V7'W_XK=2)BQM2_$!SC_HF.6VYX<[-Q]!4*376Q:^X$Q.RSOXJQ
+M94?2-1I=L25\U=1_$[Q@7.:ZN:378AK1_DI?"?CV\KWX9=522[NMY7C<A/TN
+M7VO<.N6-I=UWFF`0=EC5/#=*H(\L$'V1]"ZD:U)KM0,A=/T<4[BHVF2/4N.U
+MLN,>:^+/!U&I;.#*0'NT+R+Q)X9N;*LX%AB5]8=?Z53IT6N:X%K^.RX/Q+X<
+MHW#S_+&<_"L]L:WXO+^7S1>652FZ/+/RH1:U0X^C=>T]7\$->\%M,#/`_P!%
+M?Z1^'=@*7G/I^8Y@D-=^7]EUQ\UO&G3.X2;>"5:%2GAS7=LC*K.U,<=M)^Z]
+MF_$;P>RM;:>FVK35:9FFWCM@+R:^M*E&X=2J4]+V&"'`@A>B>25QF,RFX73?
+M56;F`<+T+HMK:LZ<7ZAJB?=>>VC8,!H!'NMNRZC4#6,U&!N)6<VKX[9.7M_X
+M:W3C08V9PO1;![B!J.5Y)^$]VUP#>8WE>KV-352;F)"X8=/-Y>*O/?+("IW-
+M0EN\2CK/AF<*G7<)SF5IYZI7U0P1.Z@MP7P3GV1WCB7'&$]AETD$_5*U&QTE
+MD$%RZ*S_`"?V67TJB-`*UZ#2T`]ME%3,;Z!Q&Z"L1IV1>8,Y^?=15GG3GA6*
+M@<<^RBJ[%&^I(Q$*M4<2[E6):=[X=&?E5JQ;$20B?4P2JUQ5):)@?"TQ45P\
+M]RJ55X;JER*XJ9+MRJ-P]V2#*H"ZK9*R>H59!`/W5B]K2>0LB^J03E:D9V@N
+MZI#?4LN[<8)!W5BYJDR#.-EEW=:)DA7>F;RH=5N]!+3D+F>K7;7`AQU#D+6Z
+MS6`$G\IW]ER75GZB'`B)5QA(RNNUAJ(#L@[^ZYVYESBX!IG`!5[JM8/J.:2"
+M>\RJ=C1=5JZ&YU87IFI''+FNB_#OHW\5U-A<P1$_&Z]0ZC3=;64%T0(E8WX=
+M=*=:6HK/;ZG#D*]XPOV,MRRI`G?*Z^#'=V\?GRW=.!\3]4TN>73O`=/^BXR\
+MN]=4OW,_HM#Q;<L-PX,?,GDK$IG^9G29Q*[9WG37CQU&K0)=2:X:A/9%![N3
+M6Y`HM!,?5'+>_P"JQMWY>K6W3!<.-2X>XU%H-Z;28T`-..96YTCI8-/43G=7
+M*]FUHPI:\#EZMI4;)8^?91:'-9_,9`_=;M>@T..<+/ZRP4[7,$\*;-;85:T_
+MC+OTLAE/<R@%FTUWAK9#$[>HNMP6MI2J5;K+V/-3RW#V"LARAZST6WNF%KV"
+M7=PN(\0^%:UO4+[<2)VR?[+MZOB&B7#S*+E`_KMH]_\`,C3&Y"GK^'3'.QY7
+M6MW4ZNFH-)&(.$#6Z3(=MR%W/B2PZ=?,-2W+14W[+BKVVK6]8L<S8[PD_P!=
+M\<YET9]6K4@/J/>!MJ,P@,EQ2+\>EH$)M3HB/]56B>-O5MV3AIQ!W33[!(.,
+M[!$V<9R8$>R4NT@H2XDF44D#;]478>9G?L40&-\?"',_ZHVF#,1[;H'8#ITB
+M!_=)C!)`$^X3`N<Z`W/ZHV4JKGDMI.).ZB;@1,8@<(?ZL*=UO6:#+'`*!\ZH
+M<"JNX8<QD'A`\2["?4T"8(E,]P(](,G=#87"!!'J2:V3$'/*=CF\@DI?U;%`
+M0;'R$+@.\%&YS-,^W`0AS2W$_0*:-E3:/_Q#W3/(G@)PX#(D'YE`7!QW**9W
+MN?LG.D-RGJ5`8@#TB)0:P3)X0/$@)0?@)]0G&R3BW$?H@-P;$S,I-.DB=O9`
+M"W3$[;82U`YF(VP@>#J_*G<"3B#'NF+F@"<%$"W2<CY5#!H'])4E,'S(.!*C
+M8YNTX4U)S-;9<`/=!V_@YH;:`[GN"M+J$:2"J7A,--C(``*L7[FMSJGA>:_V
+M:G3:\!,+KO5F?=>J=$PUIC/LO./P\I:GDD;<KTSHX!(@B%QR[:=!8-#F#=;M
+MBV6[!8UBW8@X"VK+`S]I7.MQH4&D-C!5JD1$QRJ=(G?5CW*G+@UO/U*C1NH.
+MQIG98/53II.).5K/<ZH[=9'7ITZ=62=I6:Z^/'EA])HON;_(P#RNUITZ=M:#
+MXW4'A'HU/RA6>-U9\0!M"F6@X"Y:O=>GVENHSF`UKD.X'"N]6ZK3LK')&`L2
+MCU!E%I).W*X?\1O$%9[#3H.=]%-Z;F'M4WBGQZRA4>RD[4Z8@'_1<9U#K_6.
+MK.TAYIM=V_[+.Z;;NNKXU*HF3RNCI6S:8V(Q]UC+6+V888RZ8M#HQJ.#J[I)
+MXW72]'Z=;4:`UM#BW]$UG3!>T`\\!=1:VE(V>&B8F96,;<JWY<M33C>I=,H7
+M37%M(-DKF3T?^%ZH"#&<+OKRF*3W`-()Y7$>+;Q]"\:[8`Y*WC;T87EZ]^']
+MH;JFRD'0T-G==)U.>DO8\5</&/9>0^"/';+*BUKC!;B94OBWQ_5NS.LN<-HV
+MA)-37USR\65R_P`>K'Q#Y\,K5AC:2KEM9U.HTP:,OC:,KYLNO%767534HEQY
+MQ*[O\(OQ;%A<ML^L![6N(]2W,;]<O+X[C/XO3[FS-$^5<42UW8A36%"DRF0X
+M@`]U<ZUXCZ-UGIC*MK7HOJDRTL()`6795]>IIX4RGK7+&^^/*+J-K0\Y^FG@
+M\B%Y'^+'A9U6[-]0HQK)+H$?V7LKP"X&"8[Y5#Q#T^E>6I:Y@)`X"U,JL_AE
+MN/ERM;NH5M&W&$SG%A!'!7:?B%X?-A>NK"GAQ.RXVY&C):"?NNN%]GJLFMQZ
+M3^#MVYU4#)(`PO;.E5?Y`)'"\)_!>/XDQOC9>Y=.`%N.#"QCW7A_4]IKFL9P
+MJWFD.)F92KDZL$2H8=&ZUIY052Y[HTX'<J[TJB9'(/"JL`+S(6OTEA=&F%+R
+MU&QTVD6P3VX5]N)B3"BM!I8./JCJ')`D(L)SA\A5ZSW.$94PES<E07$`Y5@B
+M+AM*KU7[G.5)4(C4#E5JAF3*K-15'G3NJU9\C.?E2U'B#PJ-T=M)*L9J"O4.
+MHJG<U1!X5BN]K1G'O*SKVL)QL5J1*K7E3<+)NGSL=U9N*AEPX'*S;JIZG9$+
+M<9M4KUQ;,;+%OZT.,DRM.]?.`?N5A]5J,:TESC[*5F,?J]P\M@@QRN8Z_6;2
+MID`DK<O[F9,;#>5R'B"OYM<Z<&5UPG*Y<,BLYKZAF)/*Z#\/>E5+SJC2&RS<
+MF5D6W3:CR):<G=>N_A;T5MOTQM0@A[ARNEN^(XYSUFZZBUMJ=I8-)(V7EGXM
+M]3+'FFS(G/9>D^)KC^$L'DO&.Z\$\?=1-UU%X#@9*]N$]<=O!)[YN?KU75GE
+MSC]$]#)AT.4#!!)@94]`MU`M])YDKGMZHV+=H\AL=NZ/2/=!;$B@T3PCU.[_
+M`**.D?45M0T4Q&,*/J-.*1(.0K]-N(4?4*8-J9WA9KP1S=.F*E4DF0J/6;8U
+M(80(6U:V\`F/=4[JWKOJDM<T#X5HY>O8-&/+6)U:TA[FL!E==?VEQ@4W>HGZ
+M*B[H]:"?,:2<J[9U8XFO8F"33#C[=U4JV&J"VWJ./,-PNZ_X34UD/=3(`[*I
+M?V[Z3'4F:)(_I!E7AKVTX6XM'3Z:+A\"52O;'63K9QRNO=;>HSF3F6_Z*K6L
+M2]^DAHWB$TU,G$U^E,V#=NRIW'3:@P&Q'.<KN:G209`U$A5Z_399#HD8W33I
+M[N#JV]6G+G-/91D8,@CV7:U>E-."R6QA4KCH#:KCY8R3QNHU[1S5O;5*];12
+M:7$]EOV'ABYJTVNJ,/\`DNU\%>'+6SH.?5HZZIYA=*;5M8M92HD1R,*V:<KY
+M?P\YI>#GZ-;F&&Y(!2H^'Z#20:1WB)S^Z],N[#RK1VH9/SE5K'IE%C-3F^LP
+MI$][]<YT#H/3"]K'6GU)*ZRT\,]/`&FB,J3IEL&WD1$8E=)1I!N,1&W9,F)=
+MUSQ\,=/J`M?;-CZK+ZQX!Z7<L,4-+CF05W(9$@#*;RR?21G98;FX\0\2?AY7
+MM230!>W<>RXWJ'2+RT>0^BX#.87U`^R;4'J:"L3Q#X5M+NB6FD,[8Y3==,<_
+ME?-;VEIS@;Y0$D#!F>R](\9^`JM#55MFN,3L"8"\^O+2M;5C3JM<TCN%9DZ2
+MR]`+2&X>'2.%$Z0<'Z(BT#(G[)B,SJ)^55"X&9+OT3C3J.N8CC>4[L`2XSW"
+M"#/O/952@SB)2(.=@"EDDP=N2E!F"5-!S&H$'YGNDXX@"`G,$C(R4H,Y('8(
+M&D:<8*6(.<!.0-(,C/"8`C,-A`[M0EID>R9L`?F$)5!)&!\)RXN`DM$"!"&B
+M)VV4U#);)D_"KM!QLIK=L.;)[0@[[PN\LL8+1D0K5;0^L`=_94O#CW"PV`("
+MOV-(FZ$B<KSWMJ1V_@:CHI-.,Q*[_HU.3Q(7(^&;;R[9I;'==IT%I)!WG=>?
+MMJ.AZ:,`&/JM>U$#(!]UG63)B``5J6W&%ATBW2$-DQ*BKO!@$A$ZH6L,B<[*
+MM2FK7C@<)5BU1IC27'D;KG>M5F_\0#?Z6E=)>'R+0_\`V]UP?6;E[KXEN=.,
+M+.7$>KPX[=M9=5HT;)H:1JB-UG]2O/XPD`R5SEK6K5G`$D#Y6WTNAL9RIOV;
+MN/HJOZ:Y[#C#ED7WAAE4Z],'.05VM-@TQ"<T6:9@9"OI*Y_N91Y77\-MLZVN
+MG3@9G"HWS-+2US8/Q*]2O[:F]A!;L%Q_B7I#7-+Z8((,X7#R^.]Q[/TWGYUD
+MP.E-+GAX$`+>I533`@[X61:L%$QI,K1H5-5.<_!7#"/5Y;LUX!4<97(>-.A"
+M[I%U+<<+IKRLYK@!LJ3[C,O$3W6\;-[8FYT\O'2;NA<Z#J`"Z?H?1`ZBVK6>
+M2#C*W+NE1J'4*<&9E2L#!18QQB<+>7DV[;W!6?1+048+`0<KFO%7AGUNN;6F
+M6B3M*ZZA=0P4VF>)5_R`^U@P9R0ICDX7^-V\M\.^(NJ=!O12KU:A9.Q<5['X
+M-\56W4J+7-JC5&1J7F'C?HYKZZC&Z2V<PN;\,=7O.D=5;3\UX;J$S*ZY8S*;
+MC7KM]26EPVJV0<*VRDUX@E<;X$ZFV_Z?3J!Q)+05W/1]+HU''NN.-<?)CIP'
+MXI](\RS?Z>"05X7UND:5RX"9E?47C^WI5>GN`'J((V7SCXUM'TNKN9W<?W77
+M&ZR;\-]L=.F_!JDYM0/)[?5>SVE0>0-UY?\`A+:Z;5K]($QO]%Z53=%./9;Q
+M_+Q_J;R.J0XD\\H@TEL]E'1)>\S@?NK=)GNJ\RO0HN=5Q^RZ#I%#0T$B%1LZ
+M4OGW6Q;"&QNI)^6MKU(C3[IB<2@&1GA)VV^$#/>0<%5[I^KE%7V@*K5<"2)C
+MY5*&JXD$*O6=C*.K4$C.54N'DM*UME'<$`95*LXP7$XX4E=Y.Y52Y=#<A6<L
+MJUW5ANDF?A9ES4()SRK-P\@N_NLZ[J``\>Q6XEJO<OP<CW67=5-),00IKVZ:
+M`<B?8K-K5-1SLM6L(+BI+7%P``7/];K0,EO^BTNK5@QCX,_5<CUZ]TM=+IB4
+MDW5GY9O7;T-+F-W)[I>%^@UNI5#6<UVG>2L_IUN_J74FM@F#W7KWAFP%ITVF
+MQS0T@!/)EZ_QC>$O]JY9WAUE*HQH8#GOLN\Z#:BUZ<R&_E;M*A%JVI7'>96G
+M=N;0L]($$!=/T\W=O-^KSNM.%_%2_=2Z8\N]P#_L+P?J-P:UT]SA.>2O3/QA
+MZPQSC0.'YDYRO*ZT$EPF">^Z^CGQ)'D\,[IPX:9C8Y$J6@X:MB1QE04CISG(
+M4U$G8$D;KD[[:M&L/*;B,(_.:E0/\EOIX1R?\*;:?6]-LF..4/5`T6)(^(E3
+M`>O"BZL6_P`.&]S"S7AG;,MJ9-N9W*KW-,LIDD+5I4P:0T]E3ZE2.(Y*7LC$
+M;1+W&JX8]U'79`@">ZUC0B/A5G4QR$5C5K=[S$P-EEW5JW4=()DY*Z.ZHN/I
+M`W45O92XN<`594L<Z_IX8UQT@GGG[JC6M@7D"F#$X"ZV_I,93(@#N0N?OG_S
+M6L;!^%J7;-FF<+`5(B!.Z*MTBFYVD-(,296UTZWU09$QRIZK#J+6MGN4I.>7
+M*5^B:1E7?#GA^:HJ5J<P<"/A;K+=]2J)87-:03A;EG6ITP&BE$>VZFVN?K,;
+M:.&&T84U&W=2;JV'*U_.81D0J]=XK.AC<`9*RK'=1?6JESR8&P'"(6S7'5L5
+MI.I:<D83,I2X''O[JIJ,RU8/XX`+HJ5!Q:#Q^JR*["VZ#N/W6_THM=3`SVE-
+M<$%2M&.;*;^#@X&W"TK5D-@1'RH[JO3I#W66E;R&-9(C*I73J3<$H.I]2@$-
+MQ"R;DUZX#FEQ)/"*+J%.A5U-(:00N"\;^#;2^I/JT`&O,YC"[BA87`>7/<2#
+MPK(L6%N1/>5+(2V/F?Q!T:ZZ9<&G68=(Q,++R!!_5?1GBCPU9=2M7T:E(:\E
+MKH&\?"\+\7=&K]'ZF^A581DQ@Y"2_*[XY>S'(+78^$,N$"20#LB,9,-2](=.
+MX6V]@R9!.-TAZ20<@H\-<<80^GDJ!GD;MVE//!(^4G``CU2$B)/:!RJ'+70"
+M!B,IMP9@GZH@,.]0CLF@`X(RH!9)SR$0+L@@;93M'NT)R`08*`#.W^JFMI;4
+M`/?91[GTCVGLI[8$UFD[R,RE'9=!J.;;MP(WRM[H=$U+QI(_J!/W6'X?IOKL
+M`I@O($P,PNN\+VX_BV:A#@X#3O[KS^2M8NXZ-3BDQL8@0NOZ#3.(;(7,]-I_
+MECV76]`I;'(G9>:UN1OV3(;"T*8B3A4[1L08.(W5L;;_`"L5LUQ5TCYV1=+R
+M^8&ZI7+R*D;B5<MG>30<\[*SMJ(O%%X*=$M#L[;KCG-%2H29"T>M7/\`$WD2
+M2`=Y]U'2IB=ESSNZ^AX<?6%TZC&\86W9PT<2JEA2(C]EI4Z8+06C;*8L^6I:
+M#CJ$[>ZGJN`$3*@I@QOD(WCT223B5TCR54NH<9V!]UD=8`%)P.=X6U5I'1)[
+MK'ZO0<_N1"-8WEQ%_5-.[+'0!,J0NBC(.47BBR=3)<!)X/98_P#$5&MTF8B#
+MV7BN/K;'U,,IGC+&E0BHZ=4S[*MU:V>'C0?S=UG'J8MZT-S`R"-E;/5VU'-<
+M3'?W6'>8V78+BDZB1JW/"AJ5#(SC?"FO7F\IRPR&_<*G>-JMI8!!GA734_U-
+M9OU5&Y]MUJMOBR*>'?!7/VCJK()'/V5FGY[7E^C$R=U>9TSG)>TO6'^8QYT@
+M3*X3Q'8-;4\YH#3JE=TYS7L=KXGZ+G/$S6N<"TG=;PRU3&?':_@W=/%G!,@1
+M$[[!>O>'#YC))7BGX4/\MI&"TD<?"]K\+$&BV(V"S)RX_J`^+@W^$/!SM\+P
+M3\0:;*GB!C6B<P?NO=_&50?PS@#!*\2\16GF>)VG4YTNQ\RNF5TQ^G=K^']D
+M*=BR!B`?V74D-C3MA9OA6CY5@P_].WT"U`S4[;`77#B/%Y;NI+2E+I/)W6C2
+MIPV4%E0),1]5<;3.T;>ZUVXGLVB<#"T[<"`)RJEM2@29@JVQS6CMP5`6N9$[
+M)B_?$H7GU$YRA=F/]PB[1UR""(S.ZK57"3D*6L8=`XE5;ATR`?[*I45PX<%4
+MZKY)"EN'&<'95:E4##HSR545[MXW!]QE4+BK(._,*:\=(D'?LLZZ<1SLM1$-
+M=X$DG'*R[^J"QP`^JL75:6D?NL:[J$DMG'RM=,54N/4^2,;X56\K"G3($`_*
+MENJ@8'.]BN=ZY?8(F.P":3M3\1=1\LG0<D0N.ZI=NN*WE@;G,*WUZ[<7RXDG
+M/TRJ/1;*YNKD5-#BP$$PNLGK-M2>U]7>_A;T!FD7503,;_1>@/MJ;6P'8'9<
+MIX,O!0H-IC@!='6N@ZEK!P5Y9=VV]O1EQ1VA:VY@GZRL7\4.N4NF])J.%0-<
+M00T'X*H=8Z_3L:[YJB0"=_\`1>1_B=XG?UB[+!4FFTD8/N5]'])CJ7*OF?J?
+MYY:C!Z_U*O?WKZI>XAQ,_$K-,$`R<=D(<2<G?W39,9]EVMW>222<)A!S$1[J
+M2W$.S('N>5`S#Y))5BEE^J<!0:K':6ANO[)_,_ZRAI.)IM/MW12>WZJZ:T^N
+MZ5P`_2[!E1=5J--:FTG!5VO;LJNEV"/U697LWU+\14):-@?E3?/+PZ:%%@T2
+M"J-PT.N''LM"V86TG`G8*"G2PXD*6<D4:S(DJE7#9U#9:UQ2!]BJ=2FT/,B<
+M_1%4J5N7NSM*DKTFTV$#`.%:)8P>F>Y"J7+BYQ(!QL@Q^L-F2(CW7-R/XL@R
+M#,1WRNCZXXMI'(F#PL7HEK1JW)J57$F<`;!:Q9R:?2K1SAD>DCLM!UJ&TPP9
+M)Y[*?IC=1\M@)^FRV[7IH%*7"7.4JSABVS&V].`V4KDT=.K+2MJM94J#2YX5
+M2C:-NJWJ9Z`5!B4Z=6M7`)=H!6E3%%K`QC8`WE;#K.C2ID,:-L*O4LV:,B"4
+M5EUF-VD(6-&DJ6[MW-?#23W4894:<Y[HJM=TX.<+:Z.T?PXU3[>ZS;H$L,XA
+M:70P'4!WV*J+6IT0'!5ZM`.)+R8]U?%(D845R`QDQ/NIH8]]9TMW.`5FRL6>
+M6W2,#"&HWS@2[8<*WTTZJ!#>$4%:U]`[JK5MXD8^%K.!B")Y45:GJ&`)4(Y_
+MJ-$-,F)',+S/\;^B-K]/;>TZ8ULG41]5ZQU>EI:0N5\6VS;OH-PQPDACL`9V
+M6;TWC=5\W/;I,$B9Y0-(@AQCV5[K5NZAU"HPA^'$01PJ+V^H&<>RW.GH,?S`
+MR/2A&6Y."G<T9]1CW3L9+9!V_54*`1!@F<%,]I!'WRD`(*(N+_S'`;`Y4"9@
+MDEH'9,9#R,&.Z<M;@ZYE,UH(_,&D($))&!"FKTZ;'!M.HVH"`Z0"(/8_"B8R
+M=C(]EUWX<>!^H>)KYHI,+*,P7P/\U+=08?0^BW74KHMMZ1<UHDQE37UDZUO!
+M0<S0\8(/<%?4O@[P!T_HG074Z5(`Z,GDF%X+^+5B+;QC5#,`N)CZE8F5MTF-
+MW4?A.C4#)!@#<@PN\\(VN14)!G<KDO"]G_(8=4!T`E>A^&[4-MVSN0O/Y+RZ
+M3IO](8?,!P5VWA^A+`2,_LN4Z'3]07==$I!M`$`SN5QR;Q6Z3-.3A*X?Z8'*
+M*J0%2NGR)#MEG;8*9UW')&Y5CK;W4.GD`XC*J].!=6U$\\H/&%8LM(#MQ&ZO
+M4M;\>.\I&'9ESZI<3N>ZT;9A<X"%0Z6PZ0MRU8-$[+CV^E>(L6=(M'NKU)L&
+M%6HZME<H`XX]EK_CSY#HTB'$Q\IZS0)QGA6J8]$(+BCZ9WG*Z:T\][47@EL$
+M2J56AK!!VC"T7T^9F<(6TAIR03^R:1R/B6Q-5FV!V7'7UJ:50M<"TSNO5;NT
+M\T&`/DK#ZIT%CS,;K'DP]NG;P^;UXKS*^Z8*M,U&$XS'99CJ#V/($QL2.%Z+
+M6Z&6N=3@D$=U7'A-KGA^D_,KA<*^AA^KQDY<WT%M1M,`G<YGD+:LNE"N2X@^
+MK)"W+7H+*8$@86MT^R;3&G$RMX^+\O-Y/U/M>&%9^'62)IQRKI\.T6TSZ=^%
+MNM9#X4PIE[<@CA;F$CC?)E?K@>J=`EITLW[!<5XDZ)=4*@<X%S9WS_DO<:MF
+MPMG3)YPL3KO2*3V.+VM."5F^/['?Q_J;B\^\!:V513F(_39>Q>'+AM"RSVE>
+M?=%Z0:5\1387$NG&5UEKYC6:#+2,0N,WOEZ/+E,X+Q1>.KL>&C2`"%P%C:ON
+MO$)>[=KI_5=]U.AIZ<XXDCGX7.>'*&GJ+ZK@)G?ZJY3=C&.4QETZCI](4K=L
+MB/3'Z*]:L+GB,RJE-X>0T;+9Z31)C"[O#GVOV-,-I@$94H:"XP8]U(U@#1,=
+MT3609X*TY'8W2/\`-$^8C]T1<W3G!4<@SGX3I2,<G]4#WMCB4SB)[*O6>`"`
+M,H%6JX.)5.L\03'ZHGN#N?DJK<56Z2B(KBH!/;]EGW575($;_HCNJS9)G?"S
+M;JX`;@\PKH#=5B)$X`6=<U23B3WE'7KR<E4>HW#:=(P86Y&;=*_4:T&>RQKR
+MY$$@?)"DO+K7))^ZQKZZ9)]7^15W^&-;#U.]`:>T&%RG6+AS@XD%L;$]E<ZK
+M=M=K=J.!&ZPZ[JES6\NGZN)E;G'-:F-MU%$TVW=T*8&[@)]EZ!T2UM;#I0UA
+MLEN9^%SUMX??;VPN2(.\^\(>I7]RZU-(/(XPLV^]FG2X>N*]:]491Z@[2,3M
+M/NK'7?%3;7ISW`^K,"?9<97J&C3+G.)>>5S'B;J=2XJ>7K,-VDK>'BWEM/)Y
+M)ZGZYU^ZO[FJ35(:02<K#>_5,YY.4QD&)^@,IG.$@-Q`7M^/%KD[8&0$[RQK
+M@&O+\"3$0>R8`:9#OLA;^8`$F-U`='<XV'=3T##QNH&EI@@$"5/0_/B=UH:M
+M(#RQ.Z*&]D%%H\IN"<;A'I'^%RJOLL@&J9V"IVK1_%/<),E77B0\S&-U7Z1^
+M=YW,K,>&I+B&MTX!*<TXI0,)5Z3S6EIE1W#KH`Z::G`K78@9[*C4C)G[E6+V
+MG>53);I`506+R^:E0_"32AK/I@$:A.RI7-TQH(:`>_*T*EE2&3)/N50O*3=6
+MFFT0J.;ZW4N*N=`T9WRKG0K)CZ8].8RFZPUK7BGB5J>%J!=5;3`QW5B5O^&N
+MF-ILUEGW6U492I,U&('"*U93MK8<+-ZC<&ZJ>32&.2I2(JC?XRX@1H!^ZOT;
+M>C09@!!8VH8,1`W4M;TC3,E155[0ZH2!@*"Z:`PGG97"W0P[R51NG3,\JHS+
+MIH%6#GW3TZ>)`VRB<-50XDJQ18XMC^GV**HWUN#3EI(/96/#?J:6<C"?J\V[
+M=%5A:Z)`/94_#URX7;FAIR@Z,T_*&=EF=0>Y[M+0=,[K2%.K5:2_TM[*C?-#
+M7P%"*XI#RH4?3JGDW9IDX)5ZFV:4++ZAJHW#7C8%%;=1P#9(5=M:GD$B5:Z6
+M^E=VW$J.[M:;7X_109?7'-\B8V'*YSJ`\RRJ0W)!X6]U\.;2ADD'NL^C95;J
+MB:;&N<7!17A'COH-?S'W%*@2"XR0-\KC*E+2Z#+?HOIR\\-U*5!S*M#6'`_F
+M;*\_\5_AN;FL:MHUM.<D`0!^BF].V.?&J\?C,DR$VD:1E=]<_AS?TR22"!M'
+M_99MWX(ZE287"DYP'8+7LW,I^7)P-6P/MW2<!I@?57>H6%Q9U-->DYL<PJ;@
+M-]L;%6-!QK.>>45-OI=$']$0;(&?T75_AEX2K^(^LL9I(H`C4Z-PK>$WI9_"
+MSP1=>).I-<ZG%NPC6[[+ZF\"^%;/H]A3H4:8:&`9"@\`^&;/HW3:="A2:T@1
+MA=93<R@WW[KE>>7*Y;1=5IM9TU^G,-*^4?Q?/F^-JH@8,9XR5]5>(;@CH]4,
+MSZ297R=XOHU*WC"NYY!)J']UCZZ>.-?PI2.BFTNYE>A].IM%```+C_!EK+VQ
+M]EW=M3`8T2%PR[=&QX;I@U@/<;+M;4BG2:-H"YGPQ;RX'3]ET55NEH`Q\KCD
+MZXP=>I+`%3O'@-V$]RIFN&C:52O8\P;JQ?J[T:FXF=_W5#QDX:6,`,DP95RT
+MK&A;:R`/JL._KNONH`?TM]UG.ZFGJ_3X;RVGZ91@-$'LMJTMB0(V5/IU+T@Q
+M_9;MDP!@$+GC-O7G=!HTM+L[^RN4:)<V3QW293&J0(5NA3C<+I(\V5/0I@LV
+MDCE%6;#/RSP%+1(:T>Z50R#Q[+;A8S7L()0O:3!.`K5>GG=,&R,$I$JN:+8]
+M2J7K&M.`K]P2,;\3*H/IFH\N!='*;28LZM;ZG!WU1"FW#2-NRO5J$"&E1>3[
+M&=U-JJ5*#29`,<J*JW20&E7GL)$94;+<N<)G=2UN0-"E+`1O\J>FPATDJQ0M
+MM+,C[IZM/&\=D`Z`:9(^JSNJ4QY1['=6*M=P<03$+,ZM<GR2)5M;QQJY^'=[
+MTJTZA5IWIHL>?R/JQ&ZTO$PZ?==8\[ISF.9I&HTXTS*\RZK6)J:P3*Z3PG?A
+MMMH>^2=LKA<I?XZ>C]OUOOM9\37&FEY;")VA9W1[5P`>`02K5S2==W9=F`M+
+MI]O``(Q*J9743=,M"\@E=%84`T"1@*M8T=#0(6C1EL#=:CRV[$`V8C"*DW.>
+M43&S!3NAN%I@-0"/\E$Z&A&\Z22>5#5J>F$5#7=P"55JEP:23*.O5@02%3KU
+MH;$B40-:J(*SKJX&H]D]W7P1*R[NJ2-4[85@&]KC.5F5ZQ<?S)KJOOG"S+V[
+M`;(,+<B6Z3W=T*;")"P[Z\=5<?5A/?575!!,K/NZV@:<''"?XYU7ZE=:&D#5
+M\A874KT-8XU)&.%-U2Z#"Z'`;Y.RX[Q3U@4P0'#7V:=UT\?CW2W4375R^XN?
+M*I9<[Z+KO!'AT!@N+AGNO-?"G6:8ZLPW$%I<,_5>U=%O[:I8M=2<TM+>"L^>
+M7>OCT>*R8[G:;J5"D+8LC`&%PGB&B*1/EM[GMA=C?7M,L<'>I<=XDKZJ;I/!
+MQV6,.W3UX<QU6YHBDZ1Z@.%Q/4JC:E<EP((*V_$5PX.(;L1W7/53KGB=U[\,
+M=3;Y_DYH(`R#E)P).<E("-@3"=WYL2/JNC`=OWRG8#$YVS"<@Q,8^$PD.Q.?
+MHB':#M^ZL4V[$;;J$''OQE3VSL[X5&E0>11:(=MW1^8[L[[I4"XT6D(O7V1M
+M]F5?3;O=(&%!TMKC3)!$%7.HLTV3S,$JG9.#;2?ZE,?KYU6J4OJ;?9&]I&(2
+MLF$-DG)4CPA%.YIEPR85-],`&8A:-9IF(5"\:0[_`%05+MS!1C)*R;V&TRYW
+M/"T[W3IP?HL7JSI],S/"JL:ZEU:??'*Z_P`(6IHVGG/$2N;Z=:FXOZ=-K,DY
+M7<NI"VL0V,Q`"O42\HJ[Z]S4\JF[`W*M65JRD!OJY)2Z;;AE+5&2K3*1W=LL
+MA.`:TZ2846D$R1`4U:#`&WLA<TC\PW156X=B-XQA4*Y&5=KY&WV6;>.+JF@8
+M[H(J=,N?JB)1UZNEHILF5*QH92AIU/.V5:L[+0-=;+BJ*%*R#O75R>R+I=%M
+M/J0`:%I5:0TQ&>,JG1IM'4&NGE23E=MFLS^6LJXI!SR3]%NFGKI<Q'=9EW1(
+MJ$PHJ&VI@TX#54ZGTZI4&IHD%;%C1DR-E>;2:1D)2.8Z!3JVKWMJ,(DX5FY-
+M4U-3*9/T70"U9,EH@)5J=-E.=()^$',5+$W+@:K?HMOHW2Z5O2UAH!4EM::Z
+MWFG;MPM+'EC2<++4C&ZM0IN&6C[+`O+"FYQ$#/9=)?R\XY[JA<4/23"NDKG*
+M_014&IH#F\J,="I`8:%U/32R2QYP<*6M:,95D9:5FPCSCQ1X$L.JVCV5+=@>
+M1AP&0O"O'_@^[\/7SFOIS2=^5R^NZENWY6!XT\,6G6NG5*->DT^GE9LLNXZX
+M9ZXKY2\*]%N.K]7HVE"G)>[/LOJO\*/!MMT/HU-OE@.W)]USGX0_AU2Z/U&I
+M7>`XEV">`O7Z5$4F!K1@!:WLRRW=*8(MQ)&!R@LA4O[L!OY0>%!UFMYERVWH
+MB9Q*ZCPMTSR:#7$03[J;33.\74*=KT&H2-F%?*GB(,K>*JU3TEH<>_=?4OXO
+MW'\/T&KG=I"^4[C57ZR]Q.SID!<LKR[83AVO@JB`T:0876TZ9!$A8O@^W#+9
+MDB)726S`ZJT3A<;RZ3MTWA>D/)!`W[+6O6CRLJMX?IBG;C"N=0,49PN.3K&;
+M3J0TF8`X51[Q4K_F^B5W5TT#IF?E5+)Q<\F?F5N1<>:T.HU&LM-!,X[JETRB
+M7.UD`2E>N=4](SQNKW3[?32&%QSO+Z/AQU&A94\<+5M\-`5.PI>D2`M&DP1`
+MW2+FFI9=LK;*9T=YY4-%F0K=('3S\+>+SY(:0=,%3-9(DIJC2,!/!;DG*TS0
+M5:9[B%$:4L_+[S*FUY@_HA.,]ME6;BK/IDB"-R@-`,W$>RLB29V^J187?V*)
+MZJYH!P@Q*B?9D'MW"O4Z+FOS]T=0"0T*4TSFV8`,G*=EKZQ`C*O!AG`D!.&!
+MIWV[IK:JM6CIID\K-NM9&,GNM.[>`#E9E<@$P2LY5O#%0JAVJ7=UE=8<T&!`
+M*U+]Q;3)E8%ZXU'[K%NGJ\>&V3?6H=+A.1PBZ#1N/XEK1,3]U?9;.JNT`3*Z
+M;P[TAK*0<6C'LN4F[PZ^3*8SE/TRR8*'J;G=7+2T'F;):2RH!,`*[8N$\+M'
+M@SR34*1!5AK2$31$<RHJCBTD#A5R3C#25#4>>\!1FJ2%#4JQ*J)*M0!BIW%8
+M@;&4-Q7);^8?"HU[@Q'=5-E=W!!PJ%W7+@3C"&ZN"2086=<5_>1/":0US7,D
+MG99EY=','Z(KRO`,&%D7EP)))6I"TKRN2#J.0LFZK9,';WV3W=R).8"S:U23
+M&J2[LJQLU]=%K<&"5D=3O@*;BYT$!2=1K:)#IQE<7XJZU3:XM:2#V73#Q^R6
+MR`\3]::QI&K(D`RN'ZC>5*]Q)D@\(NIW;KBI+I@]YRJ]2A79:MN'47-I/<6M
+M?!@GM*]>.,CE:BUEKI`),]UTGAOQ=?=.:&.+BP<$E<T3OV^$+26@B,JW&9=K
+M,[CT]*_\[4*U,EQ+7G=9/5O$5*HPZ'9(7&EPB1@)M4#G[K,\6,=+Y\K-)[ZZ
+M-6K(R"JSW9D2DT^O.1[I2"0<PMN)Z=32US=+7!_^+A!J[3["43HB>/W0[R)F
+M-@@8OQIS'.4^H[Y^Z1T_[RG&?4Z8[JH=IYS]2K-&"\`X]U`(@<_"FMY#R).>
+MQ5&M1@4FC*/[I6[6^0V>W)1Z&?[*;:?:'60!8NGLLSI;?->`#("T_$.EO37$
+M%4?#M-K:0,J8_7@R:#0`(V1.`DGA$\B1G=(@:<E5%:I$E9]U!<9,#Y6C7])]
+MEE]1=`(`^R#+Z@YK)),E85>KYCB2W`*TNIESG\85-E#55#`,NQA6*N>%:?EU
+MG7+VP-Y(72VVN]J:W`AC=IY63;TVTZE.V$`'\RZ&T=3I,T,B$J)6L+3MLI*_
+M_*$X1B"W!4%5Q<=,X4$=,'6FN'0$B[2('T*@KNTMU$[(*U[5#&',$JI38(\Q
+M\DHG'S:VMQP%<Z;:_P`36#B/0TX44_2[,Z?.=)[!7*Y@:0KKF!M.&P%4J":D
+M`;>Z:@A#26`*C<TRRNUY&)6I3'&TJM?M.@G.%>CMJVH#K4$%5;IDF85SI0!L
+MT-XR,B)4O;416C<>P5RBR20H[2F="L._EL01UG-I#\RCMJ3Z[];YTC:4=&W?
+M</+W`Z6K0ITM+0&B`LUJ(A3:&`8^%4NVFC)!QVX6FZF0.RS.J.@:>Z:&:ZH7
+MOS./9*JS6R-_A2NHZQ,9Y1T&#06$`'L56637I.8Z1A7^D5#<,\MYDH.HTP6$
+M90]#FC7$P!*"Q=6]2W,\*"Y<W0&-(DK0\27-.C8$C>,+"\+"K?76MTZ0>5G3
+M5='T2S;3M-3ADH[VIY5N]^TA:=*C%O`.0,!9'7V'0VF.5*U%/PS9&ZO?.>WG
+M"[VWI>70T1$+&\.6C;>FUVDR5T$Q0D;PIIJ/)_\`Q`W?E=&J`.$EI'[+YPZ:
+M[7U02`2797N?_B7NG-LW-:?Z<_<+PCP\[7U8-``),R.5PO-KT83AZEX<<T6[
+M<1[%=#TTS7;_`'6!TBGY=LW$1M/*W^@4]=9L'E<=K([GHS!_#C&3PFZV0R@<
+M*?HC?Y#1F54\6U`RD1@#E8=(YSJ5P&LTF9*KVM=S*9(E5+^MKJ;DJ(UW0&,6
+MLOXQV\./M6_TX><\8.ZZ"TM"&3DK'\,4R6M>=B>5VMM1::(@-&-UY^Z^A;ZS
+M2I:4X;C4>,J[2IG$H&L+2,`_"L,!C,$+>G*U-0:T>DA3M#1@'=5Z32!NI6!S
+M7;Y6XYT;FP9,$(G`8(&$6B6Y.R"H9(`B$9TK5B0XZ1(0@@L@;GA2O;/Y1LH@
+M-+P)E&]"#2!WGE'0G,D_(34LMC*EIB0!QPC-@3JX$DIPP1$0C+=3_;A&&0-I
+M[^R)I$ZE@&=E6N7.#?=7GDAA`5"Y>.5;-$FU"YJ:R1"J5`T?"NUV:C,*M78`
+MTR1LN==\69U&'`MGCA8E>A#IW6S>&'23\++NWZG:0L5WQXB]X<LYJ:G9![+J
+M*<4&<".5B^'&Z*8<<0M*XJ.J8V^5O&:CR>;R;IGD5'X,\JW9,=J!/"KVE/@[
+MA:5JS2((DJO/M-3:`U1U0`XRBJU"`H'O<79A/\0-2-.#"JUZL$PY'>5`UORL
+MNYK@@@`>ZTSLKVN6YF2%G7%<:3!37=8Y=B>\K-N:I</3B-U8EIKFMJ?^;'.%
+M0O+H#`A#?W6@&0%B7U\03.5J326I;^[&3(6+>79#LE#>7;B<9G8=U3J@EI&O
+M'<IRR&I6+X("I7UPUE,D^G$RBNKC^'9ZX=I_5<IXFZN3,$@0<+>&-M+0^)^K
+MM;2=H=$#$%>>]5NG7%1SG$F3DA2]:OJM9Y`<8[`K+)).1^J]F&,QCC>:=P`X
+M$_=*<:9D'B=D!'J.)^J>9SO\+89S=($[;E)PDDZ<I.)D"`?C9"23/MOE$%IE
+MIS'L$(B<A*`&P/U*9TEW,!11/'JQ,)LANVW=,79A,?S$`G'?E$(N;IR"2=BF
+M;,X!]LI.P!$CY14ZCJ=5M0.<2PR#G=`)$',CME/`.VI7>J]6O^H4:-*[J@TZ
+M,^6QM-K`V=\-`[*@V0Z)(![JB5KCI@R/E34!/)4+7#<3CLI:3FSN?\E4;EJ/
+M_3MWV[*2/]PH[8?R&R\C&R.!_P#(FF]?X^Q_$MQ3/3G2HN@U*?D@2$7BVSI'
+MISGM,$]I5+H%G4\D'S2.<A3"SEX,ITW6@.$@X"3V@^K95F4[FF/\34?\20V*
+MC?JM=],GN0TM6+U4AK9!'NM6[N*>@N!$+F^MWU+\C#K)]U-#/N#JJ%Q=Z1[I
+M^A_S*YJ$'2W8JJYEQ6<*;6D:CL970](LQ3<VC'I&^%J<%6;*S;5)KN!#CM[*
+M:W;5H5`720KUK3U,T-`@*T^U8RD)`^JR(FW#31P<E"X-U9._NJ]>@X.EAV0T
+MZ[FXJCW!14M0MY.RSKVKYCO+;L-U)=73?RM,RH&-QI&7%`]I0=7J"DP>GDKI
+M+2W;;VX8`H>B6;:%#(EQY6@X2PJ-2*M>?RCE4Z@(82095YXSN20J5T2!IDF5
+M4I4&R-7U073&FD3P5-;M'EQ&WNBK,UL)[)2#Z'#J43E6*](YDJKT0AM9S8V.
+MRT;AL.]7=2K$=)K0R20$U*FZXJ<Z0A8QU:J&M.!N5K6ENVBV.%%D*G1:QI:,
+M#E$UA&2I`W&221NFJD-9`)^4:5[AX:R3@']%D5CY]<O$84_5:Y)+&DGA16M,
+M@#NFD/2:-(VD80WS&M];3A3R&"2Y9G6[VFR@X`B?9-)M#5J!]73/PHV`"Z9'
+M)6+;7U6I<G1)"U.EMK7%X-4Z6JT6O$6A])M,NEQ6EX-L&T:37$1*QFT7W?5M
+M,^AJ[?HMJ:=%K3@#99:BPZFW0%B]58']1IL70560#G]%S5]6/_%&9B"LZ;=3
+M96X%L-+MDGU"UCFN_53=-=JMAO$+*Z[<BC2>[4`(W1?CPO\`\2ET"7-Q!;S]
+M%XYX5<:O56D.'I(R!$;KO_\`Q#]0=7N@`8D',_"X?P#;/-1U:K^7G=>?\UZL
+M9_&/3[*NT6S079CE=7X/+7U&D@#A<1T>7,:!!^5WW@JU.L%IDKS^UJR.\Z/1
+M!I@Z<+G_`!^2!Z=EUO3:99;\97)>/P"3)V"-1P=S4_G<GV5WIU`U:D`@E8]R
+MYQN`ULR2NK\,VYTZM..ZQY;\>O\`2X\[=%T2T+*;=+,<0MZU<ZG`,_!4/1*0
+M;2:'85^LU@!=(GN"N>,^O3E=W14BW?96:5-I;@XW5!CS/$!7K=W8;KHYU/2P
+M(($*2!EW/""D,"<^RD<<;+;G]1ZCO&$+SF2/U1@-Q*BN,-#P-E&H=VV9RHW4
+MM<D1*!E36_1ME6'4W-9C9NY3M>D`8YN)Q[JRP>D2HHU1J"FI.]7J&.R%$/4R
+M9",-T-R@!C\F`FJO])@E(Q8CK/R2/LJ-:2(W5MQ#B9.ZJ.#G/ALD]E+6\8B<
+M#"S^HOAI,&0M&Y):V'`A9/4)<TQMW7.UVQC+O'G2=1Y5.V::UR/?=3=0+0T^
+MJ3PFZ+2.L%W&5,9MKRY>N+;M@&4H!5BW.NIL<*M1:Z!$96E84QB5UKYN]K=I
+M1!9)*M1I;*%C6L:-_IRFJF>R@"L\:H)^ZK5ZH:./E-7J`'*S+NX&HYF58E-?
+MW!+3GA9-:O+LE27-=VHQ^JSKBHW(QE73-I[ROS)$>ZRKVX#?ZL=D=]<-ILR=
+MN5B=1NY80TA;D8M0]2NI=^;`637K%YTS`15R:CC+C(Y[(6T);!&(A3:(:C`U
+MNJ<_*J7-0MIR,&)5RY#:5-V#LL3J-UIEI]..=U9RU&5UR](IG5L)S*\]\2]1
+M?5K.#2<]N5O>+NJ-\WRZ;I:9!65UWH%RSI+.JM:31(RX<9A>OQX^O;CEERYH
+MO<Z>?J@?^:<=MT>`=L]Y0`QOW[KLAC$>_P`I?E;`S\%(EG<?=(``R0JAA^8P
+M3('?(3.U'GY`2)S![1A(D!T#;E1=FDG8%)TET@QV3@#(!3-@NB2@88,B4B0T
+MXR"E`)(2<`<3@(!(+0'$'.<IHSZ=65;K6[J5A0KBM3?3KET-:X$M((F1N-PJ
+MP@0-D0),';]4IF.W9.UH!U3OA.UK1I=P>90)CHVWVA6*1(=C]5'+<F0`>RGH
+M#U#V*HUK:J]M!HEN.Z/SJG_3]D%`@T@8E'([+6XV^TO%;0>F.'^PL_P[_P`L
+M"9'RM/Q.#_PJH0=@L7PNZ*4#)4P^OG9]1T8P)$0HZK&/&6S*.EJ\J>.%#=7`
+MITMP(5L3;-ZI19&D$_`*P+FA3IOTM9)]UJ7UT^L_2P$D<J&G:O=#W-CM*2&U
+M3IE'37#RPN=L`5T5A9.\N7_F/94K2W)JA[!^5;-J7$!L`'=7OI!VU,4\%N%)
+M=/UB&YCL4]2?Z5`"6`EVZRU$=0P!C"SK]S9CM^JNUZH#<C99[YJ522W`053;
+MAK"XSJ5WH-K48_S:[3DX4G3K9UU<`D>@+H:5"F*`;IA/^+"H%I9C=$\>G8@'
+ME0U*;Z1EFW924ZP>!J])45&]AQ`^0LN^<-8CNM>K_P`LDDK'O/\`F'4>58E3
+MVAU`"9]U:`#V2852S,F&F%?8V&^RM2*MF!2O(F)*T;SUO;39,K)NWZ;IND[E
+M=!TBTEK:KSD]U*U!]/M6TJ7N5<#0/=$RF!Z3DI3&_P!U&P/.D&52O:H8UQ)B
+M%9KOWW"Q>HW&I^C[HBO/F/UN[\J5CS(:P%T]DK:B:D3^5:-M2938`&B?=$4*
+MEO4=3+G'3[+"Z_;,;2.9]I74=0(93);V7*]5_FG1).5-+M1Z;;AL0T">0MNU
+MJ,M*!U"'.V4-E0#&C4,!,VF;R_:&Y:-RKIEJ^&K5AJ^:3))77V09Y>.%D='L
+M138(PMRC1(9B%ETQB&](;;N)/RN=KVIJ%U4#(X6QUA[F4RW,E4@1Y!EN_*G%
+M6M3IU;18YSA<IXTO/-!IL.85]U_IHN8/A<_U:LUE&I5J?F(P25G*Z;QCPO\`
+M%*SK7G664=XSN)51EHVPLFTJ;@71F>2NLZY2:^^?7<`3Q(6#?AKZ[7'U0?C"
+M\-RWV]V/4C0\-TJH8'O81)WG=>K?A_1+@WT[KSCHM>E6+*;-Y7K?@&U#*32>
+M5F,]UV5*D!:ZH'RN%\?,+M1G`"]#T`6A/ML%PGCD-%&J=.S5:L>;63!5ZAQN
+MO0.@6[6T6X_1<+T"GYG5"\#E>D>';9U>JRBW!)A<,KNOH>''UQ=/TNQ8ZT#M
+M)(.9"H79T5BSCW76T>D5+?I[@VL-31D+D>I:?XHNR(V&RU9K289>UIJ>'#.Z
+MNVT-<`-E0H/$B2/;*M4G<R5N0K2I.D2`$!/K/J_51T7P(G/RAJ.`F)E6LQ+5
+M)$004#WX#3E/3@Q.W;NHKCTF1PC4#Y9;5D#E7&D/9I=B51:YSR!J4P=!&K,*
+M3A;-K)IC>(`3ADM47F%P``F$[7'(),)M-"B&2,PHS(:9*EIND$_NF>T8Y515
+M>9!`RBM7"F"YQ@]RGJ`#)/RH:QEQ`/PLUJ3:/K3FN#0QP)&Y"Q;L@$CLK]T0
+MT03!63U!T@Y"YY5WPFF3>>JY`X6KTNCZ!&25G6U#76!/!70V%(``Q$<PM^-Y
+M?U66[I+0H00T\K1MJ$$.^\(+6E)D@?96B"T95KS0-5VD#*JUJ@9(DJ6Y>!B1
+M]5EW]S#3G*NDM1WEP?\`%,+)OJA+B2BN[B!(.^ZSZU0N<07+4C--<5)#A@E4
+M;ZLV)$3RCNZP:T@"7'8K#ZE=#45J1BU#U.X.HRX9X6-6+S4@9A25JCZM6&B?
+MJK]I9`L#G?7*MUU$_P"L^VMI;J<,3N5*X"FWW&<K6\H4V0&[JC=4F@DF/HLR
+M*R;TC0=9@%<)XXZ@VG_*H@ESA"ZSQ%7+R*%$R]QB%9\*_AY5OO\`U=ZPF3,%
+M=/'J7;.5U-O(KSHM]7L#=&D3&1/9=YX*Z>[J7X77%G<4]3F@P7#?,KM>O=$M
+M[6Q=0\H"!M"'P-2HNZ54M:;(;D1]UZM7BUXO+G[33YDN:;J-5S"`(,*)V@.Y
+M!Y!V7:_B=X5NNG==N'T*+G43D$3C9<;4IO:8<WXE=7HQNYM'4:-P#CE#J/Y9
+MSV"<-.H"/NC#"''L.0II0`2V8^J9P&DNX]U(&^J=TY83Z=)D*B$`8TDD)0"I
+M',>UWY7`=BA<QTP6GZJ`&-U/@<IH!.)RCTD&-N<I-V$C=$`&@XDXSA)P](WQ
+M^B+)RV4@S68:/B%0#`??*>!(W&?=+DP9^J42WOW[H"C_`#^%/:'^9,[F5"(`
+M!$J:V.0=O=4C8M@/)'K(1X_^0_9-;TM5%IU0C\G_`*_U3AO3[7Z\&CIM68(A
+M<SX;K14=I&)73]6:76%03@A<;T9XI7;PX_U<*^/^UCYV7]74U*SA3C5E4KAK
+MZQU$P.5-1.MD[\JK?URZ:;#'NM,@-2E2>0P!Q4U%M2Z<&Z2!W&$NF6)?#GB!
+M[\K6IT]`A@&.%!#2HBF!3:/JK-.F*7J&8_1/38&C41G]DY(+3$**4SF/HJMS
+M5;.<$J6H^*>2J%1VIW*`*I+]R@ITW5J@IM^I4E41#0,G[K4Z/9AK-1W/=%6>
+MFVM.G2$1CW5MVT):0/3`3&<A12:!IRH:]`.RS!'NK5-LB=DY;`W164^JZF=-
+M14;\ZQJ:/HM>[HZVD`Q*Q.IM?1)$F%8E3]-/IW@JS7N#^5@U%9O3ZA<W0V2Y
+MW*V;.V#*8)R3OE6I&8^WK&NRJXXG9=3TNH/X5H)F%CW[99IB(5[H3@:0:3)4
+M:TU14$`[!"XR8!^Z1:(B("`@"0HJIU*IHHN,@%9%O3\VKJF0KG4B7U]`_52V
+MM)K&B-E`5NS```"M,8`PD;I[>F`)_1*N-+)!.>$Z5E=9J0V))_LL$TV/K!V5
+ML=6]1(G/L%G/I"F`9(`[HS0W]44Z&EL:CA:OA&RTTP]PDG*PZ+?XSJ+6@$M"
+M[GH=J&46B%2-/I]!H&0KS&CX06M.&A2O&)V*Q76,OJK`^N&G8*&[L@ZW.@YA
+M3U&.==XV5C20R#F%F1IQK[8T[QS7C=8/C)F/*;@0NP\0T]%3S&[KCO$+@0YS
+MG9A</+=1T\<Y>=^(_P"43F,96!6BJP%@D$?,K7\7GS*[H,">.5CVY<TY>(G=
+M>2UZI&WX-M3_`!3!$97N/@FWBBTD+R/P%0\V[86[2O;_``G0TVS!"2G^M.Z)
+M;;P5Y]^)#PSI]1Q/'"]!ZE#6%OLO+OQ?N!2L7:3GLF5:QFZYWP8QKW^:<">5
+MW?0JKZ->G4IG+3*X7P++[1KSSW7>=+#6,'JSW"XU].36.G=W'7VOL"`P,JN&
+M2=ER-U4UO<00?=*K</?2T`[?=5M7K)*W<O:\N6.$PG"S1V&J/NK=(G3_`&5"
+MF_\`57:#Q`<5N)8)U1S7<E$RL3APPA@.).84%5S61E0D7//(V"9[M;2!D\JJ
+M*V)G*-E?'I^J;:TG80P@$?93_G;JW]EG^=#P8RK5.I(DX/LA9I;HZ#(..RGI
+MTQ,QC]E6I2'`J:H[F8^%8Q1UH!WD'D*$N`&-@E4>(E15'2PD'=*2`JU),`J$
+M@G,IC(='[HZ/JQ$+%NW233,Z@3J*R;U[B[3_`(EN=1$'+868]@=<#G*PZ[U#
+M]*MOZHF%LVM+`&TJ.RI`4Q$+1MZ0%.0NLFGSL[NBI-T#'^B&K4&DGMV*)[]+
+M2.ZS+^XT-,.581]1K[P0L>\KZOZI*>^N'/<<_JJGYR25IBJUP79$JO5>13)F
+M25;KPUI61U&L&M):8A61+57J=Q!.5AW0=7?#3NI[ISJCHSGE6.GVLN!(@\%:
+MZ9[5^GV#F&3![K4HT3I@&(4KJ6P@8]E(P!@+B`?=9:TK730UF7K!ZY<BG2=&
+M8V6MU2XW.RJ]#Z2_J]WZAZ&E;D8RJO\`ASX9J=4ZBV]N&R`=BO7J-C2MK44V
+ML'I'94/"EC3LJ8I,:!&\!=,ZFU]&7+T>/#5W7G\F7L\L\>6_\U\",+E/!\V_
+M4JE'(ER[_P`?4--8D`0N&L6AG7`T")*]&4X>?MI=1Z+9W=9[;FG3>U^\B5S7
+M7?PNZ#U"7TJ(I./_`,<#^R[2\?Y-9A?B0I[2K2B<'NL^NXN.5G3R"]_!>W-3
+M^7>5&CW`/]E4J?@T&"6WI/R/]%[57N*+CAID=E`74W3_`$G=3T_UT_=R>.4/
+MPFHTGDU*I<!MC_16[3P!TNA5`=3U'W`_R7J51@-,N!:%G.MP^J7D3'*>C/O:
+MXRKX"Z-5;B@-7PLGK'X;6A8]U`"8P"O2J5,3D94SJ;'B"$F$/>O!^I>!ZUO.
+MJ@<C<+(/AMNK14+FQW7T-=VE%TRT97/]9Z!:W#B13$QN,+4A^[8\<=X9H/;Z
+M*Y!(C(5>Y\(W6DFE4#CM$_ZKONJ=/K650TZE,5*8YY"ITJ+`X.HO)_Z7*S%?
+MW*\^NO#G4Z+=3[<Z8P05GU+>O2=%2FX+UP.8ZF&5&9_Q`S"AN.G=,KRVX:\]
+ML$)JK^]^7DNSMM]@3LIZ.H.DC<Y(7?WOAGI+WS3>1[95&IX9I-.IKYC`3IJ>
+M25D4`31:>_\`U(])[?\`\RZ*U\.,_AVS,J3_`,N4^[D=/:/K6^:W^&>R=PO/
+MVN\KK3Z9.-2]"K@&F1R5YWXCFCXA='*N/&;Q=XUT/GGRPUF\836%)KKW^8=O
+MW5?I`<:#71+W;+6%J*3!4_J.ZW>W.=-"FP.,```*5S6LDG)4=D[^3)^Z:H35
+MJXV"BFJ/)_+LA,P,Q\*8LXC`[*"X=VQA18K7;H!]4A5V_EU&9.%([UO/8<RI
+M+*@:U4..P,!!)TRT<YPJO:MVC3#&2`HK.D&,4SW9QV4:#!U8A.T$GX29ZMU-
+M3I@-D@HIVCTB,%,=6RE:T`2>$M(,]D%2J`<1*R^MTP:;OW6R]H</A9O6:;?*
+M,E!B="TMN2'#(VE=-0:2`5S5I2TUBZ>5O=-K.<P!VVRTS$E]3/E8^471Z;O*
+MEIAREN&ZJ9(E+HIBH6QSLLM+]&I,,<(3W6EE,N'".K;@C6TY6?U2J:=$L,@E
+M3;2G2'FW!<KU*EZA"AZ=3]&J1E7J(&%4'39I:)PHKH0P[_56(],C"KWDZ8D>
+MZBL*^AMQDA4>K/#:&D8QPM#JE,MEVK*QJSG7-<,,C,>R1FM+P99E[Q4(.3RN
+M[L*(`!"Q/"]IY=HR`%T=K3TP05:UBM4F@#9#6`\MW"E8"1)05VQ3<3V6'1ET
+M\W!/NIW/8X`(K!@=4),;H[JUQJ'V4I&-U^D/X9SNP7FOB*N6U*K2=EZ=U8'R
+M'@Q,=UY'^(#C3O*@:=UP\_3KXN].`\1UVU.H&F9WX4E&G0ITVET"?U4%_3F^
+MU5G`1QW5RQMO,+.>9]E\[*[>WIW'X=T:3M#M`B5Z[T$-;;M;,%>8?A];-8YA
+MU8X"]3Z7`MQC/==<.&:;J[P6.',+Q+\=[TT:08#NX8GW7L_6'^EQ'9?.O_B+
+MOGLZA3IXC4#/U6<N>';P3>4=#X`J"ITNFX?HNYZ;4EK9`"\W_"R\+^E4A@X7
+MJ'A:C3N'?S-@)*YV;NH^A>)RF?&F<`JM4_Y@.!*T^M6].DT/ID@.X*QZKX=$
+MS/NK.*QW-Q:HN&_"M:Q&%1MX(F2IVB#Q"Z,)A5(."(]T-=X<))^@43@1L9"C
+MU.U;*+(.K^3='T]PDSO[JO6<"S!0VCB'<J-?&A4IESM6!"LT9#=]\JDRN2\>
+MD1W5NDX1M/96,W:Y;D@"5+4>-$!5Z3O2/V3U'?TCE;VP(.D=Y451X:=X33#9
+MSV4-2"9!6;6I#O?DG<*[TNDPTY<[?.5#T:R=>W0IC\NY*UNM6++"T#V&8QNL
+MR6\KE9.'.^)G-94U,)(B%F68-2IJE%UBX=6J%L[&,*ST6V)`EIRLSFL^7+6.
+MFA8LP,X^5=:0`FITFLI@`9]U%<U6M8<Y'NNCQH;ZJ&ATN'W7/]3N"3&J?JK'
+M6+S=K2L@/=4?F8/NM=,6D22.$+R&MU$P0IQI:S.ZI7CX:8._=6,U5O*YSG"Q
+M+]Y>XMU%7+VKDC.ZJ"GYE9;ZC-Y1V%MK()$_*V*%NUC,#Z)K*B`P#<A6Z3`#
+M*Q:U(B=1&@.(D=E1OB`TAIA:-V\,81C98]^_5R""M8I:S;\O?5T-`+G;0N__
+M``VZ.ZATWS'M]3LY"YKPG84Z_5&OJD`#(!"]8Z;191Z>WRF8C?9=L)NN.=X4
+MNG6>FIJ(6@]C0PCE*SI/J58+H"O/MVLI0!PO1BXUYSX_I>H_"\_<P4^N4W'&
+M5Z?^(-$&F2%YIUX>1<-K[@$+K>8X_6CXBIN?4I%DY@2M+I/36?PP<Z7%16KJ
+M=YTVC6'8&5LV,"FUHCX6,>4G6E<6-$"?+'U5>[L*4$%FGW"U_*+Z\`>GVRI;
+MJU_D$QF%K45PU\U]"OY>[95JVHM?0@@94G6+=SKO26R)S*&F*M!H:YDM[A7I
+M$-6S-,ZL?11MI@&(RM*F!5IX<,]U6O;&HT%[:GZJ+M2KL83#CMPHGT*;S@82
+MJ6M<F=4J2A;5"(.Z49O5.D4KFBYKMB(B5R'6/#->A5UT&E_QB%Z!4IO9)()A
+M05-3@<%!YY0Z=7HO]=J]I';*GI=.96U$5'!Q]LKMWTVU6!I;OP4%3I;20ZF"
+M"-T''4>@-!Q&_(W4KNENI^GRI`Y74_P=1M3(EH.R3J0#3CZ(,&VLZ`H-&DCV
+ME2?PE#L?NMIMO3+9+,^Q3_PU+_`?NIMMZY4,@8_5<%XWIZ.N-<=L2%UM1]RS
+MTN$B,$+B_'-U5;U2DXC3ZAGMNKQ[1RF]5=Z#6<VOK`):/==+:UVW%(D[A9'A
+MVG2?:MJ-.HD29Y*5:J^SK'3^4E=;^'*?XUV5''T9A6K6-/,CE9?3K@5,\E:#
+MG!E.0<D*58*XJ:9R1]54>2[`F/=)S_,?OONE6BF(F2[A1H+6^8\4:>>\+6Z?
+M1;28`&Y'95>EVX#0?ZB=UI-`:R.0%")`X-&Z0.=]T#3("-F7`1]4:'3W5EC2
+M6B3A!3IC$J<M``@;!"&.&X1#(_5,``?E)K0TG/T45'5',8^5G]5:?X=QV@+3
+M+#DJCUK_`-J8`GG*NASMF8N-,;E:[:?H#FXA9);HN`<"2M[ISM5$-$;*]QCZ
+M.A4!I:7;@)^E@"]([IZE+3+H06A'\:W.5(TWAAO$+)ZZ6N(&)Y6M2&I@B<X"
+MRNML/\8&$1\J6-%9,;H:1F%<I,&F8P2J%'S*,?X96I;%KJ0)*J`=(;$!5;LD
+M[#;]%?J@!N(GY6;<NA[M]LJ*Q^MU=-,MC)PJ72:#JMZW`W_NCZF_S;O<D-.0
+MM3PK::Z^N-MH2,NGZ/;AENW2!C"U*+3&`%7L6AM.#@J[1TG!QA+'2)J5,%LB
+M3"BO0/(*L4YV#OJJ]^#_``Y,94L:4>GP">,K18&N9&9*HVK"6<25(*CJ9VE9
+MI%3KMN!2<X@G$2O&_P`2@*5R\D1[KU[Q%=`6KAC(Y7C/XJ/;HJ.<[<1/W7#S
+M3^+KX[_)PURQS[L:1K;/YE?LXIC><QA9%A=4F/\`+<[.\A:=*H'Z"W;W7RKQ
+MR]_^/1?P\'F:)GY/T7I_3PUEO]%Y;^'E;2QDD!>@65Q-,2Z2[W7?&QBINID.
+MIOD<+YN_\2E$"_%3,2/[KZ1N`'4C.<+P#_Q,VSO(+QGU#'W4R[CO^GO\F?\`
+M@RZ;)D/QVE>O=#N3;N#VNC$+P[\&[HL:RGL/^R];Z;<>D!T_=<[Q:^E9MTEY
+M>/N1J<Z8&W"S*KOYI,_=2L<30U`@M_55+AP\S;Z>Z3EC6EVA6!<!LKDMT>DK
+M.L=P>/U5LDP%T8L2!PF2>-E'6>-/LH7EQ,;?"=C9(U<HNATX>Z,Y4[;<F%%3
+M(9L=E9HW.(.<Y"FB_P"!8QU,R1@*Q3J`C<YX3-(J20-Q$)FT7,=JX*K-6!5=
+MIR83BKB3N%6#CJ+<A.YT!HV5J:35:TMRX?9`'ET"8E0U7[@_=14GN+Q'=9M:
+MQCJ?#]-ULWSR#)X]E4\8=6?4I_P[1I`R24-'J1I6H!,:6[%8%W6=>7I$[NRE
+MRUCJ,^O.Z:QH/N*\D3)W72=/H-I40,*/HMDVE1#W#*LUR=6(`3"</)Y<_:AK
+MU&@D8D=EB=8N=#3Z@K?5KEM*F270?E<MU"Y=<5]+7$CLNCC;L%>L^I5,J2DR
+M,F?D)4:$L!/Z(JKQ386SLG:!NG13X"R+UY<2-6.,JQ?7!((D;+,N:DB)W]UN
+M33*O5ESR.ZM]/M3Z<2HJ#/4#"VNGL'DCNIE5D!3MRT`#,!,^6$P,JU5<`P25
+M1N3(,&.RS%JCU)Q+MU5I4#4N&R<?NKCJ+GD^G;:2M?POTCSZH?4'I77%C+B+
+M'A>TFYIM:S;V7H-"A%D,0LCI%E1H51`C*WZSPRV$8PO3C.7FO2.WI:&^D94D
+M.+<]E7HUB3Z1(1U+@Z8(A=)IFN9\=40^@[&87F7B6V!H.&C9>J>*:M-UL^1Z
+MEP'7J0?:O<NT<*@\)MU]&:R/RF%N6],,<'.$-`V)65X-`_AWL/!6EUJH*-H"
+M)&,KEA^!._J="@XZ0TGNHG]3>^G+0(Y"P>FU?/O-&DD2MQENP4M.B0=UOCX*
+M!J4KB[FJ`"=E:JVI%/+99"S[^DZC6EHD+0Z3U!C6-HUY@XRE@HU;-NN6%U,E
+M0W5K=END5);\KJOX>TKT=3"V2JQL:8.G4/B5!R[NG78;K:9CA!0JOI5--PR/
+M==DZSBG`V5&\Z8'`@,!:>%(U6,/(J-P0<*&I1I%D`R#PKM?H;2=36EHY$H']
+M%!8-+W-([*HQ;FTAY+3[A%:$D:2#(4W4K*[M07AY>WW""Q>RHV8AP&0B'>!/
+MJ9'NJMU39O/Z+5#6N"SNH,TUH:@&C;!U(.C='_"#L%=M:8-NP[84GE!1T=G5
+M,%<1^)=,Z6U`V8(S]UVCW>@YA<OX_+7=/(WS_FKE/KCC>6-X.ZDZF^G3<[#@
+MMN]J-KO)X&RXOHD^:]S2!IF(^BZ"TKO-#)RN^7+G)ILV#=#-3#*M-N'.(8_'
+MRJ?2ZAT@`_=7Z=%KQ),%9O"K#0P-U2-DUNPU;CS#EC3"H7KZC*C:+#(G.5I]
+M&<TM#70(WRLJT;=C6M4PR)R@:6P(B%(2!@#=1HFCCA6*#6G`G"A9)<,;<*Y1
+M#?+!P$!`0V91S+?E1ZO7@!2@<@3E%.QN<F91$1S)2:2!M/LGW=//9%"]L4R2
+M8`69=-%:K$>EONK5]4'_`"FS+C&$5&AY=#(]2@YSJE,-NM0&!LM'H9#@#V5?
+MKU,Z]1[[H_#]08`SE;G3-[;>F:4.,R(6=48:5Z"#`GE:E/+0X[1W5+JC)<'1
+MGNLUIKV;IHM(.1&0J/4ZI=?-;4;J]^58Z6X&V$G80LV_JM?UIK9D#=-<FVB:
+M;:E(`-CY0T`ZD_3/I4U/_E@!&^F'4I&Z6$!6RS$K'ZS5\NFX`Y(CX6P7#06N
+MQI7-=:>:MYY3<@&2IV54MZ1<=9,EQB5U7AV@&4FD\CA<]1+16:P8B-ETO2:G
+MH:`-A"TD:](P(E6[8:HD*A2U/<,;*W2K-IMAQ`^JRZ1>D!H"K7IFB04S;NB3
+MFHU*L]KF2""#[I8NXBLY#<I[HCRSC,*2BWT*M?/+:9:3A<ZTY7Q=U`6]-Y=L
+MW)7BWXA=9%U5?3#9:XXS\^R]-_$5TV]0AQ!C*\CZGT^G4OQ6>\D3@2O+^HRN
+MM._@QF]U@6M"I4NVN8--/N976].M&^2(&&@'4J[:5(D#$MV"U>D.;YK&U7^D
+M<!?/RG+V>VVWX<>ZCH.D@=UVW1;HN:TQD^ZY6VITQ3:X'!V70]&T!K8,X6I:
+M.B8XN89[+R'_`,2EH7]"K/#3B#^Z];MJA\L'8+S[\=[7^)\-W0&?23/T*WE>
+M-M^+C./#?PHN-/4&4W.V);O\+V?ICR:3#@[8"\%\"5C:^(RTNP'G'U7M_0[A
+MKK>F=0R%C.:R?5EX=+;52*<']%#6J%U6&[3E5_XE@I_F`.ZCH5BZH07;]BI.
+M$L;%K(:"T$_*NMAP'ZJA9$E@!*U^EVS'M+GDXX[KHY7A!;T"^I,'*.[I.8!(
+MCLMRUL0(((@Y^$U_T[6PD)IGWFW-D$OP=U(Q@!X^%+>T#2K:3N$`@OSN%&JM
+M6YTN!CV5BJ[T@1&%2`TM!:<'=3:G&C$B2M2L6!?I$GG<J%U<9'8(JKQY1&Q5
+M4M=J(;$0LVZ638JE4ET"2/W5GIU/S'YP/=5F4'/$Q(Y4[*O\,PR,0N>]UTLD
+MG`NM5!2HEHW*;PQ;^;4;5<#,[K,O+G^.O6TF8$]UU70J'D6K3$8W6I-W3S^;
+M/UQTTWD,MPP=EE]0O&4:3BXY'<JU>5M-)QD!<9XLZ@?4&N_WE=G@1=9ZJ:]<
+MTVG?;*&PH_U/YRLCHS:E>^#GB1,KI&4PRD!$'NL[VMD@:C@QF/NLV\JAP(GE
+M6>H513ENQ]BL>]K@N.DQ*Z2.=J"XK9/)F,%1T6>94!,Y.$J=)U1\1N5J].LF
+MAHG$%+=$@;:T`#7#*T&4PVF2!PC93]$!"_5&D+';>E.]?K):!!04+<N&?OW5
+MZC:ZJDD21V6AT_IY>1`5VK-MK`U*C6-;DE=9T;IE2E:^EF8W6SX4\.M?IK5&
+M9&<KIKBVH6UL1I`@+T>'"V[</)8XRRLZ_P#$@/!PM8VKS&MTA';Q4ZB8',*]
+M<4F@PT\+O)S7&J]M;TVL@#=-7MV%A@*S3IP/=-7;+8(72,5Q_BZRFW>6[[0O
+M/NJL>R@]CL`=UZKXAI3:N)W`.%Y_UNU\RC5])F"NL<,F;X*I,9YCBX%HG^R?
+MQ=<MJM%*CM[(O#E,4K>JUW_4J%"D:_5'-W:#,;KECQ;3ZO>%K$-8'O'J//*U
+MKNEI8=,XX4_3K<,H-],8E27,-89:/9:QBUS_`%!@<"1(C=/2M65[4.:WU`J>
+MJR7N!R"I+-GE&!!!6F8KT65K?U-V&X**\K^8T5&.+7#A:/EM<9PJG4+1NG6W
+M#AE(JWT^J]]JQQ,PBKO<<M=![*CT>L0UU([[JS6)TR#$%9BU%<W%=H@`%0'J
+M88"*M+`W,I7M3\Q)W&RSKC4UA+<RJRLUNH6=:)=A5+CIMK=M\VVJ`/.T'E4B
+M*51Q%003A0UZ52A%2A4<V#P5=;76DM6A>6N:E/S&C^I475!5O`'2)X6K:WUS
+M4I-:7!W!;"L46VCW@7%#23RLW_"?Z&A3:*31JX1Z&_XBM"CTN@:32RLX-(QE
+M%_PJG_\`,Y9Y::A_(3SRN0_$"H?*+)R=PNPJO#:)<<>RX#QK<"I7<W4-UTLV
+MY1SG2G>34>3C4Y;G37%^ESI(VE<S<5=->0W'<+8Z-<#0)=[+K.6'5=-)V[;+
+M4IU&M:23@#*Q^FU6BFT@F85NM4):Q@_J.5*JU:-;5KNJ1O@`K2IVNBF'L.2%
+M6L&C4UK0(`X6D'`0LJ@I5*K'^MN.ZO4:@J1ZOI*#0'M@Q]4!MW,'H)G=1II4
+M```=_E2^PPL^VN-(#*@,\K0H.8YHC,H"8R#G/U4]/#8RHFCU[X*E9@RBG,"%
+M%=5@QIB22E=56L$\H+&@:K_.J_0=E*"Z=;D@UJDEQ.)5IS<;*1H`;M[).8"F
+MFF%UVEZ"9Q\*ET,AMQ$E;'6J0=0=!("P^G'1=:"0KBSDZ>AMLHNH@&B9.45!
+M[12;D!4^IUS4864Q)VE+`NGW#W$4VF<IKZAY5XRJ<DPI/#E$M$OB?=6/$;"&
+M,J3@%9O<K4BU;N_DM/LI35TC,`%5+6LT6@?[<J"XK5:A)9L%JZC,!UZ[;0HN
+M>TY/=<Z;G42\G+E)UZX?4KB@TZC.T*3I_3*KM#WR&[G"FTLVO^&>GNN'-JU&
+MG/\`B73M;9V%`.J/:(WDPL2XZQ;=+Z<8(U,$+S#Q_P",>H7E1]"VJN8V<1*S
+ME9.VL>>(]0ZYXWZ1TYKP:["1PTA>?^(_Q5#ZSFV@)'!7G)H]0O:DU/,>3DDR
+M59L^@7-3/E.'T7.^61TF'Y=/2_$+JKJX=.Y[KUG\/NI5^J6%*K6.7"87@W_#
+M:MM5;YK2!*]I_"FH&](I0=FQ^RZX9^T8RFK'H#&M;2WX6;UHL;;N).P4C[T,
+M9I<%E=9KMKV[@'QC98M='EWXD=4)NGT6NVW*\PZ[U5[:[J=&-1W<<PNI_$^N
+M6=6JLD[[C*\\ZG5=3K/>XX(P%X/-=W3V_I\&GTF\>)>YQ+B<GV6YT>Y>ZY:X
+M;-./=<9TBX#JHU8@X`Q'Z+I["\8`-1DCL%X\YIZ;B[SI]R'4VDNR8$+KO#Y9
+MY()[+S7I-RX!CXQ[E=MX>NBZDT%PRDNT^.OI/:*<R8'NN3_%,BIT&NW?T\?!
+M6LZ[T-TD[\2L?Q1_ZGI]1LXA==[B3M\R4G&T\759)`\R9^J]>\+70?94R"XD
+M`+RKQ_1-CXKJ%HTC5(79_A[?-N+.F)F-\K.<W)7U,,IIZ`*NMH/ZJWTZ'502
+MTK';5TAH$GZK6Z&6EP)D9^5B=NG<=!8C8!I]EN=,MZ@RTYWT^RR[*`T;`KI.
+MF-IFV!`AY'YETG;CG=1+3<:='\Q#@%-3NF&A+CQ$*A4=5=4T-!(G\P17-`,M
+MYF5TE_#E<65U9X-T2,@E5<:HC*DN3+B>Z@=4@97.NLG"1E6&$DR=D+ZY#8#B
+M/E0"K+S$QM*DHTC4=),#NEIZ_DF/<YQS[RIJ$:NQ4]C;AE4ZA)C"BZO5ITVA
+MVSMOHL7=:FMZ7*-:FREZB``,E<[XAZA2UN93=SQRJW5>J"G3=+_JL7I51W4^
+MK-`)T3_=+>$O\>:ZCPA;&I7\Y^Q/^2[#S0R@&@[+-Z70IVMH-,`ANZK=0OO+
+MGU+IC-3E\WR9^]V;Q%U5M*FYH<N1NWONJ^K<'A-XHOO-N"UCIDI^BO$AKAQ,
+MJ^WQCU^M/I5NVFP.(C"L7ER&M(!V"`U6,I`A974;L:B&D%;QC&5V:_NYD$C]
+MUGL:^M5`&)/='2I/N*L@8[+5Z;9.:YIE,LM+,!=/L88#RM*E;Z&Z8YF5/;T(
+M:V.%8<R&9"Y[VUI3\LZ829;N>\`*ZRE+HCZJW;6_J`T_*"O:V<1V72^&>F>9
+M5#W``*#IMGYE1K0,=EV71K-M"DT@?HNOCP]JSGEJ+5NQMM:@8!`63UBO4J4W
+M!H^5LUJ>OTX`*I=1IT:5N[5!@+WS\1Y<O]871VN_B"\X([K0KO/F=_=1=+`-
+M1Y`D&5.`'5H4Q^I4E`%S=TJ]/TE6*;(`$H*NQ$+I&*P.O,U6[P-X7#7].75F
+MQ.Z]!ZW3BV<>X7$W0:+FJUP,96_CCDXJ_N'6=2HUN)G`*M>&:?F-\\@R3.57
+MZ_08Z_,@Y*Z+P?:4VVX:1*F?%3&?5AE9K:<.!P%!7J-,\8D3B5JW-*E!!8/A
+M9UU;4RTC;L0K"L]P!=B-U/1I8"@J4*U)X+3J:%8M:S7$-?A#0].E\#`'9'5`
+M=0@C=&\LQD05%</:QDS]$&'6?_"7VHGTDPK=6NVI1U-S.5G>)'!S-;-Y5#IU
+M>Y?3+6C9+J4DW&N\-R7X:JM:M2:XR=DS:5>H`'NW3U+*B!ZQJGA38I7-:B>!
+M"EL_+J"!D)7EC0\LPU4J5LZF^:57(&`@NNM?(JBHT0KU+16H9RY4@;A]`-D$
+MC*&UN*E"IZV$`*&FM1;IIALG"*/^IR"E>TC3!T%%_&4O\!472[U2MIH$`K@^
+MN4Q6O7@977]5JM;0).ZY&Y9I>^N2<[+KIRC&N+:F:+B2"6\!0=*<&W$.P.$]
+MPZN*C]/Y">55:]S;C!@<*XI796-;32&95_IKWU;@N)PU<[T^M-O'*VNAU)8&
+M\G<K5'3V0T4M<Y5RF[DR3[*A;N`IM;(GLK=`^H&,+-(O6YD`@25:9I)@JK:`
+M:I"L`D/S!E9:AZE"F\2!!4;15H.D$Z58IR?=3:06YB44UE7:YL..5)=W#:;/
+M2<E4;MC:3"YF'*'I_F/J!]PTZ9VE/^B_9T'UJ@JU!(!Q*U&M],`0$%L6%@TG
+M"E$`*::$T`X)PG<`&Y*>DV3V")PD^R"C?-+Z3MX7,OTT^H$KK+K\D@;^ZY7K
+M32V]R2)/9)Q4O32M"^Y(IM..5IMM:;*>1J57H3`R@T@[K5()8>ZMF^R*%DT,
+MN=(Q[*WUNCYG321N%6J?RKH%:<"O9EL[A9O,:G;`Z.36_EDX:CZ[<LM+<AN^
+MP4+'&RO*C!R56\NIU&_TD$M!W5[FV;WI'T#IK[NY_B:HD$SE:O6:].UMO+80
+M"!$A:#13M+04J;1('"S+BQ==-<YP.5G*Z61QW417OJSJ+0XR?NAZ?X(;6KBK
+M7:3.5U%'I[+>K(`!F,K4L<;F5R_;WVU/XL3IWA"RI``4FS\+5H>'K9@@4FX]
+MEL6WE@"",JVQK2TA:](UNO)_Q(L*5N_T-`@RM7\*+F:;:,F1PG_%>AZ-0V!6
+M3^&-P*75=&I3QW5,IN/4KJV+Z7R%R_B?S[2@_1+AMNNXM&"K;-/$;K'\16#:
+MM-P(A7+A9'SKX\>^O?U'N$9WW7#=;#R2X&1PO3_Q=L76E>H^F/LO+ZC_`#[@
+MAP).T+Y^<YV]_@Z#T(-%4FH1)VSLMVP<VG<M]8+1F8C*S+"S+JL.,=L;(NHV
+MM:B\/F9P#/\`9<,N:]'%^NKI]19J:UF3MA='T+J9IM`U9[+A^A4`RD'.<-16
+MYTFK-8,&0#NN?MJL:=Q2NGW#06DRK%7U6A8YVX63TZLT-`D"%:JUGD0W]%TE
+M'CGXW='>ZN^YI-<2#Q]5RG@WKU?IMTUCCI#=]2]\ZWT:AU2B6U`TDC*\T\8_
+MAM7:Y]>T'N!G_):QS]?XY=/5AE,HZ'P_U^VZA38#5:'GW78]&K-#&D.'W7S[
+M1I]6Z-7TO%0:>#*ZKPQXVK47,96+H&#J)3+#[B]>%^/?.C515J-#CCY704JX
+MIT8IN.<$+RGPSXE9<L:^E6S\KLNC]3J5ZC==01[%8EC6>-[=[TS3_#AY:-,2
+M5F=>O&"F]K#`*O65]19TUI;49$>J2N-Z_?\`G7E3089.%TO$>;#'VR-6NQD<
+MJM5N!)@JK4J9&=U&YY7.UZIC%VE5.J,Y5NC<BDW.5DBX:R"@NNHT:5$N>0/A
+M,=UG*1KWG5&`_F((&`L#K76&,8Y]2J,=RN7\5^*[>W+A1,N7+VESU'Q!?AFA
+MY8XYB``EXYJ=3<=)4ZG6ZI?BA2RR8*[WP;T@6U-M5P`<<B53\%>%Z5G;TZM1
+MGJC)*W[RY%NT-;B"LXS?->'S>7VXC4JUPRE!VC9<AXCZE%TYC3[0KG5^J:+,
+MP<A<7U&Y?7K/<Z<G"ZY7AY\9ROEOGUFOU3E:UFQE*FTD[+%Z54&C)DA7A7JU
+M7>2P$SRIA/M,N>(LW=X#+&9/R@LNG5[BL'%KH/RM+H?1'O<*E5H]Y746=G1I
+M,#6M'9:N=O$)AID=.Z2&4Q(`*T*%DUIVA7]#)`#83N89VA8_ZJLVFUH['LB%
+M(N$$85AE(`3^Z.DT:M/Z*[-(K>C#@%=H4X(`&2DUK6>H[K4\.VKKBY#W#!6L
+M>6<N&IX:L"&M>YJZ*BSTP1@)NG6X92#1B%.88W,+W^/'4>;*[0W!#:1("YKK
+M]9SY:"M?JURUK"-6.RYVX<:UQ`[KK>(Y]KG2:1;;22K-%DGOE*VIAEL`,'E6
+M;>F/S'/LIC.$O9]):T#90U`(.5<+?;"@N&@C"Z1BLKJ4&@X?NN*ZG2!OGEHE
+M=O?L_E.7(WC)OG'&ZU\<[VXCQ/1(N-78R5I^%:X#6Y/O*C\749:\Q$<JCX3K
+M?S=#G;8^5<^F,>W77;0YD@JE4;(B<JZ1KI"56<R'_E)455#3EA'NA_A*;S,`
+M?"LN8-4@*5C`!@A15!]CZ?SG[J"O9G,N)`[K5J>QW5.],TRFD8M]0I-IN@3A
+M<_3J5*%_#?R$PNDK,+CN85'J-F`TO&%=<:B3B\I:(:]D@&3RIB*9;#L$")4?
+M2ZK74])&6A2UF@L@%2<K>%*M3T&?U5.YH^OS&.WX`5[4`\M)P5!4!:#!,3A5
+M%>@\TZH!_+SG9:;6-JT0[3)'*H56,+)#H/*N=+J!A%-QP5FK%BE2:*8V1^4W
+MV5RG2E@(B"B\D^RFFW/WU4O86Q)=C=<]U5YIWE.@X8G(6[6:ZG2-3<KFNL^9
+M<WFMN"W==OKA.BZNUH9Z&8A<[=.;JB(SRN@OZI=3#"[(P5SW4SHJ$F(2<5.X
+MU.G5`VVWVPNB\+EKJ@!G&5Q?3JY#0"0/A=7X;J"=7<8RM_4EW'5:OYD`K0LW
+MS`[+$H5"XR"M6R<0T+"M:U,;#`5EIU&3F%2MWD`<CM*N4':A&T<RHU%FG^5*
+MK7%*GJ=":=+"=AW5-I==W.G.AOZJ">VINNJ_F/)T\`J^ZV8]H;`$;)6S0P`-
+M$0IVD3)32J['.MSI=,*];N#X(*A<T5-QGNA#7T7ZOZ?9/^JTF"?;W3._+NH[
+M:J'"`I*A&(&ZBHGM$9(7-^**0;7#\Y/*Z9QEAU;;+#\5TOY0.P[J43=!=JH-
+M![8RMJBWTSLL#PW4!I@?1=#2/IV6ZS&?U)I:9A6K*N&VH+B$_4:;749B%BU[
+MAS`:<X"D_#5_(>J5&UKP^6,DK4Z9:MM;85'1J=[*MT.V:]_G5`M-C?XBN*;/
+MRA9O";#;6[[FM);CX6@;1C*4"![*]96S*3!C,)ZS6DSCZ+$YY:CF^I6T5):W
+MY52E+70X8707ENTM)61=T@QVQE6+I);@1A6QKTX*HV[A$?NK5!QVU*K'*?B9
+M:5JG3G."\X\,=0?8^(*;7XET$KV3Q6UE3IKVG.%XCU.@]G67O$@,=(*Y?UR;
+M[CZ-\*UVW/3J9!F0I^I6VIAYE>7_`(=^,6V[&V]P^"T1)PO3;#J=M>V^L/!)
+M"ZW'?,9QRUQ7D?XRV+74G0T3*\9O^EOI52ZFV9.Q7TCX_P"DF]+BT%P.87GS
+MO";J]8@LQV7B\F.WHPR]7EUC8UZ3R\M!+OT1=0#C#=,M9[KNNL=!%E4TO!PN
+M>ZS;LI"8E>;R8<;=\<[:PQ7K4Z;:5$?)[+5Z+6=3:"1]5F&[IMJEI_-V0UKT
+M0-#H:,0O/=[>C3LNGWTU`7GT^Q706U1E:@W3$_JO/>FW!(:[4872=-OFTPV7
+M3([[*XUF\.@MB16R<!:-1M"M1T.:#/*Q:%85:6K5$[%.RZJ,<3J=`]]UTQ%?
+MK?@^QOR2:329DX'^2XCQE^';J-N^M9L+"-H_[+U#IM\:K@TO`'*OW;+>YMM#
+M@""IZ\[CKCY+B^;>D=4ONB]1\BJ'-T&#)*].\'^+;>X:T.<&NCD[JKXY\(4;
+MNXJ.8S29D0%Y]U/I?5.C5O,87.8W&"KN9?Y7MP\TO;W@=8\VCZ:L@C@J(W8>
+M8)V]UXOTCQG=6Q%.J\^G'JV"V:?CINQ(^04N-=98])JWD'!D;)S>4VLU%T%>
+M5U_'#G$N8=("H5O%5_>$TJ3WDGM_V4F%[I<OP]$\1>);2V#O5+EPW6?$M[U%
+MQHVVHR<`(^C>&.K]6JZZ[BUIS!7HO@OP1:V9:^I3U.[DG_-+G)QBXY>3&=N*
+M\%^#.H=6KMK7FKR]].R]:\+^&+/IM$!M,!S1(6Q:6]I9T6A@#<+.ZCU`TZA#
+M7XE9U]R>7/RW/B-2YN:5&EI]H@+G^IW,DOF8V6?U7J3R]I!CZJM<W9?:@B)*
+M991SF.@7M9U1VAVTRJ/4&-8V0<E,^Z!P5<Z3T^KU&H`1(7/W_+7JAZ%9W-P^
+M*;2X'LNY\.>'W@!]2G!]UL^#N@T+>@V6#;>%U=&UIM:`UHA;QW8S;)TPK6P>
+MUH;IP%:IV&Q=,?9:YI@&0(]D+FM;D\K<C#.-NU@P,^^R!]$<J[5J,!@E4KJY
+M8S^H)H`6-F"8/=":C*1))`]U3NKS,"?99E[6N;EXHTVNSRDT5O6%0WMZVG3<
+M2V<KO>@68H4FXX7+^!>EMMZ+:K_SD<KLZ%:E3;&J%[/#A]KS^3+XNF&[*AU.
+M\#&&#E0=0Z@T,(&_=9-5[[A\`E>K>G'LUS7+R7.,A1=.+7W.H$#Y3U[-Q$$+
+M0Z59-I4]3FB8WA3OAG_4]-[7O+,$MY5R@W$X*KLI@NW*M-U#&WNNC)WC&57J
+MC)GA3N?@M*KW)`&#NM1*S^H-!I.7(W-/_P!<XCOLNNO3_(/=<S4IZKUXC,K5
+MZ<[VYOQ51F@Z1NN.Z56-KU@:MB5Z#XEMYH.,+SCK`-&^U@[%:LW'/JO1+7^9
+M;->T[A.ZGZN,[*KX4N6W'2Z<'40%HPX[[+#:LYF2T-D<F4`8&RK<`"57JD!T
+MSNFA#5``$1*HW;FD$&%<JO`$Y5&YR_:545"WU$CCA1WC-=$A712EH)P@<P$E
+MKCC@HE8%H[RKPT\D']%<JZ9@*/K%`4ZPJM.`5(8JVPJ-@'V4ZIW%:YIP[6?W
+M3!K7,]RIW,#FC5B$%.F`X@&$%"K3-*KOZ2KMJP8)X37#`XG&45C^:)&%*L;%
+M!W\EN>$6L]T-`M\H8E%+?\*SIMS'7#_"T-'(W6/08]@-0Y:_*VO&8_D'&9X5
+M2WJ,=TP-J-`<!A=9=[<.G/7CFN>0W:>5E==93=1@CV6SU*DQO\P+%\05)MAB
+M3*L*HVE336;JTXSA=5X<J>EW^:XUSBQ[3'W70^'ZY#)X/*WWRQU7963]39!V
+M6ST]P(&#E<MTNL=/YACW71=-JCR@0%&FS1=ZA&5H42-.^5DT'ZS(!$J\*P;3
+M+CVV6=+M)=UG/<*#'23N95ZRHBE2`@@\K/Z8T%YKOW=LM:@X$"3A3_524XVD
+MR5,`(B%$V-X.RE9"*DIMB"5+`<R`HP/3,J1IG$J*A+32=K9LK%O4:YH)&2DU
+MH."9)45>D6NUL)]PBIR)S&/=9OB6GKM25?H5P6AKL%!U5GF6C@"I2,#PP\A^
+M@KJ;=PB97(]#>&WSV.X.RW[F[93H:0X2>RVST;KO4`QOEL,E8_3Z56]N(&W=
+M2/8ZXJ:1))Y6Q94*=C:`F-<96=G8+RX;:4VVU*#4/9;'1*7E6X?4&2-UC],Z
+M<ZOU$W54SR`2ND`:VAI[8PN.5VMA6W4:+ZYI!XD<2K#R'`&?C*Y#K6JPN?/I
+MOPXY6ST'J3+F@PEV2%TQU8U_5I56`M@Y6;?6[2"M34'>D$&5#>-:*>8(2QIS
+ME;^6X[@!1MO=!C)6C<6KJ[SL`HSTQ@&1*SLTSKBJ^Y)9_2N'\9=&+*AJTF3.
+M\+T6I:-9^4`:55N;!E5A%2,K&<]N6L;IXU7IU*;I`(A;OACQ3==.J-94>2S9
+M=/USPO2JM)I0#[+C.L]&KVE8RWTM*QCG<6[C,GI?0_%%EU!@8]PF,_[A;0MK
+M.K3-1L`N"\-M*M>UK:V/+7!=#TGQ?=4G>75>=/O_`-ET_CFQSCTWO'?1YHOK
+M"!"\?\4U88^F<0=Y7J/5?$'_`!"R\L.ES@N&ZIT2K<5'.#=1)PO+YO'KAW\7
+MDWV\SJ>8ZLY\&3M"EL];ZKJ;@`!GY787GA_RO4YD+&N.G,I7&L-+?A>:XZCU
+M3RRHQ7=;T#/S`1]-ZG4J7`;J,3D2J'4V.<[,CY0V3/*)>[Z<+EZZCK-6.^Z7
+M?@4@TG/`*UZ%1M2D)$D[A>?],OG/N@ULS\G9==0J.IV8<(EWNM2\.=XK>MPQ
+MFI[2/H5:97]49C]UA=*K.JB,GYY5FI=EC\DAW8JR+[).IU`XF0-^5E=7Z1;7
+ME(L<P>K*DZK=--,&23\IK*O4J.$\>ZQE_KI*X[K7X=LN-3Z(ATJI2_#6NZD&
+MAH:!S.Z];MS3%LQK@)(S*FHUJ+1,1"LEZVZ3S61X_5\"%CJ=)]-H:S^H#+OE
+M=3X=\&6=HYM3R?4-B0NHJ%E2Z!#>58K564P,PI9;Q:7RVK]E:6MK:M+:8!(Y
+M2-^QC/Y9,MX"R;SJ;74M,RJHNV`Q*MLG#G.>URYZH75C)52[NP\ZM655O&:S
+M+"<YCLLZM3>&%KB09Q*X^_QK2Q<D5-0+S*JUKC0R`X]E4N+MU(QW"CI5C7:'
+M05F_E6C86C[CU9C==YX&HLIM;+0>ZY;PM2\QH;J))7:]`L:E(X^JU).TMXT[
+M'IU2F&`#$J[YX#=\++Z?:52P1)6I9](NZ[L@@+K+\<D52]$F"H*MP\MELF5O
+MT?#C6MFJ8A&_IMO281HSPNL\.>7QB^3&.2J&XJ.@`Y3#IM>J9=NNG%I2!.EH
+M1,H-G\L+>/Z>WMB^;\,&UZ&TP7"?E:-CT2D'`BGMW"V+>BR>,*[;AH'$+T8_
+MI\8Y9>6U4M+(TV@-=MV4E>W>6@-*O4V:L@Q[*5C`)E=ICKIC;#=T^JXYRAJ4
+M*MN=0^RZ-K`L_J3FG`[K4P9M8E2\J.=!D`=@H.J>)[?I]-IJU`/;_82\075.
+MSMG/YX7E'B^[N^HO>:;"6@K>YC/;)PSMZQ>DV?X@]+J5=)KM!^O^2Z#I_7K>
+M]IAU&H#.<%?*U6G5J7]2F7O8YIG!73^#_$'4>D5!3J/+V#:5G]W&W34E?1_\
+M4',`)RHZE8/V,KB/"OBVVZBQM-]0!T97327MUT7RU=$V.]>=!@A8%)Y_CG$@
+M22KO4KE[00X$++L:[7WD#E6])]-X@;-$X^B\U\5TCYSH:!!X7J75!KHG'T7G
+MWBVVRXYP5J.=['^'%V^7473@?W78R8_U7G/@ZLZWZL!,:L'*]!+R:8(^\[K'
+M^-"J.`;D*E<D:L%25GD#=5JIY*NA'6+B,C?@*![1&0IBX.RW<;!!IDYWY01!
+MQ@B,)XU-@%3>3Z9E`69G909O5:6J@[.%1Z82&.89QMA:O48-$\@<+&L"16>$
+MO42?5DAAV'R(2:P`AP">F`XR1A3M8W3@04%&YSB)16M,@SIW4KZ<UM)D?"FI
+M4BR6G'92D6*#1Y39(E%#>X2;HC;]4O3V_59TZ.:94'4>JN8XC0P_=#U6A2I5
+M2`0.T*#I3?(NJM7,%1]7>XN:^<NS,KMJ<//]9G4*9`QD%8'5Z(#20=MPNGNO
+M5;SB=Y6!=L<*CIR2=DJN:NJC)]6Q.RV/#]0TZ,D@SD_=9'6&:+B>"5?Z0X:0
+MP5/:>ZU.F:ZBP?',+H>DW$4XQ\KDK*J`X;SW*W>G58@DXC9(O;KK!P(&=QDJ
+M6O4#M-!I!)W6=T^J74QZN%/T]X?=NJ$&!LI81OV+6M8!V5ZG^2(*SK)PF!`5
+MZF^!NHTM429W*EIB8[*O3<)B=U9;@"%%3,_+`W4C6X'WE14\22I9$1.%%/F,
+M#/RBI@_U94-2LVGDN`4)OJ0$`?571M8KVQ)U,*@KUBRF14G'*=G46:=L+/ZK
+M<NN&:*;9E/4VPK:JYO5ZK@#"T#7?7N!3&RDL.FM:34K'?DJ*Y=3;<AEL-3AV
+M4EX9URW["A3I4\Y="N4[2I49KJ?EWA#X=LGZ&UJ_(E;59H%.`V`LWGIN37:C
+MTUA!(C'"NENH22`%%0`8X@_='4JRW2"#*QC-W:3MG=5Z>+UODM@CNL=UG<])
+MKB"[RUV/3Z$`N</S)NI6=.YH%K@#'W4LU?:.LFYJJ/3;ME2B"")[*2X(JPT;
+M$K%(JV%=S7M.DGW6CTVX:^H"3LNDLRG#.M7E?I46,IP<J&XI`;`*SJ!`A/ID
+M=Y4TTRZ](&<9[JM5ID&2`MA]$OVG*J5J,&#/LH,]S/0!&#RJ?4NDT+AGJ:(*
+MU:U.)$%`YL".`LW&7M97%]8\*T:C"YC1/LN.ZQT6O:53#,-)7L7EEPP)"S.K
+M]+94!=IB=UROCUTW,M]O)J3:E%^DL,!;_1ZC'LBH`#O*Z#_@%.LXZFJG>>'Z
+MM&KJI20F[]2R?%&_Z?0N*9.D+E^K=!8ZF][6R`<GLNW?9W%)H#ME5O*#VV[Z
+M5,"'X=A8N,R[:F6GDO4^E>K2UNTJK=='<:<AI!7=WO2*QJ'33+IWPH+NV<RE
+MIJ4B#W(7&>&?EV_=KA[*W=1J@D!L8GNM6ZZEHH"'Y'=:0L*=1I&`%G]5Z:#3
+M);&3O"YY>*QN>297E?Z)>NI40YQ;!&X4M[=MK/:01)]]ED68<RB*;C):,[J&
+MO5J4ZX:""TXP5PO'#K&C4J^:[3J,3O*N6E44-.?JL>G5`:'2`!O/*EI5W5'[
+MRUJQMN.CI7CG$MG"G_B(GU3.ZR*%9E%AAPF%4N;\@N>"M^Q&ZRZ:VO@SW17E
+M?73UMW/'9<C_`![G7,AYS[K09?.--H:<_NI<MQ=:37-4AQ<#OPJ]*N[SR2X^
+MR@N*SG"2`0/=5*M<"H2TG*YMR.@L+L.J-:YVVZ;KU;R6BHT$25SM*^%*LQY=
+M$')!70731=V!<001LDE6\=L*XJ><]KFY]N5IV5`LM]>K$8PL[I]J\7D%OY<#
+M*ZNTL:E2ES'*LPW>&<LI(+PV'4ZP=.^R]6\%61NV!SMB/\UR/AGPW5K!L-7I
+M_@WIE>SI!KFQ`V^Z]7B\4MU7G\GDNN&_TKIE*E3:"`5JQ1H4Y;I4%K3?H#LA
+M!>M<&Y)7OP\>./3S7*U6N[MU1Y:W;N%4KU''!XW*FH#+DSK6K5K:H,+>^&5<
+M9,P$3`XG`6C0Z<XMET_57:'3F-W&>RD&71I.DR,*W3MW2M-EDT$G=2MMVZ<?
+M9:D%!E)P;G*)C#VA:'DC3(2%$1(51FW>IE/98=[<>6"3]ET'5"&4\[E<CXAJ
+M[M89DK7QBN7\47=2YK.IC8'[)>'NGVE>V=J:-495^WZ9K::CQ^9+H-D:5Y4`
+M/I/"X>3^4W6<IIR'5/`M.OU5]>B(U'98G7/#%U9G#-0!WA>ULLAIU-/Z*M=]
+M/;5:6/:#VE9GBUS&Y>-/":1K6=>6RQS5VW@OQFZ@YEO>.G&Y4GC7PN&36H,@
+M@SA<!U!IH52UP+7-,;K>&5Q,IM[O5JVO5;'72(DB5R5RVM9=3)$Q*X_PCXMK
+M=,K>35J33(@2=EWEA=VW5XJ4RUQB=_\`?9=]RS;EK5%_Q!M1FEXAWV7.^)F-
+MJ,>=,@G=;/7K(T3YC20?98EW7<:#V.`U'&1LND<LHY*UJ?P_4@X8@XRN_MZQ
+MJV;'-=DCNO.NI_R[N1O*ZGP_=N=9MU'("EG*R\-USCR?LH*CAM.$#'^8-SE(
+MR'&(4:`0!ZN5+3F-DWECVD^ZD9@94#U!#1F57KN]/8\*U5;Z0[,JA7=G?<]U
+M8BM=D&GI)_18ML?+O7C43/9;%T<.))E8E0D7Q[)>D:=(2V=X[*3;`5>W=``"
+MLTFF>9]U%-1:#5]0(A3/`!E$QL"2<]D-P0,_HHJ6F\!@&4_F#W4+3Z1ZH]D\
+M_P#6HUIS=9WH;3(`,969U!AT$QE2U;DFLZH1`.TJM<UBYLR"5VTX(:A(H_F^
+MBS;RF7DN=PKM=SM!XGA4ZHFE,PI5<MUUI;7#G3I!P0=DW2ZCVB7MD'^J5<ZQ
+M2Q,3RJ5H#5I.#I`&V=EJ=,ULT*Q#P2[!'!716%4:6DE<9:5=/I+7XY*Z+IUP
+M7TFB=MH*I+RZNA<%E(0Z0!PM?I`TVV3NN5H5XIM'O]UTE@_^0WX4L5NV3\9S
+M]5I6[NZP[6J`X0?JM2SJ:FR)[Y4I&A2_.3E66.TA5:3I8`%-3)U1*C46J9Q(
+M0WEVRA1DD!)BYS\0J%[5LV_P9(/LL7*8\UJ3?#8I5*%4:GU9!15KNPH`RYI7
+MF-!_B*D-&A_;A07=MX@N`Z7.SVA7W\?Y/V\^GH%]XAZ52!UO:`-LE8UYXZZ;
+M2D4(<?:?\ES'2/P\ZO?U/.O*[P#F%UG1/PVM*+PZOZOF?\UC]Z7^N+7[6O[5
+MF#Q/?]1J1286TW<E=KX*M*9#:CQJ<X<I7G0+2TZ=_)IAL)>&:S:;M$P1A:QM
+MR_LSE).G;68``;$1PIZT"F52LGZF`RI[JJ'-#1E,NEVJ5ZFFF23NI.E4C4=J
+M>=ME'6HZR)..RO\`3@&P,=LK&*XS2]28(P43FM.WZI,&(!!^JD:.\?=:;C(Z
+MYTYMQ2/^+VP%SEN]UG<EE21P%V]0!P(,$+"\1=+;4I&HQN1V7/\`K=Q;-\4U
+MI=,<T>K=7*+VD#..,KF;-[J=4TW...Y6Q;O)8,KKVQTTP6G8P.RCJL;"@IBI
+M.ZE.N((6:T@K4L3&ZJNIGX6B[20,;J)P;R/NH*@!&!A#6IMJ@#WW5I],3,?9
+M`8;N84[X7I49;,INGGNF%!M5T$81N>:C](`5R@QK&#W4OX(R.H=/%1D:<+'J
+M]/'FZ=/LNV\K53P%5J6+7/U1NL7%K;F[;HM$PXLRL[Q'X=HUJ9T4Q)W@+O:-
+MG@`C"CO;!I&V%+A%V\AN/"M1C"6@_*Y?K?3*M'4T3\+W>ZLV"@06CZKS/QI0
+M\JY=%,@$]EBX-3)Y6\/9=%A#AGE-6B"8,CLNDONF-J.+].%B7ED6/+73'[KQ
+MY^/5>G'/;+\XNJN9I<X*Y2,-D8CA3-M&ADEH'>!NJX8]I=`7++#3I,]E5N7;
+M!Q'<JA?W%1KBQI)*G;3>*IEI=K*LML*;FESV$D]UC4CK+(R+8/#C4)]+LD+6
+MIUM&F3Z>Z"[I4J,`"&G@*(.<ZEIF,)9M;EM9K7-)M$^H@E9#*IJ7!!=`'8*V
+M+2I48&.;^;F5:L.F-#=9:1ODJS$]Y(:QM&U"Q]29WA=7TI](46L.\05D6-J'
+M.#6%=7X:Z,0W6[)/9;PP<<\_R'IO2:=2Y#V,D'*[#H_098':-ALM7PKT9C0/
+M0<CE=OT7I3&LDMX7J\?B<,\]L;PG;-HU-#F1A=I9TF!H(`677L/+K:J8`6G8
+MM+:8D_*]6.,G#BM&H6F&C`5;J#I&1'PIG.;JPJ?4GM#9#I]ETTSM6K5*=&GY
+MCR%-T[K=C&EU1I(]PN+\:]5N:5`TZ3'?(E><OZIU6E<N+*M2"=I*7+#'BIK*
+M]/HAO6K,`$5&?=/_`,=LA_\`I&_=?/K.M]>B-50_=%5ZGXAB07Q]4_<\9Z9O
+M?CXBL6X\YN?^I1N\46`.:K?NOGQW4NM:CYCJP^Z.C==1?E]6H([DJ_NX)<,W
+MT+1\1V53`K-^ZF/6+73+7A?/G3KCJ8KRVN\`^ZWJ?5+UC`U]5T_*OOA4UE'I
+M?6^L4G-TM=)'995"B;EQ>X$MW7/^'?/O*\U2=*ZI[A0H!C#^JS<O>\=+)Z]J
+MM^\,864QD*'HC-%4ZOS%6J-L:KBYQGE2T:(:\F!]%G/IG2_;N](DRCTMDF<%
+M0T8$P1*DDQD[+>/,%>_MJ55A8YLA>??B#X,IW=-U6U;I?/'T7HU0@C;*JW+`
+MX$`*Y8S)9=/FCK?3[VQK&C589:2%K?A_XDK=*Z@UM;46$1GC_<KU#QKX8H]0
+MIN>U@U[X7E?6NBU>GW+@]A`G=<;[8UOC)[(:E#JW3&U*3VY$X*Y#Q#:/HM=$
+MS.ZS_P`.^O/L[@6E<S3(ALE=EXIM&W-AYU$#(7IPRV\^>.GDW52YU7,`@[K9
+M\*LJ7%N=#H@+.ZY1ASLPX'NM/\.G#SC3C!5RK.+7:+BD0"?96*3*[@,`+5K6
+M8.0`HV6[V]L;++2@:-P)!<$#FW#,3*U7"&B?U4%9NF85&;6J7.VP"S[JM7!]
+M;/LM>Y,.R`53N2UXG2$1E?QF/5QW6=4J-=?AS7<E;%U18_\`ISW6'<6T7GI/
+MT5O236VO;N&K>)Y5VAIF)^BPPRNS.DJS:7548<W'S*RK<C21G"@N'`.V*K.O
+MV@:7#]$GW%)P!F.R"VP`M">&J%CVZ!Z@G\QO<*-.$NZL^GA.`/+!=E1U634P
+M/=2L:U]/2.%V<5:H095:J=0CG967`ZCC95ZS03'ZJ:5E]8I`T-``E9UFP`.:
+M0/HM/JC9JM9V"IT*(:\P))25FHJC21#A\:5=L:CJ+@W8#@R%7SKCCOPK)IEU
+M-N-7N$&O85IK-DF"NJL:HAN9PN#L*FF[%,SA=/T^O#1"U.5=/:UB7!H^RV;*
+MI#<?J5RO3J\O,N)6]TVKKC&R6(W*-0``!6K=S7.D+-H.DB/W6@R&@`?HLUJ5
+M<:X<HJC6ED$`J"G!(_NBN*PIT_[J:VNT%X;=@@,"CL;1E:KK<T`)4*+KE^M^
+M!P%IV[(`;VV"QJ7IKI:M&-:T-T@*WH]/^JKVXSMLK;!_W6A4ZC0\RU(7+V+1
+M;]1<TCE=F]DM(7(=<I>1U`5`8DY69=9+>G4VEPQM)L<C96[`&JXE<_T>J;D1
+M&RZ;I5(LI9Y4SO.G.<I:K`&>Z&V)#O4%/5&H?Z*!PAPSE633JT:)$-RI9RJU
+MLZ6@&`58:"8[(T51H&905F![()!"F+0/<CN5&1@D`945S'B#IQIN-9@(^%5Z
+M==%I`<2/8KJKJF*C-+A([+E.OV+[>IYM,&%B7UO^+9MM6M5KP,R2.%;`$9Y7
+M,](O@3I(R-UMT*NH`@[KK6)5M]-I]E&ZU!R$;*DP.49]+20<]EC4K6V=>L-,
+M8*I-IU:C@'#'>5HO'FU2(Y[JRVB`T#3"S_D5FV])E-V<E66,&L$%2U*()QNA
+M-(@X36A("`I:36P2>55<2",8[J1E8!%70UH;`E#4:"S*A%6=M_E.'M(R<H*M
+MS;![B-*Y?Q?T*G7H.<&Y/^^RZVK4`<51ZE4%1FF-UC+':[>54>A52]S(Q*Y[
+MQ7X?NJ)+@TP3_OA>P4NFN;5-7RR0[L%'U3I-.[9#J6X[+A<-NLRT\";1J4_3
+M4$_*MV?3&5R-`A>B^(_"5%E$O:T`_94/#/0CYI#FX"Q<-\6+[:YC`M_"DT-?
+MUCLLWJ/3S0FF*9G9>K'IS@T4VTXGE"/#--_JJ,U'E+XI.B>2WMXG<]'N*KQ_
+M*<0I+3H=Q(:6GY*]J'A^V;Z13_15+SPZ)!8T1\+$\<VW^Y7F+NAOIM#N5/;]
+M-<ZG!/U7?O\`#-6J(`PK%OX4,1IRM?MQ/>N#Z'T>+F2["[CI%G2I:-3H5YGA
+M&L!+"1[PM&T\)W3J0ESI[PM8R8LY6UK]!JVK&CU`'NNCHWM!K``]H7`7W0NI
+M6DF@]Q473K;KSW'4XPO1/)XYVY^N5=U>=2MV/!+Q]%/2ZM;>0#J'W7#5>C]6
+MKN#B]TCLIZ'0^JD:75'1]4_=PV>F3H[_`*]0H_\`Z1L?*CL+\WSBX&0L^S\+
+MU:@FKJ=\K>Z7T06S!N,*_N;XD/37U%>V%K<VY#J8<3W"P3X-MZU<O\IH;VC_
+M`$7;T;-K0"3)/NK#:=-K8`!4R\<R673E+3PA9,:)I-D>P_R5O_RY9-;'D-(^
+M`N@JQ*@KN<!NK/#BERKGJWAJQ+3_`"69]@LOJ'A>U+8ITQ]%U^E]3$84M*U!
+M;D9*7Q8_#VKSEWA]U(D"F`HO_+UQ5J`Y#5Z3<65-PF(*SKLMH'3`,]E/V[\I
+M[,KI=HVQH``>H"%;HT'U'R9,J6A2=6=J<,%:-O1#0!"WC-349O/**WHPS+8^
+MJC<P"K!G*O\`EC3@*C=RVMR,K>N&:8M+78S*1,$J:MFB#'U55\SE2<5DG.TG
+MY4535F"C)$@'"9\&8*TJK5;JF2N;\3]"HW])PT#5W74%A)V0OI:FQI4N,IMX
+M7UGI]QTN])@B#Z2N\\!=7;U'I9MJSO6T`>I:7CGH-.\LR]K1K&<+S[P]<U^D
+M]:\IQ+6DP=USG\:MGM%OQYTS^%K/>UITN."%E>`JOE=4+"XYXE>@>)[6GU/H
+M@JL&0)QRO-^AAUKXA@-YA>B_U<9V]4I`.IC!P.2HW,R5-8Q4MV$DB0AK@#(4
+MBJ58.S.P4%9TM^5;J@0>52KG!C"(I7,D20J+R-6ZO5RTX"I730(`PJB"X.#(
+MGLLDLF[U`+3N7.GV673,7I(VY4O5)VO,$S_DIZ+!AT#"CI`;S$\J>FT3@J*"
+MM1IO&VRK5;5IR'*XX$-R@=!,CA-$0LM:VD14PB_A:_\`\BMTP-`S">!_B4:<
+M"XESH."$=$D3!D!0L)#<R/D*Q;Z@V8&Z[:<:BKR'%5JC0'YY[*U=CU856X(`
+M)D2%"LZX:7W3G#@PH:K);`P?E:'E11G:52JL(>2,SA155K)'<'LK-`Z</&^P
+MF%"YI#L_]U/0=K<,25>TZ)S1_$-)D&-PM.QK.:((.>5E7NJE4:YAF3$'A7K"
+MIKPX3"16]87!'($KH^DUM%,$'=<10JNI51.&KH^B7(>T'5*TS766+Y=RM:W<
+MV,RL#IM4$3NM:V>20I2-$U64J9<H+1C[NOJ(A@]U6K$UZXHM)CF%L6K12IM:
+MP<+%_#<_*9C`Q@#>$QD&0-^Q4K#QV35#`C)*:5-:N.L$G`V5ZFXG/<K.MW28
+M5Z@9$;*BP(+8`*YSQC:N>&^4W<KI*8_EX,*I>L#ZC01)W6,HTH^&+-U"V;K!
+MD[KI:+=#`J=NP`-``$*9]R&D-E8QYK&.EID'!450#5A/1JM)B4540<+I72#M
+MR!LK=%V),+/8=.T^ZN4G`MXPBQ+(WWE(Q_LI-!/;'9"T0XP5&C.`TD2J74*-
+M.I1+7<A6W&-S/LJEZ?4`.<%2P<?=6=:SNW56DZ25H],NPX`ZMEN7=FRM;:2!
+MLN5ZA:U;*O+0=)*SC=<4RF^736U8.&_"EKU(9A8/3+T.:!^ZTC6;4<!.5NLR
+MM"RICR]4F5.6>KTRH;?_`)<B!^BL4L-EWW4D:1NIR,F$!IP`K`;.=D[62X!2
+MJINIRA%"1*OE@R(`^JC-/U0.5!4%(@9PC\H1,[JP:8&#$%-H[#"FE5:E&1*J
+MBW\RN`6\[+4-,QV3]/M0ZKJ*6$2T+.F*0&D;<!17'3F/`AH^RT]/&F?92AK6
+MMG$=E-1IR'7.B.?2.`0LSIO1/)=ZFC[+MNH5&Z(T_14&^HQH4])O9MF?P#`V
+M7`>REHVC'0(5ZI:U'$P"FM[=]-TP5C*S>ED9]QTX%X@<\*:GT<.8"6+:L;37
+M4!<%K4K6F&Z>RSCA]:<H.D`-!TA2T.E0Z2V%TE:V;B!E)M(-`.5UF++,H],:
+M6B1GW1/HMH-($>ZO5:S&LW.%F79J57P-N%9C(EY0/8VK4RR<JU2M*#698)04
+M:19$@R=U,`Z<@Y34MY7F#9:TA_0$YHTFMPW*"7!V)3OU&-)*:B<B&D-``@IG
+M/(=AOV2;3/\`4EY3@XPKP:)SC&WW3EV,G]5(*9:R0/HJ=:I_,CCLLY9Z60US
+M<EHP%';.-P^`FKEDP>>ZFZ-0&LOXE<YEEMK46J%#2,C*D<`T2?U15:M.F#D+
+M.O;IS_2QR[R,6FZA=ALAOZ+.%)U6KJ>,%7*-N7G4XRK+*``@<)645O1TMPV%
+M*&CD*4@S`,)0"?=601D'Y_LJ74Z0@&/HKYDG!5?J(]`D[*I4=`:K<3!`4;J0
+MG"L=/&JC.T(W"#')6/\`4RXK.J4(<9"C-/@K1J"./E15&3D05L4_+$8RDQD-
+MSCY5O2(E1$;@B$%2XHM<TM(!E>7_`(E]'_AKD75`$23,!>JOP")E8?B^PIWE
+MBX.:"8*93<3>G,>![L7O2G4'.ES1&3[+D.J6O\)XK((_,XG]5T/@B@^TZI5I
+M%IB?@)O&]H1U>C7:`?5O]5,,MXV,Y363H>GD?P=.,X&R>XVSA*P'_HVF!D!#
+M=P`,K>*5!5V/"HW!,D#]%/6J0)!E4Z[B3+9515JCU<?=4[C\V3"N5R)!.%2J
+MD$Y^Z(JU3Z\SLJ=O+KEQ@QRKEUAITR<*IT\S6=D_52]$7&P'1'V*D9J<[T[H
+M-!C8%'0@;-GX0&[\OJ.R:F&X.K9.(G,DHBT$=CPHL.`XB92AW=&S3I$RG]/<
+MJ;C3S.W=+)[JY;5!@'8\+-MWEM*-6KO"N6;IS/W7=P6KR"1IR%0O&X#!_4<J
+MY6>($[*O0;YUPZI,M;@+-6'>P>3@Y5.L``2<=CW5\!KG$=N5!>4Y9EON$JL:
+MH)J`D$CLI:!)<``(]U)=42*0=F?904FD.F00#L"D2Q)>M:6M#@"?CE'8.#'X
+M&?V454&I4B<`<Y*>FZ'"00-B4B5IL+:A!&/E7>GW+J#X;^4\K,L7S5+85YD:
+MM)C/Z*SAJNQZ+=M?3$>PRMNG<::)*XGICWTP"PB)6W:7IJU&42XQO"U6>G4=
+M%8\36=C4M:VENY]UFV-1OE-`.!'[+1HN$3,+&FEEASE&X225%1@Y)4\0T&8!
+M4L:!)!`"M6U0$Q]%3<,GD_*DMW:'=Y1&K2)B8A-#7OGLH#6TT9$!3V)+J>HQ
+ME8S+4VK13+BJCGMJF0=BH?$UT;:UP=U1Z1<A[0=6_NIA.$C7H574G2<A:%"L
+M*C1,3^BHT--294C&.9EI6W2+FKT[;?<J>BX0J=%X=$JS2QSNHTM!P`ALGW2/
+M>4+-.F!^J*0!D*:4%:&LU$!5*`-6L29@84EZ_P!):'&45C3AD]TJIF@2#V5#
+MK-DVYI$`96BXP0(3.`TZ5,IM8X*X8^SN""#$JW:70\T21D2MOKG3V7#"=.RY
+M.X94M;C27>F=UG&WJLY3['96E753W5JG4).-AV6!TBZ#FB7+9MWR)E;TDJVP
+MR(/=3,@`S/95J!>7^IP(XQLK$<\J-G:)R#(1`<D)J8/;*D;DPI1$^F79!`4C
+M:+0WW`4K&M&#NC;N,84TJK7;I:K-C3#:<[2HZK2]P&%:8T,I:8'T2K#B!RHJ
+MSC'8J0R_`4EO1#R9E.A3;9NJD$@Y5@6+&Y(`^5ITJ(IT\H;EHTDA36U4J=$:
+MM$*9UI2+?RH*!FI,X"LZ].Y4UNJK-I%CI`,*1E<@P>RL`->T84;J(>9[%76A
+M(QP>-]E7ZA#:>#**K2=3RT_=4:M74_0\\[J\!J-)U0YF%890:!NI:+0U@&_P
+MIV!H"FOR*OD-[;G=%Y`U1"LD-F>4_I@<IHVJFW`3MH-Q"LB"-N$FAF"#":$+
+M:`)V^4A;@[[#LK!+0))CZJ)U:FW=X31LWE\8"JW%BTNU$HZ]]39L9CW69?\`
+M5M.&3E+A+V>^AW]M2;#IV04:[:=.&!0-K/N0"\DCWX4U*FP-RDQDZ2VWL%4U
+M:SX)PCIV^@[`^ZL,\L#>041@[%:TR&FP!IGA.-_A.X`#TF4$#5NJ'?G(*$3`
+M)^$SS,]PA>2UL]T!8&(S\JMU""S&2IM7V4-_`IRWZJQFGZ8TZ3&RDK$:@=H4
+M/2\-,E2UXF9P5SLX3()<")(]D#R`(*7[#*$YAP*W.E"8TX0.&9`GY4D3_4AB
+M'^RHKUJ<CTR%4N:.JD0[E:#Q.1N%6O\`_EX5B5Q=>V%IU8O:/S'^Z#Q/0\SR
+MGZ3W*UNI4`ZX#SIE4>OF&,`B9W4DUMF\E;PR@P#``4%TZ2<?JB#M5,`G<<*K
+M=/B8(GE:G#*M4)DP<*"H9&)SPI'.!F#\@J"J=.SAW1%>OEWM[JM4#OS<=E8N
+M#J]H*A<($`_=!3KM9I)DY53IP'GO),$<*Y<?D=..,*ITP.\UT']4O1%YF<1^
+MJD83$1`'NG#1,D'W'=.3+<84#.(<!!RG`[DIZ1:1/^J:L"#G"BI&_EW/V2^O
+MZ*)E1FD22$7FL[E--;>/4*CJ;]1)<.%KVM21,P>RR[?3IT.SV4],NHF=Y^Z[
+M;_+RK=P]U)AP>PRKG3F>7:^K!(G*RJU7^)N:=,8:W)6L'?R2W[*::@0"PD\(
+M+C\H$3/Z*5Y/E1$DJ/3.\[(TJ5VX`@[S*I!A95(G[K3J`D9:%0>#Y\:02HE5
+MVG_U;A)!`P%-J(.9]E#/_JR,R,;84]P"TSIGXE$Y%0<:;I#MSW6@Q[?S+*HE
+MSJ@!&^P"M^8-@[E58W.GUG>6<K2Z('U+AU:?RX7.V58Y$D!;OABJ,@DS*UV5
+MU=A<NI.&HF%MV=TVHT9SNL&U#:@$1*O4&.9!;Q[K(Z*W,MW5RG.W985G=EK@
+MT\[Y6Q:56N`,R%*U*DJ@`2H9<TR)^JM/TO@JL]L'(E94=2Y'I870MBP_Y+3.
+M!"YZDTOO6[P%T5`%M&0N67-9O;'\8--2E(_I_P!5B=&K%A,SV71==;JH'4N6
+M:UU*K.5O#BMV<.MZ96U$0>%J4CJ;PN;Z/<9`G*W;>HV`9)6S%8JLV<U'1JC`
+M(3M=Z1"&I3CU`+/3HM4W9PI'/&GC"J4:P@ZC":K6!PWE4V6H5KF-X*O4QB`J
+MW3Z`8TEW*M`9V69^6C%V0$;8(D'Z)B&XG=$&C<X0156M(((GA<YXEZ=K];1L
+MNG.^57O*8>V"-\+-QVNW#V-1U"KI=N/==!TZZ!8)/LJ/6[!K'ZX&%5L;G2^.
+MWNM2[<[-.JMZDG#L*SK$]UCV=QJY6C1>#C=-+*OL)+)E24CE5Z1ALD>TJ>B9
+M(SMP%&ED$$`;]T1_+,[**8*)YTL10T0'5YD8'=6JL0%7LBV22`K+&!SO]5EH
+M]"G!E7K6F&[0@HTP8PK--H:`"Y%AW`!D2H;HPS^RF>0!G?Y5.\)TQ/NJE0V@
+M'F&45P8YP5%9_F),[JQ<4R1A94%"M!A7:3FGZY654)IU([*S0J>D=RM)%NZT
+MZ3`&VZQ<&N8V6A7J$TX<#"S[9NJL7`*6+M:+](QPA_B-)DIBSDG'9`:8]\\J
+M@_XKD9A+^+&5`:?J@=^`F92`=D%-HE_BS)]TOXP\%0BG\I"D,%-B2I7J$1)4
+M%057XU8*L-:`$X89SB%-BF+5Q,.<?NJUW:`$20M8#95.H0"`2%=<`+:V:*(+
+M2?NC%)VTQ[RI:0!I8.GX3GL2DG`K.;4:3#DPJ/8W.1"M$#<J-S`>$1"VYP9'
+M$)V5Z9`(*9]$'X41M@#@P$1/K;O,<?*(D1CXW5*I2>T^DJ-[JH]U=C0])&-P
+MJ]_4;Y<$A5Q=N!R%6NZFL:M7W59J[TUQ<V.#LK-5I$$\JMTA[?*)F5,^H"Z)
+MPN<EJ7@)'I$3*0$"/JDXM/*0$N@25U:(0/E(029"<3&R=\#VQLH`>`#E4[PC
+M3."%8JN),-5&[<6X,JLUD=3J,;5!,?*Q.MW-.I48UI$K9ZM0UTBXN`PN.>QP
+MORTN)SS\J\:VQ]:A<!3EHX5.L_48$X/*G+O1`.0JCYUD"2K.D`[O,J"IVB.5
+M*^1&-U`_5L4$3AJV*C<V1!^BFB#(0P8@")4-J-TUVDDJOT9I-1T?NK=P?26@
+M1'NJG1!ING@_U83+HC1+0'$@9(SPA:UWY01'<<*<M'"B+PPGE`5*F!'8*.KD
+MD/E2,=$$2HG3K+CO\J0(-;`]2?2S_$G#C'"6H^R-</%;%\.&J"9V6HUPTR1(
+M&<&%B4*K34])(]IV6D:W\EK&B2XP(7:O*.U#]3ZS01G"T[.X!;H><^Z"@P,I
+M!@R.4C0TN)&^ZSTWI>)!@`(7,(4/3K@ZB'JW4#33F55BM6U:2T`'4%GO]%:)
+M(/Z+3=3BFXSNLRZ#6.)=R,+/TO2L[2ZL7N`[*\UC74X':!E5K>D#1U%NYY4U
+M":9`F0>2D$%9HHUO?=*K^77,%3WC&N]>F>ZJD#3I,P$5;L7M-,ZCQN%L]`J^
+M6S5.YY7-V=3^2X@''"U.EUY8`>/=;B1WG2K@$`D_9;UE5#V@E<3T:YD!DKI+
+M&X``$E+$Z;HIAX)8`3Q+E:M0^F1G!5+I]68]2U*+@6C"SIK:W;W`C2XY/NGJ
+MP&EP)4'D2-0W[H;ASVLT[GY6+56>F4O,?J$[K9HP*<3LJ'1O10]6>5/5KR=`
+MY7/&;J3M!U-IKO#&G"Q.K4-!CLNGMK>*9>[<[++ZQ;R#CZJUUTQNG5?+JPZ5
+MT=A5+V@`Q]5R[V^74Y"U>E7&(E=)S&.JZBW,B)'RIW?EG*S[-X+9F58K50QD
+M24TW*@OJ@;L<GCNFL&U''4Z8G9-1H&N\.=QW6A3IM:W"QW5UI.RHV!*G:X$S
+MC"IFG)D'V1`/:%6EL&3@I`D_TJNVH08E2,K-,@N@*:5*,"80U!(E(/9,RC.6
+MX<IH9?4:(>PXRN5ZA3\FXD8GW7:5@'",K`Z[:C27#CE2\%Y4^FW):()6S:5@
+M1)*Y6D]S'D=BM;I]>0`=EISZ=-;U1Y6\PK5JZ3LLFRJ2)F%HT*D[%--RKP=*
+M&L\!D3/U0-*&X+@WY45-8"3NM.U8/^ZSNEL<6:EJVH)B2/A9;6:;0.,A%(.$
+M&2-PD>$43^TRJMZ`*9(W5C;)A5;QTMP/F54J.Q:1)5MP!'!56RGZ*RXB5%5+
+MFE+I4+':7Z<J[6$@E4JC9=LB):S@:<3A06Y(>0"5)`TX(4-%T5HW,H+1[$2%
+M%IWR84CL9P4SLA`,`@;X]T@V$1_+,(3G?ZJFS?LD`",<).$#!320`-,('#<3
+MRD02(U$)]1_[II_W*!<PJ?41D9/RKA^BI]0R.?H4B4=MFF,(G1)R906CAY6D
+M.E&Z`Y(4Q)C!E-.=]TQP<[)$PJ$X1S^JC<Z793DF$$YRB$3G&2@JP3F/E$XB
+M20<J(DF<JA&FPB("I]0H-T^DB2KK=P)D>RK=1ANG/U1**RHFG0])*"X:\OU!
+M6*+HM1/*$9V"QC$O:G-5IR20=E+2KN&3LI7@")3M:TB0/NNF@S;@!LN3>=JF
+M(2JT6N,!/2HM`01OJ!K3/[JE5.H%Q..ZM7H#1M"P.M]290IEK'"0K)MFW2KX
+MEZ@RA;%K2"3[KF>G!U6H:A$F9RH^KW%:M5-1P,#Y1V5RT,R(E7.SB1B;[JV_
+M>0%`]I.VZD=7IEH@P9V4?FLF0X#W01OU<M]E&]LC;93RUSB%$\>J`0@A=CZJ
+M-X`._P!U-4B%"1G,HBM<!L8,E5>D0+I\DY/=6KD0<D#'94NED,OS/)^ZF72X
+M]M>L,X4)@G(W5BL0!$[Y5=\[?W1",QJ#20.?91U#J=+?DIR^&1_=1%X@D['&
+MZ*E#V`;):V=D#64M(EZ6BE_C4TKP6RJ.=4`)F.ZU^D5/-O9T@Z%SMN[RP3R-
+MY6OX5KZ*+]0`<YR]&GFRFG3T@-4G)]BIM&KLJ=M5)`/!PKE/5/=JPW+#OHX]
+M,`^R!M6HUVEPPIJ4M.43J9>W.Y4_XI$BJP1/NL[J@T-/I$*U4:^D^!.RI]4J
+M"H!3$R=PE4]L!_#"#S*CR:F&F%.&^4P-X]E6I:C7<9(XD8E/J?%QH#J<1,*G
+M<MBK$1*NT7'`.GZ*.[I"-8`^B56>WT%X!C'!16-=S>25#<G0YSCL@MW%M/;=
+M6,V.IZ+<D$23A=/8W&IH/*X7IMQI#9'RNBZ==MU-,_"T.WZ94=`),!;E"H"T
+M&5R72KEKFC,_5=%8U`6`YCY4L)6U;/DY".YA[QA5;1QU29A6:)UU`)GY7+.<
+M-;7A4;2MY<(1=+:*S]9.)5+J1)#6"<J_T2`P-DPL83:XS35T@-C@+.ZG3U`X
+M6HT`MB56Z@P%NTK5CK'(]0H^H]@JMI5\NL!)W6MU.D=1QA9%>F6U)3'AG*.D
+MZ;=`L!R/E60\UZX:#(Y7-VUWHI[Y6_T&IJ9YA,F5K),:VK9C6TX&X4A@;**@
+MZ1DB%)CV'PLNAJ3C)P5-3,NS*!C94C(X&%%%H8[?"B=0G,*8D:1.Z)H&TQ*:
+M7:J*;QWA-JJ,<1RK;?S1PG<QA&=PIH4W5C'K"H]3+'4CW*U*M%GL5G=4HC1$
+MH5S%W2TN+FF`FMZL8E7[RV=Y9F)6/6.A\'ORF-8KHNGW'I&5L658%H]4+D[&
+MKH`DX6[85P<`[JTE;U%TYD(;EY`B5#;O$?W37+QJ:)Y4^--KI##HR=^Q6G3`
+M')5'I&D41C)5X`D<J-Q)_3";.F9"'5"<ND(I.(D2Y5+XB-U8)Y@*C>D:A@JI
+M4]CLK#`W5E5[/##*DU2[!A9X5)5$[*K69ZIX4SW$Y`RA<)YA+5T@J,=C2J-=
+MYI5I<M72"V8(*R>M4\2-PB5<H50]LSA&X[$+(LJ^1.X6BUX>T=U;$E2@F#E-
+MQD)-&.4Q'ND4YC>4+R3]$VXG8)I;R@/)$(9XA,#`W3:H!$H@I/('LJO4/R8,
+M'MW5C5[_`'5;J1:&AP.?96)36GY.WPI`9)4-HX&EN4^J#`<2D*)YEVZ1SNY,
+M82;AI&Z!W;;H,3_FDXD&>4+B1F50G$`PA<(.^2FY]3D-0R\"9A$.YPVC/RJ-
+M\[4\-/W5RI@;Y6?=NU5P-XY3XB]3;_(!S]TB0#A$`?X<0<J!KH!!68E[/5?&
+MQ4;J^@`RFKNG?A5:ID:9D+H-%M]0T22)5.[ZU;T\%P^BR[NB]WY7D*A<=-UY
+M=4/W3<GQ-6_4W6>O"H=%&2?99++>K<O-2N70KK+"E2=,!Q]U,6^F-*>UR_Q-
+M2,'J]*FQS6!H3LLZ;J8AH4_5VS6;D8*F9I%,-VE+VDZ9=Q9-.Q`52M9N:8:Y
+MRVJNDC;E0N;JR)51CNIUV<DJ!U2LTD03[K:J#!("KOIM<"8V09;[N!F1"8W3
+M8,'"MW%!E1TD"57?9-),&$Z0-6HQP`+N.ZS*3VLZFP^_=6[FSJ`#2Y8M_3K4
+MKYICG<)>J8]NJK$%N^%"20W.RH-K5O*!T\*$WE0$M+<!2:*O/=+M(43H&)@#
+MW55E^#,H:]XS8X[JBTTG3N$\GN/NJU.O3+`0=T_G4^ZC3YZ-8L!#P<X"W>AN
+M:VBWM/)7-5W/\QI+0X.,#NMZPK^50:<^K@B/U7HG+AG/PZ?I]1KR`=S^BU6'
+M`$GZ+!Z56@@XSB5LVQ+@-UFLXK31G)4OJT@`?51")D'Z*3U&F`0LNAVL:\$N
+M"RKFBX7\TY<UJUFN#:3B<X5>PHE]$U';.*E(I/K-=Z9D]U&_\Y!,8[J2[H$5
+M"]@)X@*J'Q(,[[#9)R7<7;9N"=D;Y<PP@M]1$X@<*RP?R)0C#OV`%X))4-`@
+MTSDB%H=0IP"3&!A9MJ^:1)`P4B5<MWF=1,1PM6TN-+!!E8]"HUP,">P*FM*L
+M-`)6H1W71+@%C3JRNGZ;7D`2=UP'1K@!@`<NKZ;<-('JQW*U4=C0K&-P(5_I
+M7J.KLN>HUVB@!,:N5T/1V_\`I@[NN&<^&]KE=@(/[(NG.#'@2F+01NHZ/HJ@
+MY2<.K?H'4T$.2O6C3)W473ZC74\%3U1(D\)6XPNJ4SJE8E^P]ETO4J3BQ8=[
+M1,%PS"R5B7#GC=Q71^':Y\@#5^JYWJ3"!($?*T.AU7LIB7+<Y8Z=E;U3H`@*
+MQ3?Q*R+*L74A!_57Z#I$R5+&Y5T.,D`HV.D>RAIOYVA22`#.RC21CI.1A&3]
+ME#(#2`G:??;A%2B)WD]T61B?LHVD;''PI0#&%E35`",F0LZ_C6&@Y6C4.EA)
+M,?"S6M-6ZB9'=+T`KT/Y.DB5S/6;?2^=.W9=A480-IGE8O6:1F8GW4TE<W0J
+M:7Z286[TNJ"X96'>TRRIQ)W*L=)N0QX!=.5J<L7AV=J[4P#9$2'7+6JIT^XI
+MEDEPD[*>B[5=MC:4:CJNG@>0(_16@Z/8?*K63HH`@B5.YT`3"SIT@Y!'"37"
+M(.Z`NGLG<X"(0.3E9U[.H9(SPKU4R"!B%FW%2:H$B42KML?Y??W4X@"5!0D,
+M$%3[A2-!)$@`;)';;=(X$PD".<JAGF/3$K-ZN06F>RTA#L0L[K#0)D<=TB5E
+MT029&ZLV]32?4HNG`Y!;';*>NW2[5)3%*T:;Q(`.Z-L]BL^UK1)<-E=H5`6^
+MGE4V+3B?T0.W_P`U(_NHW$``P44B0-DP,`2GYG2?E"3/QV*B"+LQCZ*MU*#3
+M[J5[VXB(4%[4!I20K"HK7\ASGV1"=?S[J*T=Z20$;3)F$A4T3W*8$SC]4['"
+M"!PFD?EC!50GNRHW'NB>8X4;CE`_]/NHR9=(Y12W3N<(,.$Q]4`UC)A4:\&X
+M:-2NDR8./94;@`W(R$O2?6I3]5'_`#59PTO+295B@[TCD?917C3(.RG255KG
+M.ZK//T4UP02JM4B,GZ+:!JNE0U?R[?52$MX*BJ?EW05G_G3.YX1N#2<2FTG3
+MC[(C$ZP'><V))]U.S\C0[]$'6F36:[B<J4-'EB"E[2=(JS0.,%0_E=$?JK-1
+MH.)^JKU&@'4(A5$-4G6.Q05(R/V4E9DG&%$6DSW')05:V'C,IO3!AN2BKM^Q
+M0-CR\?4HB"N1.DC[+'ZV--9KNQ6PYLS[;+*ZX/3.K961%NW>U]%IC"CN:3)P
+M(!3=+<'6HD9/NBJ%PQJ6<>BH/(I%OY<_94[NU8X%TP>5>&3O!5>Z$-R95TLJ
+MJRU>&@-=A/\`PU3NK--P#``#]T^O_I6--OFRL\%U,F"),9D+2MJWE46%L25D
+M6]1QN00=4;85ZFXEY$@C<$8XRO5'/*.IZ74U071@\%=%T^JT;F5R/2JCF@`.
+M#F]PNAZ;6AVDY@;I7GG%;K2"V8B=BI'.@1S\JO0<2R(V"L`$MC$!8KK*K=2>
+M!1#`8<]7+-I9;@"1`*HN:;B_V]-,85^K+*9C'UW655(EQU?94.I4"'!S)$[K
+M3:T:9,2H7L\QV<II5.QKC4&ND'NKQ+0V=7YN52J4OYA#9!.93N<^G3:UYD=E
+M>V9P+J30ZF0YQ(CE8K612).-6(6W6+:EE+I$X*K.MVFF(:#"EFE[9MNXZH8V
+M#/)4VKR\.,_W356.%:`2)454D3[;95EVS9IL=)NF@`.)$''NNIZ1=%S!GE<!
+M85'-J`N,2<KI>F78:WTSGB5M([SIU<5+RE3F<_"[OIL-MPWO[KS+P54-QU$'
+MM&?NO2*#]+!G"XY<Y).UZFZ'$<_*&I$DSLH&U1JW4Q=J:3W2QVE6^E5G!Y#X
+M@+8IN!:N>MW0_!P%J6U:&Y,(W!7K)D1A9%]2(:0MUYUTYD!9G4&3(V^%FM.8
+MZC3_`)>)^JALJA#8$"%I=3H^@[8652@2!NK&*W^F7&T\\%;%G5U-B8"Y2WJN
+MIQN?<K8Z9<2(U#/NJDX;['[05,P@O$N,K/IOQCE6J+YRI8Z1;VA.P'^K*C![
+MYA3!T#&5+%@Y(_+*-I.2H@1DDR4GU-(.>%-+LUV^!'*"S9$N)RH*8\^MC(5Y
+MF`!P%+VLZ"Z<D<+/OJ)()B3[K1=(!Q\*L]FMI/"5'*]6H@2<>ZRV-#:@>.%T
+MW6;?4TB..%SMRPM?&PY4C-;_`$DEU$96G8!PO!/"Y[H%<L<&D@Y6[0K`74RM
+M:25V5J0&C/ZJ9SAI$K)MKN&`<*2I>0T;J.D:0?'^92U0-RL]MWJ$DS\)VW8`
+M[&47:]4?Z2>2%EUG?SQ.<J5]T`WU$?(6;4NVFY'J/Q*7I/K?H.EC<1W"E8X$
+M0-EG6UTT4]U-_%-VF%--;6R1`$?=)A$;[<*I_$9P04[:XTZIB.%=&UI[L@RJ
+M'5WRTP),*=UPSO)^50ZC4#FELB$D2U7LWB8Y*FJ-!!)&5#T\DY@*9S3D!T<J
+M155P(,S,%6[.K``,8455ON3[J%C]#@"K$:I,B1N@.(YRHJ521DJ02=R%*'J.
+MQ(,A1N,B>41`!.<>Z!T"2,2@!Y'!*@O'>C'&V5,\S_HH+LS2.\K42H+)X(R=
+MU8:9)AQSQPJ=G!,?NK3(F`T!2%2?TX3SP<2HW.(P-D[7%Q"H*3![*.I,P70B
+M).GM"#5ZT"QHW2,`1.R=WY=Q@(()!,R50!B2`250N#INACGNK9_,8(RJ-VXF
+MZ"EZ3ZTZ>6M]MD;B'TR"H6$!@/9%4=Z-RFMI5*X&DQ./=5JK3J@*W7`)SQNJ
+MS\XP4B17((.(0UL#<%2EHE1U&@B()(5$%)L@DC)3$>HC92AIG/V2+1N$1B];
+M!!F$5*'4`8"DZXTFB2,$#=5.F/#K>!CV5O;,$]PU>W)4=1@=&DX2=.J"(^$X
+M<-,;=E16KC)!W"AJ/AT$*6X/K.256J&0<Y50-8`D\*%H(YW4DRW<X4=9TNW@
+MC]5!#7(GB2L_K-+5;DM(VW5]SM39=NJUZ"ZV(/8K4[9O2ET5Y-H<G!4U:3_F
+MJG0W0]["=NY5JMB=2QCQPN78=+-BH+@SS@IV$Z\R@NL'2)GNK2!&D!/+5$TC
+M2,IY'=&]OF*PN'&H(VQ!E:UM5)=!B3@`KF[)W\SU8C8+8HU&G3!D`X*ZXW:9
+MQO\`37D/'OL/==)TZJ&!LC)W7*].<'56`20WE=!2>W0'$B=ENO-DZFTJ-=3W
+M`]E9?5TT"XC99O3Z@-,,]E)6>^K=T[<'$R5BS2XU>Z73_D:_5+E9KSH`.=\I
+M42&-T[`;IJH:XQE9TZ2A8!`!(PHJI]4@XYA2M`CDJ!Q]L<IH05/34QWR"BO@
+MVI0@-SNG?3#G2/U4=8PTMDQ""@ZMH:V@Z9)RKH>S6T2J-)@KWT$0&^ZFNFFE
+M6D"8@)WVR*]IM\T;*G=LTLD.$*[2+'TSJ'JW454!U,YSP5-::WMF$`D:'1W*
+MT^GW/I`:<=E1=1)!(`QRH:%7RFNP1GG8K4K%>G_A:\N]7_5]MUZ'YS=($PO+
+M?PGN`ZDYWO/[KNZEP=8.K[K&N7/&\MNA5EX$A7J#@6[_`%7/V-?.#]5KV521
+M\K5CO*N#TO!!*TK3348%F_F9A6.G57!\3CW6-?&]KSWN83,PH*SFU,X'=7G-
+M#V9"I7=+2/3LLUME=69%-T9`"Q6LF1L#WX6QU1Y%-P&)"SZ--II&3]D1")D@
+M&85JPK>7'=0-I%K^W*3AI=^:0KM'16MQJ8,G=7Z%3T8."N8L:Y;`U+9MJXC!
+MW5TLK8I/)&\_*F:Z,`X6?0J3_4K-*H8_-NHU*M:]B0J]W6]6D<IZM6&2J=`^
+M;6F<=E+=*T;*GIIB-^RL,&J)_=04G`"(RIFGTRI(HG-Q"C+8$QNCU"-TU0X`
+M[;(*%]2;I(.,+G.I6VDN&.ZZ>Y!..5D=3I&/RY42L2U(I58SA;W07BO<YSE8
+M56F6O[+:\&MFM(Y.ZK.G94:#-&R8T6]P5)1EH&?HD3G*CH`4&`P$G6LGF=U*
+M#&V2B=4,Q":%6M;PTP1`6;Y`_BP,F5LW+H9DY6?2<UUP$LX/JTRW@0W"-EL0
+M/4284K'8V1M,M@<*:Y57-)W'*847F<JV8W'=.".0J,^I1J#+3$*CU`O:"'2M
+MUQ]N%D]9@`DI$JK852&",JU_$M<<E5;-@T?*G--KR3D$I-B5M1N<X05=.X,R
+MHGL<T8G"!^L8""S2?I<`7`JVUP,$+*%2,D*S:UQB"G8OG2=RHJF"8,@H@X$"
+M#&-BHGX.#A`G$D08^JK7?Y#LIPX1NH:PP03NJ52LGCS")&.95L$`$%4*9T79
+M$B.ZMN</E(B0$3(.W=$"<F84(<)PI`09<1A4$T]T!,&?V1C#2-X4-4[CA0$7
+M%PPEO.$&J(A.W:`51'5Q*S;@D7329Q[+0J&'8_19UX?YX,0EZ1I4ZD,!A#<5
+M`QFZ&D\>4!^JBNAKIQRD2HVU=>=_JF/'<(;1GELR[/[J0C,I.C2*K@J,>H'(
+M"DJ\XCV40D"`(E4"X0XC.?JFJMC:8'=%$$H71O\`NB,_J@_E.!Y"Q^FU-+W-
+M@A;G4&!U,SD$+G+8Z.H$#<[JWIF=K]1LF<D]]U&\;QA3U`-*KN=N-R@@K@@C
+M]E5?CZJW7,;'(5-T$Y)PJ@7G8C`*"KR!(CNCJNTM`DPH7DO$DR0H(*IGC/=`
+M^#3/J@_*58;EI48.X=&1N%48E(MI=6+7.Y&/HM&JX3!V*S>I?R^JM>"`!RKE
+M9XJ-#@GVK>9"<8.3,(:KMYW"![W:#&Z!SG%GJ;!0@J8]`X3Q[H&5'!H&H?9/
+MYKO\0^RSPV^3`XT[@M(TP>2M*RJ!SA)&>52\04G6_57B``?=*TJG!C=:\>6X
+MZ>3%U73GAQ$'!,;K>L7:G""<87*=%KM:R9]<X706=RXU?-<07'?W7H[>/.<N
+MJZ97#1)XY6ET?34J/N"!DP(7,6%V_4*;1+G&,+JK!OE6[&`#(6,HQCVOM,D.
+MF$1F8*C;M,_1$VH($@D^RRZGJ8;@084+6@B2#[*2HYNF-C[J-KB[`B`H'8T!
+MWPHZU/T&<%2T"T3(_1-7V)SLFC;$M2?^(OC$=E=<[U$.$CNH+>F75GU=,294
+MCVN!![I.D#4ID-<YFT*NRH1NZ1[X6@W%#D^RJW5`-9J`(^BBJ]5H<):)![*I
+M>TM%)SHB1"TK<@M@`PFZC3#K5Y9G'"6Z339_"2MIH.&6GG]5VG\9J>?5/NN`
+M_#:6TCP5T3KDMNBV2IC=UQDYKJ>G7/JP2NCZ?5!I#,%<%TR['FAL[KK.DW&H
+M`+I8Z8UTMLZ`#RI@[34!&%1M:LQE7(!9@K%=8V;)X?2&4=5@.ZS>FU8=!,1P
+M5HNJ!M,PX+-;E87B"C+]`&5GFDZDP!TB.RV:X%>[WD!1WULTL,#(V)6-ZJZ8
+MU,MU[D^Y0W+9.,#N%+6I.IU%'4>-B<JHKTWEKA'ZK1LZX']1*RW`%^(^5/1>
+M0))B.RJ.AMJH+!ZE>H50(Y7.V=?.ZTZ%Q#-6T(LJ]>5Q.D&"5/T]NFG/<K)M
+MZHK7.^!RM>DX!D8@<RLWMJ+32`8V4S2(!!WX55CP#ONIJ3\YR/=54[7@C)RF
+MJ$`'8!#,F>>R3AJ.5`#H<W&96??4YY6D_`@!4[EH<TD[I1@WM(!T*[X/?IKG
+MY0W;!!D!'X7HD5R1W4G:.N95$9V(3FJ"0(5=H=M*(`$JM18UB,%(U"23(5=L
+MZ=TVH\<()+I\M(F`%0MW.-S@QE2W+SIG95+*3=:IE+T3MNL=#1'*?S.)5=A@
+M!$)F2@L:\&$XJ'`4'P$XE*HR\3@DA9G6'2KQ$YF%F=7B8!1*:P<?+A3:HB8P
+MJMD3HC]U*7>HSL/=2+5B01\I.`)[J)K]N$X=ZXVE4#4I`@Z5&T%IV*L.<2-T
+M'YO=30=M=^,?*=U8Z<@X2`;&=RC+&'$A!`;G,1]E&^X&))RI32;MA15*(&QE
+M7E%&YJQ<"(A6VO#VZEG=29IJ!P,0C8][:8$E/J-!CNY4S"2/[=UF,N8)#E:H
+MUPX`2J+<^D]U#5._!"<5!W4=4C:440=.Z?4)WV46J!$I!W(1!/P(&RS+V/-!
+M.RT:A=!!/T6=U&=8DCX3XE7J('E`C8>Z"H01&?NAMG?R(0ETG*3I:>(9)2ID
+M%N9"$N@1O\IFD:>RJ(ZL%Q`.$+\#L$3_`,RB>X]P40HR$U68PG),25$Z9,_<
+M((+PM\LCV7,51IZD-]UTET'%IPN:ZK++L.`"U>F?K5>6Z!C?NJ=4EM0S'PIJ
+M;II!P,JM>._F$@_5(@*I,F1NJQ$O#I/NCJ.)`,('.,1C*(:H6G$?JJ\Z26G8
+MJ6KC(,_"KU<9B?JFBH*NVD?=0L=O.$=PXD_YJN7DNW,!5&5UP@W#3SW5FG/E
+M`\_NJ?6S-P#LK5)W\EI:F7]B='.7GE)SF^7`@>RCJ.=(+HREK+A)<-ONI5A`
+MXPV0GD_X/T4)J'M/NF\P_P"%&]/G7\3K1U#J!JMD-/8KG+*J69:8E>A_B=:"
+MI0D4B33;F=I7FK*FG^61)#MYX7+"ZNG:?RQE;G3*HD#_`&%T?3JQ+6F3(W7)
+M=+K%WHG![E;_`$JX`IZ&G;D+UX7<T\_DCH>CU75>J-$X;P%VG3G.=I+CMC=<
+M'X8=_P"L<^",KL^F58TD#;NK7GO%;1(:R2!E%2;B8^O"AI.+QJ+I4K"XG$Y7
+M-T!4R3RFEL<[(JC2#O\`W4%0%KMP<HM6;4&#,`>R#J)T6QAVZ>D\Z(U3*@O'
+M:[BFP3[I4/2`;;-$"2)4-;43'?@*^YLB(&.RK5&Q6$;J=*B$M#02E=-'EF#&
+M%-4_F;B"%7N\-W)0X-94&NIG.4UV"V@\$3(X*DZ406D@G>([*W<40^V?,S&R
+MF71`^!7<D`;A7.O56T[X`'/RH/"-'RG2YI'9+Q93+*WF2N6-U8Y2?RJSTZZA
+M[7$C_-=?T"]&D9_5>:V]R0X=@NC\/7\.:"9GZ+TSE7IW3J^K25L4'@LCNN3Z
+M%<A])OJ"Z"SJC2#*QDWC6BTEKA&5.ZX.C1R52:^6$S$)K5X?=^HX!6-ND:ME
+M3:*>HSGNGN&G5Q$*6E&@:3'U2J@&9(E8UIT95U1#G$EJS+RA!."MVJP'_54+
+MQD-/)[*#`?-.MA&UVK\RN5K=KP21!Y5&LQS3`.`DK.DU-^G'NK#[K13^5G:_
+M?9-=51Y>J<^RW*E;O0JFHR5N,J;`F?A<GT"Y&@`GZRMVSKAS?[J:7;6ID!H,
+M$*Q3+0!*SJ3Q$R?E6:-2>?HHUM<8[U8^%)J(,JNUX.?V4DPV>Z*)[OJH*Y!,
+M']$;G=B@<&D&5!GWP]!PI?"P)J?7DJ'J`ACC)]D?AA^DR2D1T8P,Y'=$"-U&
+MRH"S@2A+@2(<C25[@,0@),GU0A<<ZB90%T$R=_=`-T_^61./=5.GNFYW$*2]
+M=_+F?N55Z:[^>2,Y5J3MNM.-T8=`_P!55UX&8]DFOG))459+S.\A)K^2?HH"
+M9P$.HPBK?F-R!CNLKJCLF"85DU#!S/LLSJ50_E,2JB2V>`TD291.?F05!;._
+MD[_5$TB?41)YV4@LM>0-YY1!\G=5R[:<)P[.=D$X?G=&Q\#C*KL>"-]D[70X
+M9^J;%EQ.-DB\]U"7\&8*<.X!_54$YY&9E`7DNVW35'@;G*&0[Z>ZNT5.HG.4
+MJ8'D[8_9-U+4!N82M_\`D`[J7L+RVDX*3:3ID(FN]4(VNAW"I`/UM[X4=2X<
+MP9G/Z*V#+5%5I-.[=]U!"VY$_P":E;<-)`U0H*M$<*$TG;#A!H>:TM]+I5/J
+M)`$G"B:VJUQSA0=2-9M/97:5I6;@:`)=$(7D`K/Z;<5?X>'`X[(GW1+O4DUH
+MJ\'`P#PA<3&VRJLNFZO[(S781C'=42U'2V3NHAOLA\YFG\WU0"H)W.564Q/I
+MRHJFQC"=U1L;J-[@6SJPFC:O=.,?(6!UO)+APMN]<TM*Q^K4]5J\YF%J,4/3
+MWZK82?LFN8`R0JW17_RBPG93W#?5CA9QZ6J[I`)[*%SR02IZ^&D[#LJCC)C[
+MK2#+@84%PYLD#[J7&DE4[N0=0VY0VCJ/F<3\JJ]Y#_S`2I7N)$@J"L?0851G
+M==)!#I,*2S?JM!_4>ZK=7ES/92=/(;:-!S\E3+N+.DI+6M)(CA"8%,$'"5:"
+MTXSPAI@^5I!!]U%@FM!;)A/H'M]T+#Z1A%/LHV\H\54&UZ%9KID-/RO)NK4'
+M4.I50T`M!R9VRO;.I4P]CG')CO@+R?Q[9NI]0>Z<3,1[KGGQE*O@O<9EKEH+
+M0=.X^%K=-K@4M((_NL*SJ/IO:`>^3PM2E6UR74Q(WC"[X7ZN4VZGPW6+*+<Y
+M)[KLNF5I#(F8_J*\]Z3<:'TFC`,;KL^A5?,8S(GWX7;6X\?DFJZJU?-,"02K
+M=-SM,`"/=9=C5@ADM='97J+IP=6T^RYV+C4U0GYA5ZF7C?\`S1N>[41OV3&2
+M3P2HT3JFEA."!N`HNG@5*KJAG?"#J-0TK8GDX1],+V6K=49[IVB_JVF=E%4$
+MG494EN\$?/":K),D;84L:B*W'J)WE07;7:S&/W5JF"!,;SRJU1NMSL\[H([3
+M4PYG)VA7GUO_`$;@,8YG"JZ((+)!XRGZA4<VQ?\`:5/B-3PPWT!\R2=T_C$.
+MQ!/J$84_A%O_`/3Z;].$WB!HJ5MH"XR<L8=N-+G,K$;*]TF]\NJV<0=Y4O5+
+M3^67,&0=UDO+J+@3E=\,ERCU#POU!II,SE=ATZNU[&Y7D/A;J1#V`E>A]!O=
+M=-I!6\HDKJC6#:;CJW]U'9U7&[!`QW6>^Y\RJVDV<K6LJ>B@)B>5RRNN(ZX_
+MEMVM<&G'>%8W&3/NLBUJZ7`$C_):-!VHCU+-CI*-S1I,`RJ=VQI?[_"O'/[*
+M"HT203)E8L:9=>FT$B"J=>CC;'*U+ML$@`RJU>GB2D1D7%N#)!R?T5&^8YM*
+M"86U780TJA?M#J#I'"L2JG3*^FGI!S^ZV>G78TCDA8%M3(!(5BA6-+>!E:E9
+MKK+6X!;DQ[*];U/2,Q*YCI]WL)_5:MI<:B#@_5+&I6W3J>H0<?*G;4`],E9M
+MM5'W4X?MWA9K6UPNU")W2<!I&?HH`_;(E23C915+JIBBXCA%X<8#!2ZEFFX1
+MB,INA/%,B-I4G:5MP(&3[HVPJXJ-/RB#C,3*TJ8]^/E!4,'>2A)!Q@%#4)V.
+M%-"O>F1OLJ_3?^;GNBOG>@J'I9]9DR9PK?B1L:A&_P`)M>(*B:XS&(A,>ZBI
+MVU1.Z(O:3,CO"JD'![^Z3@[28)E-B<OW,8Y63U:L-?I/*MR[;?V65U)SM>!R
+MK\%RS>?+!"F+O>51M"?*!,A2TZDNW*S%6'.)_IPEN,&"HW.!.1A#J'?'""P-
+M38!VB<J2F1_LJOYFWV3M.,F/JD-K#G.(QPD''<E1%_I@<)PZ!))5B)9D&4PD
+M'>9]U%YF-T@Z>51%U%WH*&S(\D9^B;J+OY1SCY4=B[^5OE*BP9!D)&0Z5&YW
+M\R9^B?428)Y06&ET`IY)89CWRHV.QI"9S^$#5=\)##8[H7G.#$IJ;G28*"5D
+M$[1]5%?-#J9D3[2B:XYXY0ULTR"58*E@QLN`:BK46.>3&>RCM'1=.!.)4]0R
+M3"D*K/H>J02F=0<6G=3EQ`"?4-)Y*ND4'4G`8)4(-459E7RX:C.Q*!S0'3CZ
+MHBD]]4&0#!*%U:H&Q!5\M#C^7Y05J8#<-'U5FRZ8M]<U!@@A4KJX+Z#A)V6Q
+M=TV$F0,]@J-S;,-)PTC/NM[K%8'3KK1=/;)WV)5\W;2(.ZSS;:>J1."5:K63
+MB-33"F]6GP5Q7'ED[PJ8JM+Y!W2K6]700"9A9U5M:F2-/RJC4+\&#]%!<P6D
+M@*@VXK,;$%*I?#\IY5@-[@UWJ^%#7<TCG&RB=<MU;IA<,=Z79^$T*76@TT-1
+M'R5'TFI_Z8MG"GZH6NMW1)"K=$$-<X1"F7PGU:.XSNG)]#C(PGU-=(+=D#IX
+M&#PI5AFL)$A/Y9]TFS&/W3Y_V5G<=7!7+8HF1@B=]EY_^(MJUU.H]HR.>Y7H
+M5=S2")XW7,^*K8U*%0;R)SPKY)N.'COKE'E#7Z*A:!B<%:%FX/8T["9A4^JT
+MO(O:E,-(@]]U-TYWY6C`:5,+N/7G&S1=H\J3G<05U7ARZ#FX<&D#OGA<E0@,
+M9]EK]*<:=1LM(;NO3A?CS>3'<>@]-JM--H[[S\+3M*Y+1,&-ES72;IKVL`)'
+MN5MT*LZ<[*6:>>72^QS#4DD[X4FN2!G[JJTOB8GLI2X8<=Q@SA8TZ2JW5'M?
+M=MI-<7'!(4U`C0&;`=E7HM-;J#JN(;C]58JC0>T_HH)Z=8!P`/U[JVW2ZGD;
+M+,HN+KAH)P5ITX-(-[CE6K#`'1@G_)02!4(GE6)`:0)PJE4^H_=1=I6YR3[R
+MHNJ`&R?C=34W32$MVPJ?6ZFBV&PEP^J6);PZ3PSZ.D4LQ#0._P!$_6&R0^9`
+MX1=!+?\`@E$[2T)^I#52U8@'"XSMQPO+.K-8^CI`XA<_URSECM.^\+HJ;'`C
+MW5>_HM-%T@+=_+O'+=(N'6U=K3JP<P5WWAOJ8\IIU;#NO/\`K5%UO<:Q@:E<
+MZ/U(TF-!='$CNNV%]HYY<5['X;J.KU14)!$KIJ-3,`_JN#\"WH-FPATF%UMK
+M5!`,Y.5BQO&_&I)#ICZA7;&K.'$`K+IO);$J:D_R^=NRPZ;;FK`G>$P@YG*J
+M6U<.&2(4X>"!$9]UG3<H*S=\!5JS);&`5<J9!RH7,$&=PLJSJ[0`=4+-ZCB@
+M[$>ZV;FFPZ@=BLKJ[!Y+@WZ*Q*SK.F8U`XF)1U*32XQ"EZ?2/D`]T=>G#HXX
+M1&<7NI'\V!Q"N6=X6ELF9S!45:D2Z`,E5JE/2[&",K4J5U%E=A])L'?]%HV]
+M66@3NN+M+MU&J`[8+>Z;?->&RX!+$E=`Q_$J859:/?A9M*J'`:73/96:3IS.
+MRPZ'OS-%P!E'TADT_A17#AY)4W29%(P0I!?;@'V1-=GE1S]$3=.)("JI(,S^
+MZ%QW#M]DY+8WW4;S`B1\H*G47``CE1],`UDCOPBZ@X!AR%%TQ_/<\*4:E-V)
+MTHO4<[*)CL"%(TXGNFC9.<`(Q/=$'PTB5&Z9G&$%28[JB34P-=.>ZR>JN:*G
+M;*OU'::1XX63U1\U0`('LH59MG#R0)&`DYWJWP5#1GRP,YY3N,.P3ME)2IQ4
+M'!^B37^M0%VT)B\@S@#A!:+AW3L><*&0<1N$F'2X23'RE%L.YF>Z;4`2?[JN
+M7^DQ*(/P)@H)=?`!^4=-X+<[RH`Y)K@,JANHP*9SB%'8QY>#LFZ@_P#DD$'Y
+M4=@[^6=]E:D67:B<NWPDTG5_=1ZCPGU'G=!8:Z`F+LG"A:21O"1=F.$$CC.[
+MH3<^ZCJ.Q]$U-V!`03TW2<(JQ;H,Y(P%`UWJE&[;O[*Q%`NB]B8DJVXR/251
+MNSINFD#G[JW3/\H.D!3Z?#N,L@1"83VR$!C48*1/JW6@-3#MDP,;Y0UIR2F9
+M')W[HB1Q`9@E0UZA((RC<<?N57>9G,951#6$_P!U4<!K<2..%:J`Y!.%#5RZ
+M08[JHP.JM\J^%08RKU-VN@UT3(W4/B.G_*\S<A-85&NLFP<A6][2='N)`('*
+MS;EDF3WV6E7/HQ]U0NYTG)A5*J&DPDM(F2H:UI3,X&5*\Z3@F9Y3N(?3+MHP
+MIH95U8AQ);QV51]K5:202#)@K8>88=,QRJ]1WJQVV5T;8M\VNRA4D$X5'I=W
+M4;4<#N"8"Z&[:W09SC*Q:%)C>H/$R'$J7>EG:9MV7&2WY4E&Z:1$[)A;L\QQ
+M@9PDVT&J1O\`NFUFEFFYI9.I%+?\85=ML^-T_P##/[K&W3U>?U'.IO`/T69>
+MO\T/#I@<+7T4WTGM>\-<!(QNLKJ5!\%P@#OV73_*\TT\X\;T!2Z@^IOJS[[K
+M*LVP!B#J6_XX:Y];.=(R>%S=*J]M00/RY/NN&/%T]TN\8WK<^EI<TNB%K6+V
+MN+3!(`[K"LJKG-`.0=BM;I]0Z@20(A>G&N&3I>FO+;A@:YP&"(."NBMKJ:@;
+M+=+<8*YKHU:=,P=(6E;^:UX>X`P9PMWAYLG46Q,B<M.1[)7]8,ID1I.V%1L;
+M@AK9,\917-8UKAK6F8=)@%8L)?BYTR6TA+8<[)5@@NR<SQV5:A5DP9AN%8ID
+MEATX6=-2HJ;=-WJ/!6C1,CZ+.>?YD.:<G>5=M7%D#@H;'6UBF2=O90522PQO
+M&P5BK4EL$*O6)R-(AR+L]&12C:5E^(7PRF`[=X!$;Y6C.BC)9!]C,K)O#YE7
+M46R`X1/=3+IFV.SZ"7'H]$$02!CA7*P+J7T5;H'_`.44]P(5UP::6VZY3MSP
+MJC5;#Q,#B945>D#D;=E<(!(&)35F12F1]%T=]N8Z[9BI,M&W*Y:Z;4MK@B3H
+M:X;KT"ZH!U-Q+<KENOV;2UX#2!*DOK5LE=/X#ZF&T:;=63&)7HG2[EKZ33(S
+M[KPSP[U#^'NPR'-CNO4_"M^*MNPZAMRNV4W'*<73MK>J(`!D[X4XJ$C$A9-K
+M7Q\8PKC*DLP9D+C765?H7!:[2=CRK]O5&D#NL4/`$SE6K.YAP`4:VU]6(E-N
+M#PH*+PX27$`YW4@>"["S6Y456F223A9G5VC0["U:SN%E]0<'5`SDE016E/3;
+M"!&.Z&H-1DC?=6PS32;.T0HG!H<?\TA5"HT:H`V]U7K,W)P>RT*K?48C?ZJ*
+MM3EL@_,J[33*?3.K41"*G6=3J`M5FK3(SLJSQ`WRK*EC9Z;?D:6..8W6S;7;
+M2!#IGW7&4ZFDB)'LK]M=EH&8@)4G#I;JN#2)D#ZJWTJN/(&09]UR]3J`>UK-
+M6^`MSI&;=N8E2\++MK^=VVV1LK>KC*H.<X")QV0FJ01+I4;:1JC$;]D+JA._
+M[J@;@-<&NJ-DG`E$:GJW'S*`>IU/1NAZ62*8,[E5NIU"6&"$73'N=2:`=^4J
+M1K4ZO!/ZHV5H'YC$JD"<P90&JX$RD%\5B[,H75M+M_NJ3*CBZ)PE5<X@Y&%5
+M7*E8%D;K'ZC7!J1/*F=4=P5FWM7^<)G!V4O2?6I0J#R1C]4G%N_'*K47#R@0
+M>)"/6",G=1I*7[ANZ1=&5`7PW!A(O^I518;5)[(M6TE56N``(")M01ML@N!P
+M@0=TXRV9"K!\B$8?!03AS28!D]D;2.3]%5UQSGV1!Y@[_P":H74'?RS$=H45
+MBXMIP"`=U'?/EARFLG2W$B$OPBTY\&-^4P=F4$CG?N@U0X[0B++7$'Y2>[B5
+M`U_<)R\[$G"HDJ.(;,IZ;QIB,PH''N8RE3)F)*@G#R3'',*<&6B`53U28W^B
+ME;4<&C*HK=5!:=4;*6SJAUL#]"HNI^JD>?[*'IK_`$P3LE2+-5^<?,RF:<@G
+M=!4(U'GV3-B-U055Q+C!PA!`;G=*<!,XB<G*(,/.G;<('@<G=(.`$S,>ZAJN
+M$$QE6%!4_.<X]U7J.;J)DQSE'4)+]XGA152V8G9:95.K--6T<`($8[E972GQ
+MKIG:=I6U<^J@0(_NL"B2SJ+VD[DI>CZOUI`(!PJ542\R(5QS<;Y*IW1C$F=E
+M652NS\QF>5%2=#H.WRK#SCW5:!)SLBCK-;HB9"INC.^^59>3IWSL0H:[=+"1
+M^Z(K5(/YLK)NJIHWIIZ1I>9)T@G[Q*UZPEL3GV67UNBXT!4$XSNEG&EG%3R"
+MT%G92VY)!(WXA4[.MKMF'^IHC=3VKH):5)>%URL!S>93ZF^ZCAO8E*&]BG#;
+MSU].##9PH:E%AR>-YQ*NG<J/AWPFWGLURX7Q'T]MS6N',88:#.%PUQ;BG7<U
+MP.Y^B]-J?^WN_P#\2\_ZO_[I_P`KCEQD]OBN\0].AKH)P1NM6T(UM:`2%E6W
+MYA\K4L_R!=I4LVZ3HWDMI:P7:B(&<#Y6[TP4_+&LDEQX,RL#I7Y`NCZ?_P`T
+M?"Z6O)FN>7%+6#('TE0=*+_.?4<##R0`<PKM/_V[_A5+7_V_W6-\Z9G3086B
+M7%P@[@*]9AKFB(G>5E'\K?A:5CO]%?B]4]=O\V3NK%$0S)GLJ];=6:/Y6_19
+MC6^3U`00.Z3@=6G.VZ=WYDF_\QORFC?*&^;IHR"1'M*RJM)PI"I/YGXE:W5/
+M^0LZO_[+_P#&%G*_$RX==T)I;TFG)G&5=R:>TJGT7_\`+6?`_966;!8P[KEC
+M"PT^Y4PIZJ$]U!5W5NG_`,KZ+HZJ5:B13.096%U2U+R8`RNDK?\`*/RLFZV*
+MS>G2.#ZO;.MK[S`3`,KJO!?5X;387Q[2L+Q9_P"X8@\'?\_ZKKX[PQGC]>Q=
+M&O&U*;3KS&RV:%0:1ZOU7(^'/RM736WY&K.4Y7%?U%WI_9)KG-=$_5#;[!)_
+MY'+G6XT+2O,`NV]U=8_TR#ORL:WV/PM*A_R6I5B6I4'EJA3FK>DQLK5S_P`L
+MJ/IGYS\KG;J-3E.6C1![1"@>V1&_NK+]C\J`_F5-*[VD/.,;[H'@.:0)^B.X
+M_*F=_P`GZ(:5*U,M!Q/RJE5AR5H5/RGZ*H[^KZIM*I5&&<843JKV'NK=;\BI
+MW'YEK:6!97?_`!5/W*[/I%QIM&#&RX1O_O&?*Z[IO_MF_16I.&UYX+2F+F$`
+MJK3_`.2/E)OY?J5C36UN*<C(U)Y^L*&G^9ORB'YOJ4L:5;]I<TY.^REZ;(IY
+M,*.[Y16FP^%/HO!TXGZI1G*C9^<IV[JU(,``R("8D:D)V1'<?"BHZ\"7+%O7
+M`W7"V7_E6#=?^^<K>D^K]*I#!&Z/63_4HJ?_`"_HC;^8_`5THR\8@)]<[2@&
+M[OA*EL4T"-4#&X"0>.^_91'_`)A3MW;\J"PUY`B81!Y.02H'?\M'0V^B"5KB
+M#*E;4QO!]E`/S?1,W<J]ANH/&G=/TXQ3,S]5!>?F^BDZ?^1+\2+#R4QC5,IG
+M[_5#4_YGV5@<.`,GZ(I,@]U$/^8%,>/HJD,YQWDIVNS$H*WY/JDS\P45('>K
+M!PI`YI&V-L*!OYDXW*J&OS%$MD0JG3':JQ$JS=_^V<J?2_\`W04I%UY_F8(3
+M.=&1V2J_G*&K_P`L*@P_T=IY0.B<F)3-_($]7=JJ%,"%%5<'-@2"B?\`D4#O
+MS'X50%0D=_E!4=+4]3\H^4Q_*541.,MX*P.I`T>HM?L"<RM]NRP_$G_-^H5^
+M)]6`Z6`MSB5%<`NSS"*V_P#;,^$U;=2=(IOB8^JJ/<6U<#G=6G_\PJI=_F"T
+MARYK21.ZA<09`4K?Z5'<_D*;57J-QMD=U!<L\R@ZF>5/PAY*#%LIIWCJ)(`)
+I("O4!#X(DJA<_P#YE_\`B6G2_P">U9ZNFXE:#I_,$^EW^,*4;)(T_]EK
+`
+end
+
+
diff --git a/rt/lib/t/data/russian-subject-no-content-type b/rt/lib/t/data/russian-subject-no-content-type
new file mode 100644
index 0000000..03d95b8
--- /dev/null
+++ b/rt/lib/t/data/russian-subject-no-content-type
@@ -0,0 +1,42 @@
+Return-Path: <mitya@fling-wing.example.com>
+X-Real-To: <mitya@second.example.com>
+Received: from [194.87.5.31] (HELO sinbin.example.com)
+ by cgp.second.example.com (CommuniGate Pro SMTP 4.0.5/D)
+ with ESMTP-TLS id 69661026 for mitya@second.example.com; Wed, 18 Jun 2003 11:14:49 +0400
+Received: (from daemon@localhost)
+ by sinbin.example.com (8.12.8/8.11.6) id h5I7EfOj096595
+ for mitya@second.example.com; Wed, 18 Jun 2003 11:14:41 +0400 (MSD)
+ (envelope-from mitya@fling-wing.example.com)
+Received: from example.com by sinbin.example.com with ESMTP id h5I7Ee8K096580;
+ (8.12.9/D) Wed, 18 Jun 2003 11:14:40 +0400 (MSD)
+X-Real-To: <mitya@second.example.com>
+Received: from [194.87.0.31] (HELO mail.example.com)
+ by example.com (CommuniGate Pro SMTP 4.1b7/D)
+ with ESMTP id 76217696 for mitya@example.com; Wed, 18 Jun 2003 11:14:40 +0400
+Received: by mail.example.com (CommuniGate Pro PIPE 4.1b7/D)
+ with PIPE id 63920083; Wed, 18 Jun 2003 11:14:40 +0400
+Received: from [194.87.5.69] (HELO fling-wing.example.com)
+ by mail.example.com (CommuniGate Pro SMTP 4.1b7/D)
+ with ESMTP-TLS id 63920055 for mitya@example.com; Wed, 18 Jun 2003 11:14:38 +0400
+Received: from fling-wing.example.com (localhost [127.0.0.1])
+ by fling-wing.example.com (8.12.9/8.12.6) with ESMTP id h5I7Ec5R000153
+ for <mitya@example.com>; Wed, 18 Jun 2003 11:14:38 +0400 (MSD)
+ (envelope-from mitya@fling-wing.example.com)
+Received: (from mitya@localhost)
+ by fling-wing.example.com (8.12.9/8.12.6/Submit) id h5I7Ec0J000152
+ for mitya@example.com; Wed, 18 Jun 2003 11:14:38 +0400 (MSD)
+Date: Wed, 18 Jun 2003 11:14:38 +0400 (MSD)
+From: "Dmitry S. Sivachenko" <mitya@fling-wing.example.com>
+Message-Id: <200306180714.h5I7Ec0J000152@fling-wing.example.com>
+To: mitya@example.com
+Subject: ÔÅÓÔ ÔÅÓÔ
+X-Spam-Checker-Version: SpamAssassin 2.60-cvs-mail.demos (1.193-2003-06-13-exp)
+X-Spam-Level: +
+X-Spam-Status: No, hits=1.0 required=5.0 tests=SUBJ_ILLEGAL_CHARS autolearn=no
+ version=2.60-cvs-mail.demos
+X-Spam-Report: * SUBJ_ILLEGAL_CHARS 1.0 (Subject contains too many raw illegal characters)
+
+Content-Length: 6
+
+ôåóô
+
diff --git a/rt/lib/t/data/text-html-in-russian b/rt/lib/t/data/text-html-in-russian
new file mode 100644
index 0000000..b965b1b
--- /dev/null
+++ b/rt/lib/t/data/text-html-in-russian
@@ -0,0 +1,87 @@
+From rickt@other-example.com Tue Jun 17 20:39:13 2003
+Return-Path: <rickt@other-example.com>
+X-Original-To: info
+Delivered-To: mitya@vh.example.com
+Received: from example.com (mx.example.com [194.87.0.32])
+ by vh.example.com (Postfix) with ESMTP id 8D77B16E6BD
+ for <info>; Tue, 17 Jun 2003 20:39:05 +0400 (MSD)
+Received: from hotline@example.com
+ by example.com (CommuniGate Pro GROUP 4.1b7/D)
+ with GROUP id 76033026; Tue, 17 Jun 2003 20:38:00 +0400
+Received: by example.com (CommuniGate Pro PIPE 4.1b7/D)
+ with PIPE id 76033052; Tue, 17 Jun 2003 20:38:00 +0400
+Received: from [217.132.49.75] (HELO compuserve.com)
+ by example.com (CommuniGate Pro SMTP 4.1b7/D)
+ with SMTP id 76032971 for info@example.com; Tue, 17 Jun 2003 20:37:41 +0400
+Date: Wed, 18 Jun 2003 01:41:01 +0000
+From: Ó÷åáíûé Öåíòð <rickt@other-example.com>
+Subject: Ïðèãëàøàåì ðóêîâîäèòåëÿ, íà÷àëüíèêîâ ïîäðàçäåëåíèé íà òðåíèíã YXLWLJ3LPT9UHuLyGTzyuKQc06eIZ96Y6RVTCZFt
+To: Info <info@example.com>
+References: <0ID97EGL951H1907@example.com>
+In-Reply-To: <0ID97EGL951H1907@example.com>
+Message-ID: <HDE46LIK8GGJJ72I@other-example.com>
+MIME-Version: 1.0
+Content-Type: text/html; charset=Windows-1251
+Content-Transfer-Encoding: 8bit
+X-Spam-Flag: YES
+X-Spam-Checker-Version: SpamAssassin 2.60-cvs-jumbo.demos (1.190-2003-06-01-exp)
+X-Spam-Level: ++++++++++++++
+X-Spam-Status: Yes, hits=14.9 required=5.0 tests=BAYES_99,DATE_IN_FUTURE_06_12
+ FROM_ILLEGAL_CHARS,HTML_10_20,HTML_FONTCOLOR_UNKNOWN,HTML_FONT_BIG
+ MIME_HTML_ONLY,RCVD_IN_NJABL,SUBJ_HAS_SPACES,SUBJ_HAS_UNIQ_ID
+ SUBJ_ILLEGAL_CHARS autolearn=no version=2.60-cvs-jumbo.demos
+X-Spam-Report: 14.9 points, 5.0 required;
+ * 2.3 -- Subject contains lots of white space
+ * 1.0 -- BODY: HTML font color is unknown to us
+ * 0.3 -- BODY: FONT Size +2 and up or 3 and up
+ [score: 1.0000]
+ * 2.8 -- BODY: Bayesian classifier spam probability is 99 to 100%
+ * 1.0 -- BODY: Message is 10% to 20% HTML
+ * 1.0 -- From contains too many raw illegal characters
+ * 1.0 -- Subject contains a unique ID
+ * 1.0 -- Subject contains too many raw illegal characters
+ * 1.2 -- Date: is 6 to 12 hours after Received: date
+ [217.132.49.75 listed in dnsbl.njabl.org]
+ * 1.2 -- RBL: Received via a relay in dnsbl.njabl.org
+ * 2.0 -- Message only has text/html MIME parts
+Status: RO
+Content-Length: 2743
+Lines: 36
+
+<html><body><basefont face="times new roman, times, serif" size="2">
+<center>Ó÷eáíûé Öeíòp "ÊÀÄÐÛ ÄÅËÎÂÎÃÎ ÌÈÐÀ" ïpèãëaøaeò ía òpeíèíã:<br>
+<font size="5"><b>ÌÎÒÈÂÀÖÈß ÊÀÊ ÈÍÑÒÐÓÌÅÍÒ ÓÏÐÀÂËÅÍÈß ÏÅÐÑÎÍÀËÎÌ</b></font><br>
+<font color="red"><b>19 èþíÿ 2003 ã.</b></font><br>
+<b><i>Òpeíèíã ïpeäíaçía÷eí äëÿ âûcøeão è cpeäíeão óïpaâëeí÷ecêoão ïepcoíaëa.</i></b><br></center><br>
+<p align="justify"><b>Òpeíep: Áopìoòoâ Ïaâeë.</b> Ïpaêòè÷ecêèé ïcèõoëoã, oïûò paáoòû áoëee 10 ëeò â oáëacòè ïcèõoëoãèè è áèçíec-òpeíèíãoâ. Àâòop pÿäa ïóáëèêaöèé è ìeòoäè÷ecêèõ ïocoáèé paçëè÷íûõ íaïpaâëeíèé ïcèõoëoãèè, â òoì ÷ècëe: “Òeõíoëoãèÿ äeëoâoão oáùeíèÿ”, “Òeõíèêè è ïpèeìû ýôôeêòèâíûõ ïepeãoâopoâ”, “Ñòpaòeãèè ôopìèpoâaíèÿ êopïopaòèâíoão èìèäæa” è äp. Çaêoí÷èë ËÃÓ ôaêóëüòeò coöèaëüíoé ïcèõoëoãèè, Ðoccèécêóþ Àêaäeìèþ ãocóäapcòâeííoé cëóæáû ïpè Ïpeçèäeíòe ÐÔ, êópcû MBA.<br><br>
+<b><u>Öeëè òpeíèíãa:</u></b><br>
+1. Îcâoèòü ïpèeìû óïpaâëeíèÿ ìoòèâaöèeé;<br>
+2. Ïoëó÷èòü ïpaêòè÷ecêèe íaâûêè ìoòèâaöèè ïepcoíaëa ê paáoòe;<br>
+3. Îcâoèòü ocíoâíûe íaâûêè êoìaíäooápaçoâaíèÿ;<br>
+4. Îâëaäeòü ïpaêòè÷ecêèìè ìeòoäaìè coçäaíèÿ è ócèëeíèÿ paáo÷eé ìoòèâaöèè, êoìaíäooápaçoâaíèÿ.<br><br>
+<b><u>Çaäa÷è òpeíèíãa:</u></b><br>
+&nbsp;- Îcâoèòü ìeòoäû ïoáóæäeíèÿ äpóãèõ ëþäeé ê âûïoëíeíèþ oïpeäeëeííoé äeÿòeëüíocòè;<br>
+&nbsp;- Íaó÷èòücÿ íaïpaâëÿòü ïoáóæäeíèÿ coòpóäíèêoâ â cooòâeòcòâèe c çaäa÷aìè opãaíèçaöèè.<br><br>
+<b><u>Ñoäepæaíèe ïpoãpaììû:</u></b><br>
+<b>I. Ìaòepèaëüíûe è íeìaòepèaëüíûe ôopìû ìoòèâaöèè:</b><br>
+1. Ìecòo è poëü ìoòèâaöèè â óïpaâëeíèè ïepcoíaëoì;<br>
+2. Ïpaêòèêa óïpaâëeíèÿ opãaíèçaöèÿìè.<br>
+<b>II. Ïpaêòè÷ecêoe ïpèìeíeíèe ìoòèâaöèè â óïpaâëeíèè ïepcoíaëoì:</b><br>
+1. Àíòèìoòèâèpóþùèe pacïopÿæeíèÿ;<br>
+2. Ìoòèâaöèÿ è oöeíêa äeÿòeëüíocòè (poëü aòòecòaöèè coòpóäíèêoâ);<br>
+3. Ìoòèâaöèÿ è ïpaêòèêa íaêaçaíèé.<br><br>
+<b><u>Â çaâepøeíèè ïpoãpaììû ó÷acòíèêè cìoãóò:</u></b><br>
+1. Îpèeíòèpoâaòü coòpóäíèêoâ ía äocòèæeíèe oïpeäeëeííoão peçóëüòaòa;<br>
+2. Îâëaäeòü íeoáõoäèìûìè íaâûêaìè óïpaâëeíèÿ ìoòèâaöèeé ïepcoíaëa;<br>
+3. Ïpèìeíÿòü ïoëó÷eííûe çíaíèÿ â ïpaêòèêe óïpaâëeíèÿ ïepcoíaëoì;<br>
+4. Îïpeäeëÿòü èíäèâèäóaëüíûe ocoáeííocòè (ïpeäïo÷òeíèÿ) ìoòèâaöèè coòpóäíèêoâ â opãaíèçaöèè.<br>
+<i> õoäe òpeíèíãa ècïoëüçóeòcÿ paáo÷èé è cïpaâo÷íûé ìaòepèaë ïo ìoòèâaöèè è còèìóëèpoâaíèþ ïepcoíaëa poccèécêèõ êoìïaíèé. Ïo oêoí÷aíèè âûäaeòcÿ cepòèôèêaò.</i><br><br>
+<center>Ïpoäoëæèòeëüíocòü: 1 äeíü, 8 ÷acoâ (äâa ïepepûâa, oáeä)<br>
+<b>Ñòoèìocòü ó÷acòèÿ: 4 700 póáëeé áeç ÍÄÑ.</b><br>
+921-5862, 928-4156, 928-4200, 928-5321</center><br>
+<font size=1> Åcëè èíôopìaöèÿ ïoäoáíoão poäa Âac íe èíòepecóeò è ïo äpóãèì âoïpocaì - ïèøèòe: <a href="mailto:motiv@mailje.nl">seminar</a></font>
+<br><font size="1" color="#ffffff">3ZkRPb60QBbiHef1IRVl</font>
+</body></html>
+
+
+
diff --git a/rt/lib/t/data/text-html-with-umlaut b/rt/lib/t/data/text-html-with-umlaut
new file mode 100644
index 0000000..90e5d3f
--- /dev/null
+++ b/rt/lib/t/data/text-html-with-umlaut
@@ -0,0 +1,35 @@
+Return-Path: <gst@example.com>
+Delivered-To: j@pallas.eruditorum.org
+Received: from vis.example.com (vis.example.com [212.68.68.251])
+ by pallas.eruditorum.org (Postfix) with SMTP id 59236111C3
+ for <jesse@example.com>; Thu, 12 Jun 2003 02:14:44 -0400 (EDT)
+Received: (qmail 29541 invoked by uid 502); 12 Jun 2003 06:14:42 -0000
+Received: from sivd.example.com (HELO example.com) (192.168.42.1)
+ by 192.168.42.42 with SMTP; 12 Jun 2003 06:14:42 -0000
+Received: received from 172.20.72.174 by odie.example.com; Thu, 12 Jun 2003 08:14:27 +0200
+Received: by mailserver.example.com with Internet Mail Service (5.5.2653.19) id <LJSB7T54>; Thu, 12 Jun 2003 08:14:39 +0200
+Message-ID: <50362EC956CBD411A339009027F6257E013DD495@mailserver.example.com>
+Date: Thu, 12 Jun 2003 08:14:39 +0200
+From: "Stever, Gregor" <gst@example.com>
+MIME-Version: 1.0
+X-Mailer: Internet Mail Service (5.5.2653.19)
+To: "'jesse@example.com'" <jesse@example.com>
+Subject: An example of mail containing text-html with an umlaut in the content
+Date: Thu, 12 Jun 2003 08:14:39 +0200
+Content-Type: text/html;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML><HEAD>
+<META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; charset=3Diso-8859-=
+1">
+
+
+<META content=3D"MSHTML 6.00.2800.1170" name=3DGENERATOR></HEAD>
+<BODY>
+<DIV><FONT face=3DArial><FONT size=3D2>Hello,<BR><BR>ist this kind of Messa=
+ges, that=20
+causes rt to crash.<BR><BR>Mit freundlichen Gr=FC=DFen<BR>Gregor=20
+Stever&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ^^causes Error<SPAN=20
+class=3D975501206-12062003>!!</SPAN></FONT></FONT></DIV></BODY></HTML>
diff --git a/rt/lib/t/regression/00placeholder b/rt/lib/t/regression/00placeholder
new file mode 100644
index 0000000..0afc604
--- /dev/null
+++ b/rt/lib/t/regression/00placeholder
@@ -0,0 +1 @@
+1;
diff --git a/rt/lib/t/regression/mime_tests b/rt/lib/t/regression/mime_tests
new file mode 100644
index 0000000..26e4dbf
--- /dev/null
+++ b/rt/lib/t/regression/mime_tests
@@ -0,0 +1,19 @@
+use RT::Ticket;
+use RT::Queue;
+
+use MIME::Parser;
+use File::Temp;
+use RT::EmailParser;
+
+open (HANDLE, "data/nested-mime-sample");
+my $parser = RT::EmailParser->new()
+ $parser->ParseMIMEEntityFromFileHandle(\*HANDLE);
+my $entity = $parser->Entity;
+
+my $q = RT::Queue->new($RT::SystemUser);
+$q->Load('general');
+ok ($q->Id, "Queue is loaded");
+my $Ticket = RT::Ticket->new($RT::SystemUser);
+my ($tid, $ttid, $msg) =$Ticket->Create( Queue => $q->Id, Subject => "Nested mime test", MIMEObj => $entity);
+ok ($tid, $msg);
+ok($Ticket->Id);
diff --git a/rt/m4/rt_enable_layout.m4 b/rt/m4/rt_enable_layout.m4
new file mode 100644
index 0000000..cadec1c
--- /dev/null
+++ b/rt/m4/rt_enable_layout.m4
@@ -0,0 +1,36 @@
+dnl
+dnl @synopsis RT_ENABLE_LAYOUT()
+dnl
+dnl Enable a specific directory layout for the installation to use.
+dnl This configures a command-line parameter that can be specified
+dnl at ./configure invocation.
+dnl
+dnl The use of this feature in this way is a little hackish, but
+dnl better than a heap of options for every directory.
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 code.
+dnl
+
+AC_DEFUN([RT_ENABLE_LAYOUT],[
+AC_ARG_ENABLE(layout,
+ AC_HELP_STRING([--enable-layout=LAYOUT],
+ [Use a specific directory layout (Default: RT3)]),
+ LAYOUT=$enableval)
+
+if test "x$LAYOUT" = "x"; then
+ LAYOUT="RT3"
+fi
+RT_LAYOUT($srcdir/config.layout, $LAYOUT)
+AC_MSG_CHECKING(for chosen layout)
+if test "x$rt_layout_name" = "xno"; then
+ if test "x$LAYOUT" = "xno"; then
+ AC_MSG_RESULT(none)
+ else
+ AC_MSG_RESULT($LAYOUT)
+ fi
+ AC_MSG_ERROR([a valid layout must be specified (or the default used)])
+else
+ AC_SUBST(rt_layout_name)
+ AC_MSG_RESULT($rt_layout_name)
+fi
+])
diff --git a/rt/m4/rt_expand_var.m4 b/rt/m4/rt_expand_var.m4
new file mode 100644
index 0000000..cec884a
--- /dev/null
+++ b/rt/m4/rt_expand_var.m4
@@ -0,0 +1,18 @@
+dnl
+dnl @synopsis RT_EXPAND_VAR(baz, $fraz)
+dnl
+dnl Iteratively expands the second parameter, until successive iterations
+dnl yield no change. The result is then assigned to the first parameter.
+dnl
+dnl This code is heavily borrowed from the Apache 2 codebase.
+dnl
+
+AC_DEFUN([RT_EXPAND_VAR],[
+ ap_last=''
+ ap_cur='$2'
+ while test "x${ap_cur}" != "x${ap_last}"; do
+ ap_last="${ap_cur}"
+ ap_cur=`eval "echo ${ap_cur}"`
+ done
+ $1="${ap_cur}"
+])
diff --git a/rt/m4/rt_layout.m4 b/rt/m4/rt_layout.m4
new file mode 100644
index 0000000..393b321
--- /dev/null
+++ b/rt/m4/rt_layout.m4
@@ -0,0 +1,74 @@
+dnl
+dnl @synopsis RT_LAYOUT(configlayout, layoutname)
+dnl
+dnl This macro reads an Apache-style layout file (specified as the
+dnl configlayout parameter), and searches for a specific layout
+dnl (named using the layoutname parameter).
+dnl
+dnl The entries for a given layout are then inserted into the
+dnl environment such that they become available as substitution
+dnl variables. In addition, the rt_layout_name variable is set
+dnl (but not exported) if the layout is valid.
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 codebase.
+dnl
+
+AC_DEFUN([RT_LAYOUT],[
+ if test ! -f $srcdir/config.layout; then
+ AC_MSG_WARN([Layout file $srcdir/config.layout not found])
+ rt_layout_name=no
+ else
+ pldconf=./config.pld
+ $PERL -0777 -p -e "\$layout = '$2';" -e '
+ s/.*<Layout\s+$layout>//gims;
+ s/\<\/Layout\>.*//s;
+ s/^#.*$//m;
+ s/^\s+//gim;
+ s/\s+$/\n/gim;
+ s/\+$/\/rt3/gim;
+ # m4 will not let us just use $1, we need @S|@1
+ s/^\s*((?:bin|sbin|libexec|data|sysconf|sharedstate|localstate|lib|include|oldinclude|info|man)dir)\s*:\s*(.*)$/@S|@1=@S|@2/gim;
+ s/^\s*(.*?)\s*:\s*(.*)$/\(test "x\@S|@@S|@1" = "xNONE" || test "x\@S|@@S|@1" = "x") && @S|@1=@S|@2/gim;
+ ' < $1 > $pldconf
+
+ if test -s $pldconf; then
+ rt_layout_name=$2
+ . $pldconf
+ changequote({,})
+ for var in prefix exec_prefix bindir sbindir \
+ sysconfdir mandir libdir datadir htmldir \
+ localstatedir logfiledir masonstatedir \
+ sessionstatedir customdir custometcdir customhtmldir \
+ customlexdir customlibdir manualdir; do
+ eval "val=\"\$$var\""
+ val=`echo $val | sed -e 's:\(.\)/*$:\1:'`
+ val=`echo $val |
+ sed -e 's:[\$]\([a-z_]*\):${\1}:g'`
+ eval "$var='$val'"
+ done
+ changequote([,])
+ else
+ rt_layout_name=no
+ fi
+ #rm $pldconf
+ fi
+ RT_SUBST_EXPANDED_ARG(prefix)
+ RT_SUBST_EXPANDED_ARG(exec_prefix)
+ RT_SUBST_EXPANDED_ARG(bindir)
+ RT_SUBST_EXPANDED_ARG(sbindir)
+ RT_SUBST_EXPANDED_ARG(sysconfdir)
+ RT_SUBST_EXPANDED_ARG(mandir)
+ RT_SUBST_EXPANDED_ARG(libdir)
+ RT_SUBST_EXPANDED_ARG(datadir)
+ RT_SUBST_EXPANDED_ARG(htmldir)
+ RT_SUBST_EXPANDED_ARG(manualdir)
+ RT_SUBST_EXPANDED_ARG(localstatedir)
+ RT_SUBST_EXPANDED_ARG(logfiledir)
+ RT_SUBST_EXPANDED_ARG(masonstatedir)
+ RT_SUBST_EXPANDED_ARG(sessionstatedir)
+ RT_SUBST_EXPANDED_ARG(customdir)
+ RT_SUBST_EXPANDED_ARG(custometcdir)
+ RT_SUBST_EXPANDED_ARG(customhtmldir)
+ RT_SUBST_EXPANDED_ARG(customlexdir)
+ RT_SUBST_EXPANDED_ARG(customlibdir)
+])dnl
diff --git a/rt/m4/rt_subst_expanded_arg.m4 b/rt/m4/rt_subst_expanded_arg.m4
new file mode 100644
index 0000000..02002b0
--- /dev/null
+++ b/rt/m4/rt_subst_expanded_arg.m4
@@ -0,0 +1,14 @@
+dnl
+dnl @synopsis RT_SUBST_EXPANDED_ARG(var)
+dnl
+dnl Export (via AC_SUBST) a given variable, along with an expanded
+dnl version of the variable (same name, but with exp_ prefix).
+dnl
+dnl This code is heavily borrowed *cough* from the Apache 2 source.
+dnl
+
+AC_DEFUN([RT_SUBST_EXPANDED_ARG],[
+ RT_EXPAND_VAR(exp_$1, [$]$1)
+ AC_SUBST($1)
+ AC_SUBST(exp_$1)
+])
diff --git a/rt/sbin/extract-message-catalog b/rt/sbin/extract-message-catalog
new file mode 100644
index 0000000..a7ba633
--- /dev/null
+++ b/rt/sbin/extract-message-catalog
@@ -0,0 +1,246 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+# Portions Copyright 2002 Autrijus Tang <autrijus@autrijus.org>
+
+use strict;
+
+use File::Find;
+use File::Copy;
+use Regexp::Common;
+use Carp;
+
+use vars qw($DEBUG $FILECAT);
+
+$DEBUG = 1;
+
+@ARGV = <lib/RT/I18N/*.po> unless @ARGV;
+
+$FILECAT = {};
+
+# extract all strings and stuff them into $FILECAT
+File::Find::find( { wanted => \&extract_strings_from_code, follow => 1 }, '.' );
+
+# ensure proper escaping and [_1] => %1 transformation
+foreach my $str ( sort keys %{$FILECAT} ) {
+ my $entry = $FILECAT->{$str};
+ my $oldstr = $str;
+
+ $str =~ s/\\/\\\\/g;
+ $str =~ s/\"/\\"/g;
+ $str =~ s/((?<!~)(?:~~)*)\[_(\d+)\]/$1%$2/g;
+ $str =~ s/((?<!~)(?:~~)*)\[([A-Za-z#*]\w*),([^\]]+)\]/"$1%$2(".escape($3).")"/eg;
+ $str =~ s/~([\[\]])/$1/g;
+
+ delete $FILECAT->{$oldstr};
+ $FILECAT->{$str} = $entry;
+}
+
+# update all language dictionaries
+foreach my $dict (@ARGV) {
+ $dict = "lib/RT/I18N/$dict.po" unless -f $dict or $dict =~ m!/!;
+
+ my $lang = $dict;
+ $lang =~ s|.*/||;
+ $lang =~ s|\.po$||;
+
+ update($lang, $dict);
+}
+
+
+# {{{ pull strings out of the code.
+
+sub extract_strings_from_code {
+ my $file = $_;
+
+ local $/;
+ return if ( -d $_ );
+ return if ( $File::Find::dir =~ 'lib/blib|lib/t/autogen|var|m4|local' );
+ return if ( /\.po$|\.bak$|~|,D|,B$|extract-message-catalog$/ );
+ return if ( /^[\.#]/ );
+ return if ( -f "$_.in" );
+
+ print "Looking at $File::Find::name\n";
+ my $filename = $File::Find::name;
+ $filename =~ s'^\./'';
+ $filename =~ s'\.in$'';
+
+ unless (open _, $file) {
+ print "Cannot open $file for reading ($!), skipping.\n";
+ return;
+ }
+
+ $_ = <_>;
+
+ # Mason filter: <&|/l>...</&>
+ my $line = 1;
+ while (m!\G.*?<&\|/l(.*?)&>(.*?)</&>!sg) {
+ my ( $vars, $str ) = ( $1, $2 );
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $str =~ s/\\'/\'/g;
+ #print "STR IS $str\n";
+ push @{ $FILECAT->{$str} }, [ $filename, $line, $vars ];
+ }
+
+ # Localization function: loc(...)
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?\bloc$RE{balanced}{-parens=>'()'}{-keep}/sg) {
+ my $match = $1;
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+
+ my ( $vars, $str );
+ if ( $match =~
+ /\(\s*($RE{delimited}{-delim=>q{'"}}{-keep})(.*?)\s*\)$/ ) {
+
+ $str = substr( $1, 1, -1 ); # $str comes before $vars now
+ $vars = $9;
+ }
+ else {
+ next;
+ }
+
+ $vars =~ s/[\n\r]//g;
+ $str =~ s/\\'/\'/g;
+
+ push @{ $FILECAT->{$str} }, [ $filename, $line, $vars ];
+ }
+
+ # Comment-based mark: "..." # loc
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?($RE{delimited}{-delim=>q{'"}}{-keep})[\}\)\],]*\s*\#\s*loc\s*$/smg) {
+ my $str = substr($1, 1, -1);
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $str =~ s/\\'/\'/g;
+ push @{ $FILECAT->{$str} }, [ $filename, $line, '' ];
+ }
+
+ # Comment-based pair mark: "..." => "..." # loc_pair
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?(\w+)\s*=>\s*($RE{delimited}{-delim=>q{'"}}{-keep})[\}\)\],]*\s*\#\s*loc_pair\s*$/smg) {
+ my $key = $1;
+ my $val = substr($2, 1, -1);
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $key =~ s/\\'/\'/g;
+ $val =~ s/\\'/\'/g;
+ push @{ $FILECAT->{$key} }, [ $filename, $line, '' ];
+ push @{ $FILECAT->{$val} }, [ $filename, $line, '' ];
+ }
+
+ close (_);
+}
+# }}} extract from strings
+
+sub update {
+ my $lang = shift;
+ my $file = shift;
+ my ( %Lexicon, %Header);
+ my $out = '';
+
+ unless (!-e $file or -w $file) {
+ warn "Can't write to $lang, skipping...\n";
+ return;
+ }
+
+ print "Updating $lang...\n";
+
+ my @lines;
+ @lines = (<LEXICON>) if open (LEXICON, $file);
+ @lines = grep { !/^(#(:|\.)\s*|$)/ } @lines;
+ while (@lines) {
+ my $msghdr = "";
+ $msghdr .= shift @lines while ( $lines[0] && $lines[0] !~ /^msgid/ );
+ my $msgid = shift @lines;
+ my $msgstr = "";
+ $msgstr .= shift @lines while ( $lines[0] && $lines[0] =~ /^(msgstr|")/ );
+
+ last unless $msgid;
+
+ chomp $msgid;
+ chomp $msgstr;
+ $msgid =~ s/^msgid "(.*)"$/$1/ or warn $msgid;
+ $msgstr =~ s/^msgstr "(.*)"$/$1/ms or warn $msgstr;
+
+ $Lexicon{$msgid} = $msgstr;
+ $Header{$msgid} = $msghdr;
+ }
+
+ my $is_english = ( $lang =~ /^en(?:[^A-Za-z]|$)/ );
+
+ foreach my $str ( sort keys %{$FILECAT} ) {
+ $Lexicon{$str} ||= '';;
+ }
+ foreach ( sort keys %Lexicon ) {
+ my $f = join ( ' ', sort map $_->[0].":".$_->[1], @{ $FILECAT->{$_} } );
+ my $nospace = $_;
+ $nospace =~ s/ +$//;
+
+ if ( !$Lexicon{$_} and $Lexicon{$nospace} ) {
+ $Lexicon{$_} =
+ $Lexicon{$nospace} . ( ' ' x ( length($_) - length($nospace) ) );
+ }
+
+ next if !length( $Lexicon{$_} ) and $is_english;
+
+ my %seen;
+ $out .= $Header{$_} if exists $Header{$_};
+ if ( $f && $f !~ /^\s+$/ ) {
+
+ $out .= "#: $f\n";
+ }
+ elsif ($_) {
+ $out .= "#: NOT FOUND IN SOURCE\n";
+ }
+ foreach my $entry ( grep { $_->[2] } @{ $FILECAT->{$_} } ) {
+ my ( $file, $line, $var ) = @{$entry};
+ $var =~ s/^\s*,\s*//;
+ $var =~ s/\s*$//;
+ $out .= "#. ($var)\n" unless $seen{$var}++;
+ }
+ $out .= "msgid \"$_\"\nmsgstr \"$Lexicon{$_}\"\n\n";
+ }
+
+ open PO, ">$file" or die $!;
+ print PO $out;
+ close PO;
+
+ return 1;
+}
+
+sub escape {
+ my $text = shift;
+ $text =~ s/\b_(\d+)/%$1/;
+ return $text;
+}
+
+__END__
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
diff --git a/rt/sbin/extract_pod_tests b/rt/sbin/extract_pod_tests
new file mode 100644
index 0000000..ed01c7d
--- /dev/null
+++ b/rt/sbin/extract_pod_tests
@@ -0,0 +1,129 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use vars qw($VERSION);
+$VERSION = '0.06';
+
+use Pod::Tests;
+use Symbol;
+
+=pod
+
+=head1 NAME
+
+extract_pod_tests - RT-specific variant of pod2tests
+
+=head1 SYNOPSIS
+
+ pod2test [-Mmodule] [input [output]]
+
+=head1 DESCRIPTION
+
+B<pod2test> is a front-end for Test::Inline. It generates the
+"Bodies" of MakeMaker style .t testing files from embedded tests and
+code examples.
+
+If output is not specified, the resulting .t file will go to STDOUT.
+Otherwise, it will go to the given output file. If input is not
+given, it will draw from STDIN.
+
+If the given file contains no tests or code examples, no output will
+be given and no output file will be created.
+
+=cut
+
+my($infile, $outfile) = @ARGV;
+my($infh,$outfh);
+
+
+if( defined $infile ) {
+ $infh = gensym;
+ open($infh, $infile) or
+ die "Can't open the POD file $infile: $!";
+}
+else {
+ $infh = \*STDIN;
+}
+
+unless ($outfile) {
+ ( my $test = $infile ) =~ s/\.(pm|pod)$//;
+ $test =~ s/^lib\W//;
+ $test =~ s/\W/-/;
+ $test =~ s/\//__/g;
+
+ $outfile = "lib/t/autogen/autogen-$test.t";
+}
+
+
+my $p = Pod::Tests->new;
+$p->parse_fh($infh);
+
+# XXX Hack to put the filename into the #line directive
+$p->{file} = $infile || '';
+
+my @tests = $p->build_tests($p->tests);
+my @examples = $p->build_examples($p->examples);
+
+exit unless @tests or @examples;
+
+
+if( defined $outfile) {
+ $outfh = gensym;
+ open($outfh, ">$outfile") or
+ die "Can't open the test file $outfile: $!";
+}
+else {
+ $outfh = \*STDOUT;
+}
+
+
+
+foreach my $test (@tests, @examples) {
+ print $outfh "$test\n";
+}
+
+print $outfh "1;\n";
+
+=pod
+
+=head1 BUGS and CAVEATS
+
+This is a very simple rough cut. It only does very rudimentary tests
+on the examples.
+
+=head1 AUTHOR
+
+
+
+Based on pod2tests by Michael G Schwern <schwern@pobox.com>
+
+=head1 SEE ALSO
+
+L<Test::Inline>
+
+=cut
+
+1;
diff --git a/rt/sbin/factory b/rt/sbin/factory
new file mode 100644
index 0000000..64b0ae3
--- /dev/null
+++ b/rt/sbin/factory
@@ -0,0 +1,428 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use DBI;
+
+my $database = shift;
+my $namespace = shift;
+
+my $CollectionBaseclass = 'RT::SearchBuilder';
+my $RecordBaseclass = 'RT::Record';
+
+my $driver = 'mysql';
+my $hostname = 'localhost';
+my $user = 'root';
+my $password = '';
+
+
+my $LicenseBlock = << '.';
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+.
+
+my $Attribution = << '.';
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+.
+
+my $dsn = "DBI:$driver:database=$database;host=$hostname";
+
+my $dbh = DBI->connect( $dsn, $user, $password );
+
+#get all tables out of database
+my @tables = $dbh->tables();
+
+my ( %tablemap, $typemap, %modulemap );
+
+foreach my $table (@tables) {
+ next if ($table eq 'sessions');
+ $tablemap{$table} = $table;
+ $modulemap{$table} = $table;
+ if ( $table =~ /^(.*)s$/ ) {
+ $tablemap{$1} = $table;
+ $modulemap{$1} = $1;
+ }
+}
+$tablemap{'CreatedBy'} = 'User';
+$tablemap{'UpdatedBy'} = 'User';
+
+$typemap{'id'} = 'ro';
+$typemap{'Creator'} = 'auto';
+$typemap{'Created'} = 'auto';
+$typemap{'Updated'} = 'auto';
+$typemap{'UpdatedBy'} = 'auto';
+$typemap{'LastUpdated'} = 'auto';
+$typemap{'LastUpdatedBy'} = 'auto';
+
+foreach my $table (@tables) {
+ next if ($table eq 'sessions');
+ my $tablesingle = $table;
+ $tablesingle =~ s/s$//;
+ my $tableplural = $tablesingle . "s";
+
+ if ( $tablesingle eq 'ACL' ) {
+ $tablesingle = "ACE";
+ $tableplural = "ACL";
+ }
+
+ my %requirements;
+
+ my $CollectionClassName = $namespace . "::" . $tableplural;
+ my $RecordClassName = $namespace . "::" . $tablesingle;
+
+ my $path = $namespace;
+ $path =~ s/::/\//g;
+
+ my $RecordClassPath = $path . "/" . $tablesingle . ".pm";
+ my $CollectionClassPath = $path . "/" . $tableplural . ".pm";
+
+ #create a collection class
+ my $CreateInParams;
+ my $CreateOutParams;
+ my $ClassAccessible = "";
+ my $FieldsPod = "";
+ my $CreatePod = "";
+ my %fields;
+ my $sth = $dbh->prepare("DESCRIBE $table");
+ $sth->execute;
+
+ while ( my $row = $sth->fetchrow_hashref() ) {
+ my $field = $row->{'Field'};
+ my $type = $row->{'Type'};
+ my $default = $row->{'Default'};
+ $fields{$field} = 1;
+
+ #generate the 'accessible' datastructure
+
+ if ( $typemap{$field} eq 'auto' ) {
+ $ClassAccessible .= " $field =>
+ {read => 1, auto => 1,";
+ }
+ elsif ( $typemap{$field} eq 'ro' ) {
+ $ClassAccessible .= " $field =>
+ {read => 1,";
+ }
+ else {
+ $ClassAccessible .= " $field =>
+ {read => 1, write => 1,";
+
+ }
+
+ $ClassAccessible .= " type => '$type', default => '$default'},\n";
+
+ #generate pod for the accessible fields
+ $FieldsPod .= "
+=head2 $field
+
+Returns the current value of $field.
+(In the database, $field is stored as $type.)
+
+";
+
+ unless ( $typemap{$field} eq 'auto' || $typemap{$field} eq 'ro' ) {
+ $FieldsPod .= "
+
+=head2 Set$field VALUE
+
+
+Set $field to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, $field will be stored as a $type.)
+
+";
+ }
+
+ $FieldsPod .= "
+=cut
+
+";
+
+ if ( $modulemap{$field} ) {
+ $FieldsPod .= "
+=head2 ${field}Obj
+
+Returns the $modulemap{$field} Object which has the id returned by $field
+
+
+=cut
+
+sub ${field}Obj {
+ my \$self = shift;
+ my \$$field = ${namespace}::$modulemap{$field}->new(\$self->CurrentUser);
+ \$$field->Load(\$self->__Value('$field'));
+ return(\$$field);
+}
+";
+ $requirements{ $tablemap{$field} } =
+ "use ${namespace}::$modulemap{$field};";
+
+ }
+
+ unless ( $typemap{$field} eq 'auto' || $field eq 'id' ) {
+
+ #generate create statement
+ $CreateInParams .= " $field => '$default',\n";
+ $CreateOutParams .=
+ " $field => \$args{'$field'},\n";
+
+ #gerenate pod for the create statement
+ $CreatePod .= " $type '$field'";
+ $CreatePod .= " defaults to '$default'" if ($default);
+ $CreatePod .= ".\n";
+
+ }
+
+ }
+
+ $Create = "
+sub Create {
+ my \$self = shift;
+ my \%args = (
+$CreateInParams
+ \@_);
+ \$self->SUPER::Create(
+$CreateOutParams);
+
+}
+";
+ $CreatePod .= "\n=cut\n\n";
+
+ my $CollectionClass = $LicenseBlock . $Attribution .
+
+ "
+
+=head1 NAME
+
+ $CollectionClassName -- Class Description
+
+=head1 SYNOPSIS
+
+ use $CollectionClassName
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package $CollectionClassName;
+
+use $CollectionBaseclass;
+use $RecordClassName;
+
+use vars qw( \@ISA );
+\@ISA= qw($CollectionBaseclass);
+
+
+sub _Init {
+ my \$self = shift;
+ \$self->{'table'} = '$table';
+ \$self->{'primary_key'} = 'id';
+
+";
+
+ if ( $fields{'SortOrder'} ) {
+
+ $CollectionClass .= "
+
+ # By default, order by name
+ \$self->OrderBy( ALIAS => 'main',
+ FIELD => 'SortOrder',
+ ORDER => 'ASC');
+";
+ }
+ $CollectionClass .= "
+ return ( \$self->SUPER::_Init(\@_) );
+}
+
+
+=head2 NewItem
+
+Returns an empty new $RecordClassName item
+
+=cut
+
+sub NewItem {
+ my \$self = shift;
+ return($RecordClassName->new(\$self->CurrentUser));
+}
+" . MagicImport($CollectionClassName);
+
+ my $RecordClassHeader = $Attribution . "
+
+=head1 NAME
+
+$RecordClassName
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package $RecordClassName;
+use $RecordBaseclass;
+";
+
+ foreach my $key ( keys %requirements ) {
+ $RecordClassHeader .= $requirements{$key} . "\n";
+ }
+ $RecordClassHeader .= "
+
+use vars qw( \@ISA );
+\@ISA= qw( $RecordBaseclass );
+
+sub _Init {
+ my \$self = shift;
+
+ \$self->Table('$table');
+ \$self->SUPER::_Init(\@_);
+}
+
+";
+
+ my $RecordClass = $LicenseBlock . $RecordClassHeader . "
+
+$RecordInit
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+$CreatePod
+
+$Create
+
+$FieldsPod
+
+sub _ClassAccessible {
+ {
+
+$ClassAccessible
+ }
+};
+
+" . MagicImport($RecordClassName);
+
+ print "About to make $RecordClassPath, $CollectionClassPath\n";
+ `mkdir -p $path`;
+
+ open( RECORD, ">$RecordClassPath" );
+ print RECORD $RecordClass;
+ close(RECORD);
+
+ open( COL, ">$CollectionClassPath" );
+ print COL $CollectionClass;
+ close($COL);
+
+}
+
+sub MagicImport {
+ my $class = shift;
+
+ #if (exists \$warnings::{unimport}) {
+ # no warnings qw(redefine);
+
+ my $path = $class;
+ $path =~ s#::#/#gi;
+
+
+ my $content = "
+ eval \"require @{[$class]}_Overlay\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Overlay.pm}) {
+ die \$@;
+ };
+
+ eval \"require @{[$class]}_Vendor\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Vendor.pm}) {
+ die \$@;
+ };
+
+ eval \"require @{[$class]}_Local\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Local.pm}) {
+ die \$@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows \"overlay\" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+If you'll be working with perl 5.6.0 or greater, each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+@{[$class]}_Overlay, @{[$class]}_Vendor, @{[$class]}_Local
+
+=cut
+
+
+1;
+";
+
+ return $content;
+}
+
+# }}}
+
diff --git a/rt/sbin/license_tag b/rt/sbin/license_tag
new file mode 100644
index 0000000..689b246
--- /dev/null
+++ b/rt/sbin/license_tag
@@ -0,0 +1,196 @@
+#!/usr/bin/perl
+
+
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+my $LICENSE = <<EOL;
+
+Copyright (c) 1996-2003 Jesse Vincent <jesse\@bestpractical.com>
+
+(Except where explicitly superseded by other copyright notices)
+
+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.
+
+Unless otherwise specified, all modifications, corrections or
+extensions to this work which alter its source code become the
+property of Best Practical Solutions, LLC when submitted for
+inclusion in the work.
+
+
+EOL
+
+use File::Find;
+
+my @MAKE = qw(Makefile);
+
+File::Find::find({ no_chdir => 1, wanted => \&tag_pm}, 'lib');
+File::Find::find({ no_chdir => 1, wanted => \&tag_mason}, 'html');
+File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'sbin');
+File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'bin');
+tag_makefile ('Makefile.in');
+tag_makefile ('README');
+
+
+sub tag_mason {
+ my $pm = $_;
+ next unless (-f $pm);
+ next if ($pm =~ /images/);
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/%# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^%# BEGIN LICENSE BLOCK/ms) {
+ print "has license section";
+ $file =~ s/^%# BEGIN LICENSE BLOCK(.*?)%# END LICENSE BLOCK/%# BEGIN LICENSE BLOCK\n$pmlic%# END LICENSE BLOCK/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="%# BEGIN LICENSE BLOCK\n$pmlic%# END LICENSE BLOCK\n". $file;
+ }
+ $file =~ s/%# END LICENSE BLOCK(\n+)/%# END LICENSE BLOCK\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_makefile {
+ my $pm = shift;
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN LICENSE BLOCK/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN LICENSE BLOCK(.*?)# END LICENSE BLOCK/# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK\n". $file;
+ }
+ $file =~ s/# END LICENSE BLOCK(\n+)/# END LICENSE BLOCK\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_pm {
+ my $pm = $_;
+ next unless $pm =~ /\.pm\z/s;
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN LICENSE BLOCK/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN LICENSE BLOCK(.*?)# END LICENSE BLOCK/# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK\n". $file;
+ }
+ $file =~ s/# END LICENSE BLOCK(\n+)/# END LICENSE BLOCK\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file $pm";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_script {
+ my $pm = $_;
+ return unless (-f $pm);
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /msg;
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN LICENSE BLOCK/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN LICENSE BLOCK(.*?)# END LICENSE BLOCK/# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK/ms;
+
+
+ } else {
+ print "no license section";
+ if ($file =~ /^(#!.*?)\n/) {
+
+ my $lic ="# BEGIN LICENSE BLOCK\n$pmlic# END LICENSE BLOCK\n";
+ $file =~ s/^(#!.*?)\n/$1\n$lic/;
+
+ }
+ }
+ $file =~ s/# END LICENSE BLOCK(\n+)/# END LICENSE BLOCK\n\n/mg;
+ print "\n";
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
diff --git a/rt/sbin/regression_harness b/rt/sbin/regression_harness
new file mode 100644
index 0000000..fc1e293
--- /dev/null
+++ b/rt/sbin/regression_harness
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+open (FH,"make regression|");
+
+my $skip_frontmatter = 1;
+while (<FH>) {
+ next if /^ok/;
+ $skip_frontmatter = 0 if (/autogen/);
+ print $_ unless ($skip_frontmatter);
+}
diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database
new file mode 100644
index 0000000..1781988
--- /dev/null
+++ b/rt/sbin/rt-setup-database
@@ -0,0 +1,624 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
+use vars
+ qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
+
+use lib "/opt/rt3/lib";
+
+#This drags in RT's config.pm
+# We do it in a begin block because RT::Handle needs to know the type to do its
+# inheritance
+use RT;
+use Carp;
+use RT::User;
+use RT::CurrentUser;
+use RT::Template;
+use RT::ScripAction;
+use RT::ACE;
+use RT::Group;
+use RT::User;
+use RT::Queue;
+use RT::ScripCondition;
+use RT::CustomField;
+use RT::Scrip;
+
+RT::LoadConfig();
+use Term::ReadKey;
+use Getopt::Long;
+
+my %args;
+
+GetOptions(
+ \%args,
+ 'prompt-for-dba-password', 'force', 'debug',
+ 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
+ 'datadir=s'
+);
+
+$| = 1; #unbuffer that output.
+
+require RT::Handle;
+my $Handle = RT::Handle->new($RT::DatabaseType);
+$Handle->BuildDSN;
+my $dbh;
+
+if ( $args{'prompt-for-dba-password'} ) {
+ $args{'dba-password'} = get_dba_password();
+ chomp( $args{'dba-password'} );
+}
+
+unless ( $args{'action'} ) {
+ help();
+ die;
+}
+if ( $args{'action'} eq 'init' ) {
+ $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ print "Now creating a database for RT.\n";
+ if ($RT::DatabaseType ne 'Oracle' ||
+ $args{'dba'} ne $RT::DatabaseUser) {
+ create_db();
+ } else {
+ print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
+ }
+
+ $dbh->disconnect;
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die $DBI::errstr;
+
+ print "Now populating database schema.\n";
+ insert_schema();
+ print "Now inserting database ACLs\n";
+ insert_acl() unless ($RT::DatabaseType eq 'Oracle');
+ print "Now inserting RT core system objects\n";
+ insert_initial_data();
+ print "Now inserting RT data\n";
+ insert_data( $RT::EtcPath . "/initialdata" );
+}
+elsif ( $args{'action'} eq 'drop' ) {
+ unless ( $dbh =
+ DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
+ {
+ warn $DBI::errstr;
+ warn "Database doesn't appear to exist. Aborting database drop.";
+ exit(0);
+ }
+ drop_db();
+}
+elsif ( $args{'action'} eq 'insert_initial' ) {
+ insert_initial_data();
+}
+elsif ( $args{'action'} eq 'insert' ) {
+ insert_data( $args{'datafile'} );
+}
+elsif ($args{'action'} eq 'acl') {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_acl($args{'datadir'});
+}
+elsif ($args{'action'} eq 'schema') {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_schema($args{'datadir'});
+}
+
+else {
+ print STDERR '$0 called with an invalid --action parameter';
+ exit(-1);
+}
+
+# {{{ sub insert_schema
+sub insert_schema {
+ my $base_path = (shift || $RT::EtcPath);
+ my (@schema);
+ print "Creating database schema.\n";
+
+ if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
+ no warnings 'unopened';
+
+ open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
+ open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
+
+ my $statement = "";
+ foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
+ $line =~ s/\#.*//g;
+ $line =~ s/--.*//g;
+ $statement .= $line;
+ if ( $line =~ /;(\s*)$/ ) {
+ $statement =~ s/;(\s*)$//g;
+ push @schema, $statement;
+ $statement = "";
+ }
+ }
+
+ local $SIG{__WARN__} = sub {};
+ my $is_local = 0; # local/etc/schema needs to be nonfatal.
+ foreach my $statement (@schema) {
+ if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
+ print STDERR "SQL: $statement\n" if defined $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute or $is_local ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+
+ }
+ else {
+ die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
+ }
+ print "schema sucessfully inserted\n";
+
+}
+
+# }}}
+
+# {{{ sub drop_db
+sub drop_db {
+ return if ( $RT::DatabaseType eq 'SQLite' );
+ if ( $RT::DatabaseType eq 'Oracle' ) {
+ print <<END;
+
+To delete the tables and sequences of the RT Oracle database by running
+ \@etc/drop.Oracle
+through SQLPlus.
+
+END
+ return;
+ }
+ unless ( $args{'force'} ) {
+ print <<END;
+
+About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
+WARNING: This will erase all data in $RT::DatabaseName.
+
+END
+ exit unless _yesno();
+
+ }
+
+ print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
+
+ $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
+}
+
+# }}}
+
+# {{{ sub create_db
+sub create_db {
+ print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
+ if ( $RT::DatabaseType eq 'SQLite' ) {
+ return;
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
+ if ($DBI::errstr) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
+ }
+ }
+ elsif ($RT::DatabaseType eq 'Oracle') {
+ insert_acl();
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ $ENV{DB_LOCALE} = 'en_us.utf8';
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
+ }
+ else {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
+ }
+}
+
+# }}}
+
+sub get_dba_password {
+ print
+"In order to create a new database and grant RT access to that database,\n";
+ print "this script needs to connect to your "
+ . $RT::DatabaseType
+ . " instance on "
+ . $RT::DatabaseHost . " as "
+ . $args{'dba'} . ".\n";
+ print
+"Please specify that user's database password below. If the user has no database\n";
+ print "password, just press return.\n\n";
+ print "Password: ";
+ ReadMode('noecho');
+ my $password = ReadLine(0);
+ ReadMode('normal');
+ return ($password);
+}
+
+# {{{ sub _yesno
+sub _yesno {
+ print "Proceed [y/N]:";
+ my $x = scalar(<STDIN>);
+ $x =~ /^y/i;
+}
+
+# }}}
+
+# {{{ insert_acls
+sub insert_acl {
+
+ my $base_path = (shift || $RT::EtcPath);
+
+ if ( $RT::DatabaseType =~ /^oracle$/i ) {
+ do $base_path . "/acl.Oracle"
+ || die "Couldn't find ACLS for Oracle\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^pg$/i ) {
+ do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
+ do $base_path . "/acl.mysql"
+ || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^informix$/i ) {
+ do $base_path . "/acl.Informix"
+ || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
+ return;
+ }
+ else {
+ die "Unknown RT database type";
+ }
+
+ my @acl = acl($dbh);
+ foreach my $statement (@acl) {
+ print STDERR $statement if $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+}
+
+# }}}
+
+=head2 get_system_dsn
+
+Returns a dsn suitable for database creates and drops
+and user creates and drops
+
+=cut
+
+sub get_system_dsn {
+
+ my $dsn = $Handle->DSN;
+
+ #with mysql, you want to connect sans database to funge things
+ if ( $RT::DatabaseType eq 'mysql' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName//;
+
+ # with postgres, you want to connect to database1
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ # with Informix, you want to connect sans database:
+ $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
+ }
+ return $dsn;
+}
+
+sub insert_initial_data {
+
+ RT::InitLogging();
+
+ #connect to the db, for actual RT work
+ require RT::Handle;
+ $RT::Handle = RT::Handle->new();
+ $RT::Handle->Connect();
+
+ #Put together a current user object so we can create a User object
+ my $CurrentUser = new RT::CurrentUser();
+
+ print "Checking for existing system user ($CurrentUser)...";
+ my $test_user = RT::User->new($CurrentUser);
+ $test_user->Load('RT_System');
+ if ( $test_user->id ) {
+ print "found!\n\nYou appear to have a functional RT database.\n"
+ . "Exiting, so as not to clobber your existing data.\n";
+ exit(-1);
+
+ }
+ else {
+ print "not found. This appears to be a new installation.\n";
+ }
+
+ print "Creating system user...";
+ my $RT_System = new RT::User($CurrentUser);
+
+ my ( $val, $msg ) = $RT_System->_BootstrapCreate(
+ Name => 'RT_System',
+ RealName => 'The RT System itself',
+ Comments =>
+'Do not delete or modify this user. It is integral to RT\'s internal database structures',
+ Creator => '1' );
+
+ unless ($val) {
+ print "$msg\n";
+ exit(1);
+ }
+ print "done.\n";
+ $RT::Handle->Disconnect();
+
+}
+
+# load some sort of data into the database
+
+sub insert_data {
+ my $datafile = shift;
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByName('RT_System');
+
+ if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
+
+ print "Creating Superuser ACL...";
+
+ my $superuser_ace = RT::ACE->new($CurrentUser);
+ $superuser_ace->_BootstrapCreate(
+ PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => '1' );
+
+ }
+
+ # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+ # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
+
+ require $datafile
+ || die "Couldn't find initial data for import\n" . $@;
+
+ if (@Groups) {
+ print "Creating groups...";
+ foreach $item (@Groups) {
+ my $new_entry = RT::Group->new($CurrentUser);
+ my ( $return, $msg ) = $new_entry->_Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Users) {
+ print "Creating users...";
+ foreach $item (@Users) {
+ my $new_entry = new RT::User($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Queues) {
+ print "Creating queues...";
+ for $item (@Queues) {
+ my $new_entry = new RT::Queue($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@ACL) {
+ print "Creating ACL...";
+ for my $item (@ACL) {
+
+ my ($princ, $object);
+
+ # Global rights or Queue rights?
+ if ($item->{'Queue'}) {
+ $object = RT::Queue->new($CurrentUser);
+ $object->Load( $item->{'Queue'} );
+ } else {
+ $object = $RT::System;
+ }
+
+ # Group rights or user rights?
+ if ($item->{'GroupDomain'}) {
+ $princ = RT::Group->new($CurrentUser);
+ if ($item->{'GroupDomain'} eq 'UserDefined') {
+ $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+ } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
+ $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+ } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+ $item->{'Queue'}) {
+ $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
+ Queue => $object->id);
+ } else {
+ $princ->Load( $item->{'GroupId'} );
+ }
+ } else {
+ $princ = RT::User->new($CurrentUser);
+ $princ->Load( $item->{'UserId'} );
+ }
+
+ # Grant it
+ my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+ Right => $item->{'Right'},
+ Object => $object );
+
+ if ($return) {
+ print $return. ".";
+ }
+ else {
+ print $msg . ".";
+
+ }
+
+ }
+ print "done.\n";
+ }
+ if (@CustomFields) {
+ print "Creating custom fields...";
+ for $item (@CustomFields) {
+ my $new_entry = new RT::CustomField($CurrentUser);
+ my $values = $item->{'Values'};
+ delete $item->{'Values'};
+ my $q = $item->{'Queue'};
+ my $q_obj = RT::Queue->new($CurrentUser);
+ $q_obj->Load($q);
+ if ( $q_obj->Id ) {
+ $item->{'Queue'} = $q_obj->Id;
+ }
+ elsif ( $q == 0 ) {
+ $item->{'Queue'} = 0;
+ }
+ else {
+ print "(Error: Could not find queue " . $q . ")\n"
+ unless ( $q_obj->Id );
+ next;
+ }
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+
+ foreach my $value ( @{$values} ) {
+ my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
+ print "(Error: $emsg)\n" unless ($eval);
+ }
+
+ print "(Error: $msg)\n" unless ($return);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@ScripActions) {
+ print "Creating ScripActions...";
+
+ for $item (@ScripActions) {
+ my $new_entry = RT::ScripAction->new($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@ScripConditions) {
+ print "Creating ScripConditions...";
+
+ for $item (@ScripConditions) {
+ my $new_entry = RT::ScripCondition->new($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@Templates) {
+ print "Creating templates...";
+
+ for $item (@Templates) {
+ my $new_entry = new RT::Template($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Scrips) {
+ print "Creating scrips...";
+
+ for $item (@Scrips) {
+ my $new_entry = new RT::Scrip($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ if ($return) {
+ print $return. ".";
+ }
+ else {
+ print "(Error: $msg)\n";
+ }
+ }
+ print "done.\n";
+ }
+ $RT::Handle->Disconnect();
+
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+ my $username = shift;
+ my $user = RT::User->new($RT::SystemUser);
+ $user->Load($username);
+ my $equiv_group = RT::Group->new($RT::SystemUser);
+ $equiv_group->LoadACLEquivalenceGroup($user);
+ return ( $equiv_group->Id );
+}
+
+sub help {
+
+ print <<EOF;
+
+$0: Set up RT's database
+
+--action init Initialize the database
+ drop Drop the database.
+ This will ERASE ALL YOUR DATA
+ insert_initial
+ Insert RT's core system objects
+ insert Insert data into RT's database.
+ By default, will use RT's installation data.
+ To use a local or supplementary datafile, specify it
+ using the '--datafile' option below.
+
+ acl Initialize only the database ACLs
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+ schema Initialize only the database schema
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+--datafile /path/to/datafile
+--datadir /path/to/ Used to specify a path to find the local
+ database schema and acls to be installed.
+
+
+--dba dba's username
+--dba-password dba's password
+--prompt-for-dba-password Ask for the database administrator's password interactively
+
+
+EOF
+
+}
+
+1;
diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in
new file mode 100644
index 0000000..56f4e87
--- /dev/null
+++ b/rt/sbin/rt-setup-database.in
@@ -0,0 +1,624 @@
+#!@PERL@ -w
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+use strict;
+use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
+use vars
+ qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
+
+use lib "@RT_LIB_PATH@";
+
+#This drags in RT's config.pm
+# We do it in a begin block because RT::Handle needs to know the type to do its
+# inheritance
+use RT;
+use Carp;
+use RT::User;
+use RT::CurrentUser;
+use RT::Template;
+use RT::ScripAction;
+use RT::ACE;
+use RT::Group;
+use RT::User;
+use RT::Queue;
+use RT::ScripCondition;
+use RT::CustomField;
+use RT::Scrip;
+
+RT::LoadConfig();
+use Term::ReadKey;
+use Getopt::Long;
+
+my %args;
+
+GetOptions(
+ \%args,
+ 'prompt-for-dba-password', 'force', 'debug',
+ 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
+ 'datadir=s'
+);
+
+$| = 1; #unbuffer that output.
+
+require RT::Handle;
+my $Handle = RT::Handle->new($RT::DatabaseType);
+$Handle->BuildDSN;
+my $dbh;
+
+if ( $args{'prompt-for-dba-password'} ) {
+ $args{'dba-password'} = get_dba_password();
+ chomp( $args{'dba-password'} );
+}
+
+unless ( $args{'action'} ) {
+ help();
+ die;
+}
+if ( $args{'action'} eq 'init' ) {
+ $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ print "Now creating a database for RT.\n";
+ if ($RT::DatabaseType ne 'Oracle' ||
+ $args{'dba'} ne $RT::DatabaseUser) {
+ create_db();
+ } else {
+ print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
+ }
+
+ $dbh->disconnect;
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die $DBI::errstr;
+
+ print "Now populating database schema.\n";
+ insert_schema();
+ print "Now inserting database ACLs\n";
+ insert_acl() unless ($RT::DatabaseType eq 'Oracle');
+ print "Now inserting RT core system objects\n";
+ insert_initial_data();
+ print "Now inserting RT data\n";
+ insert_data( $RT::EtcPath . "/initialdata" );
+}
+elsif ( $args{'action'} eq 'drop' ) {
+ unless ( $dbh =
+ DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
+ {
+ warn $DBI::errstr;
+ warn "Database doesn't appear to exist. Aborting database drop.";
+ exit(0);
+ }
+ drop_db();
+}
+elsif ( $args{'action'} eq 'insert_initial' ) {
+ insert_initial_data();
+}
+elsif ( $args{'action'} eq 'insert' ) {
+ insert_data( $args{'datafile'} );
+}
+elsif ($args{'action'} eq 'acl') {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_acl($args{'datadir'});
+}
+elsif ($args{'action'} eq 'schema') {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_schema($args{'datadir'});
+}
+
+else {
+ print STDERR '$0 called with an invalid --action parameter';
+ exit(-1);
+}
+
+# {{{ sub insert_schema
+sub insert_schema {
+ my $base_path = (shift || $RT::EtcPath);
+ my (@schema);
+ print "Creating database schema.\n";
+
+ if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
+ no warnings 'unopened';
+
+ open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
+ open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
+
+ my $statement = "";
+ foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
+ $line =~ s/\#.*//g;
+ $line =~ s/--.*//g;
+ $statement .= $line;
+ if ( $line =~ /;(\s*)$/ ) {
+ $statement =~ s/;(\s*)$//g;
+ push @schema, $statement;
+ $statement = "";
+ }
+ }
+
+ local $SIG{__WARN__} = sub {};
+ my $is_local = 0; # local/etc/schema needs to be nonfatal.
+ foreach my $statement (@schema) {
+ if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
+ print STDERR "SQL: $statement\n" if defined $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute or $is_local ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+
+ }
+ else {
+ die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
+ }
+ print "schema sucessfully inserted\n";
+
+}
+
+# }}}
+
+# {{{ sub drop_db
+sub drop_db {
+ return if ( $RT::DatabaseType eq 'SQLite' );
+ if ( $RT::DatabaseType eq 'Oracle' ) {
+ print <<END;
+
+To delete the tables and sequences of the RT Oracle database by running
+ \@etc/drop.Oracle
+through SQLPlus.
+
+END
+ return;
+ }
+ unless ( $args{'force'} ) {
+ print <<END;
+
+About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
+WARNING: This will erase all data in $RT::DatabaseName.
+
+END
+ exit unless _yesno();
+
+ }
+
+ print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
+
+ $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
+}
+
+# }}}
+
+# {{{ sub create_db
+sub create_db {
+ print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
+ if ( $RT::DatabaseType eq 'SQLite' ) {
+ return;
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
+ if ($DBI::errstr) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
+ }
+ }
+ elsif ($RT::DatabaseType eq 'Oracle') {
+ insert_acl();
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ $ENV{DB_LOCALE} = 'en_us.utf8';
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
+ }
+ else {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
+ }
+}
+
+# }}}
+
+sub get_dba_password {
+ print
+"In order to create a new database and grant RT access to that database,\n";
+ print "this script needs to connect to your "
+ . $RT::DatabaseType
+ . " instance on "
+ . $RT::DatabaseHost . " as "
+ . $args{'dba'} . ".\n";
+ print
+"Please specify that user's database password below. If the user has no database\n";
+ print "password, just press return.\n\n";
+ print "Password: ";
+ ReadMode('noecho');
+ my $password = ReadLine(0);
+ ReadMode('normal');
+ return ($password);
+}
+
+# {{{ sub _yesno
+sub _yesno {
+ print "Proceed [y/N]:";
+ my $x = scalar(<STDIN>);
+ $x =~ /^y/i;
+}
+
+# }}}
+
+# {{{ insert_acls
+sub insert_acl {
+
+ my $base_path = (shift || $RT::EtcPath);
+
+ if ( $RT::DatabaseType =~ /^oracle$/i ) {
+ do $base_path . "/acl.Oracle"
+ || die "Couldn't find ACLS for Oracle\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^pg$/i ) {
+ do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
+ do $base_path . "/acl.mysql"
+ || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^informix$/i ) {
+ do $base_path . "/acl.Informix"
+ || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
+ return;
+ }
+ else {
+ die "Unknown RT database type";
+ }
+
+ my @acl = acl($dbh);
+ foreach my $statement (@acl) {
+ print STDERR $statement if $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+}
+
+# }}}
+
+=head2 get_system_dsn
+
+Returns a dsn suitable for database creates and drops
+and user creates and drops
+
+=cut
+
+sub get_system_dsn {
+
+ my $dsn = $Handle->DSN;
+
+ #with mysql, you want to connect sans database to funge things
+ if ( $RT::DatabaseType eq 'mysql' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName//;
+
+ # with postgres, you want to connect to database1
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ # with Informix, you want to connect sans database:
+ $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
+ }
+ return $dsn;
+}
+
+sub insert_initial_data {
+
+ RT::InitLogging();
+
+ #connect to the db, for actual RT work
+ require RT::Handle;
+ $RT::Handle = RT::Handle->new();
+ $RT::Handle->Connect();
+
+ #Put together a current user object so we can create a User object
+ my $CurrentUser = new RT::CurrentUser();
+
+ print "Checking for existing system user ($CurrentUser)...";
+ my $test_user = RT::User->new($CurrentUser);
+ $test_user->Load('RT_System');
+ if ( $test_user->id ) {
+ print "found!\n\nYou appear to have a functional RT database.\n"
+ . "Exiting, so as not to clobber your existing data.\n";
+ exit(-1);
+
+ }
+ else {
+ print "not found. This appears to be a new installation.\n";
+ }
+
+ print "Creating system user...";
+ my $RT_System = new RT::User($CurrentUser);
+
+ my ( $val, $msg ) = $RT_System->_BootstrapCreate(
+ Name => 'RT_System',
+ RealName => 'The RT System itself',
+ Comments =>
+'Do not delete or modify this user. It is integral to RT\'s internal database structures',
+ Creator => '1' );
+
+ unless ($val) {
+ print "$msg\n";
+ exit(1);
+ }
+ print "done.\n";
+ $RT::Handle->Disconnect();
+
+}
+
+# load some sort of data into the database
+
+sub insert_data {
+ my $datafile = shift;
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByName('RT_System');
+
+ if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
+
+ print "Creating Superuser ACL...";
+
+ my $superuser_ace = RT::ACE->new($CurrentUser);
+ $superuser_ace->_BootstrapCreate(
+ PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => '1' );
+
+ }
+
+ # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+ # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
+
+ require $datafile
+ || die "Couldn't find initial data for import\n" . $@;
+
+ if (@Groups) {
+ print "Creating groups...";
+ foreach $item (@Groups) {
+ my $new_entry = RT::Group->new($CurrentUser);
+ my ( $return, $msg ) = $new_entry->_Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Users) {
+ print "Creating users...";
+ foreach $item (@Users) {
+ my $new_entry = new RT::User($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Queues) {
+ print "Creating queues...";
+ for $item (@Queues) {
+ my $new_entry = new RT::Queue($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless ($return);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@ACL) {
+ print "Creating ACL...";
+ for my $item (@ACL) {
+
+ my ($princ, $object);
+
+ # Global rights or Queue rights?
+ if ($item->{'Queue'}) {
+ $object = RT::Queue->new($CurrentUser);
+ $object->Load( $item->{'Queue'} );
+ } else {
+ $object = $RT::System;
+ }
+
+ # Group rights or user rights?
+ if ($item->{'GroupDomain'}) {
+ $princ = RT::Group->new($CurrentUser);
+ if ($item->{'GroupDomain'} eq 'UserDefined') {
+ $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+ } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
+ $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+ } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+ $item->{'Queue'}) {
+ $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
+ Queue => $object->id);
+ } else {
+ $princ->Load( $item->{'GroupId'} );
+ }
+ } else {
+ $princ = RT::User->new($CurrentUser);
+ $princ->Load( $item->{'UserId'} );
+ }
+
+ # Grant it
+ my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+ Right => $item->{'Right'},
+ Object => $object );
+
+ if ($return) {
+ print $return. ".";
+ }
+ else {
+ print $msg . ".";
+
+ }
+
+ }
+ print "done.\n";
+ }
+ if (@CustomFields) {
+ print "Creating custom fields...";
+ for $item (@CustomFields) {
+ my $new_entry = new RT::CustomField($CurrentUser);
+ my $values = $item->{'Values'};
+ delete $item->{'Values'};
+ my $q = $item->{'Queue'};
+ my $q_obj = RT::Queue->new($CurrentUser);
+ $q_obj->Load($q);
+ if ( $q_obj->Id ) {
+ $item->{'Queue'} = $q_obj->Id;
+ }
+ elsif ( $q == 0 ) {
+ $item->{'Queue'} = 0;
+ }
+ else {
+ print "(Error: Could not find queue " . $q . ")\n"
+ unless ( $q_obj->Id );
+ next;
+ }
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+
+ foreach my $value ( @{$values} ) {
+ my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
+ print "(Error: $emsg)\n" unless ($eval);
+ }
+
+ print "(Error: $msg)\n" unless ($return);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@ScripActions) {
+ print "Creating ScripActions...";
+
+ for $item (@ScripActions) {
+ my $new_entry = RT::ScripAction->new($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@ScripConditions) {
+ print "Creating ScripConditions...";
+
+ for $item (@ScripConditions) {
+ my $new_entry = RT::ScripCondition->new($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if (@Templates) {
+ print "Creating templates...";
+
+ for $item (@Templates) {
+ my $new_entry = new RT::Template($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if (@Scrips) {
+ print "Creating scrips...";
+
+ for $item (@Scrips) {
+ my $new_entry = new RT::Scrip($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ if ($return) {
+ print $return. ".";
+ }
+ else {
+ print "(Error: $msg)\n";
+ }
+ }
+ print "done.\n";
+ }
+ $RT::Handle->Disconnect();
+
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+ my $username = shift;
+ my $user = RT::User->new($RT::SystemUser);
+ $user->Load($username);
+ my $equiv_group = RT::Group->new($RT::SystemUser);
+ $equiv_group->LoadACLEquivalenceGroup($user);
+ return ( $equiv_group->Id );
+}
+
+sub help {
+
+ print <<EOF;
+
+$0: Set up RT's database
+
+--action init Initialize the database
+ drop Drop the database.
+ This will ERASE ALL YOUR DATA
+ insert_initial
+ Insert RT's core system objects
+ insert Insert data into RT's database.
+ By default, will use RT's installation data.
+ To use a local or supplementary datafile, specify it
+ using the '--datafile' option below.
+
+ acl Initialize only the database ACLs
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+ schema Initialize only the database schema
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+--datafile /path/to/datafile
+--datadir /path/to/ Used to specify a path to find the local
+ database schema and acls to be installed.
+
+
+--dba dba's username
+--dba-password dba's password
+--prompt-for-dba-password Ask for the database administrator's password interactively
+
+
+EOF
+
+}
+
+1;
diff --git a/rt/sbin/rt-test-dependencies b/rt/sbin/rt-test-dependencies
new file mode 100644
index 0000000..c1591b1
--- /dev/null
+++ b/rt/sbin/rt-test-dependencies
@@ -0,0 +1,278 @@
+#!/usr/bin/perl
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+#
+# This is just a basic script that checks to make sure that all
+# the modules needed by RT before you can install it.
+#
+
+use strict;
+no warnings qw(numeric redefine);
+use Getopt::Long;
+use CPAN;
+my %args;
+my %deps;
+GetOptions(\%args,'install', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', 'with-ORACLE', 'with-FASTCGI', 'with-SPEEDYCGI', 'with-MODPERL1', 'with-MODPERL2' ,'with-DEV');
+
+if (!keys %args) {
+ help();
+ exit(0);
+}
+if ($args{'with-MODPERL2'}) {
+ warn_modperl2();
+}
+
+$args{'with-MASON'} = 1;
+$args{'with-CORE'} = 1;
+$args{'with-DEV'} =1;
+$args{'with-CLI'} =1;
+$args{'with-MAILGATE'} =1;
+if ($] < 5.007) {
+$args{'with-I18N-COMPAT'} = 1;
+}
+
+sub warn_modperl2 {
+ print <<'.';
+ NOTE: mod_perl 2.0 isn't quite ready for prime_time just yet;
+ Best Practical Solutions strongly recommends that sites use
+ Apache 1.3 or FastCGI. If you MUST use mod_perl 2.0 (or 1.99),
+ please read the mailing list archives before asking for help.
+.
+ sleep 5;
+}
+
+
+sub help {
+
+ print <<'.';
+
+By default, testdeps determine whether you have
+installed all the perl modules RT needs to run.
+
+ --install Install missing modules
+
+The following switches will tell the tool to check for specific dependencies
+
+ --with-mysql Database interface for MySQL
+ --with-postgresql Database interface for PostgreSQL
+ --with-sqlite Database interface and driver for SQLite (unsupported)
+ --with-oracle Database interface for oracle (unsupported)
+
+ --with-fastcgi Libraries needed to support the fastcgi handler
+ --with-speedycgi Libraries needed to support the speedycgi handler
+ --with-modperl1 Libraries needed to support the modperl 1 handler
+ --with-modperl2 Libraries needed to support the modperl 2 handler
+
+ --with-dev Tools needed for RT development
+.
+}
+
+
+sub _ {
+ map { /(\S+)\s*(\S*)/; $1 => ($2 ? $2 :'') } split ( /\n/, $_[0] );
+}
+
+$deps{'CORE'} = [ _( << '.') ];
+Digest::MD5 2.27
+DBI 1.37
+Test::Inline
+Class::ReturnValue 0.40
+DBIx::SearchBuilder 0.97
+Text::Template
+File::Spec 0.8
+HTML::Entities
+Net::Domain
+Log::Dispatch 2.0
+Locale::Maketext 1.06
+Locale::Maketext::Lexicon 0.32
+Locale::Maketext::Fuzzy
+MIME::Entity 5.108
+Mail::Mailer 1.57
+Net::SMTP
+Text::Wrapper
+Time::ParseDate
+File::Temp
+Term::ReadKey
+Text::Autoformat
+Text::Quoted 1.3
+Scalar::Util
+.
+
+$deps{'MASON'} = [ _( << '.') ];
+Params::Validate 0.02
+Cache::Cache
+Exception::Class
+HTML::Mason 1.16
+MLDBM
+Errno
+FreezeThaw
+Digest::MD5 2.27
+CGI::Cookie 1.20
+Storable 2.08
+Apache::Session 1.53
+.
+
+$deps{'MAILGATE'} = [ _( << '.') ];
+HTML::TreeBuilder
+HTML::FormatText
+Getopt::Long
+LWP::UserAgent
+.
+
+$deps{'CLI'} = [ _( << '.') ];
+Getopt::Long 2.24
+.
+
+$deps{'DEV'} = [ _( << '.') ];
+Regexp::Common
+Time::HiRes
+Test::Inline
+WWW::Mechanize
+.
+
+$deps{'FASTCGI'} = [ _( << '.') ];
+CGI 2.92
+FCGI
+CGI::Fast
+.
+
+$deps{'SPEEDYCGI'} = [ _( << '.') ];
+CGI 2.92
+CGI::SpeedyCGI
+.
+
+
+$deps{'MODPERL1'} = [ _( << '.') ];
+CGI 2.92
+Apache::Request
+Apache::DBI 0.92
+.
+
+$deps{'MODPERL2'} = [ _( << '.') ];
+CGI 2.92
+Apache::DBI
+.
+
+$deps{'I18N-COMPAT'} = [ _( << '.') ];
+Text::Iconv
+Encode::compat 0.04
+.
+
+$deps{'MYSQL'} = [ _( << '.') ];
+DBD::mysql 2.1018
+.
+$deps{'ORACLE'} = [ _( << '.') ];
+DBD::Oracle
+.
+$deps{'POSTGRESQL'} = [ _( << '.') ];
+DBD::Pg
+.
+
+print "perl:\n";
+print "\t5.8.0";
+eval {require 5.008};
+if ($@) {
+print "...missing.\n";
+ eval {require 5.006001};
+ if ($@) {
+ print " RT is known to be non-functional on versions of perl older than 5.6.1. Please upgrade to 5.8.0 or newer";
+ die;
+ } else {
+ print " RT is not supported on perl 5.6.1\n";
+ }
+} else {
+ print "...found\n";
+
+}
+
+
+foreach my $type (keys %args) {
+next unless ($type =~ /^with-(.*?)$/);
+my $type = $1;
+print "$type dependencies:\n";
+ my @deps = (@{$deps{$type}});
+ while (@deps) {
+ my $module = shift @deps;
+ my $version = shift @deps;
+my $ret;
+ $ret =test_dep($module, $version);
+
+if ($args{'install'} && !$ret) {
+ resolve_dep($module);
+}
+}
+}
+sub test_dep {
+ my $module = shift;
+ my $version = shift;
+
+ print "\t$module $version";
+ eval "use $module $version" ;
+ if ($@) {
+ my $error = $@;
+ $error =~ s/\n(.*)$//s;
+ print "...MISSING\n";
+ print "\t\t$error\n" if $error =~ /this is only/;
+
+ return undef;
+ } else {
+ print "...found\n";
+return 1;
+ }
+}
+
+sub resolve_dep {
+ my $module = shift;
+ use CPAN;
+ CPAN::Shell->install($module);
+
+}
+
+
+sub print_help {
+ print << "EOF";
+
+$0 FLAG DBTYPE
+
+
+$0 is a tool for RT that will tell you if you've got all
+the modules RT depends on properly installed.
+
+Flags: (only one flag is valid for a given run)
+
+-quiet will check to see if we've got everything we need
+ and will exit with a return code of (1) if we don't.
+
+-warn will tell you what isn't properly installed
+
+-fix will use CPANPLUS.pm or CPAN.pm to magically make everything better
+
+DBTYPE is one of:
+ oracle, pg, mysql
+
+EOF
+
+ exit(0);
+}
diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in
new file mode 100644
index 0000000..7a15080
--- /dev/null
+++ b/rt/sbin/rt-test-dependencies.in
@@ -0,0 +1,278 @@
+#!@PERL@
+# BEGIN LICENSE BLOCK
+#
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+#
+# (Except where explictly superceded by other copyright notices)
+#
+# 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.
+#
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+#
+#
+# END LICENSE BLOCK
+
+#
+# This is just a basic script that checks to make sure that all
+# the modules needed by RT before you can install it.
+#
+
+use strict;
+no warnings qw(numeric redefine);
+use Getopt::Long;
+use CPAN;
+my %args;
+my %deps;
+GetOptions(\%args,'install', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', 'with-ORACLE', 'with-FASTCGI', 'with-SPEEDYCGI', 'with-MODPERL1', 'with-MODPERL2' ,'with-DEV');
+
+if (!keys %args) {
+ help();
+ exit(0);
+}
+if ($args{'with-MODPERL2'}) {
+ warn_modperl2();
+}
+
+$args{'with-MASON'} = 1;
+$args{'with-CORE'} = 1;
+$args{'with-DEV'} =1;
+$args{'with-CLI'} =1;
+$args{'with-MAILGATE'} =1;
+if ($] < 5.007) {
+$args{'with-I18N-COMPAT'} = 1;
+}
+
+sub warn_modperl2 {
+ print <<'.';
+ NOTE: mod_perl 2.0 isn't quite ready for prime_time just yet;
+ Best Practical Solutions strongly recommends that sites use
+ Apache 1.3 or FastCGI. If you MUST use mod_perl 2.0 (or 1.99),
+ please read the mailing list archives before asking for help.
+.
+ sleep 5;
+}
+
+
+sub help {
+
+ print <<'.';
+
+By default, testdeps determine whether you have
+installed all the perl modules RT needs to run.
+
+ --install Install missing modules
+
+The following switches will tell the tool to check for specific dependencies
+
+ --with-mysql Database interface for MySQL
+ --with-postgresql Database interface for PostgreSQL
+ --with-sqlite Database interface and driver for SQLite (unsupported)
+ --with-oracle Database interface for oracle (unsupported)
+
+ --with-fastcgi Libraries needed to support the fastcgi handler
+ --with-speedycgi Libraries needed to support the speedycgi handler
+ --with-modperl1 Libraries needed to support the modperl 1 handler
+ --with-modperl2 Libraries needed to support the modperl 2 handler
+
+ --with-dev Tools needed for RT development
+.
+}
+
+
+sub _ {
+ map { /(\S+)\s*(\S*)/; $1 => ($2 ? $2 :'') } split ( /\n/, $_[0] );
+}
+
+$deps{'CORE'} = [ _( << '.') ];
+Digest::MD5 2.27
+DBI 1.37
+Test::Inline
+Class::ReturnValue 0.40
+DBIx::SearchBuilder 0.97
+Text::Template
+File::Spec 0.8
+HTML::Entities
+Net::Domain
+Log::Dispatch 2.0
+Locale::Maketext 1.06
+Locale::Maketext::Lexicon 0.32
+Locale::Maketext::Fuzzy
+MIME::Entity 5.108
+Mail::Mailer 1.57
+Net::SMTP
+Text::Wrapper
+Time::ParseDate
+File::Temp
+Term::ReadKey
+Text::Autoformat
+Text::Quoted 1.3
+Scalar::Util
+.
+
+$deps{'MASON'} = [ _( << '.') ];
+Params::Validate 0.02
+Cache::Cache
+Exception::Class
+HTML::Mason 1.16
+MLDBM
+Errno
+FreezeThaw
+Digest::MD5 2.27
+CGI::Cookie 1.20
+Storable 2.08
+Apache::Session 1.53
+.
+
+$deps{'MAILGATE'} = [ _( << '.') ];
+HTML::TreeBuilder
+HTML::FormatText
+Getopt::Long
+LWP::UserAgent
+.
+
+$deps{'CLI'} = [ _( << '.') ];
+Getopt::Long 2.24
+.
+
+$deps{'DEV'} = [ _( << '.') ];
+Regexp::Common
+Time::HiRes
+Test::Inline
+WWW::Mechanize
+.
+
+$deps{'FASTCGI'} = [ _( << '.') ];
+CGI 2.92
+FCGI
+CGI::Fast
+.
+
+$deps{'SPEEDYCGI'} = [ _( << '.') ];
+CGI 2.92
+CGI::SpeedyCGI
+.
+
+
+$deps{'MODPERL1'} = [ _( << '.') ];
+CGI 2.92
+Apache::Request
+Apache::DBI 0.92
+.
+
+$deps{'MODPERL2'} = [ _( << '.') ];
+CGI 2.92
+Apache::DBI
+.
+
+$deps{'I18N-COMPAT'} = [ _( << '.') ];
+Text::Iconv
+Encode::compat 0.04
+.
+
+$deps{'MYSQL'} = [ _( << '.') ];
+DBD::mysql 2.1018
+.
+$deps{'ORACLE'} = [ _( << '.') ];
+DBD::Oracle
+.
+$deps{'POSTGRESQL'} = [ _( << '.') ];
+DBD::Pg
+.
+
+print "perl:\n";
+print "\t5.8.0";
+eval {require 5.008};
+if ($@) {
+print "...missing.\n";
+ eval {require 5.006001};
+ if ($@) {
+ print " RT is known to be non-functional on versions of perl older than 5.6.1. Please upgrade to 5.8.0 or newer";
+ die;
+ } else {
+ print " RT is not supported on perl 5.6.1\n";
+ }
+} else {
+ print "...found\n";
+
+}
+
+
+foreach my $type (keys %args) {
+next unless ($type =~ /^with-(.*?)$/);
+my $type = $1;
+print "$type dependencies:\n";
+ my @deps = (@{$deps{$type}});
+ while (@deps) {
+ my $module = shift @deps;
+ my $version = shift @deps;
+my $ret;
+ $ret =test_dep($module, $version);
+
+if ($args{'install'} && !$ret) {
+ resolve_dep($module);
+}
+}
+}
+sub test_dep {
+ my $module = shift;
+ my $version = shift;
+
+ print "\t$module $version";
+ eval "use $module $version" ;
+ if ($@) {
+ my $error = $@;
+ $error =~ s/\n(.*)$//s;
+ print "...MISSING\n";
+ print "\t\t$error\n" if $error =~ /this is only/;
+
+ return undef;
+ } else {
+ print "...found\n";
+return 1;
+ }
+}
+
+sub resolve_dep {
+ my $module = shift;
+ use CPAN;
+ CPAN::Shell->install($module);
+
+}
+
+
+sub print_help {
+ print << "EOF";
+
+$0 FLAG DBTYPE
+
+
+$0 is a tool for RT that will tell you if you've got all
+the modules RT depends on properly installed.
+
+Flags: (only one flag is valid for a given run)
+
+-quiet will check to see if we've got everything we need
+ and will exit with a return code of (1) if we don't.
+
+-warn will tell you what isn't properly installed
+
+-fix will use CPANPLUS.pm or CPAN.pm to magically make everything better
+
+DBTYPE is one of:
+ oracle, pg, mysql
+
+EOF
+
+ exit(0);
+}
diff --git a/test/cgi-test b/test/cgi-test
new file mode 100755
index 0000000..2dda484
--- /dev/null
+++ b/test/cgi-test
@@ -0,0 +1,560 @@
+#!/usr/bin/perl -Tw
+#
+# $Id: cgi-test,v 1.3 2001-08-21 02:32:54 ivan Exp $
+#
+# This is the beginning of a test suite for the web interface.
+# It's also excellent for populating your database with some meaningful test
+# data. (a derivative is used by the web demo)
+# It only works on an empty database (probably need empty counters too, and
+# no arbirary RADIUS attributes).
+# Usage: cgi-test http://base.freeside.url/with/path/ username password
+# (Yes, if you were properly paranoid and are using SSL, you'll need to get
+# libwww-perl working with SSL to use this.)
+
+use strict;
+#use diagnostics;
+use subs qw( big_ugly_data_structure );
+use CGI;
+use LWP::UserAgent;
+
+my ( $base_url, $username, $password ) = ( shift, shift, shift );
+#trust 'em
+$base_url =~ /^(.*)$/; $base_url = $1;
+$username =~ /^(.*)$/; $username = $1;
+$password =~ /^(.*)$/; $password = $1;
+
+my @data = &big_ugly_data_structure;
+
+my $ua = new LWP::UserAgent;
+{
+ local $^W = 0;
+ eval '
+ sub LWP::UserAgent::get_basic_credentials {
+ #my $self = shift;
+ ( $username, $password );
+ }
+ ';
+}
+
+my $data;
+while ( $data = shift @data ) {
+ my $cgi = new CGI ( $data->{'params'} );
+ my $full_url = $base_url. $data->{'url'}. '?'. $cgi->query_string;
+ #my $request = new HTTP::Request( 'POST', $full_url );
+ my $request = new HTTP::Request( 'GET', $full_url );
+ my $response = $ua->request( $request );
+ if ( $response->is_redirect ) {
+ die "Unexpected redirect!\n".
+ "URL: $full_url\n".
+ "To: ". $response->base. "\n"
+ ;
+ } elsif ( $response->is_success ) {
+ my $location = $response->base;
+ my $expected_location = $data->{'location'};
+ #if ( $location =~ /^$base_url$expected_location$/ ) {
+ if ( $location eq $base_url. $expected_location ) {
+ #warn "cool, got expected response $location from $full_url\n";
+ } else {
+ die "Strange, regular response, but unexpected base!\n".
+ "URL: $full_url\n".
+ "Base : ". $response->base. "\n".
+ "Expected: $base_url$expected_location\n".
+ "Output: ". $response->content. "\n"
+ ;
+ }
+ } elsif ( $response->is_error ) {
+ die "Strange, I got an error\n".
+ "URL: $full_url\n".
+ "Error: ". $response->error_as_HTML. "\n".
+ "Output: ". $response->content. "\n"
+ ;
+ } elsif ( $response->is_info ) {
+ die "Strange, I got an info reponse\n".
+ "URL: $full_url\n".
+ "Output: ". $response->content. "\n"
+ ;
+ } else {
+ die "Really strange, got an unrecognized response from LWP::UserAgent!\n";
+ }
+}
+
+#---
+
+sub big_ugly_data_structure {
+
+ (
+ { 'url' => 'edit/process/part_svc.cgi',
+ 'params' => {
+ 'svcpart' => '',
+ 'svc' => 'Shell',
+ 'svcdb' => 'svc_acct',
+ 'svc_acct__popnum_flag' => '',
+ 'svc_acct__popnum' => '',
+ 'svc_acct__dir_flag' => '',
+ 'svc_acct__dir' => '',
+ 'svc_acct__username_flag' => '',
+ 'svc_acct__username' => '',
+ 'svc_acct__uid_flag' => '',
+ 'svc_acct__uid' => '',
+ 'svc_acct__quota_flag' => 'F',
+ 'svc_acct__quota' => '10',
+ 'svc_acct__slipip_flag' => 'F',
+ 'svc_acct__slipip' => '',
+ 'svc_acct___password_flag' => '',
+ 'svc_acct___password' => '',
+ 'svc_acct__gid_flag' => '',
+ 'svc_acct__gid' => '',
+ 'svc_acct__shell_flag' => 'D',
+ 'svc_acct__shell' => '/bin/sh',
+ 'svc_acct__finger_flag' => '',
+ 'svc_acct__finger' => '',
+ 'svc_domain__domain_flag' => '',
+ 'svc_domain__domain' => '',
+ 'svc_acct_sm__domuser_flag' => '',
+ 'svc_acct_sm__domuser' => '',
+ 'svc_acct_sm__domuid_flag' => '',
+ 'svc_acct_sm__domuid' => '',
+ 'svc_acct_sm__domsvc_flag' => '',
+ 'svc_acct_sm__domsvc' => '',
+ },
+ 'location' => 'browse/part_svc.cgi',
+ },
+ { 'url' => 'edit/process/part_svc.cgi',
+ 'params' => {
+ 'svcpart' => '',
+ 'svc' => 'SLIP/PPP',
+ 'svcdb' => 'svc_acct',
+ 'svc_acct__popnum_flag' => '',
+ 'svc_acct__popnum' => '',
+ 'svc_acct__dir_flag' => '',
+ 'svc_acct__dir' => '',
+ 'svc_acct__username_flag' => '',
+ 'svc_acct__username' => '',
+ 'svc_acct__uid_flag' => '',
+ 'svc_acct__uid' => '',
+ 'svc_acct__quota_flag' => 'F',
+ 'svc_acct__quota' => '10',
+ 'svc_acct__slipip_flag' => 'D',
+ 'svc_acct__slipip' => '0.0.0.0',
+ 'svc_acct___password_flag' => '',
+ 'svc_acct___password' => '',
+ 'svc_acct__gid_flag' => '',
+ 'svc_acct__gid' => '',
+ 'svc_acct__shell_flag' => 'D',
+ 'svc_acct__shell' => '/bin/sh',
+ 'svc_acct__finger_flag' => '',
+ 'svc_acct__finger' => '',
+ 'svc_domain__domain_flag' => '',
+ 'svc_domain__domain' => '',
+ 'svc_acct_sm__domuser_flag' => '',
+ 'svc_acct_sm__domuser' => '',
+ 'svc_acct_sm__domuid_flag' => '',
+ 'svc_acct_sm__domuid' => '',
+ 'svc_acct_sm__domsvc_flag' => '',
+ 'svc_acct_sm__domsvc' => '',
+ },
+ 'location' => 'browse/part_svc.cgi',
+ },
+ { 'url' => 'edit/process/part_svc.cgi',
+ 'params' => {
+ 'svcpart' => '',
+ 'svc' => 'POP Mailbox',
+ 'svcdb' => 'svc_acct',,
+ 'svc_acct__popnum_flag' => 'F',
+ 'svc_acct__popnum' => '',
+ 'svc_acct__dir_flag' => '',
+ 'svc_acct__dir' => '',
+ 'svc_acct__username_flag' => '',
+ 'svc_acct__username' => '',
+ 'svc_acct__uid_flag' => '',
+ 'svc_acct__uid' => '',
+ 'svc_acct__quota_flag' => 'F',
+ 'svc_acct__quota' => '10',
+ 'svc_acct__slipip_flag' => 'F',
+ 'svc_acct__slipip' => '',
+ 'svc_acct___password_flag' => '',
+ 'svc_acct___password' => '',
+ 'svc_acct__gid_flag' => '',
+ 'svc_acct__gid' => '',
+ 'svc_acct__shell_flag' => 'F',
+ 'svc_acct__shell' => '/bin/passwd',
+ 'svc_acct__finger_flag' => '',
+ 'svc_acct__finger' => '',
+ 'svc_domain__domain_flag' => '',
+ 'svc_domain__domain' => '',
+ 'svc_acct_sm__domuser_flag' => '',
+ 'svc_acct_sm__domuser' => '',
+ 'svc_acct_sm__domuid_flag' => '',
+ 'svc_acct_sm__domuid' => '',
+ 'svc_acct_sm__domsvc_flag' => '',
+ 'svc_acct_sm__domsvc' => '',
+ },
+ 'location' => 'browse/part_svc.cgi',
+ },
+ { 'url' => 'edit/process/part_svc.cgi',
+ 'params' => {
+ 'svcpart' => '',
+ 'svc' => 'Domain',
+ 'svcdb' => 'svc_domain',,
+ 'svc_acct__popnum_flag' => '',
+ 'svc_acct__popnum' => '',
+ 'svc_acct__dir_flag' => '',
+ 'svc_acct__dir' => '',
+ 'svc_acct__username_flag' => '',
+ 'svc_acct__username' => '',
+ 'svc_acct__uid_flag' => '',
+ 'svc_acct__uid' => '',
+ 'svc_acct__quota_flag' => '',
+ 'svc_acct__quota' => '',
+ 'svc_acct__slipip_flag' => '',
+ 'svc_acct__slipip' => '',
+ 'svc_acct___password_flag' => '',
+ 'svc_acct___password' => '',
+ 'svc_acct__gid_flag' => '',
+ 'svc_acct__gid' => '',
+ 'svc_acct__shell_flag' => '',
+ 'svc_acct__shell' => '',
+ 'svc_acct__finger_flag' => '',
+ 'svc_acct__finger' => '',
+ 'svc_domain__domain_flag' => '',
+ 'svc_domain__domain' => '',
+ 'svc_acct_sm__domuser_flag' => '',
+ 'svc_acct_sm__domuser' => '',
+ 'svc_acct_sm__domuid_flag' => '',
+ 'svc_acct_sm__domuid' => '',
+ 'svc_acct_sm__domsvc_flag' => '',
+ 'svc_acct_sm__domsvc' => '',
+ },
+ 'location' => 'browse/part_svc.cgi',
+ },
+ { 'url' => 'edit/process/part_svc.cgi',
+ 'params' => {
+ 'svcpart' => '',
+ 'svc' => 'Domain email alias',
+ 'svcdb' => 'svc_acct_sm',,
+ 'svc_acct__popnum_flag' => '',
+ 'svc_acct__popnum' => '',
+ 'svc_acct__dir_flag' => '',
+ 'svc_acct__dir' => '',
+ 'svc_acct__username_flag' => '',
+ 'svc_acct__username' => '',
+ 'svc_acct__uid_flag' => '',
+ 'svc_acct__uid' => '',
+ 'svc_acct__quota_flag' => '',
+ 'svc_acct__quota' => '',
+ 'svc_acct__slipip_flag' => '',
+ 'svc_acct__slipip' => '',
+ 'svc_acct___password_flag' => '',
+ 'svc_acct___password' => '',
+ 'svc_acct__gid_flag' => '',
+ 'svc_acct__gid' => '',
+ 'svc_acct__shell_flag' => '',
+ 'svc_acct__shell' => '',
+ 'svc_acct__finger_flag' => '',
+ 'svc_acct__finger' => '',
+ 'svc_domain__domain_flag' => '',
+ 'svc_domain__domain' => '',
+ 'svc_acct_sm__domuser_flag' => '',
+ 'svc_acct_sm__domuser' => '',
+ 'svc_acct_sm__domuid_flag' => '',
+ 'svc_acct_sm__domuid' => '',
+ 'svc_acct_sm__domsvc_flag' => '',
+ 'svc_acct_sm__domsvc' => '',
+ },
+ 'location' => 'browse/part_svc.cgi',
+ },
+
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Personal SLIP/PPP',
+ 'comment' => '$30/setup, $19.99/month',
+ 'setup' => '30',
+ 'recur' => '19.99',
+ 'freq' => '1',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '1',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '0',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Personal SLIP/PPP',
+ 'comment' => '$0/setup, $179.88/year',
+ 'setup' => '0',
+ 'recur' => '179.88',
+ 'freq' => '12',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '1',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '0',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Personal POP mailbox',
+ 'comment' => '$10/setup, $5/month',
+ 'setup' => '10',
+ 'recur' => '5',
+ 'freq' => '1',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '0',
+ 'pkg_svc3' => '1',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '0',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Business SLIP/PPP',
+ 'comment' => '$30/setup, $29.99/month',
+ 'setup' => '30',
+ 'recur' => '29.99',
+ 'freq' => '1',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '1',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '1',
+ 'pkg_svc5' => '1',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Business SLIP/PPP',
+ 'comment' => '$0/setup, $299.88/year',
+ 'setup' => '0',
+ 'recur' => '299.88',
+ 'freq' => '12',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '1',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '1',
+ 'pkg_svc5' => '1',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Business POP mailbox',
+ 'comment' => '$10/setup, $5/month',
+ 'setup' => '10',
+ 'recur' => '5',
+ 'freq' => '1',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '0',
+ 'pkg_svc3' => '1',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '1',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'UNIX shell',
+ 'comment' => '$20/setup, $9.99/month',
+ 'setup' => '20',
+ 'recur' => '9.99',
+ 'freq' => '1',
+ 'pkg_svc1' => '1',
+ 'pkg_svc2' => '0',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '0',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Point-to-point T1',
+ 'comment' => '$1000/setup, $1000/month',
+ 'setup' => '1000',
+ 'recur' => '1000',
+ 'freq' => '1',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '0',
+ 'pkg_svc3' => '5',
+ 'pkg_svc4' => '1',
+ 'pkg_svc5' => '5',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+ { 'url' => 'edit/process/part_pkg.cgi',
+ 'params' => {
+ 'pkgpart' => '',
+ 'pkg' => 'Cisco 2501 Router',
+ 'comment' => '$2500',
+ 'setup' => '2500',
+ 'recur' => '0',
+ 'freq' => '0',
+ 'pkg_svc1' => '0',
+ 'pkg_svc2' => '0',
+ 'pkg_svc3' => '0',
+ 'pkg_svc4' => '0',
+ 'pkg_svc5' => '0',
+ },
+ 'location' => 'browse/part_pkg.cgi',
+ },
+
+ { 'url' => 'edit/process/agent_type.cgi',
+ 'params' => {
+ 'typenum' => '',
+ 'atype' => 'Internal Sales',
+ 'pkgpart1' => 'ON',
+ 'pkgpart2' => 'ON',
+ 'pkgpart3' => 'ON',
+ 'pkgpart4' => 'ON',
+ 'pkgpart5' => 'ON',
+ 'pkgpart6' => 'ON',
+ 'pkgpart7' => 'ON',
+ 'pkgpart8' => 'ON',
+ 'pkgpart9' => 'ON',
+ },
+ 'location' => 'browse/agent_type.cgi',
+ },
+
+ { 'url' => 'edit/process/agent.cgi',
+ 'params' => {
+ 'agentnum' => '',
+ 'agent' => 'Internal Sales',
+ 'typenum' => '1',
+ 'freq' => '',
+ 'prog' => '',
+ },
+ 'location' => 'browse/agent.cgi',
+ },
+
+ { 'url' => 'edit/process/part_referral.cgi',
+ 'params' => {
+ 'refnum' => '',
+ 'referral' => 'Another customer',
+ },
+ 'location' => 'browse/part_referral.cgi',
+ },
+ { 'url' => 'edit/process/part_referral.cgi',
+ 'params' => {
+ 'refnum' => '',
+ 'referral' => 'Newspaper ad',
+ },
+ 'location' => 'browse/part_referral.cgi',
+ },
+
+ { 'url' => 'edit/process/svc_acct_pop.cgi',
+ 'params' => {
+ 'popnum' => '',
+ 'city' => 'Line Lexington',
+ 'state' => 'PA',
+ 'ac' => '215',
+ 'exch' => '996',
+ },
+ 'location' => 'browse/svc_acct_pop.cgi',
+ },
+ { 'url' => 'edit/process/svc_acct_pop.cgi',
+ 'params' => {
+ 'popnum' => '',
+ 'city' => 'Oakland',
+ 'state' => 'CA',
+ 'ac' => '510',
+ 'exch' => '208',
+ },
+ 'location' => 'browse/svc_acct_pop.cgi',
+ },
+
+ { 'url' => 'edit/process/cust_main.cgi',
+ 'params' => {
+ 'custnum' => '',
+ 'agentnum' => '1',
+ 'refnum' => '1',
+ 'last' => 'Hogan',
+ 'first' => 'Shawn D.',
+ 'ss' => '',
+ 'company' => 'Digital Point Solutions',
+ 'address1' => '3570 Tony Drive',
+ 'address2' => '',
+ 'city' => 'San Diego',
+ 'state' => 'CA / US',
+ 'zip' => '92122-2307',
+ 'daytime' => '',
+ 'night' => '',
+ 'fax' => '',
+ 'tax' => '',
+ 'invoicing_list_POST' => '',
+ 'invoicing_list' => '',
+ 'payby' => 'BILL',
+ 'CARD_payinfo' => '',
+ 'CARD_month' => '1',
+ 'CARD_year' => '1999',
+ 'CARD_payname' => '',
+ 'BILL_payinfo' => '',
+ 'BILL_month' => '12',
+ 'BILL_year' => '2037',
+ 'BILL_payname' => 'Accounts Payable',
+ 'COMP_payinfo' => '',
+ 'COMP_month' => '1',
+ 'COMP_year' => '1999',
+ 'pkgpart_svcpart' => '1_2',
+ 'username' => 'cyborg',
+ '_password' => '',
+ 'popnum' => '1',
+ 'otaker' => 'example',
+ },
+ 'location' => 'view/cust_main.cgi?1',
+ },
+ { 'url' => 'edit/process/cust_main.cgi',
+ 'params' => {
+ 'custnum' => '',
+ 'agentnum' => '1',
+ 'refnum' => '2',
+ 'last' => 'Ford',
+ 'first' => 'Bill',
+ 'ss' => '',
+ 'company' => 'Boardtown Corporation',
+ 'address1' => '116 East Main Street',
+ 'address2' => '',
+ 'city' => 'Starkville',
+ 'state' => 'MS / US',
+ 'zip' => '39759',
+ 'daytime' => '',
+ 'night' => '',
+ 'fax' => '',
+ 'tax' => '',
+ 'invoicing_list_POST' => '',
+ 'invoicing_list' => '',
+ 'payby' => 'BILL',
+ 'CARD_payinfo' => '',
+ 'CARD_month' => '1',
+ 'CARD_year' => '1999',
+ 'CARD_payname' => '',
+ 'BILL_payinfo' => '',
+ 'BILL_month' => '12',
+ 'BILL_year' => '2037',
+ 'BILL_payname' => 'Accounts Payable',
+ 'COMP_payinfo' => '',
+ 'COMP_month' => '1',
+ 'COMP_year' => '1999',
+ 'pkgpart_svcpart' => '3_3',
+ 'username' => 'billf',
+ '_password' => '',
+ 'popnum' => '',
+ 'otaker' => 'example',
+ },
+ 'location' => 'view/cust_main.cgi?2',
+ },
+
+
+ );
+}
+
diff --git a/test/dup-test b/test/dup-test
new file mode 100755
index 0000000..b073cee
--- /dev/null
+++ b/test/dup-test
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use FS::UID qw(adminsuidsetup);
+use FS::svc_acct;
+
+my $user = 'ivan';
+my $svcpart = '2';
+
+my $counter = 10;
+
+my $pid = open(KID_TO_WRITE, "-|");
+
+if ( $pid ) { #parent
+ doit();
+} else { #kid
+ doit();
+ exit;
+}
+
+sub doit {
+
+ adminsuidsetup $user or die;
+
+ my $svc_acct = new FS::svc_acct ( {
+ 'svcpart' => $svcpart,
+ 'username' => "dup$counter",
+ } );
+ my $error = $svc_acct->insert;
+ warn $error if $error;
+
+}
+